From ef80c07075d341eb1dde1545c18d42161649500b Mon Sep 17 00:00:00 2001
From: Zhong Lufan <lufanzhong@gmail.com>
Date: Fri, 8 Oct 2021 19:32:59 +0800
Subject: [PATCH 001/143] Fix unsupported regex for QQ blocked rules

---
 internal/filtering/blocked.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index df50f739..11bb4e66 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -181,7 +181,7 @@ var serviceRulesArray = []svc{
 	}},
 	{"qq", []string{
 		// block qq.com and subdomains excluding WeChat domains
-		"^(?!weixin|wx)([^.]+\\.)?qq\\.com$",
+		"||qq.com^$denyallow=wx*.qq.com|weixin.qq.com",
 		"||qqzaixian.com^",
 	}},
 	{"wechat", []string{

From 773b80a96978b41cef5556bb881150ff26f25d84 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 21 Feb 2022 17:06:12 +0300
Subject: [PATCH 002/143] Pull request: client: upd i18n

Updates #2643.

Squashed commit of the following:

commit 048c245ab682f0799c2f7a7f0435a1898a482392
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Feb 21 16:58:10 2022 +0300

    client: upd i18n
---
 client/src/__locales/be.json    |   3 +-
 client/src/__locales/bg.json    |   1 +
 client/src/__locales/cs.json    | 136 +++++++++++++-------------
 client/src/__locales/da.json    | 102 ++++++++++----------
 client/src/__locales/de.json    | 150 ++++++++++++++---------------
 client/src/__locales/es.json    | 142 +++++++++++++--------------
 client/src/__locales/fa.json    |   2 +-
 client/src/__locales/fi.json    | 146 ++++++++++++++--------------
 client/src/__locales/fr.json    | 156 +++++++++++++++---------------
 client/src/__locales/hr.json    |   1 -
 client/src/__locales/hu.json    |   1 -
 client/src/__locales/id.json    |   3 +-
 client/src/__locales/it.json    | 166 ++++++++++++++++----------------
 client/src/__locales/ja.json    | 116 +++++++++++-----------
 client/src/__locales/ko.json    | 138 +++++++++++++-------------
 client/src/__locales/nl.json    | 152 ++++++++++++++---------------
 client/src/__locales/no.json    |   4 +-
 client/src/__locales/pl.json    | 148 ++++++++++++++--------------
 client/src/__locales/pt-br.json | 140 +++++++++++++--------------
 client/src/__locales/pt-pt.json | 136 +++++++++++++-------------
 client/src/__locales/ro.json    | 152 ++++++++++++++---------------
 client/src/__locales/ru.json    | 148 ++++++++++++++--------------
 client/src/__locales/si-lk.json |   1 -
 client/src/__locales/sk.json    |   4 +-
 client/src/__locales/sl.json    | 150 ++++++++++++++---------------
 client/src/__locales/sr-cs.json |   3 +-
 client/src/__locales/sv.json    |  19 ++--
 client/src/__locales/tr.json    | 156 +++++++++++++++---------------
 client/src/__locales/uk.json    |  74 +++++++-------
 client/src/__locales/vi.json    |   3 +-
 client/src/__locales/zh-cn.json | 144 +++++++++++++--------------
 client/src/__locales/zh-hk.json |   3 +-
 client/src/__locales/zh-tw.json | 146 ++++++++++++++--------------
 33 files changed, 1418 insertions(+), 1428 deletions(-)

diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json
index 10079eb1..db3d9e85 100644
--- a/client/src/__locales/be.json
+++ b/client/src/__locales/be.json
@@ -593,7 +593,7 @@
     "allowed": "Дазволены",
     "filtered": "Адфільтраваныя",
     "rewritten": "Перапісаныя",
-    "safe_search": "Бяспечны пошук",
+    "safe_search": "Уключыць Бяспечны пошук",
     "blocklist": "Чорны спіс",
     "milliseconds_abbreviation": "мс",
     "cache_size": "Памер кэша",
@@ -623,7 +623,6 @@
     "adg_will_drop_dns_queries": "AdGuard Home скіне ўсе DNS-запыты ад гэтага кліента.",
     "filter_allowlist": "УВАГА: Гэта дзеянне таксама выключыць правіла «{{disallowed_rule}}» са спіса дазволеных кліентаў.",
     "last_rule_in_allowlist": "Няможна заблакаваць гэтага кліента, бо вынятак правіла «{{disallowed_rule}}» АДКЛЮЧЫЦЬ рэжым белага спіса.",
-    "experimental": "Эксперыментальны",
     "use_saved_key": "Скарыстаць захаваны раней ключ",
     "parental_control": "Бацькоўскі кантроль",
     "safe_browsing": "Бяспечны інтэрнэт",
diff --git a/client/src/__locales/bg.json b/client/src/__locales/bg.json
index 8f8991f5..1b6a1db5 100644
--- a/client/src/__locales/bg.json
+++ b/client/src/__locales/bg.json
@@ -242,6 +242,7 @@
     "show_whitelisted_responses": "В белия списък",
     "show_processed_responses": "Обработен",
     "allowed": "В белия списък",
+    "safe_search": "Безопасно търсене",
     "filter_category_general": "General",
     "filter_category_security": "Сигурност",
     "port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това.",
diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json
index 95935953..8d89f204 100644
--- a/client/src/__locales/cs.json
+++ b/client/src/__locales/cs.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Nastavení klienta",
-    "example_upstream_reserved": "Můžete zadat odchozí DNS připojení <0>pro konkrétní doménu(y)</0>",
-    "example_upstream_comment": "Můžete zadat komentář",
+    "example_upstream_reserved": "odchozí DNS připojení <0>pro konkrétní doménu(y)</0>;",
+    "example_upstream_comment": "komentář.",
     "upstream_parallel": "Použijte paralelní požadavky na urychlení řešení simultánním dotazováním na všechny navazující servery.",
     "parallel_requests": "Paralelní požadavky",
     "load_balancing": "Optimalizace vytížení",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Konfigurace DHCP byla úspěšně uložena",
     "dhcp_ipv4_settings": "Nastavení DHCP IPv4",
     "dhcp_ipv6_settings": "Nastavení DHCP IPv6",
-    "form_error_required": "Povinné pole",
-    "form_error_ip4_format": "Neplatná adresa IPv4",
-    "form_error_ip4_range_start_format": "Neplatná adresa IPv4 na začátku rozsahu",
-    "form_error_ip4_range_end_format": "Neplatná adresa IPv4 na konci rozsahu",
-    "form_error_ip4_gateway_format": "Neplatná adresa IPv4 brány",
-    "form_error_ip6_format": "Neplatná adresa IPv6",
-    "form_error_ip_format": "Neplatná adresa IP",
-    "form_error_mac_format": "Neplatná adresa MAC",
-    "form_error_client_id_format": "ID klienta musí obsahovat pouze čísla, malá písmena a spojovníky",
-    "form_error_server_name": "Neplatný název serveru",
-    "form_error_subnet": "Podsíť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
-    "form_error_positive": "Musí být větší než 0",
-    "out_of_range_error": "Musí být mimo rozsah \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Musí být menší než začátek rozsahu",
-    "greater_range_start_error": "Musí být větší než začátek rozsahu",
-    "greater_range_end_error": "Musí být větší než konec rozsahu",
-    "subnet_error": "Adresy musí být v jedné podsíti",
-    "gateway_or_subnet_invalid": "Neplatná maska podsítě",
+    "form_error_required": "Povinné pole.",
+    "form_error_ip4_format": "Neplatná adresa IPv4.",
+    "form_error_ip4_range_start_format": "Neplatná adresa IPv4 na začátku rozsahu.",
+    "form_error_ip4_range_end_format": "Neplatná adresa IPv4 na konci rozsahu.",
+    "form_error_ip4_gateway_format": "Neplatná adresa IPv4 brány.",
+    "form_error_ip6_format": "Neplatná adresa IPv6.",
+    "form_error_ip_format": "Neplatná IP adresa.",
+    "form_error_mac_format": "Neplatná adresa MAC.",
+    "form_error_client_id_format": "ID klienta musí obsahovat pouze čísla, malá písmena a spojovníky.",
+    "form_error_server_name": "Neplatný název serveru.",
+    "form_error_subnet": "Podsíť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\".",
+    "form_error_positive": "Musí být větší než 0.",
+    "out_of_range_error": "Musí být mimo rozsah \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Musí být menší než začátek rozsahu.",
+    "greater_range_start_error": "Musí být větší než začátek rozsahu.",
+    "greater_range_end_error": "Musí být větší než konec rozsahu.",
+    "subnet_error": "Adresy musí být v jedné podsíti.",
+    "gateway_or_subnet_invalid": "Neplatná maska podsítě.",
     "dhcp_form_gateway_input": "IP brána",
     "dhcp_form_subnet_input": "Maska podsítě",
     "dhcp_form_range_title": "Rozsah IP adres",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Vyberte seznamy povolených",
     "enter_valid_blocklist": "Zadejte platnou adresu URL na seznam blokovaných.",
     "enter_valid_allowlist": "Zadejte platnou adresu URL na seznam povolených.",
-    "form_error_url_format": "Neplatný formát URL",
-    "form_error_url_or_path_format": "Neplatná URL nebo úplná cesta k seznamu",
+    "form_error_url_format": "Neplatný formát URL.",
+    "form_error_url_or_path_format": "Neplatná URL nebo úplná cesta k seznamu.",
     "custom_filter_rules": "Vlastní pravidla filtrování",
     "custom_filter_rules_hint": "Na každý řádek vložte jedno pravidlo. Můžete použít buď pravidla blokování reklam nebo syntaxe hostitelských souborů.",
     "system_host_files": "Systémové soubory hostitelů",
     "examples_title": "Příklady",
     "example_meaning_filter_block": "zablokovat přístup k doméně example.org a všem jejím subdoménám",
     "example_meaning_filter_whitelist": "odblokovat přístup k doméně example.org a všem jejím subdoménám",
-    "example_meaning_host_block": "AdGuard Home nyní vrátí adresu 127.0.0.1 pro doménu example.org (ale ne pro její subdomény).",
-    "example_comment": "! Sem se přidává komentář",
-    "example_comment_meaning": "jen komentář",
-    "example_comment_hash": "# Také komentář",
-    "example_regex_meaning": "blokuje přístup doménám, které vyhovují regulárnímu výrazu",
-    "example_upstream_regular": "obyčejný DNS (přes UDP)",
-    "example_upstream_dot": "šifrovaný <0>DNS skrze TLS</0>",
-    "example_upstream_doh": "šifrovaný <0>DNS skrze HTTPS</0>",
-    "example_upstream_doq": "šifrovaný <0>DNS skrze QUIC</0>",
-    "example_upstream_sdns": "můžete použít <0>DNS razítka</0> pro <1>DNSCrypt</1> nebo <2>DNS skrze HTTPS</2> řešitele",
-    "example_upstream_tcp": "obyčejný DNS (přes TCP)",
+    "example_meaning_host_block": "odezva s adresou 127.0.0.1 pro doménu example.org (ale ne pro její subdomény);",
+    "example_comment": "! Sem se přidává komentář.",
+    "example_comment_meaning": "jen komentář;",
+    "example_comment_hash": "# Také komentář.",
+    "example_regex_meaning": "blokuje přístup doménám, které vyhovují regulárnímu výrazu.",
+    "example_upstream_regular": "obvyklý DNS (přes UDP);",
+    "example_upstream_dot": "šifrovaný <0>DNS skrze TLS</0>;",
+    "example_upstream_doh": "šifrovaný <0>DNS skrze HTTPS</0>;",
+    "example_upstream_doq": "šifrovaný <0>DNS skrze QUIC</0> (experimentální);",
+    "example_upstream_sdns": "<0>DNS razítka</0> pro <1>DNSCrypt</1> nebo <2>DNS skrze HTTPS</2> řešitele;",
+    "example_upstream_tcp": "obvyklý DNS (přes TCP);",
     "all_lists_up_to_date_toast": "Všechny seznamy jsou již aktuální",
     "updated_upstream_dns_toast": "Odchozí servery byly úspěšně uloženy",
     "dns_test_ok_toast": "Specifikované DNS servery pracují správně",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Pro striktní vyhledávání použijte dvojité uvozovky",
     "query_log_retention_confirm": "Opravdu chcete změnit uchovávání protokolu dotazů? Pokud snížíte hodnotu intervalu, některá data budou ztracena",
     "anonymize_client_ip": "Anonymizovat IP klienta",
-    "anonymize_client_ip_desc": "Neukládat úplnou IP adresu klienta do protokolů a statistik",
+    "anonymize_client_ip_desc": "Neukládat úplnou IP adresu klienta do protokolů a statistik.",
     "dns_config": "Konfigurace DNS serveru",
     "dns_cache_config": "Konfigurace mezipaměti DNS",
-    "dns_cache_config_desc": "Zde můžete konfigurovat mezipaměť DNS",
+    "dns_cache_config_desc": "Zde můžete konfigurovat mezipaměť DNS.",
     "blocking_mode": "Režim blokování",
     "default": "Výchozí",
     "nxdomain": "NXDOMAIN",
@@ -277,7 +277,7 @@
     "dns_over_quic": "DNS skrze QUIC",
     "client_id": "ID klienta",
     "client_id_placeholder": "Zadejte ID klienta",
-    "client_id_desc": "Různé klienty lze identifikovat pomocí speciálního ID klienta. <a>Zde</a> se můžete dozvědět více o tom, jak klienty identifikovat.",
+    "client_id_desc": "Klienty lze identifikovat pomocí ID klienta. <a>Zde</a> se můžete dozvědět více o tom, jak klienty identifikovat.",
     "download_mobileconfig_doh": "Stáhnout .mobileconfig pro DNS skrze HTTPS",
     "download_mobileconfig_dot": "Stáhnout .mobileconfig pro DNS skrze TLS",
     "download_mobileconfig": "Stáhnout konfigurační soubor",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Síťové rozhraní",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Vaše administrátorské webové rozhraní AdGuard Home bude k dispozici na těchto adresách:",
-    "form_error_port": "Zadejte platné číslo portu",
+    "form_error_port": "Zadejte platné číslo portu.",
     "install_settings_dns": "DNS server",
     "install_settings_dns_desc": "Budete muset nakonfigurovat Vaše zařízení nebo router, aby používali DNS server na následujících adresách:",
     "install_settings_all_interfaces": "Všechna rozhraní",
@@ -334,7 +334,7 @@
     "install_devices_router_list_4": "Na některých typech routerů nemůžete nastavit vlastní DNS server. V tomto případě může AdGuard Home pomoci, pokud jej nastavíte jako <0>DHCP server</0>. V ostatních případech byste si v manuálu k Vašemu routeru měli zjistit, jak přizpůsobit vlastní DNS servery.",
     "install_devices_windows_list_1": "Otevřete ovládací panel prostřednictvím nabídky Start nebo vyhledání v systému Windows.",
     "install_devices_windows_list_2": "Přejděte na kategorii Síť a Internet a poté na Centrum sítí a sdílení.",
-    "install_devices_windows_list_3": "Na levé straně obrazovky najděte možnost \"Změnit nastavení adaptéru\" a klepněte na něj.",
+    "install_devices_windows_list_3": "Na levé straně panelu klikněte na \"Změnit nastavení adaptéru\".",
     "install_devices_windows_list_4": "Vyberte své aktivní spojení, klikněte na něj pravým tlačítkem myši a zvolte Vlastnosti.",
     "install_devices_windows_list_5": "V seznamu najděte \"Internet Protocol Version 4 (TCP/IP)\", (nebo IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), vyberte jej a znovu klikněte na Vlastnosti.",
     "install_devices_windows_list_6": "Zvolte \"Použít následující adresy serveru DNS\" a zadejte adresy serveru AdGuard Home.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Otevřít hlavní panel",
     "install_saved": "Úspěšně uloženo",
     "encryption_title": "Šifrování",
-    "encryption_desc": "Podpora šifrování (HTTPS/TLS) pro webové rozhraní DNS i administrátora",
+    "encryption_desc": "Podpora šifrování (HTTPS/TLS) pro webové rozhraní DNS i administrátora.",
     "encryption_config_saved": "Konfigurace šifrování byla uložena",
     "encryption_server": "Název serveru",
     "encryption_server_enter": "Zadejte název domény",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Pokud je nakonfigurován port HTTPS, AdGuard Home administrátorské rozhraní bude přístupné přes HTTPS a bude také poskytovat DNS skrze HTTPS na '/dns-query'.",
     "encryption_dot": "DNS skrze TLS port",
     "encryption_dot_desc": "Pokud je tento port nakonfigurován, AdGuard Home bude na tomto portu spouštět DNS skrze TLS server.",
-    "encryption_doq": "DNS skrze QUIC port",
+    "encryption_doq": "Port DNS skrze QUIC (experimentální)",
     "encryption_doq_desc": "Pokud je tento port nakonfigurován, AdGuard Home spustí na tomto portu server DNS skrze QUIC. Je to experimentální a nemusí být spolehlivé. V současnosti také není příliš mnoho klientů, kteří to podporují.",
     "encryption_certificates": "Certifikáty",
     "encryption_certificates_desc": "Chcete-li používat šifrování, musíte pro svou doménu poskytnout platný řetězec certifikátů SSL. Certifikát můžete získat bezplatně na adrese <0>{{link}}</ 0>, nebo jej můžete zakoupit od jednoho z důvěryhodných certifikačních úřadů.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Zde můžete nakopírovat/vložit soukromý klíč k certifikátu PEM.",
     "encryption_enable": "Povolit šifrování (HTTPS, DNS skrze HTTPS a DNS skrze TLS)",
     "encryption_enable_desc": "Pokud je šifrování zapnuto, administrátorské rozhraní AdGuard Home bude pracovat skrze HTTPS a DNS server bude naslouchat požadavky přes DNS skrze HTTPS a DNS skrze TLS.",
-    "encryption_chain_valid": "Certifikační řetězec je platný",
-    "encryption_chain_invalid": "Certifikační řetězec je neplatný",
-    "encryption_key_valid": "Toto je platný {{type}} osobní klíč",
-    "encryption_key_invalid": "Toto je neplatný {{type}} osobní klíč",
+    "encryption_chain_valid": "Certifikační řetězec je platný.",
+    "encryption_chain_invalid": "Certifikační řetězec je neplatný.",
+    "encryption_key_valid": "Toto je platný {{type}} osobní klíč.",
+    "encryption_key_invalid": "Toto je neplatný {{type}} osobní klíč.",
     "encryption_subject": "Subjekt",
     "encryption_issuer": "Vydavatel",
     "encryption_hostnames": "Názvy hostitelů",
     "encryption_reset": "Opravdu chcete obnovit nastavení šifrování?",
     "topline_expiring_certificate": "Váš SSL certifikát brzy vyprší. Aktualizujte <0>Nastavení šifrování</0>.",
     "topline_expired_certificate": "Váš SSL certifikát vypršel. Aktualizujte <0>Nastavení šifrování</0>.",
-    "form_error_port_range": "Zadejte číslo portu v rozmezí 80-65535",
-    "form_error_port_unsafe": "Toto není bezpečný port",
-    "form_error_equal": "Nesmí se shodovat",
-    "form_error_password": "Heslo se neshoduje",
+    "form_error_port_range": "Zadejte číslo portu v rozmezí 80-65535.",
+    "form_error_port_unsafe": "Toto není bezpečný port.",
+    "form_error_equal": "Nesmí se shodovat.",
+    "form_error_password": "Heslo se neshoduje.",
     "reset_settings": "Resetovat nastavení",
     "update_announcement": "AdGuard Home {{version}} je nyní k dispozici! <0>Klikněte zde<0> pro více informací.",
     "setup_guide": "Průvodce nastavením",
     "dns_addresses": "Adresy DNS",
     "dns_start": "Spouští se DNS server",
-    "dns_status_error": "Chyba při kontrole stavu DNS serveru",
+    "dns_status_error": "Chyba při kontrole stavu DNS serveru.",
     "down": "Dolů",
     "fix": "Opravit",
     "dns_providers": "Zde je <0>seznam známých poskytovatelů DNS</0>, z nichž si můžete vybrat.",
@@ -405,8 +405,8 @@
     "update_failed": "Automatická aktualizace selhala. Prosím <a>následujte tyto kroky</a> a aktualizujte ručně.",
     "manual_update": "Prosím <a>následujte tyto kroky</a> a aktualizujte ručně.",
     "processing_update": "Čekejte prosím, AdGuard Home se aktualizuje",
-    "clients_title": "Klienti",
-    "clients_desc": "Konfigurace zařízení připojených k AdGuard Home",
+    "clients_title": "Stálí klienti",
+    "clients_desc": "Konfigurace stálých klientských záznamů pro zařízení připojená k AdGuard Home.",
     "settings_global": "Globální",
     "settings_custom": "Vlastní",
     "table_client": "Klient",
@@ -417,7 +417,7 @@
     "client_edit": "Upravit klienta",
     "client_identifier": "Identifikátor",
     "ip_address": "IP adresa",
-    "client_identifier_desc": "Klienti můžou být identifikováni podle IP adresy, CIDR, MAC adresy nebo speciálního ID klienta (může být použito pro DoT/DoH/DoQ). <0>Zde</0> se můžete dozvědět více o tom, jak klienty identifikovat.",
+    "client_identifier_desc": "Klienti můžou být identifikováni podle jejich IP adresy, CIDR, MAC adresy nebo ID klienta (může být použito pro DoT/DoH/DoQ). <0>Zde</0> se můžete dozvědět více o tom, jak klienty identifikovat.",
     "form_enter_ip": "Zadejte IP",
     "form_enter_subnet_ip": "Zadejte adresu IP adresu do podsítě \"{{cidr}}\"",
     "form_enter_mac": "Zadejte MAC",
@@ -432,14 +432,14 @@
     "clients_not_found": "Nenalezeni žádní klienti",
     "client_confirm_delete": "Opravdu chcete odstranit klienta \"{{key}}\"?",
     "list_confirm_delete": "Opravdu chcete smazat tento seznam?",
-    "auto_clients_title": "Klienti (doba spuštění)",
-    "auto_clients_desc": "Data o klientech, kteří používají AdGuard Home, ale nejsou uloženi v konfiguraci",
+    "auto_clients_title": "Spuštění klienti",
+    "auto_clients_desc": "Zařízení, která nejsou na seznamu stálých klientů, a mohou nadále používat AdGuard Home.",
     "access_title": "Nastavení přístupu",
     "access_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home.",
     "access_allowed_title": "Povolení klienti",
-    "access_allowed_desc": "Seznam CIDR, IP adres nebo ID klientů. Pokud je nakonfigurován, AdGuard Home bude přijímat požadavky pouze od těchto klientů.",
+    "access_allowed_desc": "Seznam CIDR, IP adres nebo <a>ID klientů</a>. Pokud tento seznam obsahuje položky, AdGuard Home bude přijímat požadavky pouze od těchto klientů.",
     "access_disallowed_title": "Blokovaní klienti",
-    "access_disallowed_desc": "Seznam CIDR, IP adres nebo ID klientů. Pokud je nakonfigurován, AdGuard Home bude odmítat požadavky od těchto klientů. Pokud jsou povolení klienti nakonfigurováni, je toto pole ignorováno.",
+    "access_disallowed_desc": "Seznam CIDR, IP adres nebo <a>ID klientů</a>. Pokud tento seznam obsahuje položky, AdGuard Home bude odmítat požadavky od těchto klientů. Pokud jsou povolení klienti nakonfigurováni, je toto pole ignorováno.",
     "access_blocked_title": "Blokované domény",
     "access_blocked_desc": "Nezaměňujte to s filtry. AdGuard Home zruší dotazy DNS odpovídající těmto doménám a tyto dotazy se neobjeví ani v protokolu dotazů. Zde můžete určit přesné názvy domén, zástupné znaky a pravidla filtrování URL adres, např. \"example.org\", \"*.example.org\" nebo \"||example.org^\".",
     "access_settings_saved": "Nastavení přístupu bylo úspěšně uloženo",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Přesměrování DNS",
     "form_domain": "Zadejte doménu",
     "form_answer": "Zadejte IP adresu nebo název domény",
-    "form_error_domain_format": "Neplatný formát domény",
-    "form_error_answer_format": "Neplatný formát odpovědi",
+    "form_error_domain_format": "Neplatný formát domény.",
+    "form_error_answer_format": "Neplatný formát odpovědi.",
     "configure": "Konfigurovat",
     "main_settings": "Hlavní nastavení",
     "block_services": "Blokovat specifické služby",
@@ -507,7 +507,7 @@
     "filter_updated": "Seznam byl úspěšně aktualizován",
     "statistics_configuration": "Konfigurace statistik",
     "statistics_retention": "Uchovávání statistik",
-    "statistics_retention_desc": "Pokud hodnotu intervalu snížíte, některá data budou ztracena",
+    "statistics_retention_desc": "Pokud hodnotu intervalu snížíte, některá data budou ztracena.",
     "statistics_clear": " Vyčistit statistiky",
     "statistics_clear_confirm": "Opravdu chcete vyčistit statistiky?",
     "statistics_retention_confirm": "Opravdu chcete změnit uchovávání statistik? Pokud snížíte hodnotu intervalu, některá data budou ztracena",
@@ -532,7 +532,7 @@
     "netname": "Název sítě",
     "network": "Síť",
     "descr": "Popis",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Další informace</0> o vytváření vlastních seznamů hostitelů.",
     "blocked_by_response": "Zakázáno dle CNAME nebo IP v odpovědi",
     "blocked_by_cname_or_ip": "Zakázáno dle CNAME nebo IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Jsou prováděny následující úlohy: <0>Deaktivace systému DNSStubListener</0> <0>Nastavení adresy serveru DNS na 127.0.0.1</0> <0>Nahrazení cíle symbolického odkazu z /etc/resolv.conf do /run/systemd/resolve/resolv.conf</0> <0>Zastavení služby DNSStubListener (znovu načtení služby systemd-resolved)</0>",
     "autofix_warning_result": "Výsledkem je, že všechny požadavky DNS z vašeho systému jsou ve výchozím nastavení zpracovány službou AdGuard Home.",
     "tags_title": "Značky",
-    "tags_desc": "Můžete vybrat značky, které jsou přiřazeny klientovi. Značky mohou být zahrnuty do pravidel filtrování a umožňují Vám je přesněji použít. <0>Dozvědět se více</0>",
+    "tags_desc": "Můžete vybrat značky, které jsou přiřazeny klientovi. Značky mohou být zahrnuty do pravidel filtrování a umožňují Vám je přesněji použít. <0>Dozvědět se více</0>.",
     "form_select_tags": "Vyberte značky klienta",
     "check_title": "Zkontrolovat filtrování",
-    "check_desc": "Zkontrolujte, zda je název hostitele filtrován",
+    "check_desc": "Zkontrolujte, zda je název hostitele filtrován.",
     "check": "Zkontrolovat",
     "form_enter_host": "Zadejte název hostitele",
     "filtered_custom_rules": "Filtrováno pomocí vlastních pravidel filtrování",
@@ -598,15 +598,15 @@
     "blocklist": "Zakázaný",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Velikost mezipaměti",
-    "cache_size_desc": "Velikost mezipaměti DNS (v bajtech)",
+    "cache_size_desc": "Velikost mezipaměti DNS (v bajtech).",
     "cache_ttl_min_override": "Přepsat minimální hodnotu TTL",
     "cache_ttl_max_override": "Přepsat maximální hodnotu TTL",
     "enter_cache_size": "Zadejte velikost mezipaměti (v bajtech)",
     "enter_cache_ttl_min_override": "Zadejte minimální hodnotu TTL (v sekundách)",
     "enter_cache_ttl_max_override": "Zadejte maximální hodnotu TTL (v sekundách)",
-    "cache_ttl_min_override_desc": "Prodlužte nejkratší hodnotu TTL (v sekundách) obdrženou z odchozího serveru při ukládání DNS odpovědí do mezipaměti",
-    "cache_ttl_max_override_desc": "Nastavte maximální hodnotu TTL (v sekundách) pro položky v mezipaměti DNS",
-    "ttl_cache_validation": "Minimální hodnota TTL mezipaměti musí být menší nebo rovna maximální hodnotě",
+    "cache_ttl_min_override_desc": "Prodlužte nejkratší hodnotu TTL (v sekundách) obdrženou z odchozího serveru při ukládání DNS odpovědí do mezipaměti.",
+    "cache_ttl_max_override_desc": "Nastavte maximální hodnotu TTL (v sekundách) pro položky v mezipaměti DNS.",
+    "ttl_cache_validation": "Minimální přepis TTL mezipaměti musí být menší nebo roven maximální hodnotě.",
     "cache_optimistic": "Optimistické ukládání do mezipaměti",
     "cache_optimistic_desc": "Nechte AdGuard Home odpovědět z mezipaměti, i když už platnost položek skončila. Také se je pokuste obnovit.",
     "filter_category_general": "Obecné",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home zruší všechny DNS dotazy tohoto klienta.",
     "filter_allowlist": "VAROVÁNÍ: Tato akce také vyloučí pravidlo \"{{disallowed_rule}}\" ze seznamu povolených klientů.",
     "last_rule_in_allowlist": "Nelze zakázat tohoto klienta, protože vyloučení pravidla \"{{disallowed_rule}}\" ZRUŠÍ seznam \"Povolených klientů\".",
-    "experimental": "Experimentální",
     "use_saved_key": "Použít dříve uložený klíče",
     "parental_control": "Rodičovská ochrana",
     "safe_browsing": "Bezpečné prohlížení",
-    "served_from_cache": "{{value}} <i>(převzato z mezipaměti)</i>"
+    "served_from_cache": "{{value}} <i>(převzato z mezipaměti)</i>",
+    "form_error_password_length": "Heslo musí být alespoň {{value}} znaků dlouhé."
 }
diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json
index 04d8a393..fb67607d 100644
--- a/client/src/__locales/da.json
+++ b/client/src/__locales/da.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Klientindstillinger",
-    "example_upstream_reserved": "DNS-upstream kan agives <0>for et eller flere specifikke domæner</0>",
-    "example_upstream_comment": "Du kan angive en kommentaren",
+    "example_upstream_reserved": "en upstream <0>for bestemte domæner</0>;",
+    "example_upstream_comment": "en kommentaren.",
     "upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.",
     "parallel_requests": "Parallelle forespørgsler",
     "load_balancing": "Belastningsfordeling",
@@ -37,21 +37,21 @@
     "dhcp_ipv6_settings": "DHCP IPv6-indstillinger",
     "form_error_required": "Obligatorisk felt",
     "form_error_ip4_format": "Ugyldig IPv4-adresse",
-    "form_error_ip4_range_start_format": "Ugyldig IPv4-adresse for områdestart",
-    "form_error_ip4_range_end_format": "Ugyldig IPv4-adresse for områdeafslutning",
-    "form_error_ip4_gateway_format": "Ugyldig IPv4-adresse for gateway",
-    "form_error_ip6_format": "Ugyldig IPv6-adresse",
-    "form_error_ip_format": "Ugyldig IP-adresse",
-    "form_error_mac_format": "Ugyldig MAC-adresse",
-    "form_error_client_id_format": "Klient-ID må kun indeholde cifre, minuskler og bindestreger",
-    "form_error_server_name": "Ugyldigt servernavn",
-    "form_error_subnet": "Subnet \"{{cidr}}\" indeholder ikke IP-adressen \"{{ip}}\"",
-    "form_error_positive": "Skal være større end 0",
-    "out_of_range_error": "Skal være uden for området \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Skal være mindre end starten på området",
-    "greater_range_start_error": "Skal være større end starten på ​​området",
-    "greater_range_end_error": "Skal være større end slutningen på ​​området",
-    "subnet_error": "Adresser ska være i ét undernet",
+    "form_error_ip4_range_start_format": "Ugyldig IPv4-startadresse for området.",
+    "form_error_ip4_range_end_format": "Ugyldig IPv4-slutadresse for området.",
+    "form_error_ip4_gateway_format": "Ugyldig IPv4 gateway-adresse.",
+    "form_error_ip6_format": "Ugyldig IPv6-adresse.",
+    "form_error_ip_format": "Ugyldig IP-adresse.",
+    "form_error_mac_format": "Ugyldig MAC-adresse.",
+    "form_error_client_id_format": "KlientID må kun indeholde cifre, minuskler og bindestreger.",
+    "form_error_server_name": "Ugyldigt servernavn.",
+    "form_error_subnet": "Undernet \"{{cidr}}\" indeholder ikke IP-adressen \"{{ip}}\".",
+    "form_error_positive": "Skal være større end 0.",
+    "out_of_range_error": "Skal være uden for området \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Skal være mindre end starten på området.",
+    "greater_range_start_error": "Skal være større end starten på ​​området.",
+    "greater_range_end_error": "Skal være større end områdeslutning.",
+    "subnet_error": "Adresser ska være i ét undernet.",
     "gateway_or_subnet_invalid": "Undernetmaske ugyldig",
     "dhcp_form_gateway_input": "Gateway IP",
     "dhcp_form_subnet_input": "Undernetmaske",
@@ -197,23 +197,23 @@
     "enter_valid_blocklist": "Angiv en gyldig URL til sortlisten.",
     "enter_valid_allowlist": "Angiv en gyldig URL til hvidlisten.",
     "form_error_url_format": "Ugyldigt URL-format",
-    "form_error_url_or_path_format": "Ugyldig URL eller absolut listesti",
+    "form_error_url_or_path_format": "Ugyldig URL eller absolut sti til liste.",
     "custom_filter_rules": "Tilpassede filtreringsregler",
     "custom_filter_rules_hint": "Angiv én regel pr. linje. Du kan bruge enten adblockingregler eller værtsfilsyntaks.",
     "system_host_files": "System hosts-filer",
     "examples_title": "Eksempler",
-    "example_meaning_filter_block": "blokér adgang til example.org-domænet samt alle underdomæner",
-    "example_meaning_filter_whitelist": "afblokér adgang til example.ord-domænet samt alle underdomæner",
-    "example_meaning_host_block": "AdGuard Home returnerer nu adressen 127.0.0.1 for example.org-domænet (men ikke underdomænerne).",
-    "example_comment": "! Hér angives en evt. kommentar",
-    "example_comment_meaning": "bare en kommentar",
-    "example_comment_hash": "# Også en kommentar",
+    "example_meaning_filter_block": "blokér adgang til eksmpel.dk-domænet og alle underdomæner;",
+    "example_meaning_filter_whitelist": "afblokér adgang til eksempel.dk-domænet og alle underdomæner;",
+    "example_meaning_host_block": "besvar med 127.0.0.1 for eksempel.dk-domænet (men ikke underdomænerne);",
+    "example_comment": "! Hér angives en kommentar.",
+    "example_comment_meaning": "kun en kommentar;",
+    "example_comment_hash": "# Også en kommentar.",
     "example_regex_meaning": "blokér adgang til domæner matchernde det angivne regulære udtryk",
     "example_upstream_regular": "almindelig DNS (over UDP)",
     "example_upstream_dot": "krypteret <0>DNS-over-TLS</0>",
     "example_upstream_doh": "krypteret <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "krypteret <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "du kan bruge <0>DNS Stamps</0> til <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-resolvers",
+    "example_upstream_doq": "krypteret <0>DNS-over-QUIC</0>(eksperimentel);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> til <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-opløsere;",
     "example_upstream_tcp": "almindelig DNS (over TCP)",
     "all_lists_up_to_date_toast": "Alle lister er allerede opdaterede",
     "updated_upstream_dns_toast": "Upstream-servere er gemt",
@@ -262,7 +262,7 @@
     "anonymize_client_ip_desc": "Gem ikke klientens fulde IP-adresse i logfiler og statistikker",
     "dns_config": "DNS-serveropsætning",
     "dns_cache_config": "DNS-cacheopsætning",
-    "dns_cache_config_desc": "Hér kan du opsætte DNS-cache",
+    "dns_cache_config_desc": "Hér kan DNS-cache opsættes.",
     "blocking_mode": "Blokeringstilstand",
     "default": "Standard",
     "nxdomain": "NXDOMAIN",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-Quic",
-    "client_id": "Klient-ID",
-    "client_id_placeholder": "Angiv klient-ID",
-    "client_id_desc": "Forskellige klienter kan identificeres via et specielt klient-ID. <a>Hér</a> finder du ud af mere om, hvordan klienter identificeres.",
+    "client_id": "KlientID",
+    "client_id_placeholder": "Angiv en KlientID",
+    "client_id_desc": "Klienter kan identificeres via KlientID. Læs mere om, hvordan klienter identificeres <a>hér</a>.",
     "download_mobileconfig_doh": "Download .mobileconfig til DNS-over-HTTPS",
     "download_mobileconfig_dot": "Download .mobileconfig til DNS-over-TLS",
     "download_mobileconfig": "Download opsætningsfil",
@@ -334,11 +334,11 @@
     "install_devices_router_list_4": "På visse routertyper kan en tilpasset DNS-server ikke opsættes. I så tilfælde kan det hjælpe, hvis du opsætter AdGuard Home som en <0>DHCP-server</0>. Ellers bør du tjekke i routermanualen, hvordan du tilpasser DNS-servere i din givne routermodel.",
     "install_devices_windows_list_1": "Åbn Kontrolpanel via menuen Start eller Windows-søgning.",
     "install_devices_windows_list_2": "Gå til kategorien Netværk og Internet og derefter til Netværks- og delingscenter.",
-    "install_devices_windows_list_3": "Find punktet \"Skift adapterindstillinger\" til venstre på skærmen, klik på det.",
-    "install_devices_windows_list_4": "Vælg din aktive forbindelse, højreklik på den og vælg Egenskaber.",
+    "install_devices_windows_list_3": "Find og klik på \"Skift adapterindstillinger\" i venstre panel.",
+    "install_devices_windows_list_4": "Højreklik på den aktive forbindelse, og vælg Egenskaber.",
     "install_devices_windows_list_5": "Find \"Internet Protocol Version 4 (TCP/IPv4)\" (eller for IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\") på listen, vælg den og klik derefter på Egenskaber igen.",
     "install_devices_windows_list_6": "Vælg \"Brug følgende DNS-serveradresser og angiv dine AdGuard Home-serveradresser.",
-    "install_devices_macos_list_1": "Klik på Apple-ikonet og gå til Systemindstillinger.",
+    "install_devices_macos_list_1": "Klik på Apple-ikonet og gå til Systempræferencer.",
     "install_devices_macos_list_2": "Klik på Netværk.",
     "install_devices_macos_list_3": "Vælg den første forbindelse på din liste, og klik på Avanceret.",
     "install_devices_macos_list_4": "Vælg fanen DNS og angiv dine AdGuard Home-serveradresser.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Åbn Dashboard",
     "install_saved": "Gemt",
     "encryption_title": "Kryptering",
-    "encryption_desc": "Kryptering (HTTPS/TLS) understøtter både DNS og admin webgrænseflade",
+    "encryption_desc": "Krypteringsunderstøttelse (HTTPS/TLS) til både DNS og admin-webgrænseflade.",
     "encryption_config_saved": "Krypteringsopsætning gemt",
     "encryption_server": "Servernavn",
     "encryption_server_enter": "Angiv dit domænenavn",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Er HTTPS-porten opsat, vil AdGuard Home admin grænsefladen være tilgængelig via HTTPS, og den vil muliggøre DNS-over-HTTPS på '/dns-query' placeringen.",
     "encryption_dot": "DNS-over-TLS port",
     "encryption_dot_desc": "Er denne port opsat, vil AdGuard Home køre en DNS-over-TLS server på denne port.",
-    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq": "DNS-over-QUIC port (eksperimentel)",
     "encryption_doq_desc": "Er denne port opsat, vil AdGuard Home køre en DNS-over-QUIC server på denne port. Den er eksperimentel og er måske ikke pålidelig. Derudover understøttes den pt. heller ikke af ret mange klienter.",
     "encryption_certificates": "Certifikater",
     "encryption_certificates_desc": "For at kunne bruge kryptering skal du angive en gyldig SSL-certifikatkæde til dit domæne. Du kan få et gratis certifikat via <0>{{link}}</ 0>, eller du kan købe det via en af de betroede Certifikatmyndigheder.",
@@ -390,14 +390,14 @@
     "topline_expired_certificate": "Dit SSL-certifikat er udløbet. Opdatér <0>Krypteringsindstillinger</0>.",
     "form_error_port_range": "Angiv portnummer i intervallet 80-65535",
     "form_error_port_unsafe": "Dette er en usikker port",
-    "form_error_equal": "Må ikke være ens",
-    "form_error_password": "Adgangskoden matcher ikke",
+    "form_error_equal": "Må ikke svare til.",
+    "form_error_password": "Adgangskoder matcher ikke.",
     "reset_settings": "Nulstil indstillinger",
     "update_announcement": "AdGuard Home {{version}} er nu tilgængelig! <0>Kik hér</0> for mere info.",
     "setup_guide": "Installationsvejledning",
     "dns_addresses": "DNS-adresser",
     "dns_start": "DNS-server starter",
-    "dns_status_error": "Fejl ved tjek af DNS-serverstatus",
+    "dns_status_error": "Fejl under tjek af DNS-serverstatus.",
     "down": "Ned",
     "fix": "Korrigér",
     "dns_providers": "Her er en <0>liste over kendte DNS-udbydere</ 0> at vælge imellem.",
@@ -405,8 +405,8 @@
     "update_failed": "Autoopdatering mislykkedes. Følg <a>disse trin</a> for at opdatere manuelt.",
     "manual_update": "<a>Følg disse trin</a> for at opdatere manuelt.",
     "processing_update": "Vent venligst, AdGuard Home bliver opdateret",
-    "clients_title": "Klienter",
-    "clients_desc": "Opsæt enheder forbundet til AdGuard Home",
+    "clients_title": "Blivende klienter",
+    "clients_desc": "Opsæt blivende klientposter for enheder tilsluttet AdGuard Home.",
     "settings_global": "Global",
     "settings_custom": "Tilpasset",
     "table_client": "Klient",
@@ -417,7 +417,7 @@
     "client_edit": "Redigér Klient",
     "client_identifier": "Identifikator",
     "ip_address": "IP-adresse",
-    "client_identifier_desc": "Klienter kan identificeres ud fra IP-/MAC-adresser, CIDR eller et særligt klient-ID (kan bruges til DoT/DoH/DoQ). <0>Hér</0> kan du læse mere om, hvordan klienter identificeres.",
+    "client_identifier_desc": "Klienter kan identificeres ud fra IP-/MAC-adresser, CIDR eller et særligt KlientID (kan bruges til DoT/DoH/DoQ). Læs mere om, hvordan klienter identificeres <0>hér</0>.",
     "form_enter_ip": "Angiv IP",
     "form_enter_subnet_ip": "Indtast en IP-adresse i subnettet \"{{cidr}}\"",
     "form_enter_mac": "Angiv MAC",
@@ -433,13 +433,13 @@
     "client_confirm_delete": "Sikker på, at du vil slette klient \"{{key}}\"?",
     "list_confirm_delete": "Sikker på, at du vil slette denne liste?",
     "auto_clients_title": "Klienter (runtime)",
-    "auto_clients_desc": "Data om de klienter, som bruger AdGuard Home, men ikke gemt i opsætningen",
+    "auto_clients_desc": "Enheder, som ikke er på listen over Blivende klienter, som stadig kan bruge AdGuard Home.",
     "access_title": "Adgangsindstillinger",
     "access_desc": "Her kan du opsætte adgangsregler for AdGuard Home DNS-serveren.",
     "access_allowed_title": "Tilladte klienter",
-    "access_allowed_desc": "En liste over CIDR-, IP-adresser eller klient-ID'er. Hvis opsat, accepterer AdGuard Home kun forespørgsler fra disse klienter.",
+    "access_allowed_desc": "En liste over CIDR'er, IP-adresser eller <a>KlientID'er</a>. Har listen poster, accepterer AdGuard Home kun forespørgsler fra disse klienter.",
     "access_disallowed_title": "Ikke tilladte klienter",
-    "access_disallowed_desc": "En liste over CIDR-, IP-adresser eller klient-ID'er. Hvis opsat, dropper AdGuard Home forespørgsler fra disse klienter. Opsættes tilladte klienter, ignoreres dette felt.",
+    "access_disallowed_desc": "En liste over CIDR'er, IP-adresser eller <a>KlientID'er</a>. Har listen poster, dropper AdGuard Home forespørgsler fra disse klienter. Har Tilladte klienter poster, ignoreres dette felt.",
     "access_blocked_title": "Ikke tilladte domæner",
     "access_blocked_desc": "Ikke at forveksle med filtre. AdGuard Home dropper DNS-forespørgsler matchende disse domæner, ej heller vil forespørgslerne optræde i forespørgselsloggen. Der kan angives præcise domænenavne, jokertegn eller URL-filterregler, f.eks. \"eksempel.org\", \"*.eksempel.org\", \"||eksempel.org^\" eller tilsvarende.",
     "access_settings_saved": "Adgangsindstillinger gemt",
@@ -532,7 +532,7 @@
     "netname": "Netværksnavn",
     "network": "Netværk",
     "descr": "Beskrivelse",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Læs mere</0> om at oprette dine egne værtslister.",
     "blocked_by_response": "Blokeret af CNAME eller IP i svar",
     "blocked_by_cname_or_ip": "Blokeret af CNAME eller IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Den vil udføre disse opgaver: <0>Deaktivere system DNSStubListener</0> <0>Opsætte DNS-serveradressen til 127.0.0.1</0> <0>Erstatte symbolsk linkmål for /etc/resolv.conf med /run/systemd/resolve/resolv.conf</0> <0>Stoppe DNSStubListener (genindlæs systemd-opløst tjeneste)</0>",
     "autofix_warning_result": "Det betyder, at alle DNS-forespørgsler fra dit system som standard behandles af AdGuard Home.",
     "tags_title": "Tags",
-    "tags_desc": "Du kan vælge de tags, som svarer til klienten. Tags kan inkluderes i filtreringsreglerne, hvilket lader anvende dem mere præcist. <0>Læs mere</0>",
+    "tags_desc": "Der kan vælges tags, som svarer til klienten. Medtag tags i filtreringsregler for at anvende dem mere præcist. <0>Læs mere</0>.",
     "form_select_tags": "Vælg klient tags",
     "check_title": "Tjek filtreringen",
-    "check_desc": "Tjek, om værtsnavnet er filtreret",
+    "check_desc": "Tjek, om værtsnavnet filtreres.",
     "check": "Tjek",
     "form_enter_host": "Angiv et værtsnavn",
     "filtered_custom_rules": "Filtreret af tilpassede filtreringsregler",
@@ -598,14 +598,14 @@
     "blocklist": "Sortliste",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Cache-størrelse",
-    "cache_size_desc": "DNS-cache størrelse (i bytes)",
+    "cache_size_desc": "DNS cache-størrelse (i bytes).",
     "cache_ttl_min_override": "Tilsidesæt minimum TTL",
     "cache_ttl_max_override": "Tilsidesæt maksimal TTL",
     "enter_cache_size": "Angiv cache-størrelse (bytes)",
     "enter_cache_ttl_min_override": "Angiv minimum TTL (sekunder)",
     "enter_cache_ttl_max_override": "Angiv maksimum TTL (sekunder)",
     "cache_ttl_min_override_desc": "Forlæng korte time-to-live værdier (sekunder) modtaget fra upstream-serveren, når DNS-svar cachelagres",
-    "cache_ttl_max_override_desc": "Indstil en maksimal time-to-live (sekunder) for registreringer i DNS-cachen",
+    "cache_ttl_max_override_desc": "Angiv en maksimal time-to-live (sekunder) for poster i DNS-cachen.",
     "ttl_cache_validation": "Minimum cache TTL-værdi skal være mindre end eller lig med den maksimale værdi",
     "cache_optimistic": "Optimistisk caching",
     "cache_optimistic_desc": "Får AdGuard Home til at svare fra cachen, selv når posterne er udløbet, og prøver også at opdatere dem.",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home vil afbryde alle DNS-forespørgsler fra denne klient.",
     "filter_allowlist": "ADVARSEL: Denne handling udelukker også reglen \"{{disallowed_rule}}\" fra listen over tilladte klienter.",
     "last_rule_in_allowlist": "Kan ikke afvise denne klient, da udelukkelse af reglen \"{{disallowed_rule}}\" DEAKTIVERER listen \"Tilladte klienter\".",
-    "experimental": "Eksperimentel",
     "use_saved_key": "Brug den tidligere gemte nøgle",
     "parental_control": "Forældrekontrol",
     "safe_browsing": "Sikker Browsing",
-    "served_from_cache": "{{value}} <i>(leveret fra cache)</i>"
+    "served_from_cache": "{{value}} <i>(leveret fra cache)</i>",
+    "form_error_password_length": "Adgangskoden skal udgøre mindst {{value}} tegn."
 }
diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json
index 4fb4cd02..fc9f5878 100644
--- a/client/src/__locales/de.json
+++ b/client/src/__locales/de.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Client-Einstellungen",
-    "example_upstream_reserved": "Sie können DNS-Upstream <0>für bestimmte Domain(s)</0> angeben",
-    "example_upstream_comment": "Sie können den Kommentar angeben",
+    "example_upstream_reserved": "ein Upstream <0>für bestimmte Domains</0>;",
+    "example_upstream_comment": "ein Kommentar.",
     "upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.",
     "parallel_requests": "Paralleles Abfragen",
     "load_balancing": "Lastverteilung",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP-Konfiguration erfolgreich gespeichert",
     "dhcp_ipv4_settings": "DHCP-IPv4-Einstellungen",
     "dhcp_ipv6_settings": "DHCP-IPv6-Einstellungen",
-    "form_error_required": "Pflichtfeld",
-    "form_error_ip4_format": "Ungültige IPv4-Adresse",
-    "form_error_ip4_range_start_format": "Ungültiger Bereichsbeginn der IPv4-Adresse",
-    "form_error_ip4_range_end_format": "Ungültiges Bereichsende der IPv4-Adresse",
-    "form_error_ip4_gateway_format": "Ungültiges Gateway-IPv4-Adresse",
-    "form_error_ip6_format": "Ungültige IPv6-Adresse",
-    "form_error_ip_format": "Ungültige IP-Adresse",
-    "form_error_mac_format": "Ungültige MAC-Adresse",
-    "form_error_client_id_format": "Client-ID muss nur Zahlen, Kleinbuchstaben und Bindestriche enthalten",
-    "form_error_server_name": "Ungültiger Servername",
-    "form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“",
-    "form_error_positive": "Muss größer als 0 (Null) sein",
-    "out_of_range_error": "Muss außerhalb des Bereichs „{{start}}“-„{{end}}“ liegen",
-    "lower_range_start_error": "Muss niedriger als der Bereichsbeginn sein",
-    "greater_range_start_error": "Muss größer als der Bereichsbeginn sein",
-    "greater_range_end_error": "Muss größer als das Bereichsende sein",
-    "subnet_error": "Die Adressen müssen innerhalb eines Subnetzes liegen",
-    "gateway_or_subnet_invalid": "Ungültige Subnetzmaske",
+    "form_error_required": "Pflichtfeld.",
+    "form_error_ip4_format": "Ungültige IPv4-Adresse.",
+    "form_error_ip4_range_start_format": "Ungültige IPv4-Adresse des Bereichsbeginns.",
+    "form_error_ip4_range_end_format": "Ungültige IPv4-Adresse des Bereichsendes.",
+    "form_error_ip4_gateway_format": "Ungültige IPv4-Adresse des Gateways.",
+    "form_error_ip6_format": "Ungültige IPv6-Adresse.",
+    "form_error_ip_format": "Ungültige IP-Adresse.",
+    "form_error_mac_format": "Ungültige MAC-Adresse.",
+    "form_error_client_id_format": "Client-ID muss nur Zahlen, Kleinbuchstaben und Bindestriche enthalten.",
+    "form_error_server_name": "Ungültiger Servername.",
+    "form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“.",
+    "form_error_positive": "Muss größer als 0 sein.",
+    "out_of_range_error": "Muss außerhalb des Bereichs „{{start}}“-„{{end}}“ liegen.",
+    "lower_range_start_error": "Muss niedriger als der Bereichsbeginn sein.",
+    "greater_range_start_error": "Muss größer als der Bereichsbeginn sein.",
+    "greater_range_end_error": "Muss größer als das Bereichsende sein.",
+    "subnet_error": "Die Adressen müssen innerhalb eines Subnetzes liegen.",
+    "gateway_or_subnet_invalid": "Ungültige Subnetzmaske.",
     "dhcp_form_gateway_input": "Gateway-IP",
     "dhcp_form_subnet_input": "Subnetz-Maske",
     "dhcp_form_range_title": "Bereich von IP-Adressen",
@@ -187,7 +187,7 @@
     "cancel_btn": "Abbrechen",
     "enter_name_hint": "Name eingeben",
     "enter_url_or_path_hint": "URL oder absoluten Pfad der Liste eingeben",
-    "check_updates_btn": "Nach Updates suchen",
+    "check_updates_btn": "Nach Aktualisierungen suchen",
     "new_blocklist": "Neue Sperrliste",
     "new_allowlist": "Neue Freigabeliste",
     "edit_blocklist": "Sperrliste bearbeiten",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Freigabeliste wählen",
     "enter_valid_blocklist": "Gültige Webadresse zur Sperrliste eingeben.",
     "enter_valid_allowlist": "Gültige Webadresse zur Freigabeliste eingeben.",
-    "form_error_url_format": "Ungültiges URL-Format",
+    "form_error_url_format": "Ungültiges URL-Format.",
     "form_error_url_or_path_format": "Ungültige URL oder absoluter Pfad der Liste",
     "custom_filter_rules": "Benutzerdefinierte Filterregeln",
     "custom_filter_rules_hint": "Geben Sie pro Zeile eine Regel ein. Sie können entweder Werbefilterregeln oder Host-Datei-Syntax verwenden.",
     "system_host_files": "Hosts-Datei des Systems",
     "examples_title": "Beispiele",
-    "example_meaning_filter_block": "blockiert den Zugang zur Domain example.org und all ihren Subdomains",
-    "example_meaning_filter_whitelist": "entblockt den Zugang zur Domain example.org und all ihren Subdomains",
-    "example_meaning_host_block": "AdGuard Home wird jetzt die Adresse 127.0.0.1 für die Domain example.org zurückgeben (aber nicht für die Subdomains).",
-    "example_comment": "! Hier steht ein Kommentar",
-    "example_comment_meaning": "Nur ein Kommentar",
-    "example_comment_hash": "# Auch ein Kommentar",
-    "example_regex_meaning": "Zugriff auf die Domains sperren, die dem <0>angegebenen regulären Ausdruck</0> entsprechen",
-    "example_upstream_regular": "regulärer DNS (über UDP)",
-    "example_upstream_dot": "verschlüsseltes <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "verschlüsseltes <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "verschlüsseltes <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "Sie können <0>DNS-Stempel</0> für <1>DNSCrypt</1> oder <2>DNS-over-HTTPS</2> Resolver benutzen",
-    "example_upstream_tcp": "regulärer DNS (über TCP)",
+    "example_meaning_filter_block": "Zugriff auf die Domain example.org und alle ihre Subdomains sperren;",
+    "example_meaning_filter_whitelist": "Zugriff auf die Domain example.org und alle ihre Subdomains entsperren;",
+    "example_meaning_host_block": "Adresse 127.0.0.1 für die Domain example.org zurückgeben (aber nicht für ihre Subdomains);",
+    "example_comment": "! Hier steht ein Kommentar.",
+    "example_comment_meaning": "nur ein Kommentar;",
+    "example_comment_hash": "# Auch ein Kommentar.",
+    "example_regex_meaning": "Zugriff auf die Domains sperren, die dem angegebenen regulären Ausdruck entsprechen.",
+    "example_upstream_regular": "reguläres DNS (over UDP);",
+    "example_upstream_dot": "verschlüsseltes <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "verschlüsseltes <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "verschlüsseltes <0>DNS-over-QUIC</0> (experimentell);",
+    "example_upstream_sdns": "<0>DNS-Stempel</0> für <1>DNSCrypt</1> oder <2>DNS-over-HTTPS</2> Resolver;",
+    "example_upstream_tcp": "reguläres DNS (over TCP);",
     "all_lists_up_to_date_toast": "Alle Listen sind bereits auf dem neuesten Stand",
     "updated_upstream_dns_toast": "Upstream-Server erfolgreich gespeichert",
     "dns_test_ok_toast": "Angegebene DNS-Server arbeiten ordnungsgemäß",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Doppelte Anführungszeichen für die strikte Suche verwenden",
     "query_log_retention_confirm": "Möchten Sie die Aufbewahrung des Abfrageprotokolls wirklich ändern? Wenn Sie den Zeitabstand verringern, gehen einige Daten verloren.",
     "anonymize_client_ip": "Client-IP anonymisieren",
-    "anonymize_client_ip_desc": "Vollständige IP-Adresse des Clients nicht in Protokollen und Statistiken speichern",
+    "anonymize_client_ip_desc": "Vollständige IP-Adresse des Clients nicht in Protokollen und Statistiken speichern.",
     "dns_config": "DNS-Serverkonfiguration",
-    "dns_cache_config": "Konfiguration des DNS-Zwischenspeicher",
-    "dns_cache_config_desc": "Hier können Sie den DNS-Zwischenspeicher konfigurieren",
+    "dns_cache_config": "Konfiguration des DNS-Cache",
+    "dns_cache_config_desc": "Hier können Sie den DNS-Cache konfigurieren.",
     "blocking_mode": "Sperrmodus",
     "default": "Standard",
     "nxdomain": "NXDomain",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Netzwerk-Schnittstelle\n",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Ihre AdGuard Home Admin-Weboberfläche ist unter den folgenden Adressen verfügbar:",
-    "form_error_port": "Gültige Portnummer eingeben",
+    "form_error_port": "Geben Sie eine gültige Portnummer ein.",
     "install_settings_dns": "DNS-Server",
     "install_settings_dns_desc": "Sie müssen Ihre Geräte oder Ihren Router so konfigurieren, dass er den DNS-Server unter den folgenden Adressen verwendet:",
     "install_settings_all_interfaces": "Alle Schnittstellen",
@@ -334,12 +334,12 @@
     "install_devices_router_list_4": "Bei einigen Routertypen kann kein eigener DNS-Server eingerichtet werden. In diesem Fall kann es helfen, AdGuard Home als <0>DHCP-Server</0> einzurichten. Andernfalls sollten Sie im Handbuch des Routers nachsehen, wie Sie DNS-Server auf Ihrem konkreten Router-Modell anpassen können.",
     "install_devices_windows_list_1": "Öffnen Sie die Systemsteuerung über das Startmenü oder die Windows-Suche.",
     "install_devices_windows_list_2": "Öffnen Sie die Kategorie „Netzwerk und Internet“ und dann „Netzwerk- und Freigabecenter“.",
-    "install_devices_windows_list_3": "Suchen Sie auf der linken Seite des Bildschirms nach „Adaptereinstellungen ändern“ und klicken Sie darauf.",
-    "install_devices_windows_list_4": "Wählen Sie Ihre aktive Verbindung aus, klicken Sie mit der rechten Maustaste darauf und wählen Sie „Eigenschaften“.",
+    "install_devices_windows_list_3": "Klicken Sie in der linken Leiste auf „Adaptereinstellungen ändern“.",
+    "install_devices_windows_list_4": "Klicken Sie mit der rechten Maustaste auf Ihre aktive Verbindung und wählen Sie „Eigenschaften“.",
     "install_devices_windows_list_5": "Suchen Sie in der Liste nach „Internet Protokoll Version 4 (TCP/IP)“ (oder, für IPv6, „Internet Protocol Version 6 (TCP/IPv6)“), markieren Sie diese und klicken Sie dann erneut auf „Eigenschaften“.",
     "install_devices_windows_list_6": "Wählen Sie „Folgende DNS-Serveradressen verwenden“ und geben Sie Ihre AdGuard Home-Serveradressen ein.",
-    "install_devices_macos_list_1": "Klicken Sie auf das Apple-Symbol (oben links in der Menüzeile) und wählen den Eintrag „Systemeinstellungen“.",
-    "install_devices_macos_list_2": "Klicken Sie dort auf „Netzwerk“",
+    "install_devices_macos_list_1": "Klicken Sie auf das Apple-Symbol und gehen Sie zu Systemeinstellungen.",
+    "install_devices_macos_list_2": "Klicken Sie auf „Netzwerk“.",
     "install_devices_macos_list_3": "Wählen Sie die erste Verbindung in Ihrer Liste aus und klicken Sie auf „Weitere Optionen“.",
     "install_devices_macos_list_4": "Wählen Sie den Tab „DNS“ und geben Sie dort Ihre AdGuard Home-Serveradressen ein.",
     "install_devices_android_list_1": "Tippen Sie auf dem Startbildschirm des Android-Menüs auf „Einstellungen“.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Übersicht öffnen",
     "install_saved": "Erfolgreich gespeichert",
     "encryption_title": "Verschlüsselung",
-    "encryption_desc": "Verschlüsselungsunterstützung (HTTPS/TLS) für DNS- und Admin-Weboberfläche",
+    "encryption_desc": "Verschlüsselungsunterstützung (HTTPS/TLS) für DNS- und Admin-Weboberfläche.",
     "encryption_config_saved": "Verschlüsselungskonfiguration gespeichert",
     "encryption_server": "Servername",
     "encryption_server_enter": "Domain-Namen eingeben",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Wenn der HTTPS-Port konfiguriert ist, ist die AdGuard Home-Administrationsschnittstelle über HTTPS zugänglich und bietet auch DNS-over-HTTPS am Server „/dns-query“.",
     "encryption_dot": "DNS-over-TLS",
     "encryption_dot_desc": "Wenn dieser Port konfiguriert ist, führt AdGuard Home auf diesem Port einen DNS-over-TLS-Server aus.",
-    "encryption_doq": "Port für DNS-over-QUIC",
+    "encryption_doq": "Port für DNS-over-QUIC (experimentell)",
     "encryption_doq_desc": "Wenn dieser Port eingerichtet ist, wird AdGuard Home einen DNS-over-QUIC-Server auf diesem Port ausführen. Es ist experimentell und möglicherweise nicht zuverlässig. Außerdem gibt es im Moment nicht allzu viele Clients, die ihn unterstützen.",
     "encryption_certificates": "Zertifikate",
     "encryption_certificates_desc": "Um die Verschlüsselung verwenden zu können, müssen Sie eine gültige SSL-Zertifikatskette für Ihre Domain angeben. Sie können ein kostenloses Zertifikat für <0>{{link}}</0> erhalten oder es bei einer der vertrauenswürdigen Zertifizierungsstellen kaufen.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Kopieren Sie Ihren PEM-codierten privaten Schlüssel für Ihr Zertifikat und fügen Sie ihn hier ein.",
     "encryption_enable": "Verschlüsselung aktivieren (HTTPS, DNS-over-HTTPS und DNS-over-TLS)",
     "encryption_enable_desc": "Wenn die Verschlüsselung aktiviert ist, funktioniert die AdGuard Home Admin-Oberfläche über HTTPS, und der DNS-Server wartet auf Anfragen über DNS-over-HTTPS und DNS-over-TLS.",
-    "encryption_chain_valid": "Zertifikatskette ist gültig",
-    "encryption_chain_invalid": "Zertifikatskette ist ungültig",
-    "encryption_key_valid": "Das ist ein gültiger {{type}} privater Schlüssel",
-    "encryption_key_invalid": "Das ist ein ungültiger {{type}} privater Schlüssel",
+    "encryption_chain_valid": "Zertifikatskette ist gültig.",
+    "encryption_chain_invalid": "Zertifikatskette ist ungültig.",
+    "encryption_key_valid": "Dies ist ein gültiger {{type}} privater Schlüssel.",
+    "encryption_key_invalid": "Dies ist ein ungültiger {{type}} privater Schlüssel.",
     "encryption_subject": "Ausgestellt für",
     "encryption_issuer": "Ausgestellt von",
     "encryption_hostnames": "Hostnamen",
     "encryption_reset": "Möchten Sie die Verschlüsselungseinstellungen wirklich zurücksetzen?",
     "topline_expiring_certificate": "Ihr SSL-Zertifikat läuft demnächst ab. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.",
     "topline_expired_certificate": "Ihr SSL-Zertifikat ist abgelaufen. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.",
-    "form_error_port_range": "Port zwischen 80 und 65535 eingeben",
-    "form_error_port_unsafe": "Dies ist ein unsicherer Port",
-    "form_error_equal": "Sollten nicht übereinstimmen",
-    "form_error_password": "Passwörter stimmen nicht überein",
+    "form_error_port_range": "Geben Sie die Portnummer zwischen 80 und 65535 ein.",
+    "form_error_port_unsafe": "Dies ist ein unsicherer Port.",
+    "form_error_equal": "Sollten nicht übereinstimmen.",
+    "form_error_password": "Passwörter stimmen nicht überein.",
     "reset_settings": "Einstellungen zurücksetzen",
     "update_announcement": "AdGuard Home {{version}} ist jetzt verfügbar! <0>Klicken Sie hier</0> für weitere Informationen.",
     "setup_guide": "Einrichtungsassistent",
     "dns_addresses": "DNS-Adressen",
     "dns_start": "DNS-Server wird gestartet",
-    "dns_status_error": "Fehler bei Statusabfrage des DNS-Server",
+    "dns_status_error": "Fehler bei Statusabfrage des DNS-Server.",
     "down": "Nicht erreichbar",
     "fix": "Beheben",
     "dns_providers": "Hier finden Sie eine <0>Liste der bekannten DNS-Anbieter</0> zur Auswahl.",
@@ -405,8 +405,8 @@
     "update_failed": "Das automatische Aktualisieren ist fehlgeschlagen. Bitte <a>folgen Sie den Schritten</a>, um manuell zu aktualisieren.",
     "manual_update": "Bitte <a>befolgen Sie diese Schritte</a>, um manuell zu aktualisieren.",
     "processing_update": "Bitte warten Sie, AdGuard Home wird aktualisiert …",
-    "clients_title": "Clients",
-    "clients_desc": "Geräte einrichten, die mit AdGuard Home verbunden sind",
+    "clients_title": "Persistente Clients",
+    "clients_desc": "Datensätze persistenter Clients für Geräte konfigurieren, die mit AdGuard Home verbunden sind.",
     "settings_global": "Allgemein",
     "settings_custom": "Benutzerdefiniert",
     "table_client": "Client",
@@ -432,14 +432,14 @@
     "clients_not_found": "Keine Clients gefunden",
     "client_confirm_delete": "Möchten Sie den Client „{{key}}“ wirklich löschen?",
     "list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?",
-    "auto_clients_title": "Clients (Laufzeit)",
-    "auto_clients_desc": "Daten zu den Clients, die AdGuard Home verwenden, aber nicht in der Konfiguration gespeichert sind",
+    "auto_clients_title": "Laufzeit-Clients",
+    "auto_clients_desc": "Geräte, die nicht auf der Liste der persistenten Clients stehen und trotzdem AdGuard Home verwenden dürfen.",
     "access_title": "Zugriffsrechte",
     "access_desc": "Hier können Sie die Zugriffsregeln für den AdGuard Home DNS-Server konfigurieren.",
     "access_allowed_title": "Zugelassene Clients",
-    "access_allowed_desc": "Eine Liste von CIDRs, IP-Adressen oder Client-IDs. Wenn konfiguriert, akzeptiert AdGuard Home Anfragen von diesen Clients.",
+    "access_allowed_desc": "Eine Liste von CIDRs, IP-Adressen oder <a>Client-IDs</a>. Wenn diese Liste gefüllt ist, akzeptiert AdGuard Home nur Anfragen von diesen Clients.",
     "access_disallowed_title": "Nicht zugelassene Clients",
-    "access_disallowed_desc": "Eine Liste von CIDRs, IP-Adressen oder Client-IDs. Wenn konfiguriert, löscht AdGuard Home Anfragen von diesen Clients. Wenn erlaubte Clients konfiguriert sind, wird dieses Feld ignoriert.",
+    "access_disallowed_desc": "Eine Liste von CIDRs, IP-Adressen oder <a>ClientIDs</a>. Wenn diese Liste gefüllt ist, weist AdGuard Home Anfragen von diesen Clients zurück. Dieses Feld wird ignoriert, wenn es Einträge in der Liste „Zugelassene Clients“ gibt.",
     "access_blocked_title": "Nicht zugelassene Domains",
     "access_blocked_desc": "Verwechseln Sie dies nicht mit Filtern. AdGuard Home verwirft DNS-Abfragen, die mit diesen Domänen übereinstimmen, und diese Abfragen erscheinen nicht einmal im Abfrageprotokoll. Hier können Sie die genauen Domain-Namen, Wildcards und URL-Filter-Regeln angeben, z.B. 'beispiel.org', '*.beispiel.org' oder '||beispiel.org^'.",
     "access_settings_saved": "Zugriffseinstellungen erfolgreich gespeichert",
@@ -475,8 +475,8 @@
     "dns_rewrites": "DNS-Umschreibungen",
     "form_domain": "Domain eingeben",
     "form_answer": "IP-Adresse oder Domainname eingeben",
-    "form_error_domain_format": "Ungültiges Domainformat",
-    "form_error_answer_format": "Ungültiges Antwortformat",
+    "form_error_domain_format": "Ungültiges Domainformat.",
+    "form_error_answer_format": "Ungültiges Antwortformat.",
     "configure": "Konfigurieren",
     "main_settings": "Grundeinstellungen",
     "block_services": "Bestimmte Dienste sperren",
@@ -507,7 +507,7 @@
     "filter_updated": "Der Filter wurde erfolgreich aktualisiert",
     "statistics_configuration": "Statistikkonfiguration",
     "statistics_retention": "Statistiken speichern",
-    "statistics_retention_desc": "Wenn Sie den Zeitraum verringern, werden einige Daten verloren gehen",
+    "statistics_retention_desc": "Wenn Sie den Intervallwert verringern, gehen einige Daten verloren.",
     "statistics_clear": "Statistiken leeren",
     "statistics_clear_confirm": "Möchten Sie die Statistiken wirklich löschen?",
     "statistics_retention_confirm": "Möchten Sie wirklich die Aufbewahrung der Statistiken ändern? Wenn Sie den Zeitabstand verringern, gehen einige Daten verloren.",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Es werden folgende Aufgaben ausgeführt: <0>Deaktivieren des DNSStubListener-Systems</0> <0>Festlegen der DNS-Server-Adresse auf 127.0.0.1</0> <0>Ersetzen des symbolischen Linkziels von /etc/resolv.conf auf /run/systemd/resolve/resolv.conf</0> <0>Anhalten des DNSStubListener (systemseitig aufgelöster Dienst wird nachladen)</0>",
     "autofix_warning_result": "Als Folge daraus werden alle DNS-Anforderungen von Ihrem System standardmäßig von AdGuardHome verarbeitet.",
     "tags_title": "Schlagwörter",
-    "tags_desc": "Sie können die Schlagwörter auswählen, die dem Client entsprechen. Die Schlagwörter können in die Filterregeln aufgenommen werden und erlauben Ihnen, sie genauer anzuwenden. <0>Mehr erfahren</0>",
+    "tags_desc": "Sie können die Schlagwörter auswählen, die dem Client entsprechen. Die Schlagwörter können in die Filterregeln aufgenommen werden und erlauben Ihnen, sie genauer anzuwenden. <0>Mehr erfahren</0>.",
     "form_select_tags": "Schlagwörter des Clients auswählen",
     "check_title": "Filterung überprüfen",
-    "check_desc": "Prüfen, ob der Hostname gefiltert wird",
+    "check_desc": "Prüfen, ob der Hostname gefiltert wird.",
     "check": "Prüfen",
     "form_enter_host": "Gerätenamen eingeben",
     "filtered_custom_rules": "Nach benutzerdefinierten Filterregeln gefiltert",
@@ -597,18 +597,18 @@
     "safe_search": "Sichere Suche",
     "blocklist": "Sperrliste",
     "milliseconds_abbreviation": "ms",
-    "cache_size": "Größe des Zwischenspeichers",
-    "cache_size_desc": "Größe des DNS-Zwischenspeichers (in Bytes)",
+    "cache_size": "Größe des Cache",
+    "cache_size_desc": "Größe des DNS-Zwischenspeichers (in Bytes).",
     "cache_ttl_min_override": "TTL-Minimalwert überschreiben (in Sekunden)",
     "cache_ttl_max_override": "TTL-Höchstwert überschreiben (in Sekunden)",
-    "enter_cache_size": "Größe des Zwischenspeichers eingeben",
+    "enter_cache_size": "Größe des Cache (Bytes) eingeben",
     "enter_cache_ttl_min_override": "TTL-Minimalwert eingeben",
     "enter_cache_ttl_max_override": "TTL-Höchstwert eingeben",
-    "cache_ttl_min_override_desc": "Überschreibt den TTL-Minimalwert, der vom vorgeschalteten Server empfangen wurde. Dieser Wert darf nicht mehr als 3600 (Sek.) (≙ 1 Stunde) betragen.",
-    "cache_ttl_max_override_desc": "Überschreibt den TLL-Maximalwert, der vom vorgeschalteten Server empfangen wurde.",
-    "ttl_cache_validation": "Der minimale Cache des TTL-Wertes muss kleiner oder gleich dem maximalen Wert sein",
-    "cache_optimistic": "Optimistisches Zwischenspeichern",
-    "cache_optimistic_desc": "Sorgt dafür, dass AdGuard Home auch dann aus dem Zwischenspeicher antwortet, wenn die Einträge abgelaufen sind, und versucht zudem, diese zu aktualisieren.",
+    "cache_ttl_min_override_desc": "Kurze Time-to-Live-Werte (Sekunden) verlängern, die vom Upstream-Server beim Caching von DNS-Antworten empfangen werden.",
+    "cache_ttl_max_override_desc": "Maximalen Time-to-Live-Wert (Sekunden) für Einträge im DNS-Cache festlegen.",
+    "ttl_cache_validation": "Der minimale Cache-TTL-Override muss kleiner oder gleich dem maximalen Wert sein.",
+    "cache_optimistic": "Optimistisches Caching",
+    "cache_optimistic_desc": "Sorgt dafür, dass AdGuard Home auch dann aus dem Cache antwortet, wenn die Einträge abgelaufen sind, und versucht zudem, diese zu aktualisieren.",
     "filter_category_general": "Allgemein",
     "filter_category_security": "Sicherheit",
     "filter_category_regional": "Regional",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home wird alle DNS-Abfragen von diesem Client verwerfen.",
     "filter_allowlist": "Warnhinweis: Durch diese Aktion wird außerdem die Regel „{{disallowed_rule}}“ aus der Liste der zugelassenen Clients ausgeschlossen.",
     "last_rule_in_allowlist": "Dieser Client kann nicht gesperrt werden, da das Ausschließen der Regel „{{disallowed_rule}}“ die Liste „Zugelassene Clients“ deaktivieren würde.",
-    "experimental": "Experimentell",
     "use_saved_key": "Zuvor gespeicherten Schlüssel verwenden",
     "parental_control": "Kindersicherung",
     "safe_browsing": "Internetsicherheit",
-    "served_from_cache": "{{value}} <i>(aus dem Zwischenspeicher abgerufen)</i>"
+    "served_from_cache": "{{value}} <i>(aus dem Cache abgerufen)</i>",
+    "form_error_password_length": "Das Passwort muss mindestens {{value}} Zeichen enthalten."
 }
diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json
index 753330ac..76eedb0f 100644
--- a/client/src/__locales/es.json
+++ b/client/src/__locales/es.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Configuración de clientes",
-    "example_upstream_reserved": "puedes especificar el DNS de subida <0>para un dominio específico</0>",
-    "example_upstream_comment": "puedes especificar el comentario",
+    "example_upstream_reserved": "un DNS de subida <0>para un dominio específico</0>.",
+    "example_upstream_comment": "un comentario.",
     "upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.",
     "parallel_requests": "Consultas paralelas",
     "load_balancing": "Balanceo de carga",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Configuración DHCP guardado correctamente",
     "dhcp_ipv4_settings": "Configuración DHCP IPv4",
     "dhcp_ipv6_settings": "Configuración DHCP IPv6",
-    "form_error_required": "Campo obligatorio",
-    "form_error_ip4_format": "Dirección IPv4 no válida",
-    "form_error_ip4_range_start_format": "Dirección IPv4 no válida del inicio de rango",
-    "form_error_ip4_range_end_format": "Dirección IPv4 no válida del final de rango",
-    "form_error_ip4_gateway_format": "Dirección IPv4 no válida de la puerta de enlace",
-    "form_error_ip6_format": "Dirección IPv6 no válida",
-    "form_error_ip_format": "Dirección IP no válida",
-    "form_error_mac_format": "Dirección MAC no válida",
-    "form_error_client_id_format": "El ID de cliente debe contener solo números, letras minúsculas y guiones",
-    "form_error_server_name": "Nombre de servidor no válido",
-    "form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\"",
-    "form_error_positive": "Debe ser mayor que 0",
-    "out_of_range_error": "Debe estar fuera del rango \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Debe ser inferior que el inicio de rango",
-    "greater_range_start_error": "Debe ser mayor que el inicio de rango",
-    "greater_range_end_error": "Debe ser mayor que el final de rango",
-    "subnet_error": "Las direcciones deben estar en una subred",
-    "gateway_or_subnet_invalid": "Máscara de subred no válida",
+    "form_error_required": "Campo obligatorio.",
+    "form_error_ip4_format": "Dirección IPv4 no válida.",
+    "form_error_ip4_range_start_format": "Dirección IPv4 no válida del inicio de rango.",
+    "form_error_ip4_range_end_format": "Dirección IPv4 no válida del final de rango.",
+    "form_error_ip4_gateway_format": "Dirección IPv4 no válida de la puerta de enlace.",
+    "form_error_ip6_format": "Dirección IPv6 no válida.",
+    "form_error_ip_format": "Dirección IP no válida.",
+    "form_error_mac_format": "Dirección MAC no válida.",
+    "form_error_client_id_format": "El ID de cliente debe contener solo números, letras minúsculas y guiones.",
+    "form_error_server_name": "Nombre de servidor no válido.",
+    "form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\".",
+    "form_error_positive": "Debe ser mayor que 0.",
+    "out_of_range_error": "Debe estar fuera del rango \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Debe ser inferior que el inicio de rango.",
+    "greater_range_start_error": "Debe ser mayor que el inicio de rango.",
+    "greater_range_end_error": "Debe ser mayor que el final de rango.",
+    "subnet_error": "Las direcciones deben estar en una subred.",
+    "gateway_or_subnet_invalid": "Máscara de subred no válida.",
     "dhcp_form_gateway_input": "IP de puerta de enlace",
     "dhcp_form_subnet_input": "Máscara de subred",
     "dhcp_form_range_title": "Rango de direcciones IP",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Elegir listas de permitido",
     "enter_valid_blocklist": "Ingresa una URL válida para la lista de bloqueo.",
     "enter_valid_allowlist": "Ingresa una URL válida para la lista de permitido.",
-    "form_error_url_format": "Formato de URL no válido",
-    "form_error_url_or_path_format": "URL o ruta absoluta no válida para la lista",
+    "form_error_url_format": "Formato de URL no válido.",
+    "form_error_url_or_path_format": "URL o ruta absoluta no válida para la lista.",
     "custom_filter_rules": "Reglas de filtrado personalizado",
     "custom_filter_rules_hint": "Ingresa una regla por línea. Puedes utilizar reglas de bloqueo o la sintaxis de los archivos hosts.",
     "system_host_files": "Archivos hosts del sistema",
     "examples_title": "Ejemplos",
-    "example_meaning_filter_block": "bloquea el acceso al dominio ejemplo.org y a todos sus subdominios",
-    "example_meaning_filter_whitelist": "desbloquea el acceso al dominio ejemplo.org y a todos sus subdominios",
-    "example_meaning_host_block": "AdGuard Home devolverá la dirección 127.0.0.1 para el dominio ejemplo.org (pero no para sus subdominios).",
-    "example_comment": "! Aquí va un comentario",
-    "example_comment_meaning": "solo un comentario",
-    "example_comment_hash": "# También un comentario",
-    "example_regex_meaning": "bloquea el acceso a los dominios que coincidan con la expresión regular especificada",
-    "example_upstream_regular": "DNS regular (mediante UDP)",
-    "example_upstream_dot": "cifrado <0>DNS mediante TLS</0>",
-    "example_upstream_doh": "cifrado <0>DNS mediante HTTPS</0>",
-    "example_upstream_doq": "cifrado <0>DNS mediante QUIC</0>",
-    "example_upstream_sdns": "puedes usar <0>DNS Stamps</0> para <1>DNSCrypt</1> o resolutores <2>DNS mediante HTTPS</2>",
-    "example_upstream_tcp": "DNS regular (mediante TCP)",
+    "example_meaning_filter_block": "bloquea el acceso al dominio ejemplo.org y a todos sus subdominios.",
+    "example_meaning_filter_whitelist": "desbloquea el acceso al dominio ejemplo.org y a todos sus subdominios.",
+    "example_meaning_host_block": "responde con 127.0.0.1 para ejemplo.org (pero no para sus subdominios).",
+    "example_comment": "! Aquí va un comentario.",
+    "example_comment_meaning": "solo un comentario.",
+    "example_comment_hash": "# También un comentario.",
+    "example_regex_meaning": "bloquea el acceso a los dominios que coincidan con la expresión regular especificada.",
+    "example_upstream_regular": "DNS regular (mediante UDP).",
+    "example_upstream_dot": "cifrado <0>DNS mediante TLS</0>.",
+    "example_upstream_doh": "cifrado <0>DNS mediante HTTPS</0>.",
+    "example_upstream_doq": "cifrado <0>DNS mediante QUIC</0> (experimental).",
+    "example_upstream_sdns": "<0>DNS Stamps</0> para <1>DNSCrypt</1> o resolutores <2>DNS mediante HTTPS</2>.",
+    "example_upstream_tcp": "DNS regular (mediante TCP).",
     "all_lists_up_to_date_toast": "Todas las listas ya están actualizadas",
     "updated_upstream_dns_toast": "Servidores DNS de subida guardados correctamente",
     "dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Usar comillas dobles para una búsqueda estricta",
     "query_log_retention_confirm": "¿Estás seguro de que deseas cambiar la retención del registro de consultas? Si disminuye el valor del intervalo, se perderán algunos datos",
     "anonymize_client_ip": "Anonimizar IP del cliente",
-    "anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros y estadísticas",
+    "anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros o estadísticas.",
     "dns_config": "Configuración del servidor DNS",
     "dns_cache_config": "Configuración de la caché DNS",
-    "dns_cache_config_desc": "Aquí puedes configurar la caché DNS",
+    "dns_cache_config_desc": "Aquí puedes configurar la caché DNS.",
     "blocking_mode": "Modo de bloqueo",
     "default": "Predeterminado",
     "nxdomain": "NXDOMAIN",
@@ -277,7 +277,7 @@
     "dns_over_quic": "DNS mediante QUIC",
     "client_id": "ID de cliente",
     "client_id_placeholder": "Ingresa el ID del cliente",
-    "client_id_desc": "Diferentes clientes pueden ser identificados por un ID de cliente especial. <a>Aquí</a> puedes obtener más información sobre cómo identificar clientes.",
+    "client_id_desc": "Los clientes pueden ser identificados por un ID de cliente. Obtén más información sobre cómo identificar clientes <a>aquí</a>.",
     "download_mobileconfig_doh": "Descargar .mobileconfig para DNS mediante HTTPS",
     "download_mobileconfig_dot": "Descargar .mobileconfig para DNS mediante TLS",
     "download_mobileconfig": "Descargar archivo de configuración",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Interfaz de escucha",
     "install_settings_port": "Puerto",
     "install_settings_interface_link": "La interfaz web de administración de AdGuard Home estará disponible en las siguientes direcciones:",
-    "form_error_port": "Ingresa un número de puerto válido",
+    "form_error_port": "Ingresa un número de puerto válido.",
     "install_settings_dns": "Servidor DNS",
     "install_settings_dns_desc": "Deberás configurar tus dispositivos o router para usar el servidor DNS en las siguientes direcciones:",
     "install_settings_all_interfaces": "Todas las interfaces",
@@ -334,8 +334,8 @@
     "install_devices_router_list_4": "En algunos tipos de router, no se puede configurar un servidor DNS personalizado. En ese caso, configurar AdGuard Home como <0>servidor DHCP</0> puede ayudar. De lo contrario, debes consultar el manual del router para saber cómo personalizar los servidores DNS en tu modelo de router específico.",
     "install_devices_windows_list_1": "Abre el Panel de control a través del menú Inicio o en el buscador de Windows.",
     "install_devices_windows_list_2": "Ve a la categoría Redes e Internet, luego a Centro de redes y recursos compartidos.",
-    "install_devices_windows_list_3": "En el lado izquierdo de la pantalla, busca \"Cambiar configuración del adaptador\" y luego haz clic en él.",
-    "install_devices_windows_list_4": "Selecciona tu conexión activa, haz clic derecho sobre ella y elige Propiedades.",
+    "install_devices_windows_list_3": "En el panel izquierdo, haz clic en \"Cambiar configuración del adaptador\".",
+    "install_devices_windows_list_4": "Haz clic derecho en tu conexión activa y selecciona Propiedades.",
     "install_devices_windows_list_5": "Busca en la lista el \"Protocolo de Internet versión 4 (TCP/IPv4)\" (o \"Protocolo de Internet versión 6 (TCP/IPv6)\"), selecciónalo y vuelve a hacer clic en Propiedades.",
     "install_devices_windows_list_6": "Elige \"Usar las siguientes direcciones de servidor DNS\" e ingresa las direcciones de tu servidor AdGuard Home.",
     "install_devices_macos_list_1": "Haz clic en el icono de Apple y ve a Preferencias del sistema.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Abrir panel de control",
     "install_saved": "Guardado correctamente",
     "encryption_title": "Cifrado",
-    "encryption_desc": "Soporte de cifrado (HTTPS/TLS) tanto para DNS como para la interfaz web de administración",
+    "encryption_desc": "Soporte de cifrado (HTTPS/TLS) tanto para DNS como para la interfaz web de administración.",
     "encryption_config_saved": "Configuración de cifrado guardado",
     "encryption_server": "Nombre del servidor",
     "encryption_server_enter": "Ingresa el nombre del dominio",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Si el puerto HTTPS está configurado, la interfaz de administración de AdGuard Home será accesible a través de HTTPS, y también proporcionará DNS mediante HTTPS en la ubicación '/dns-query'.",
     "encryption_dot": "Puerto DNS mediante TLS",
     "encryption_dot_desc": "Si este puerto está configurado, AdGuard Home ejecutará un servidor DNS mediante TLS en este puerto.",
-    "encryption_doq": "Puerto DNS mediante QUIC",
+    "encryption_doq": "Puerto DNS mediante QUIC (experimental)",
     "encryption_doq_desc": "Si este puerto está configurado, AdGuard Home ejecutará un servidor DNS mediante QUIC en este puerto. Es experimental y puede no ser confiable. Además, no hay muchos clientes que lo soporten por el momento.",
     "encryption_certificates": "Certificados",
     "encryption_certificates_desc": "Para utilizar el cifrado, debes proporcionar una cadena de certificado SSL válida para tu dominio. Puedes obtener un certificado gratuito en <0>{{link}}</0> o puedes comprarlo en una de las autoridades de certificación de confianza.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Copia/pega aquí tu clave privada codificada PEM para tu certificado.",
     "encryption_enable": "Habilitar cifrado (HTTPS, DNS mediante HTTPS y DNS mediante TLS)",
     "encryption_enable_desc": "Si el cifrado está habilitado, la interfaz de administración de AdGuard Home funcionará a través de HTTPS, y el servidor DNS escuchará las peticiones DNS mediante HTTPS y DNS mediante TLS.",
-    "encryption_chain_valid": "La cadena de certificado es válida",
-    "encryption_chain_invalid": "La cadena de certificado no es válida",
-    "encryption_key_valid": "Esta es una clave privada {{type}} válida",
-    "encryption_key_invalid": "Esta es una clave privada {{type}} no válida",
+    "encryption_chain_valid": "La cadena de certificado es válida.",
+    "encryption_chain_invalid": "La cadena de certificado no es válida.",
+    "encryption_key_valid": "Esta es una clave privada {{type}} válida.",
+    "encryption_key_invalid": "Esta es una clave privada {{type}} no válida.",
     "encryption_subject": "Asunto",
     "encryption_issuer": "Emisor",
     "encryption_hostnames": "Nombres de hosts",
     "encryption_reset": "¿Estás seguro de que deseas restablecer la configuración de cifrado?",
     "topline_expiring_certificate": "Tu certificado SSL está a punto de expirar. Actualiza la <0>configuración de cifrado</0>.",
     "topline_expired_certificate": "Tu certificado SSL ha expirado. Actualiza la <0>configuración de cifrado</0>.",
-    "form_error_port_range": "Ingresa el número del puerto en el rango de 80 a 65535",
-    "form_error_port_unsafe": "Este es un puerto inseguro",
-    "form_error_equal": "No debe ser igual",
-    "form_error_password": "La contraseña no coincide",
+    "form_error_port_range": "Ingresa el número del puerto en el rango de 80 a 65535.",
+    "form_error_port_unsafe": "Este es un puerto inseguro.",
+    "form_error_equal": "No debe ser igual.",
+    "form_error_password": "La contraseña no coincide.",
     "reset_settings": "Restablecer configuración",
     "update_announcement": "¡AdGuard Home {{version}} ya está disponible! <0>Haz clic aquí</0> para más información.",
     "setup_guide": "Guía de configuración",
     "dns_addresses": "Direcciones DNS",
     "dns_start": "El servidor DNS está iniciando",
-    "dns_status_error": "Error al obtener el estado del servidor DNS",
+    "dns_status_error": "Error al obtener el estado del servidor DNS.",
     "down": "Abajo",
     "fix": "Corregir",
     "dns_providers": "Aquí hay una <0>lista de proveedores DNS</0> conocidos para elegir.",
@@ -405,8 +405,8 @@
     "update_failed": "Error en la actualización automática. Por favor <a>sigue estos pasos</a> para actualizar manualmente.",
     "manual_update": "Por favor <a>sigue estos pasos</a> para actualizar manualmente.",
     "processing_update": "Por favor espera, AdGuard Home se está actualizando",
-    "clients_title": "Clientes",
-    "clients_desc": "Configurar dispositivos conectados con AdGuard Home",
+    "clients_title": "Clientes persistentes",
+    "clients_desc": "Configurar registros de clientes persistentes para dispositivos conectados a AdGuard Home.",
     "settings_global": "Global",
     "settings_custom": "Personalizado",
     "table_client": "Cliente",
@@ -417,7 +417,7 @@
     "client_edit": "Editar cliente",
     "client_identifier": "Identificador",
     "ip_address": "Dirección IP",
-    "client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC, CIDR o un ID de cliente especial (puede ser utilizado para DoT/DoH/DoQ). <0>Aquí</0> puedes obtener más información sobre cómo identificar clientes.",
+    "client_identifier_desc": "Los clientes pueden ser identificados por su dirección IP, MAC, CIDR o un ID de cliente (puede ser utilizado para DoT/DoH/DoQ). Obtén más información sobre cómo identificar clientes <0>aquí</0>.",
     "form_enter_ip": "Ingresa la IP",
     "form_enter_subnet_ip": "Ingresa una dirección IP en la subred \"{{cidr}}\"",
     "form_enter_mac": "Ingresa la MAC",
@@ -432,14 +432,14 @@
     "clients_not_found": "No se han encontrado clientes",
     "client_confirm_delete": "¿Estás seguro de que deseas eliminar el cliente \"{{key}}\"?",
     "list_confirm_delete": "¿Estás seguro de que deseas eliminar esta lista?",
-    "auto_clients_title": "Clientes (activos)",
-    "auto_clients_desc": "Datos de los clientes que utilizan AdGuard Home, pero no se almacenan en la configuración",
+    "auto_clients_title": "Clientes activos",
+    "auto_clients_desc": "Dispositivos que no están en la lista de clientes persistentes que aún pueden utilizar AdGuard Home.",
     "access_title": "Configuración de acceso",
     "access_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home.",
     "access_allowed_title": "Clientes permitidos",
-    "access_allowed_desc": "Lista de CIDR, direcciones IP o ID de clientes. Si está configurado, AdGuard Home aceptará peticiones solo de estos clientes.",
+    "access_allowed_desc": "Lista de CIDR, direcciones IP o <a>ID de clientes</a>. Si esta lista tiene entradas, AdGuard Home aceptará peticiones solo de estos clientes.",
     "access_disallowed_title": "Clientes no permitidos",
-    "access_disallowed_desc": "Lista de CIDR, direcciones IP o ID de clientes. Si está configurado, AdGuard Home descartará las peticiones de estos clientes. Si se configuran clientes permitidos, este campo será ignorado.",
+    "access_disallowed_desc": "Lista de CIDR, direcciones IP o <a>ID de clientes</a>. Si esta lista tiene entradas, AdGuard Home descartará las peticiones de estos clientes. Este campo será ignorado si hay entradas en clientes permitidos.",
     "access_blocked_title": "Dominios no permitidos",
     "access_blocked_desc": "No debe confundirse con filtros. AdGuard Home descartará las consultas DNS que coincidan con estos dominios, y estas consultas ni siquiera aparecerán en el registro de consultas. Puedes especificar nombres de dominio exactos, comodines o reglas de filtrado de URL, por ejemplo: \"ejemplo.org\", \"*.ejemplo.org\" o \"||ejemplo.org^\" correspondientemente.",
     "access_settings_saved": "Configuración de acceso guardado correctamente",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Reescrituras DNS",
     "form_domain": "Ingresa el nombre del dominio o comodín",
     "form_answer": "Ingresa la dirección IP o el nombre del dominio",
-    "form_error_domain_format": "Formato de dominio no válido",
-    "form_error_answer_format": "Formato de respuesta no válido",
+    "form_error_domain_format": "Formato de dominio no válido.",
+    "form_error_answer_format": "Formato de respuesta no válido.",
     "configure": "Configurar",
     "main_settings": "Configuración principal",
     "block_services": "Bloquear servicios específicos",
@@ -507,7 +507,7 @@
     "filter_updated": "La lista ha sido actualizada correctamente",
     "statistics_configuration": "Configuración de estadísticas",
     "statistics_retention": "Retención de estadísticas",
-    "statistics_retention_desc": "Si disminuye el valor del intervalo, se perderán algunos datos",
+    "statistics_retention_desc": "Si disminuye el valor del intervalo, se perderán algunos datos.",
     "statistics_clear": "Borrar estadísticas",
     "statistics_clear_confirm": "¿Estás seguro de que deseas borrar las estadísticas?",
     "statistics_retention_confirm": "¿Estás seguro de que deseas cambiar la retención de estadísticas? Si disminuye el valor del intervalo, se perderán algunos datos",
@@ -532,7 +532,7 @@
     "netname": "Nombre de la red",
     "network": "Red",
     "descr": "Descripción",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Más información</0> sobre cómo crear tus propias listas de hosts.",
     "blocked_by_response": "Bloqueado por CNAME o IP en respuesta",
     "blocked_by_cname_or_ip": "Bloqueado por CNAME o IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Realizará estas tareas: <0>Deshabilitar el sistema DNSStubListener</0> <0>Establecer la dirección del servidor DNS en 127.0.0.1</0> <0>Reemplazar el destino del enlace simbólico de /etc/resolv.conf por /run/systemd/resolve/resolv.conf</0> <0>Detener DNSStubListener (recargar el servicio systemd-resolved)</0>",
     "autofix_warning_result": "Como resultado, todas las peticiones DNS de tu sistema serán procesadas por AdGuard Home de manera predeterminada.",
     "tags_title": "Etiquetas",
-    "tags_desc": "Puedes seleccionar las etiquetas que correspondan al cliente. Las etiquetas pueden ser incluidas en las reglas de filtrado y te permiten aplicarlas con mayor precisión. <0>Más información</0>",
+    "tags_desc": "Puedes seleccionar las etiquetas que correspondan al cliente. Incluye etiquetas en las reglas de filtrado para aplicarlas con mayor precisión. <0>Más información</0>.",
     "form_select_tags": "Seleccione las etiquetas del cliente",
     "check_title": "Comprobar filtrado",
-    "check_desc": "Comprueba si el nombre del host está siendo filtrado",
+    "check_desc": "Comprueba si un nombre del host está siendo filtrado.",
     "check": "Comprobar",
     "form_enter_host": "Ingresa un nombre de host",
     "filtered_custom_rules": "Filtrado por reglas de filtrado personalizado",
@@ -598,15 +598,15 @@
     "blocklist": "Lista de bloqueo",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Tamaño de la caché",
-    "cache_size_desc": "Tamaño de la caché DNS (en bytes)",
+    "cache_size_desc": "Tamaño de la caché DNS (en bytes).",
     "cache_ttl_min_override": "Anular TTL mínimo",
     "cache_ttl_max_override": "Anular TTL máximo",
     "enter_cache_size": "Ingresa el tamaño de la caché (bytes)",
     "enter_cache_ttl_min_override": "Ingresa el TTL mínimo (en segundos)",
     "enter_cache_ttl_max_override": "Ingresa el TTL máximo (en segundos)",
-    "cache_ttl_min_override_desc": "Amplia el corto tiempo de vida de los valores recibidos del servidor DNS de subida al almacenar en caché las respuestas DNS",
-    "cache_ttl_max_override_desc": "Establece un valor de tiempo de vida máximo para las entradas en la caché DNS",
-    "ttl_cache_validation": "El valor TTL mínimo de la caché debe ser menor o igual al valor máximo",
+    "cache_ttl_min_override_desc": "Amplía el corto tiempo de vida (segundos) de los valores recibidos del servidor DNS de subida al almacenar en caché las respuestas DNS.",
+    "cache_ttl_max_override_desc": "Establece un valor de tiempo de vida (segundos) máximo para las entradas en la caché DNS.",
+    "ttl_cache_validation": "La anulación TTL mínimo de la caché debe ser menor o igual al máximo.",
     "cache_optimistic": "Caché optimista",
     "cache_optimistic_desc": "Haz que AdGuard Home responda desde la caché incluso cuando las entradas estén expiradas y también intente actualizarlas.",
     "filter_category_general": "General",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home descartará todas las consultas DNS de este cliente.",
     "filter_allowlist": "ADVERTENCIA: Esta acción también excluirá la regla \"{{disallowed_rule}}\" de la lista de clientes permitidos.",
     "last_rule_in_allowlist": "No se puede desautorizar a este cliente porque al excluir la regla \"{{disallowed_rule}}\" DESHABILITARÁ la lista de \"Clientes permitidos\".",
-    "experimental": "experimental",
     "use_saved_key": "Usar la clave guardada previamente",
     "parental_control": "Control parental",
     "safe_browsing": "Navegación segura",
-    "served_from_cache": "{{value}} <i>(servido desde la caché)</i>"
+    "served_from_cache": "{{value}} <i>(servido desde la caché)</i>",
+    "form_error_password_length": "La contraseña debe tener al menos {{value}} caracteres."
 }
diff --git a/client/src/__locales/fa.json b/client/src/__locales/fa.json
index 22e78275..d465b2bf 100644
--- a/client/src/__locales/fa.json
+++ b/client/src/__locales/fa.json
@@ -482,7 +482,7 @@
     "allowed": "اجازه داده شده",
     "filtered": "فیلتر شده",
     "rewritten": "بازنویسی شده",
-    "safe_search": "جستجوی اَمن",
+    "safe_search": "فعالسازی جستجوی اَمن",
     "blocklist": "لیست سیاه",
     "milliseconds_abbreviation": "هـ ثـ",
     "filter_category_general": "General",
diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json
index d17d3646..899748a7 100644
--- a/client/src/__locales/fi.json
+++ b/client/src/__locales/fi.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Päätelaiteasetukset",
-    "example_upstream_reserved": "voit määrittää DNS-ylävirran <0>tietyille verkkotunnuksille</0>",
-    "example_upstream_comment": "voit määrittää kommentin",
+    "example_upstream_reserved": "ylävirta <0>tietyille verkkotunnuksille</0>;",
+    "example_upstream_comment": "kommentti.",
     "upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirran palvelimia samanaikaisesti.",
     "parallel_requests": "Rinnakkaiset pyynnöt",
     "load_balancing": "Kuormantasaus",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP-asetukset tallennettiin",
     "dhcp_ipv4_settings": "DHCP:n IPv4-asetukset",
     "dhcp_ipv6_settings": "DHCP:n IPv6-asetukset",
-    "form_error_required": "Vaaditaan",
-    "form_error_ip4_format": "Virheellinen IPv4-osoite",
-    "form_error_ip4_range_start_format": "Virheellinen IPv4-osoitealueen aloitusosoite",
-    "form_error_ip4_range_end_format": "Virheellinen IPv4-osoitealueen päätösosoite",
-    "form_error_ip4_gateway_format": "Virheellinen yhdyskäytävän IPv4-osoite",
-    "form_error_ip6_format": "Virheellinen IPv6-osoite",
-    "form_error_ip_format": "Virheellinen IP-osoite",
-    "form_error_mac_format": "Virheellinen MAC-osoite",
-    "form_error_client_id_format": "Päätelaitteen tunniste voi sisältää ainoastaan numeroita, pieniä kirjaimia sekä yhdysviivoja",
-    "form_error_server_name": "Virheellinen palvelimen nimi",
-    "form_error_subnet": "Aliverkko \"{{cidr}}\" ei sisällä IP-osoitetta \"{{ip}}\"",
-    "form_error_positive": "Oltava suurempi kuin 0",
-    "out_of_range_error": "Oltava alueen \"{{start}}\"-\"{{end}}\" ulkopuolella",
-    "lower_range_start_error": "Oltava alueen aloitusarvoa pienempi",
-    "greater_range_start_error": "Oltava alueen aloitusarvoa suurempi",
-    "greater_range_end_error": "Oltava alueen päätösarvoa pienempi",
-    "subnet_error": "Osoitteiden tulee olla yhdessä aliverkossa",
-    "gateway_or_subnet_invalid": "Virheellinen aliverkon peite",
+    "form_error_required": "Pakollinen kenttä.",
+    "form_error_ip4_format": "Virheellinen IPv4-osoite.",
+    "form_error_ip4_range_start_format": "Virheellinen IPv4-osoitealueen aloitusosoite.",
+    "form_error_ip4_range_end_format": "Virheellinen IPv4-osoitealueen päätösosoite.",
+    "form_error_ip4_gateway_format": "Virheellinen yhdyskäytävän IPv4-osoite.",
+    "form_error_ip6_format": "Virheellinen IPv6-osoite.",
+    "form_error_ip_format": "Virheellinen IP-osoite.",
+    "form_error_mac_format": "Virheellinen MAC-osoite.",
+    "form_error_client_id_format": "Päätelaitteen ID voi sisältää ainoastaan numeroita, pieniä kirjaimia sekä yhdysviivoja.",
+    "form_error_server_name": "Virheellinen palvelimen nimi.",
+    "form_error_subnet": "Aliverkko \"{{cidr}}\" ei sisällä IP-osoitetta \"{{ip}}\".",
+    "form_error_positive": "Oltava suurempi kuin 0.",
+    "out_of_range_error": "Oltava alueen \"{{start}}\" - \"{{end}}\" ulkopuolella.",
+    "lower_range_start_error": "Oltava alueen aloitusarvoa pienempi.",
+    "greater_range_start_error": "Oltava alueen aloitusarvoa suurempi.",
+    "greater_range_end_error": "Oltava alueen päätösarvoa pienempi.",
+    "subnet_error": "Osoitteiden tulee olla yhdessä aliverkossa.",
+    "gateway_or_subnet_invalid": "Virheellinen aliverkon peite.",
     "dhcp_form_gateway_input": "Yhdyskäytävän IP-osoite",
     "dhcp_form_subnet_input": "Aliverkon peite",
     "dhcp_form_range_title": "IP-osoitealue",
@@ -143,7 +143,7 @@
     "use_adguard_browsing_sec_hint": "AdGuard Home tarkistaa onko verkkotunnus turvallisen selauksen verkkopalvelun estämä. Se käyttää tarkastukseen tietosuojapainotteista rajapintaa: palvelimelle lähetetään vain pieni osa verkkotunnuksen SHA256-hajautusarvosta.",
     "use_adguard_parental": "Käytä AdGuardin lapsilukko-palvelua",
     "use_adguard_parental_hint": "AdGuard Home tarkistaa, sisältääkö verkkotunnus aikuisille tarkoitettua sisältöä. Se käyttää samaa tietosuojapainotteista rajapintaa, kuin turvallisen selauksen palvelu.",
-    "enforce_safe_search": "Pakota turvallinen haku",
+    "enforce_safe_search": "Käytä turvallista hakua",
     "enforce_save_search_hint": "AdGuard Home voi pakottaa turvallisen haun käyttöön seuraavissa hakukoneissa: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
     "no_servers_specified": "Palvelimia ei ole määritetty",
     "general_settings": "Yleiset asetukset",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Valitse sallittujen listat",
     "enter_valid_blocklist": "Syötä estolistan URL-osoite.",
     "enter_valid_allowlist": "Syötä sallittujen listan URL-osoite.",
-    "form_error_url_format": "Virheellinen URL-osoitteen muoto",
-    "form_error_url_or_path_format": "Syötä listan URL-osoite tai tarkka tiedostosijainti",
+    "form_error_url_format": "Virheellinen URL-osoitteen muoto.",
+    "form_error_url_or_path_format": "Syötä listan URL-osoite tai tarkka tiedostosijainti.",
     "custom_filter_rules": "Omat suodatussäännöt",
     "custom_filter_rules_hint": "Syötä yksi sääntö per rivi. Voit käyttää mainoseston sääntöjen tai hosts-tiedostojen syntakseja.",
     "system_host_files": "Järjestelmän hosts-tiedostot",
     "examples_title": "Esimerkkejä",
-    "example_meaning_filter_block": "estä pääsy verkkotunnukseen example.org ja sen aliverkkotunnuksiin",
-    "example_meaning_filter_whitelist": "salli pääsy verkkotunnukseen example.org ja sen aliverkkotunnuksiin",
-    "example_meaning_host_block": "AdGuard Home palauttaa verkkotunnukselle example.org osoitteen 127.0.0.1 (muttei sen aliverkkotunnuksille)",
-    "example_comment": "! Tähän tulee kommentti",
-    "example_comment_meaning": "voit määrittää kommentin",
-    "example_comment_hash": "# Myös tämä on kommentti",
-    "example_regex_meaning": "estä pääsy säännöllistä lauseketta vastaaviin verkkotunnuksiin",
-    "example_upstream_regular": "tavallinen DNS (UDP)",
-    "example_upstream_dot": "salattu <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "salattu <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "salattu <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "voit käyttää <0>DNS Stamp</0> -merkintöjä <1>DNSCrypt</1> tai <2>DNS-over-HTTPS</2> -resolvereille",
-    "example_upstream_tcp": "tavallinen DNS (TCP)",
+    "example_meaning_filter_block": "estä pääsy verkkotunnukseen example.org sekä kaikkiin sen aliverkkotunnuksiin;",
+    "example_meaning_filter_whitelist": "salli pääsy verkkotunnukseen example.org sekä kaikkiin sen aliverkkotunnuksiin;",
+    "example_meaning_host_block": "vastaa verkkotunnukselle example.org IP-osoitteella 127.0.0.1 (muttei sen aliverkkotunnuksille);",
+    "example_comment": "! Tähän tulee kommentti.",
+    "example_comment_meaning": "vain kommentti;",
+    "example_comment_hash": "# Tämäkin on kommentti.",
+    "example_regex_meaning": "estä pääsy määritettyä säännöllistä lauseketta vastaaviin verkkotunnuksiin.",
+    "example_upstream_regular": "tavallinen DNS (UDP:n välityksellä);",
+    "example_upstream_dot": "salattu <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "salattu <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "salattu <0>DNS-over-QUIC</0> (kokeellinen);",
+    "example_upstream_sdns": "<0>DNS Stamp</0> -merkinnät <1>DNSCrypt</1> tai <2>DNS-over-HTTPS</2> -resolvereille;",
+    "example_upstream_tcp": "tavallinen DNS (TCP:n välityksellä);",
     "all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla",
     "updated_upstream_dns_toast": "Ylävirtojen palvelimet tallennettiin",
     "dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Käytä tarkalle haulle lainausmerkkejä",
     "query_log_retention_confirm": "Haluatko varmasti muuttaa pyyntöhistoriasi säilytysaikaa? Jos lyhennät aikaa, joitakin tietoja menetetään",
     "anonymize_client_ip": "Piilota päätelaitteen IP-osoite",
-    "anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin",
+    "anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin.",
     "dns_config": "DNS-palvelimen määritys",
     "dns_cache_config": "DNS-välimuistin määritys",
-    "dns_cache_config_desc": "Täällä voit määrittää DNS-välimuistin asetukset",
+    "dns_cache_config_desc": "Tässä voit määrittää DNS-välimuistin.",
     "blocking_mode": "Estotila",
     "default": "Oletus",
     "nxdomain": "NXDOMAIN",
@@ -277,7 +277,7 @@
     "dns_over_quic": "DNS-over-QUIC",
     "client_id": "Päätelaitteen ID",
     "client_id_placeholder": "Syötä päätelaitteen ID",
-    "client_id_desc": "Eri päätelaitteet voidaan tunnistaa erityisillä tunnisteilla. <a>Täältä</a> löydät lisätietoja päätelaitteiden tunnistuksesta.",
+    "client_id_desc": "Päätelaitteet voidaan tunnistaa erityisillä ID-tunnisteilla. Lue lisää päätelaitteiden tunnistuksesta <a>täältä</a>.",
     "download_mobileconfig_doh": "Lataa .mobileconfig-tiedosto DNS-over-HTTPS -käytölle",
     "download_mobileconfig_dot": "Lataa .mobileconfig-tiedosto DNS-over-TLS -käytölle",
     "download_mobileconfig": "Lataa asetustiedosto",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Käytettävä verkkosovitin",
     "install_settings_port": "Portti",
     "install_settings_interface_link": "AdGuard Home -asennuksesi hallintapaneeli on käytettävissä seuraavilla osoitteilla:",
-    "form_error_port": "Syötä oikea portin numero",
+    "form_error_port": "Syötä oikea portin numero.",
     "install_settings_dns": "DNS-palvelin",
     "install_settings_dns_desc": "Sinun on määritettävä laitteesi tai reitittimesi käyttämään DNS-palvelinta seuraavissa osoitteissa:",
     "install_settings_all_interfaces": "Kaikki verkkosovittimet",
@@ -334,11 +334,11 @@
     "install_devices_router_list_4": "Joissakin reitittimissä ei ole mahdollista määrittää omaa DNS-palvelinta. Tällöin AdGuard Homen määritys <0>DHCP-palvelimeksi</0> voi auttaa. Muutoin on selvitettävä reitittimen käyttöohjeesta, miten sen DNS-palvelinasetukset muutetaan.",
     "install_devices_windows_list_1": "Avaa \"Ohjauspaneeli\" Käynnistä-valikon tai Windowsin haun kautta.",
     "install_devices_windows_list_2": "Avaa \"Verkko ja Internet\" -ryhmä ja sitten \"Verkko ja jakamiskeskus\".",
-    "install_devices_windows_list_3": "Etsi ikkunan vasemmasta laidasta \"Muuta sovittimen asetuksia\" ja paina sitä.",
-    "install_devices_windows_list_4": "Valitse aktiivinen yhteytesi, paina sitä hiiren kakkospainikkeella ja valitse valikosta \"Ominaisuudet\".",
+    "install_devices_windows_list_3": "Paina ikkunan vasemmasta laidasta \"Muuta sovittimen asetuksia\".",
+    "install_devices_windows_list_4": "Paina aktiivista yhteyttäsi hiiren kakkospainikkeella ja valitse \"Ominaisuudet\".",
     "install_devices_windows_list_5": "Etsi listasta \"Internet protokolla versio 4 (TCP/IP)\", valitse se ja paina jälleen \"Ominaisuudet\".",
     "install_devices_windows_list_6": "Valitse \"Käytä seuraavia DNS-palvelinten osoitteita\" ja syötä AdGuard Home -palvelimesi osoitteet.",
-    "install_devices_macos_list_1": "Avaa Omenavalikko ja valitse \"Järjestelmäasetukset \".",
+    "install_devices_macos_list_1": "Paina Omena-kuvaketta ja valitse \"Järjestelmäasetukset\".",
     "install_devices_macos_list_2": "Paina \"Verkko\".",
     "install_devices_macos_list_3": "Valitse listan ensimmäinen yhteys ja paina \"Lisävalinnat\".",
     "install_devices_macos_list_4": "Valitse DNS-välilehti ja syötä AdGuard Home -palvelimesi osoitteet.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Avaa hallintapaneeli",
     "install_saved": "Tallenus onnistui",
     "encryption_title": "Salaus",
-    "encryption_desc": "Salaus (HTTPS/TLS) DNS-palvelimelle ja selaimen hallintasivulle",
+    "encryption_desc": "Salaustuki (HTTPS/TLS) DNS:lle ja verkkokäyttölliittymälle.",
     "encryption_config_saved": "Salausasetukset tallennettiin",
     "encryption_server": "Palvelimen nimi",
     "encryption_server_enter": "Syötä verkkotunnuksesi",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Jos HTTPS-portti on määritetty, on AdGuard Homen hallintapaneeli käytettävissä HTTPS-yhteydellä ja lisäksi tämä mahdollistaa myös DNS-over-HTTPS -yhteyden '/dns-query' -kohteessa.",
     "encryption_dot": "DNS-over-TLS -portti",
     "encryption_dot_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-TLS -palvelimen tässä portissa.",
-    "encryption_doq": "DNS-over-QUIC -portti",
+    "encryption_doq": "DNS-over-QUIC -portti (kokeellinen)",
     "encryption_doq_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-QUIC -palvelimen tässä portissa. Ominaisuus on kokeellinen, eikä välttämättä luotettava. Lisäksi tätä tukevia päätelaitteita ei vielä ole kovin paljon.",
     "encryption_certificates": "Varmenteet",
     "encryption_certificates_desc": "Salauksen käyttämiseksi, on syötettävä verkkotunnuksellesi myönnetty, aito SSL-varmenneketju. Voit hankkia ilmaisen varmenteen osoitteesta <0>{{link}}</0> tai ostaa sellaisen joltakin luotetulta varmentajalta.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Kopioi/liitä tähän varmenteesi PEM-koodattu yksityinen avain.",
     "encryption_enable": "Käytä salausta (HTTPS, DNS-over-HTTPS ja DNS-over-TLS)",
     "encryption_enable_desc": "Jos salaus on käytössä, AdGuard Homen hallinta on käytettävissä HTTPS-yhteydellä ja DNS-palvelin kuuntelee pyyntöjä DNS-over-HTTPS ja DNS-over-TLS -yhteyksillä.",
-    "encryption_chain_valid": "Varmenneketju on pätevä",
-    "encryption_chain_invalid": "Varmenneketju ei kelpaa",
-    "encryption_key_valid": "Yksityinen {{type}}-avain on pätevä",
-    "encryption_key_invalid": "Yksityinen {{type}}-avain ei kelpaa",
+    "encryption_chain_valid": "Varmenneketju on kelvollinen.",
+    "encryption_chain_invalid": "Varmenneketju ei kelpaa.",
+    "encryption_key_valid": "Tämä yksityinen {{type}}-avain on kelvollinen.",
+    "encryption_key_invalid": "Tämä yksityinen {{type}}-avain ei kelpaa.",
     "encryption_subject": "Aihe",
     "encryption_issuer": "Toimittaja",
     "encryption_hostnames": "Isäntänimet",
     "encryption_reset": "Haluatko varmasti palauttaa salausasetukset?",
     "topline_expiring_certificate": "SSL-varmenteesi on erääntymässä. Päivitä <0>Salausasetukset</0>.",
     "topline_expired_certificate": "SSL-varmenteesi on erääntynyt. Päivitä <0>Salausasetukset</0>.",
-    "form_error_port_range": "Syötä portti väliltä 80-65535",
-    "form_error_port_unsafe": "Tämä portti ei ole turvallinen",
-    "form_error_equal": "Ei voi olla sama",
-    "form_error_password": "Salasanat eivät täsmää",
+    "form_error_port_range": "Syötä portti väliltä 80-65535.",
+    "form_error_port_unsafe": "Tämä portti ei ole turvallinen.",
+    "form_error_equal": "Ei voi olla sama.",
+    "form_error_password": "Salasanat eivät täsmää.",
     "reset_settings": "Tyhjennä asetukset",
     "update_announcement": "AdGuard Home {{version}} on nyt saatavilla! <0>Paina tästä</0> saadaksesi lisätietoja.",
     "setup_guide": "Asennusopas",
     "dns_addresses": "DNS-osoitteet",
     "dns_start": "DNS-palvelin käynnistyy",
-    "dns_status_error": "Virhe tarkistettaessa DNS-palvelimen tilaa",
+    "dns_status_error": "Virhe tarkistettaessa DNS-palvelimen tilaa.",
     "down": "yhteydetön",
     "fix": "Korjaa",
     "dns_providers": "Katso <0>luettelo tunnetuista DNS-palveluista</0>, joista valita.",
@@ -405,8 +405,8 @@
     "update_failed": "Automaattinen päivitys epäonnistui. Seuraa <a>näitä ohjeita</a> päivittääksesi manuaalisesti.",
     "manual_update": "Seuraa <a>näitä ohjeita</a> päivittääksesi manuaalisesti.",
     "processing_update": "Odota kun AdGuard Home päivittyy",
-    "clients_title": "Päätelaitteet",
-    "clients_desc": "Määritä AdGuard Homeen yhdistetyt päätelaitteet",
+    "clients_title": "Pysyvät päätelaitteet",
+    "clients_desc": "Määritä pysyvät AdGuard Homeen yhdistetyt päätelaittetiedot.",
     "settings_global": "Yleinen",
     "settings_custom": "Oma",
     "table_client": "Päätelaite",
@@ -417,9 +417,9 @@
     "client_edit": "Muokkaa päätelaitetta",
     "client_identifier": "Tunniste",
     "ip_address": "IP-osoite",
-    "client_identifier_desc": "Päätelaitteet voidaan tunnistaa IP-osoitteista, CIDR-merkinnöistä, MAC-osoitteista tai erityisistä päätelaitteen ID-tunnisteista (voidaan käyttää DoT/DoH/DoQ kanssa). <0>Täältä</0> voit lukea lisää päätelaitteiden tunnistuksesta.",
+    "client_identifier_desc": "Päätelaitteet voidaan tunnistaa IP- tai MAC-osoitteista, CIDR-merkinnöistä tai erityisistä päätelaite ID -tunnisteista (voidaan käyttää DoT/DoH/DoQ yhteydessä). Lue lisää päätelaitteiden tunnistuksesta <0>täältä</0>.",
     "form_enter_ip": "Syötä IP-osoite",
-    "form_enter_subnet_ip": "Syötä IP-osoite aliverkkoon \"{{cidr}}\"",
+    "form_enter_subnet_ip": "Syötä aliverkossa \"{{cidr}}\" oleva IP-osoite",
     "form_enter_mac": "Syötä MAC-osoite",
     "form_enter_id": "Muokkaa tunnistetta",
     "form_add_id": "Lisää tunniste",
@@ -432,14 +432,14 @@
     "clients_not_found": "Päätelaitteita ei löytynyt",
     "client_confirm_delete": "Haluatko varmasti poistaa päätelaitteen \"{{key}}\"?",
     "list_confirm_delete": "Haluatko varmasti poistaa tämän listan?",
-    "auto_clients_title": "Päätelaitteet (määrittämättömät)",
-    "auto_clients_desc": "Tiedot niistä AdGuard Homea käyttävistä päätelaitteista, joita ei ole määritetty asetuksissa",
+    "auto_clients_title": "Määrittämättömät päätelaitteet",
+    "auto_clients_desc": "Päätelaitteet, joita ei ole määritetty pysyviksi ja jotka voivat silti käyttää AdGuard Homea.",
     "access_title": "Käytön asetukset",
     "access_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.",
     "access_allowed_title": "Sallitut päätelaitteet",
-    "access_allowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai päätelaitteiden ID-tunnisteista. Jos määritetty, hyväksyy AdGuard Home pyyntöjä vain näiltä päätelaitteilta.",
+    "access_allowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai <a>päätelaite ID</a> -tunnisteista. Jos listalla on kohteita, hyväksyy AdGuard Home pyyntöjä vain näiltä päätelaitteilta.",
     "access_disallowed_title": "Kielletyt päätelaitteet",
-    "access_disallowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai päätelaitteiden ID-tunnisteista. Jos määritetty, hylkää AdGuard Home näiden päätelaitteiden pyynnöt. Tätä kenttää ei huomioida, jos sallittuja päätelaitteita on määritetty.",
+    "access_disallowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai <a>päätelaite ID</a> -tunnisteista. Jos listalla on kohteita, hylkää AdGuard Home näiden päätelaitteiden pyynnöt. Tätä kenttää ei huomioida, jos sallittuja päätelaitteita on määritetty.",
     "access_blocked_title": "Kielletyt verkkotunnukset",
     "access_blocked_desc": "Ei pidä sekoittaa suodattimiin. AdGuard Home hylkää näiden verkkotunnusten DNS-pyynnöt, eivätkä nämä pyynnöt näy edes pyyntöhistoriassa. Tähän voidaan syöttää tarkkoja verkkotunnuksia, jokerimerkkejä tai URL-suodatussääntöjä, kuten \"example.org\", \"*.example.org\" tai \"||example.org^\".",
     "access_settings_saved": "Käytön asetukset tallennettiin",
@@ -475,8 +475,8 @@
     "dns_rewrites": "DNS-uudelleenohjaukset",
     "form_domain": "Syötä verkkotunnus tai jokerimerkki",
     "form_answer": "Syötä IP-osoite tai verkkotunnus",
-    "form_error_domain_format": "Virheellinen verkkotunnuksen muoto",
-    "form_error_answer_format": "Virheellinen vastauksen muoto",
+    "form_error_domain_format": "Virheellinen verkkotunnuksen muoto.",
+    "form_error_answer_format": "Virheellinen vastauksen muoto.",
     "configure": "Määritä",
     "main_settings": "Pääasetukset",
     "block_services": "Estä tietyt palvelut",
@@ -507,7 +507,7 @@
     "filter_updated": "Listan päivitettiin",
     "statistics_configuration": "Tilastoinnin määritys",
     "statistics_retention": "Tilastojen säilytys",
-    "statistics_retention_desc": "Jos aikaa lyhennetään, joitakin tietoja menetetään.",
+    "statistics_retention_desc": "Jos aikajaksoa lyhennetään, joitakin tietoja menetetään.",
     "statistics_clear": "Tyhjennä tilastot",
     "statistics_clear_confirm": "Haluatko varmasti tyhjentää tilastot?",
     "statistics_retention_confirm": "Haluatko varmasti muuttaa tilastojen säilytysaikaa? Jos aikaa lyhennetään, joitakin tietoja menetetään.",
@@ -532,7 +532,7 @@
     "netname": "Verkon nimi",
     "network": "Verkko",
     "descr": "Kuvaus",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Lue lisää</0> omien hosts-listojesi luonnista.",
     "blocked_by_response": "Vastauksen sisältämän CNAME:n tai IP:n estämä",
     "blocked_by_cname_or_ip": "CNAME:n tai IP:n estämä",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Suorittaa toiminnot: <0>Poistaa käytöstä järjestelmän DNSStubListener-palvelun</0> <0>Määrittää DNS-palvelimen osoitteeksi 127.0.0.1</0> <0>Muuttaa sijainnnin /etc/resolv.conf symbolisen linkin kohteeksi /run/systemd/resolve/resolv.conf</0> <0>Pysäyttää DNSStubListener-palvelun (uudelleenlataa systemd-resolved -palvelu)</0>",
     "autofix_warning_result": "Tämän jälkeen järjestelmäsi kaikki DNS-pyynnöt käsittelee oletusarvoisesti AdGuard Home.",
     "tags_title": "Tunnisteet",
-    "tags_desc": "Voit valita päätelaitteeseen viittaavia tunnisteita. Tunnisteet voidaan sisällyttää suodatussääntöihin ja näin voit kohdistaa niitä tarkemmin. <0>Lue lisää</0>",
+    "tags_desc": "Voit valita päätelaitetta vastaavia tunnisteita. Tunnisteet voidaan sisällyttää suodatussääntöihin ja näin voit kohdistaa niitä tarkemmin. <0>Lue lisää</0>.",
     "form_select_tags": "Valitse päätelaitteen tunnisteet",
     "check_title": "Tarkasta suodatus",
-    "check_desc": "Tarkasta suodatetaanko isäntänimeä",
+    "check_desc": "Tarkasta onko isäntänimi suodatettu.",
     "check": "Tarkasta",
     "form_enter_host": "Syötä osoite",
     "filtered_custom_rules": "Suodatettu omilla suodatussäännöillä",
@@ -598,15 +598,15 @@
     "blocklist": "Estolista",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Välimuistin koko",
-    "cache_size_desc": "DNS-välimuistin koko (tavuina)",
+    "cache_size_desc": "DNS-välimuistin koko (tavuina).",
     "cache_ttl_min_override": "Korvaa vähimmäis-TTL",
     "cache_ttl_max_override": "Korvaa enimmäis-TTL",
     "enter_cache_size": "Syötä välimuistin koko (tavuina)",
     "enter_cache_ttl_min_override": "Syötä vähimmäis-TTL (sekunteina)",
     "enter_cache_ttl_max_override": "Syötä enimmäis-TTL (sekunteina)",
     "cache_ttl_min_override_desc": "Pidennä ylävirran palvelimelta vastaanotettuja, lyhyitä elinaika-arvoja (sekunteina) tallennettaessa DNS-vastauksia välimuistiin.",
-    "cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden suurin elinaika-arvo (sekunteina)",
-    "ttl_cache_validation": "Välimuistin elinajan vähimmäisarvon tulee olla pienempi tai sama kuin enimmäisarvon",
+    "cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden enimmäiselinaika (sekunteina).",
+    "ttl_cache_validation": "Välimuistin vähimmäiselinajan tulee olla pienempi tai sama kuin enimmäiselinajan.",
     "cache_optimistic": "Optimistinen välimuisti",
     "cache_optimistic_desc": "Pakota AdGuard Home vastaamaan välimuistista vaikka sen tiedot olisivat vanhentuneet. Pyri samalla myös päivittämään tiedot.",
     "filter_category_general": "Yleiset",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home hylkää tämän päätelaitteen DNS-pyynnöt.",
     "filter_allowlist": "VAROITUS: Toiminto ohittaa \"{{disallowed_rule}}\" -säännön sallittujen päätelaitteiden listalta.",
     "last_rule_in_allowlist": "Et voi estää tätä päätelaitetta, koska säännön \"{{disallowed_rule}}\" ohitus POISTAA KÄYTÖSTÄ \"Sallitut päätelaitteet\" -listan.",
-    "experimental": "Kokeellinen",
     "use_saved_key": "Käytä aiemmin tallennettua avainta",
     "parental_control": "Lapsilukko",
     "safe_browsing": "Turvallinen selaus",
-    "served_from_cache": "{{value}} <i>(jaettu välimuistista)</i>"
+    "served_from_cache": "{{value}} <i>(jaettu välimuistista)</i>",
+    "form_error_password_length": "Salasanan on oltava ainakin {{value}} merkkiä."
 }
diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json
index e3260cdd..b10433ae 100644
--- a/client/src/__locales/fr.json
+++ b/client/src/__locales/fr.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Paramètres du client",
-    "example_upstream_reserved": "Vous pouvez spécifier un DNS en amont <0>pour un/des domaine(s) spécifique(s)</0>",
-    "example_upstream_comment": "Vous pouvez spécifier un commentaire",
+    "example_upstream_reserved": "un amont <0>pour des domaines spécifiques</0> ;",
+    "example_upstream_comment": " un commentaire.",
     "upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.",
     "parallel_requests": "Demandes en parallèle",
     "load_balancing": "Équilibrage de charge",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Configuration du serveur DHCP sauvegardée",
     "dhcp_ipv4_settings": "Paramètres IPv4 du DHCP",
     "dhcp_ipv6_settings": "Paramètres IPv6 du DHCP",
-    "form_error_required": "Champ requis",
-    "form_error_ip4_format": "Adresse IPv4 invalide",
-    "form_error_ip4_range_start_format": "Adresse de début de plage IPv4 incorrecte",
-    "form_error_ip4_range_end_format": "Adresse de fin de plage IPv4 incorrecte",
-    "form_error_ip4_gateway_format": "Adresse de passerelle IPv4 invalide",
-    "form_error_ip6_format": "Adresse IPv6 invalide",
-    "form_error_ip_format": "Adresse IP invalide",
-    "form_error_mac_format": "Adresse MAC invalide",
-    "form_error_client_id_format": "L'ID du client ne doit contenir que des chiffres, des lettres minuscules et des traits d'union.",
-    "form_error_server_name": "Nom de serveur invalide",
-    "form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} »",
-    "form_error_positive": "Doit être supérieur à 0",
-    "out_of_range_error": "Doit être hors plage « {{start}} » - « {{end}} »",
-    "lower_range_start_error": "Doit être inférieur au début de plage",
-    "greater_range_start_error": "Doit être supérieur au début de plage",
-    "greater_range_end_error": "Doit être supérieur à la fin de plage",
-    "subnet_error": "Les adresses doivent être dans le même sous-réseau",
-    "gateway_or_subnet_invalid": "Masque de sous-réseau invalide",
+    "form_error_required": "Champ requis.",
+    "form_error_ip4_format": "Adresse IPv4 invalide.",
+    "form_error_ip4_range_start_format": "Adresse de début de plage IPv4 incorrecte.",
+    "form_error_ip4_range_end_format": "Adresse de fin de plage IPv4 incorrecte.",
+    "form_error_ip4_gateway_format": "Adresse de passerelle IPv4 invalide.",
+    "form_error_ip6_format": "Adresse IPv6 invalide.",
+    "form_error_ip_format": "Adresse IP invalide.",
+    "form_error_mac_format": "Format MAC invalide.",
+    "form_error_client_id_format": "ClientID ne doit contenir que des chiffres, des lettres minuscules et des traits d'union.",
+    "form_error_server_name": "Nom de serveur invalide.",
+    "form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} ».",
+    "form_error_positive": "Doit être supérieur à 0.",
+    "out_of_range_error": "Doit être hors plage « {{start}} » - « {{end}} ».",
+    "lower_range_start_error": "Doit être inférieur au début de plage.",
+    "greater_range_start_error": "Doit être supérieur au début de plage.",
+    "greater_range_end_error": "Doit être supérieur à la fin de plage.",
+    "subnet_error": "Les adresses doivent être dans le même sous-réseau.",
+    "gateway_or_subnet_invalid": "Masque de sous-réseau invalide.",
     "dhcp_form_gateway_input": "IP de la passerelle",
     "dhcp_form_subnet_input": "Masque de sous-réseau",
     "dhcp_form_range_title": "Rangée des adresses IP",
@@ -143,7 +143,7 @@
     "use_adguard_browsing_sec_hint": "AdGuard Home vérifiera si le domaine est bloqué par le service web de sécurité de la navigation. Il utilisera une API de recherche respectueuse de la vie privée pour effectuer la vérification : seul un préfixe court du hachage SHA256 du nom de domaine est envoyé au serveur.",
     "use_adguard_parental": "Utiliser le contrôle parental d'AdGuard",
     "use_adguard_parental_hint": "AdGuard Home va vérifier s'il y a du contenu pour adultes sur le domaine. Ce sera fait par aide du même API discret que celui utilisé par le service de Sécurité de navigation.",
-    "enforce_safe_search": "Utiliser la recherche sécurisée",
+    "enforce_safe_search": "Utiliser la Recherche Sécurisée",
     "enforce_save_search_hint": "AdGuard Home appliquera la recherche sécurisée dans les moteurs de recherche suivants : Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
     "no_servers_specified": "Pas de serveurs spécifiés",
     "general_settings": "Paramètres généraux",
@@ -165,10 +165,10 @@
     "enabled_filtering_toast": "Filtrage activé",
     "disabled_safe_browsing_toast": "Navigation sécurisée désactivée",
     "enabled_safe_browsing_toast": "Navigation sécurisée activée",
-    "disabled_parental_toast": "Contrôle parental désactivé",
-    "enabled_parental_toast": "Contrôle parental activé",
-    "disabled_safe_search_toast": "Recherche sécurisée désactivée",
-    "enabled_save_search_toast": "Recherche sécurisée activée",
+    "disabled_parental_toast": "Contrôle Parental désactivé",
+    "enabled_parental_toast": "Contrôle Parental activé",
+    "disabled_safe_search_toast": "Recherche Sécurisée désactivée",
+    "enabled_save_search_toast": "Recherche Sécurisée activée",
     "enabled_table_header": "Activé",
     "name_table_header": "Nom",
     "list_url_table_header": "URL de la liste",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Choisir des listes d’autorisation",
     "enter_valid_blocklist": "Saisissez une URL valide vers la liste de blocage.",
     "enter_valid_allowlist": "Saisissez une URL valide vers la liste d’autorisation.",
-    "form_error_url_format": "Format d’URL incorrect",
-    "form_error_url_or_path_format": "Entrez une URL ou le chemin absolu de la liste",
+    "form_error_url_format": "Format d’URL incorrect.",
+    "form_error_url_or_path_format": "Entrez une URL ou le chemin absolu de la liste.",
     "custom_filter_rules": "Règles de filtrage d'utilisateur",
     "custom_filter_rules_hint": "Saisissez la règle en une ligne. C'est possible d'utiliser les règles de blocage ou la syntaxe des fichiers hosts.",
     "system_host_files": "Fichier d'hôtes système",
     "examples_title": "Exemples",
-    "example_meaning_filter_block": "bloque l’accès au domaine example.org et à tous ses sous-domaines",
-    "example_meaning_filter_whitelist": "débloque l’accès au domaine example.org et à tous ses sous-domaines",
-    "example_meaning_host_block": "AdGuard Home va retourner l'adresse 127.0.0.1 au domaine example.org (mais pas aux sous-domaines).",
-    "example_comment": "! Voici comment ajouter une déscription",
-    "example_comment_meaning": "commentaire",
-    "example_comment_hash": "# Et comme ça aussi on peut laisser des commentaires",
-    "example_regex_meaning": "bloque l’accès aux domaines correspondants à l'expression régulière spécifiée",
-    "example_upstream_regular": "DNS classique (au-dessus de UDP)",
-    "example_upstream_dot": "<0>DNS-over-TLS</0> chiffré",
-    "example_upstream_doh": "<0>DNS-over-HTTPS</0> chiffré",
-    "example_upstream_doq": "<0>DNS-over-QUIC</0> chiffré",
-    "example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps</0> pour <1>DNSCrypt</1> ou les resolveurs <2>DNS_over_HTTPS</2>",
-    "example_upstream_tcp": "DNS classique (au-dessus de TCP)",
+    "example_meaning_filter_block": "bloque l’accès au domaine example.org et à tous ses sous-domaines ;",
+    "example_meaning_filter_whitelist": "débloque l’accès au domaine example.org et à tous ses sous-domaines ;",
+    "example_meaning_host_block": "AdGuard Home va retourner l'adresse 127.0.0.1 au domaine example.org (mais pas aux sous-domaines) ;",
+    "example_comment": "! Voici comment ajouter une déscription.",
+    "example_comment_meaning": "juste un commentaire ;",
+    "example_comment_hash": "# Aussi un commentaire.",
+    "example_regex_meaning": "bloque l’accès aux domaines correspondants à l'expression régulière spécifiée .",
+    "example_upstream_regular": "DNS classique (au-dessus de UDP) ;",
+    "example_upstream_dot": "<0>DNS-over-TLS</0> chiffré ;",
+    "example_upstream_doh": "<0>DNS-over-HTTPS</0> chiffré ;",
+    "example_upstream_doq": "<0>DNS-over-QUIC</0> chiffré (expérimental) ;",
+    "example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps</0> pour <1>DNSCrypt</1> ou les résolveurs <2>DNS_over_HTTPS</2> ;",
+    "example_upstream_tcp": "DNS classique (au-dessus de TCP) ;",
     "all_lists_up_to_date_toast": "Toutes les listes sont déjà à jour",
     "updated_upstream_dns_toast": "Serveurs en amont enregistrés",
     "dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Utilisez les doubles guillemets pour une recherche stricte",
     "query_log_retention_confirm": "Êtes-vous sûr de vouloir modifier la rétention des journaux de requêtes ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues",
     "anonymize_client_ip": "Anonymiser l’IP du client",
-    "anonymize_client_ip_desc": "Ne pas enregistrer l’adresse IP complète du client dans les journaux et statistiques",
+    "anonymize_client_ip_desc": "Ne pas enregistrer l’adresse IP complète du client dans les journaux et statistiques .",
     "dns_config": "Configuration du serveur DNS",
     "dns_cache_config": "Configuration du cache DNS",
-    "dns_cache_config_desc": "Ici, vous pouvez configurer le cache DNS",
+    "dns_cache_config_desc": "Ici, vous pouvez configurer le cache DNS .",
     "blocking_mode": "Mode du blocage",
     "default": "Par défaut",
     "nxdomain": "NXDOMAIN",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "ID du client",
-    "client_id_placeholder": "Saisissez le ID du client",
-    "client_id_desc": "Les clients différents peuvent être identifiés par aide d'un ID client spécial. Vous trouverez plus d'information sur l'identification des clients <a>ici</a> .",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Saisissez le ClientID",
+    "client_id_desc": "Les clients différents peuvent être identifiés par aide d'un ClientID spécial. Vous trouverez plus d'information sur l'identification des clients <a>ici</a> .",
     "download_mobileconfig_doh": "Télécharger .mobileconfig pour DNS-sur-HTTPS",
     "download_mobileconfig_dot": "Télécharger .mobileconfig pour DNS-sur-TLS",
     "download_mobileconfig": "Télécharger le fichier de configuration",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Interface d'écoute",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Votre interface web administrateur AdGuard Home sera disponible sur les adresses suivantes :",
-    "form_error_port": "Entrez un numéro de port valide",
+    "form_error_port": "Entrez un port valide.",
     "install_settings_dns": "Serveur DNS",
     "install_settings_dns_desc": "Vous devrez configurer vos appareils et votre routeur pour utiliser le serveur DNS sur les adresses suivantes :",
     "install_settings_all_interfaces": "Toutes les interfaces",
@@ -334,8 +334,8 @@
     "install_devices_router_list_4": "Vous ne pouvez pas définir un serveur DNS personnalisé sur certains types de routeurs. Dans ce cas, la configuration de AdGuard Home en tant que <0>serveur DHCP</0> peut aider. Sinon, vous devez rechercher le manuel sur la façon de personnaliser les serveurs DNS pour votre modèle de routeur particulier.",
     "install_devices_windows_list_1": "Ouvrez votre Panneau de configuration depuis le menu Démarrer ou la recherche Windows.",
     "install_devices_windows_list_2": "Allez dans la catégorie Réseau et Internet et ensuite dans le Centre Réseau et Partage.",
-    "install_devices_windows_list_3": "Sur la partie gauche de l'écran, recherchez « Modifier les paramètres de l'adaptateur » et cliquez dessus.",
-    "install_devices_windows_list_4": "Sélectionnez votre connexion active, clic droit dessus et sélectionnez Propriétés.",
+    "install_devices_windows_list_3": "Dans le panneau de gauche, cliquez sur \"Modifier les paramètres de l'adaptateur\".",
+    "install_devices_windows_list_4": "Cliquez avec le bouton droit de la souris sur votre connexion active et sélectionnez Propriétés.",
     "install_devices_windows_list_5": "Recherchez « Protocole Internet Version 4 (TCP/IPv4) » (soit, pour IPv6, « Protocole Internet Version 6 (TCP/IPv6) ») dans la liste, sélectionnez-la puis cliquez à nouveau sur Propriétés.",
     "install_devices_windows_list_6": "Sélectionnez « Utiliser l’adresse de serveur DNS suivante » et saisissez votre adresse de serveur AdGuard Home.",
     "install_devices_macos_list_1": "Cliquez sur l'icône Apple et allez dans les Préférences Système.",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Si le port HTTPS est configuré, l'interface administrateur de AdGuard Home sera accessible via HTTPS et fournira aussi un service DNS-over-HTTPS sur l'emplacement '/dns-query'.",
     "encryption_dot": "Port DNS-over-TLS",
     "encryption_dot_desc": "Si ce port est configuré, AdGuard Home exécutera un serveur DNS-over-TLS sur ce port.",
-    "encryption_doq": "Port DNS sur QUIC",
+    "encryption_doq": "Port DNS sur QUIC (expérimental)",
     "encryption_doq_desc": "Si ce port est configuré, AdGuard Home exécutera un serveur DNS sur QUIC sur ce port. Ceci est expérimental et possiblement pas entièrement fiable. Peu de clients le prennent en charge actuellement.",
     "encryption_certificates": "Certificats",
     "encryption_certificates_desc": "Pour utiliser le chiffrement, vous devez fournir une chaîne de certificats SSL valide pour votre domaine. Vous pouvez en obtenir une gratuitement sur <0>{{link}}</0> ou vous pouvez en acheter une via les Autorités de Certification de confiance.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Copiez/coller votre clé privée PEM encodée pour votre certificat ici.",
     "encryption_enable": "Activer le chiffrement (HTTPS, DNS-over-HTTPS et DNS-over-TLS)",
     "encryption_enable_desc": "Si le chiffrement est activé, l'interface administrateur AdGuard Home fonctionnera via HTTPS et le serveur DNS écoutera les requêtes via DNS-over-HTTPS et DNS-over-TLS.",
-    "encryption_chain_valid": "Chaîne de certificat valide",
-    "encryption_chain_invalid": "Chaîne de certificat invalide",
-    "encryption_key_valid": "Ceci est une clé privée {{type}} valide",
-    "encryption_key_invalid": "Ceci est une clé privée {{type}} invalide",
+    "encryption_chain_valid": "Chaîne de certificat valide.",
+    "encryption_chain_invalid": "Chaîne de certificat invalide.",
+    "encryption_key_valid": "Ceci est une clé privée {{type}} valide.",
+    "encryption_key_invalid": "Ceci est une clé privée {{type}} invalide.",
     "encryption_subject": "Objet",
     "encryption_issuer": "Émetteur",
     "encryption_hostnames": "Noms d'hôte",
     "encryption_reset": "Voulez-vous vraiment réinitialiser les paramètres de chiffrement ?",
     "topline_expiring_certificate": "Votre certificat SSL est sur le point d'expirer. Mettez à jour vos <0>Paramètres de chiffrement</0>.",
     "topline_expired_certificate": "Votre certificat SSL a expiré. Mettez à jour vos <0>Paramètres de chiffrement</0>.",
-    "form_error_port_range": "Saisissez une valeur de port entre 80 et 65535",
-    "form_error_port_unsafe": "C'est un port non fiable",
-    "form_error_equal": "Ne doit pas être égal",
-    "form_error_password": "Mots de passe différents",
+    "form_error_port_range": "Saisissez une valeur de port entre 80 et 65535.",
+    "form_error_port_unsafe": "C'est un port non fiable.",
+    "form_error_equal": "Ne doit pas être égal .",
+    "form_error_password": "Mots de passe différents.",
     "reset_settings": "Réinitialiser les paramètres",
     "update_announcement": "AdGuard Home {{version}} est disponible ! <0>Cliquez ici</0> pour plus d'informations.",
     "setup_guide": "Guide d'installation",
     "dns_addresses": "Adresses DNS",
     "dns_start": "Démarrage du serveur DNS",
-    "dns_status_error": "Erreur lors de la récupération du statut du serveur DNS",
+    "dns_status_error": "Erreur lors de la récupération du statut du serveur DNS .",
     "down": "Descendant",
     "fix": "Corriger",
     "dns_providers": "Voici une <0>liste de fournisseurs DNS connus</0>.",
@@ -405,8 +405,8 @@
     "update_failed": "Échec de la mise à jour automatique. Veuillez <a>suivre ces étapes</a> pour mettre à jour manuellement.",
     "manual_update": "Veuillez <a>suivre ces étapes</a> pour mettre à jour manuellement.",
     "processing_update": "Veuillez patienter, AdGuard Home est en cours de mise à jour",
-    "clients_title": "Clients",
-    "clients_desc": "Configurer les appareils connectés à AdGuard Home",
+    "clients_title": "Clients persistants",
+    "clients_desc": "Configurez des enregistrements clients persistants pour les appareils connectés à AdGuard Home.",
     "settings_global": "Général",
     "settings_custom": "Personnalisé",
     "table_client": "Client",
@@ -417,7 +417,7 @@
     "client_edit": "Modifier le client",
     "client_identifier": "Identifiant",
     "ip_address": "Adresse IP",
-    "client_identifier_desc": "Les clients peuvent être identifiés par les adresses IP, CIDR, MAC ou un ID client spécial (qui peut être utilisé pour DoT/DoH/DoQ). Vous trouverez plus d'information sur l'identification des clients <0>ici</0> .",
+    "client_identifier_desc": "Les clients peuvent être identifiés par leur adresse IP, CIDR, adresse MAC ou ClientID (peut être utilisé pour DoT/DoH/DoQ). En savoir plus sur la façon d'identifier les clients <0>ici</0>.",
     "form_enter_ip": "Saisissez l'IP",
     "form_enter_subnet_ip": "Saisissez une adresse IP dans le sous-réseau « {{cidr}} »",
     "form_enter_mac": "Saisissez MAC",
@@ -432,14 +432,14 @@
     "clients_not_found": "Aucun client trouvé",
     "client_confirm_delete": "Voulez-vous vraiment supprimer le client « {{key}} »?",
     "list_confirm_delete": "Voulez-vous vraiment supprimer cette liste ?",
-    "auto_clients_title": "Clients (exécution)",
-    "auto_clients_desc": "Les données des clients qu'utilisent AdGuard Home, mais non stockées dans la configuration",
+    "auto_clients_title": "Clients d'exécution",
+    "auto_clients_desc": "Appareils ne figurant pas sur la liste des clients persistants qui peuvent encore utiliser AdGuard Home.",
     "access_title": "Paramètres d'accès",
     "access_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home.",
     "access_allowed_title": "Clients autorisés",
-    "access_allowed_desc": "Une liste de CIDR, d'adresses IP ou d'ID client. S'il est configuré, AdGuard Home acceptera uniquement les demandes de ces clients.",
+    "access_allowed_desc": "Une liste de CIDRs, d'adresses IP, ou de <a>ClientIDs</a>. Si cette liste comporte des entrées, AdGuard Home n'acceptera que les demandes provenant de ces clients.",
     "access_disallowed_title": "Clients non autorisés",
-    "access_disallowed_desc": "Une liste d'adresses IP ou CIDR. Si configuré, AdGuard Home bloquera les requêtes provenant de ces adresses IP. Si des clients sont configurés, ce champ sera ignoré.",
+    "access_disallowed_desc": "Une liste de CIDRs, d'adresses IP, ou de <a>ClientIDs</a>. Si cette liste comporte des entrées, AdGuard Home abandonnera les demandes provenant de ces clients. Ce champ est ignoré s'il y a des entrées dans Clients autorisés.",
     "access_blocked_title": "Domaines interdits",
     "access_blocked_desc": "A ne pas confondre avec les filtres. AdGuard Home rejette les requêtes DNS correspondant à ces domaines, et ces requêtes n'apparaissent même pas dans le journal des requêtes. Vous pouvez spécifier des noms de domaine exacts, des caractères génériques ou des règles de filtrage d'URL, par exemple « exemple.org », « *.exemple.org » ou « ||example.org^ » de manière correspondante.",
     "access_settings_saved": "Paramètres d'accès enregistrés avec succès",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Réécritures DNS",
     "form_domain": "Saisissez un domaine ou caracrtère générique",
     "form_answer": "Saisissez une adresse IP ou un nom de domaine",
-    "form_error_domain_format": "Format de domaine invalide",
-    "form_error_answer_format": "Format de réponse invalide",
+    "form_error_domain_format": "Nom de domaine invalide.",
+    "form_error_answer_format": "Format de réponse invalide.",
     "configure": "Configurer",
     "main_settings": "Paramètres principaux",
     "block_services": "Bloquer des services spécifiques",
@@ -507,7 +507,7 @@
     "filter_updated": "Le filtre a été mis à jour avec succès",
     "statistics_configuration": "Configuration des statistiques",
     "statistics_retention": "Maintien des statistiques",
-    "statistics_retention_desc": "Si vous baissez la valeur de l'intervalle, des données seront perdues",
+    "statistics_retention_desc": "Si vous baissez la valeur de l'intervalle, des données seront perdues .",
     "statistics_clear": " Effacer les statistiques",
     "statistics_clear_confirm": "Voulez-vous vraiment effacer les statistiques ?",
     "statistics_retention_confirm": "Êtes-vous sûr de vouloir modifier le maintien des statistiques ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues",
@@ -532,7 +532,7 @@
     "netname": "Nom du réseau",
     "network": "Réseau",
     "descr": "Description",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Apprenez-en plus</0> à propos de la création de vos propres listes de blocage d’hôtes.",
     "blocked_by_response": "Bloqué par un CNAME ou une réponse IP",
     "blocked_by_cname_or_ip": "Bloqué par CNAME ou adresse IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Ceci effectuera les tâches suivantes : <0>Désactiver le système DNSStubListener</0> <0>Définir l’adresse du serveur DNS à 127.0.0.1 </0> <0>Remplacer la cible du lien symbolique de /etc/resolv.conf par /run/systemd/resolve/resolv.conf</0> <0>Arrêter DNSStubListener (recharger le service résolu par systemd)</0>",
     "autofix_warning_result": "Par conséquent, toutes les demandes DNS de votre système seront traitées par AdGuardHome par défaut.",
     "tags_title": "Mots clés",
-    "tags_desc": "Vous pouvez sélectionner les mots clés qui correspondent au client. Les mots clés peuvent être inclus dans les règles de filtrage et vous permettent de les appliquer plus précisément. <0>En savoir plus</0>",
+    "tags_desc": "Vous pouvez sélectionner les mots clés qui correspondent au client. Les mots clés peuvent être inclus dans les règles de filtrage et vous permettent de les appliquer plus précisément. <0>En savoir plus</0> .",
     "form_select_tags": "Sélectionner les mots clés du client",
     "check_title": "Vérification du filtrage",
-    "check_desc": "Vérifier si le nom d’hôte est filtré",
+    "check_desc": "Vérifier si le nom d’hôte est filtré .",
     "check": "Vérifier",
     "form_enter_host": "Saisissez un nom d’hôte",
     "filtered_custom_rules": "Filtré par des règles de filtrage personnalisées",
@@ -594,19 +594,19 @@
     "allowed": "Autorisé",
     "filtered": "Filtré",
     "rewritten": "Réécrit",
-    "safe_search": "Recherche sécurisée",
+    "safe_search": "Recherche Sécurisée",
     "blocklist": "Liste de blocage",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Taille du cache",
-    "cache_size_desc": "Taille du cache DNS (en bytes)",
+    "cache_size_desc": "Taille du cache DNS (en bytes) .",
     "cache_ttl_min_override": "Remplacer le TTL minimum",
     "cache_ttl_max_override": "Remplacer le TTL maximum",
     "enter_cache_size": "Entrer la taille du cache (octets)",
     "enter_cache_ttl_min_override": "Entrez le TTL minimum (secondes)",
     "enter_cache_ttl_max_override": "Entrez le TTL maximum (secondes)",
-    "cache_ttl_min_override_desc": "Prolonger les valeurs courtes de durée de vie (en secondes) reçues du serveur en amont lors de la mise en cache des réponses DNS",
-    "cache_ttl_max_override_desc": "Établir la valeur de durée de vie TTL maximale (en secondes) pour les saisies dans le cache du DNS",
-    "ttl_cache_validation": "La valeur TTL minimale du cache doit être inférieure ou égale à la valeur maximale",
+    "cache_ttl_min_override_desc": "Prolonger les valeurs courtes de durée de vie (en secondes) reçues du serveur en amont lors de la mise en cache des réponses DNS .",
+    "cache_ttl_max_override_desc": "Établir la valeur de durée de vie TTL maximale (en secondes) pour les saisies dans le cache du DNS .",
+    "ttl_cache_validation": "La valeur TTL minimale du cache doit être inférieure ou égale à la valeur maximale .",
     "cache_optimistic": "Caching optimiste",
     "cache_optimistic_desc": "Faites en sorte qu'AdGuard Home réponde à partir du cache même lorsque les entrées ont expiré et essayez également de les actualiser.",
     "filter_category_general": "Général",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home ignorera toutes les requêtes DNS de ce client.",
     "filter_allowlist": "ATTENTION : Cette action exclura également la règle « {{disallowed_rule}} » de la liste des clients autorisés.",
     "last_rule_in_allowlist": "Impossible d’interdire ce client, car l’exclusion de la règle « {{disallowed_rule}} » DÉSACTIVERA la liste des « clients autorisés ».",
-    "experimental": "Expérimental",
     "use_saved_key": "Utiliser la clef précédemment enregistrée",
     "parental_control": "Contrôle parental",
     "safe_browsing": "Navigation sécurisée",
-    "served_from_cache": "{{value}} <i>(depuis le cache)</i>"
+    "served_from_cache": "{{value}} <i>(depuis le cache)</i>",
+    "form_error_password_length": "Le mot de passe doit comporter au moins {{value}} caractères."
 }
diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json
index e7642fd2..5b0dc1dc 100644
--- a/client/src/__locales/hr.json
+++ b/client/src/__locales/hr.json
@@ -623,7 +623,6 @@
     "adg_will_drop_dns_queries": "AdGuard Home odbaciti će sve DNS upite od ovog klijenta.",
     "filter_allowlist": "UPOZORENJE: Ova akcija će također isključiti pravilo \"{{disallowed_rule}}\" s popisa dopuštenih klijenata.",
     "last_rule_in_allowlist": "Ovaj klijent nije moguće onemogućiti jer će isključivanje pravila \"{{disallowed_rule}}\" ONEMOGUĆITI popis \"Dopušteni klijenti\".",
-    "experimental": "Eksperimentalno",
     "use_saved_key": "Korištenje prethodno spremljenog ključa",
     "parental_control": "Roditeljska zaštita",
     "safe_browsing": "Sigurno surfanje",
diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json
index 6c876315..b49b6506 100644
--- a/client/src/__locales/hu.json
+++ b/client/src/__locales/hu.json
@@ -623,7 +623,6 @@
     "adg_will_drop_dns_queries": "Az AdGuard Home eldobja az összes DNS kérést erről a kliensről.",
     "filter_allowlist": "FIGYELMEZTETÉS: Ez a művelet a \"{{disallowed_rule}}\" szabályt is kizárja az engedélyezett ügyfelek listájából.",
     "last_rule_in_allowlist": "Nem lehet letiltani ezt az ügyfelet, mert a \"{{disallowed_rule}}\" szabály kizárása letiltja az \"Allowed clients\" listát.",
-    "experimental": "Kísérleti",
     "use_saved_key": "Előzőleg mentett kulcs használata",
     "parental_control": "Szülői felügyelet",
     "safe_browsing": "Biztonságos böngészés",
diff --git a/client/src/__locales/id.json b/client/src/__locales/id.json
index 6449b62d..58148123 100644
--- a/client/src/__locales/id.json
+++ b/client/src/__locales/id.json
@@ -593,7 +593,7 @@
     "allowed": "Dibolehkan",
     "filtered": "Tersaring",
     "rewritten": "Tulis ulang",
-    "safe_search": "Pencarian aman",
+    "safe_search": "Aktifkan Pencarian Aman",
     "blocklist": "Daftar blokir",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Ukuran cache",
@@ -623,7 +623,6 @@
     "adg_will_drop_dns_queries": "AdGuard Home akan menghapus semua permintaan DNS dari klien ini.",
     "filter_allowlist": "PERINGATAN: Tindakan ini juga akan mengecualikan aturan \"{{disallowed_rule}}\" dari daftar klien yang diizinkan.",
     "last_rule_in_allowlist": "Tidak dapat melarang klien ini karena mengecualikan aturan \"{{disallowed_rule}}\" akan MENONAKTIFKAN daftar \"Klien yang diizinkan\".",
-    "experimental": "Eksperimental",
     "use_saved_key": "Gunakan kunci yang disimpan sebelumnya",
     "parental_control": "Kontrol Orang Tua",
     "safe_browsing": "Penjelajahan Aman",
diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json
index f0be3db7..fd560562 100644
--- a/client/src/__locales/it.json
+++ b/client/src/__locales/it.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Impostazioni client",
-    "example_upstream_reserved": "Puoi specificare un upstream DNS<0>per lo specifico dominio(i)</0>",
-    "example_upstream_comment": "Puoi specificare un commento",
+    "example_upstream_reserved": "un upstream <0>per specifici domini</0>;",
+    "example_upstream_comment": "un commento.",
     "upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.",
     "parallel_requests": "Richieste parallele",
     "load_balancing": "Bilanciamento del carico",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Salvataggio configurazione server DHCP riuscito",
     "dhcp_ipv4_settings": "Impostazioni DHCP IPv4",
     "dhcp_ipv6_settings": "Impostazioni DHCP IPv6",
-    "form_error_required": "Campo richiesto",
-    "form_error_ip4_format": "Indirizzo IPv4 non valido",
-    "form_error_ip4_range_start_format": "Indirizzo IPV4 non valido dell'intervallo iniziale",
-    "form_error_ip4_range_end_format": "Indirizzo IPV4 non valido dell'intervallo finale",
-    "form_error_ip4_gateway_format": "Indirizzo gateway IPv4 non valido",
-    "form_error_ip6_format": "Indirizzo IPv6 non valido",
-    "form_error_ip_format": "Indirizzo IP non valido",
-    "form_error_mac_format": "Indirizzo MAC non valido",
-    "form_error_client_id_format": "Il client ID deve contenere solo numeri, lettere minuscole e trattini",
-    "form_error_server_name": "Nome server non valido",
-    "form_error_subnet": "La subnet \"{{cidr}}\" non contiene l'indirizzo IP \"{{ip}}\"",
-    "form_error_positive": "Deve essere maggiore di 0",
-    "out_of_range_error": "Deve essere fuori intervallo \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Deve essere inferiore dell'intervallo di inizio",
-    "greater_range_start_error": "Deve essere maggiore dell'intervallo di inizio",
-    "greater_range_end_error": "Deve essere maggiore dell'intervallo di fine",
-    "subnet_error": "Gli indirizzi devono trovarsi in una sottorete",
-    "gateway_or_subnet_invalid": "Maschera di sottorete non valida",
+    "form_error_required": "Campo richiesto.",
+    "form_error_ip4_format": "Indirizzo IPv4 non valido.",
+    "form_error_ip4_range_start_format": "Indirizzo IPV4 non valido dell'intervallo iniziale.",
+    "form_error_ip4_range_end_format": "Indirizzo IPV4 non valido dell'intervallo finale.",
+    "form_error_ip4_gateway_format": "Indirizzo gateway IPv4 non valido.",
+    "form_error_ip6_format": "Indirizzo IPv6 non valido.",
+    "form_error_ip_format": "Indirizzo IP non valido.",
+    "form_error_mac_format": "Indirizzo MAC non valido.",
+    "form_error_client_id_format": "Il ClientID deve contenere solo numeri, lettere minuscole, e trattini.",
+    "form_error_server_name": "Nome server non valido.",
+    "form_error_subnet": "Il subnet \"{{cidr}}\" non contiene l'indirizzo IP \"{{ip}}\".",
+    "form_error_positive": "Deve essere maggiore di 0.",
+    "out_of_range_error": "Deve essere fuori intervallo \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Deve essere inferiore dell'intervallo di inizio.",
+    "greater_range_start_error": "Deve essere maggiore dell'intervallo di inizio.",
+    "greater_range_end_error": "Deve essere maggiore dell'intervallo di fine.",
+    "subnet_error": "Gli indirizzi devono trovarsi in una sottorete.",
+    "gateway_or_subnet_invalid": "Maschera di sottorete non valida.",
     "dhcp_form_gateway_input": "IP Gateway",
     "dhcp_form_subnet_input": "Maschera di sottorete",
     "dhcp_form_range_title": "Intervallo di indirizzi IP",
@@ -143,7 +143,7 @@
     "use_adguard_browsing_sec_hint": "AdGuard Home verificherà se il dominio è bloccato dal servizio web di sicurezza della navigazione. Utilizzerà l'API di ricerca rispettosa della privacy per eseguire il controllo: solo un breve prefisso hash SHA256 del nome di dominio viene inviato al server.",
     "use_adguard_parental": "Utilizza il Controllo Parentale di AdGuard",
     "use_adguard_parental_hint": "AdGuard Home verificherà se il dominio contiene materiale per adulti. Utilizza le stesse API privacy-friendly del servizio web 'sicurezza di navigazione'.",
-    "enforce_safe_search": "Utilizza ricerca sicura",
+    "enforce_safe_search": "Utilizza Ricerca Sicura",
     "enforce_save_search_hint": "AdGuard Home forzerà la ricerca sicura sui seguenti motori di ricerca: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
     "no_servers_specified": "Nessun server specificato",
     "general_settings": "Impostazioni generali",
@@ -165,10 +165,10 @@
     "enabled_filtering_toast": "Attiva filtri",
     "disabled_safe_browsing_toast": "Disattiva Navigazione Sicura",
     "enabled_safe_browsing_toast": "Attiva Navigazione Sicura",
-    "disabled_parental_toast": "Disattiva il Controllo Parentale",
-    "enabled_parental_toast": "Attiva Controllo Parentale",
-    "disabled_safe_search_toast": "Ricerca sicura disattivata",
-    "enabled_save_search_toast": "Attiva ricerca sicura",
+    "disabled_parental_toast": "Il Controllo Parentale è disattivato",
+    "enabled_parental_toast": "Il Controllo Parentale è attivo",
+    "disabled_safe_search_toast": "La Ricerca Sicura è disattivata",
+    "enabled_save_search_toast": "La Ricerca Sicura è attiva",
     "enabled_table_header": "Attivo",
     "name_table_header": "Nome",
     "list_url_table_header": "Elenco URL",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Scegli liste bianche",
     "enter_valid_blocklist": "Inserisci un URL valido alla lista nera.",
     "enter_valid_allowlist": "Inserisci un URL valido alla lista bianca.",
-    "form_error_url_format": "Formato url non valido",
-    "form_error_url_or_path_format": "URL o percorso assoluto dell'elenco non validi",
+    "form_error_url_format": "Formato URL non valido.",
+    "form_error_url_or_path_format": "URL o percorso assoluto dell'elenco non validi.",
     "custom_filter_rules": "Regole filtri personalizzate",
     "custom_filter_rules_hint": "Inserisci una regola per riga. Puoi utilizzare la sintassi delle regole blocca-annunci o quelle dei file hosts.",
     "system_host_files": "File host di sistema",
     "examples_title": "Esempi",
-    "example_meaning_filter_block": "blocca accesso al dominio example.org e a tutti i suoi sottodomini",
-    "example_meaning_filter_whitelist": "consente l'accesso al dominio esempio.org e a tutti i relativi sottodomini",
-    "example_meaning_host_block": "AdGuard Home restituirà 127.0.0.1 come indirizzo per il dominio example.org (ma non per i suoi sottodomini)",
-    "example_comment": "! Qui va un commento",
-    "example_comment_meaning": "un commento",
-    "example_comment_hash": "# Un altro commento",
-    "example_regex_meaning": "blocca l'accesso ai domini che corrispondono alla specifica espressione regolare",
-    "example_upstream_regular": "DNS regolari (via UDP)",
-    "example_upstream_dot": "<0>DNS su TLS</0> crittografato",
-    "example_upstream_doh": "<0>DNS su HTTPS</0> crittografato",
-    "example_upstream_doq": "<0>DNS su QUIC</0> crittografato",
-    "example_upstream_sdns": "puoi utilizzare <0>DNS Stamps</0> per <1>DNSCrypt</1> oppure dei risolutori <2>DNS-over-HTTPS</2>",
-    "example_upstream_tcp": "DNS regolari (via TCP)",
+    "example_meaning_filter_block": "blocca accesso al dominio example.org e a tutti i suoi sottodomini;",
+    "example_meaning_filter_whitelist": "consente l'accesso al dominio esempio.org e a tutti i relativi sottodomini;",
+    "example_meaning_host_block": "restituisce 127.0.0.1 per example.org (ma non per i suoi sottodomini);",
+    "example_comment": "! Qui va un commento.",
+    "example_comment_meaning": "solo un commento;",
+    "example_comment_hash": "# Anche un commento.",
+    "example_regex_meaning": "blocca l'accesso ai domini corrispondenti alla specifica espressione regolare.",
+    "example_upstream_regular": "DNS regolari (tramite UDP);",
+    "example_upstream_dot": "<0>DNS su TLS</0> crittografato;",
+    "example_upstream_doh": "<0>DNS su HTTPS</0> crittografato;",
+    "example_upstream_doq": "<0>DNS su QUIC</0> crittografato (sperimentale);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> per <1>DNSCrypt</1> oppure i risolutori <2>DNS su HTTPS</2>;",
+    "example_upstream_tcp": "DNS regolari (tramite TCP);",
     "all_lists_up_to_date_toast": "Tutti gli elenchi sono aggiornati",
     "updated_upstream_dns_toast": "I server upstream sono stati salvati correttamente",
     "dns_test_ok_toast": "I server DNS specificati funzionano correttamente",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Utilizzare le doppie virgolette per una ricerca precisa",
     "query_log_retention_confirm": "Sei sicuro di voler modificare il registro delle richieste? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi",
     "anonymize_client_ip": "Anonimizza client IP",
-    "anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro e nelle statistiche",
+    "anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro o nelle statistiche.",
     "dns_config": "Configurazione server DNS",
     "dns_cache_config": "Configurazione cache DNS",
-    "dns_cache_config_desc": "Qui puoi configurare la cache DNS",
+    "dns_cache_config_desc": "Qui puoi configurare la cache DNS.",
     "blocking_mode": "Modalità di blocco",
     "default": "Predefinito",
     "nxdomain": "NXDOMAIN",
@@ -274,10 +274,10 @@
     "dnscrypt": "DNSCrypt",
     "dns_over_https": "DNS su HTTPS",
     "dns_over_tls": "DNS su TLS",
-    "dns_over_quic": "DNS su Quic",
-    "client_id": "ID client",
-    "client_id_placeholder": "Inserisci ID client",
-    "client_id_desc": "Client differenti possono essere identificati da uno speciale ID. <a>Qui</a> potrai saperne di più sui metodi per identificarli.",
+    "dns_over_quic": "DNS su QUIC",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Inserisci un ClientID",
+    "client_id_desc": "I client possono essere identificati attraverso un ClientID. <a>Qui</a> potrai saperne di più sui metodi per identificarli.",
     "download_mobileconfig_doh": "Scarica .mobileconfig per DNS su HTTPS",
     "download_mobileconfig_dot": "Scarica .mobileconfig per DNS su TLS",
     "download_mobileconfig": "Scarica file di configurazione",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Interfaccia d'ascolto",
     "install_settings_port": "Porta",
     "install_settings_interface_link": "La tua interfaccia web di amministrazione di AdGuard Home sarà disponibile ai seguenti indirizzi:",
-    "form_error_port": "Immettere un valore di porta valido",
+    "form_error_port": "Immettere un valore di porta valido.",
     "install_settings_dns": "Server DNS",
     "install_settings_dns_desc": "Sarà necessario configurare i dispositivi o il router per utilizzare il server DNS nei seguenti indirizzi:",
     "install_settings_all_interfaces": "Tutte le interfacce",
@@ -334,12 +334,12 @@
     "install_devices_router_list_4": "Su alcuni tipi di router, non è possibile configurare un server DNS personalizzato. In tal caso, configurare AdGuard Home come un <0>server DHCP</0> potrebbe aiutare. In alternativa, dovresti leggere il manuale di istruzioni per capire come personalizzare i server DNS sul tuo specifico modello di router.",
     "install_devices_windows_list_1": "Apri il Pannello di controllo tramite il menu Start o la ricerca di Windows.",
     "install_devices_windows_list_2": "Vai a Rete e categoria Internet e poi a Centro connessioni di rete e condivisione.",
-    "install_devices_windows_list_3": "Sul lato sinistro dello schermo, trova \"Cambia impostazioni adattatore\" e clicca su di esso.",
-    "install_devices_windows_list_4": "Seleziona la tua connessione attiva, fai clic destro su di essa e scegli Proprietà.",
+    "install_devices_windows_list_3": "Sul lato sinistro dello schermo, clicca su \"Cambia impostazioni adattatore\".",
+    "install_devices_windows_list_4": "Fai clic destro sulla tua connessione attiva e seleziona Proprietà.",
     "install_devices_windows_list_5": "Trova \"Protocollo Internet versione 4 (TCP/IPv4)\" (o, per IPv6, \"Protocollo Internet versione 6 (TCP/IPv6)\" nell'elenco, selezionalo e quindi clicca nuovamente su Proprietà.",
     "install_devices_windows_list_6": "Scegli \"Utilizza i seguenti indirizzi server DNS\" ed inserisci i tuoi indirizzi server AdGuard Home.",
-    "install_devices_macos_list_1": "Fai clic sull'icona Apple e vai su Preferenze di Sistema.",
-    "install_devices_macos_list_2": "Clicca sulla rete.",
+    "install_devices_macos_list_1": "Fai clic sull'icona Apple e dirigiti sulle Preferenze di Sistema.",
+    "install_devices_macos_list_2": "Clicca su Rete.",
     "install_devices_macos_list_3": "Seleziona la prima connessione nel tuo elenco e clicca su Avanzate.",
     "install_devices_macos_list_4": "Seleziona la scheda DNS e inserisci gli indirizzi del tuo server AdGuard Home",
     "install_devices_android_list_1": "Dalla schermata Home Menu di Android, clicca Impostazioni.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Apri pannello di controllo",
     "install_saved": "Salvataggio riuscito",
     "encryption_title": "crittografia",
-    "encryption_desc": "Supporto di crittografia (HTTPS / TLS) per interfaccia web sia di DNS che di amministrazione",
+    "encryption_desc": "Supporto alla crittografia (HTTPS / TLS) per DNS ed interfaccia web amministrazione.",
     "encryption_config_saved": "Configurazione crittografia salvata",
     "encryption_server": "Nome server",
     "encryption_server_enter": "Inserisci il tuo nome di dominio",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Se la porta HTTPS è configurata, l'interfaccia di amministrazione di AdGuard Home sarà accessibile tramite HTTPS e fornirà anche DNS su HTTPS nella posizione \"/ dns-query\".",
     "encryption_dot": "DNS su porta TLS",
     "encryption_dot_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su TLS su questa porta.",
-    "encryption_doq": "DNS su porta QUIC",
+    "encryption_doq": "Porta DNS su QUIC (sperimentale)",
     "encryption_doq_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su porta QUIC. Questa opzione è sperimentale e potrebbe non risultare affidabile. Inoltre, al momento non sono molti i client a supportarla.",
     "encryption_certificates": "Certificati",
     "encryption_certificates_desc": "Per utilizzare la crittografia, è necessario fornire una catena di certificati SSL valida per il proprio dominio. Puoi ottenere un certificato gratuito su <0> {{link}} </ 0> o puoi acquistarlo da una delle Autorità di certificazione attendibili.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Copia/Incolla qui la tua chiave privata codificata PEM per il tuo certificato.",
     "encryption_enable": "Attiva crittografia (HTTPS, DNS su HTTPS e DNS su TLS)",
     "encryption_enable_desc": "Se la crittografia è attiva, l'interfaccia di amministrazione di AdGuard Home funzionerà su HTTPS e il server DNS ascolterà le richieste su DNS su HTTPS e DNS su TLS.",
-    "encryption_chain_valid": "La catena di certificati è valida",
-    "encryption_chain_invalid": "La catena di certificati non è valida",
-    "encryption_key_valid": "Questa è una chiave privata {{type}} valida",
-    "encryption_key_invalid": "Questa è una chiave privata {{type}} non valida",
+    "encryption_chain_valid": "La catena di certificati è valida.",
+    "encryption_chain_invalid": "La catena di certificati non è valida.",
+    "encryption_key_valid": "Questa è una chiave privata {{type}} valida.",
+    "encryption_key_invalid": "Questa è una chiave privata {{type}} non valida.",
     "encryption_subject": "Soggetto",
     "encryption_issuer": "Emittente",
     "encryption_hostnames": "Nomi host",
     "encryption_reset": "Sei sicuro di voler ripristinare le impostazioni di crittografia?",
     "topline_expiring_certificate": "Il tuo certificato SSL sta per scadere. Aggiorna le<0> Impostazioni di crittografia </ 0>.",
     "topline_expired_certificate": "Il tuo certificato SSL è scaduto. Aggiorna le <0> Impostazioni di crittografia </ 0>.",
-    "form_error_port_range": "Immettere il valore della porta nell'intervallo 80-65535",
-    "form_error_port_unsafe": "Questa è una porta non sicura",
-    "form_error_equal": "Non dovrebbe essere uguale",
-    "form_error_password": "Password non corrispondente",
+    "form_error_port_range": "Immettere il valore della porta nell'intervallo 80-65535.",
+    "form_error_port_unsafe": "Questa è una porta non sicura.",
+    "form_error_equal": "Non deve essere uguale.",
+    "form_error_password": "Password non corrispondente.",
     "reset_settings": "Reimposta impostazioni",
     "update_announcement": "AdGuard Home {{version}} è ora disponibile! <0>Clicca qui</0> per più informazioni.",
     "setup_guide": "Configurazione guidata",
     "dns_addresses": "Indirizzo DNS",
     "dns_start": "Il server DNS si sta avviando",
-    "dns_status_error": "Errore nel recupero dello stato del server DNS",
+    "dns_status_error": "Errore nel recupero dello stato del server DNS.",
     "down": "Spenta",
     "fix": "Risolvi",
     "dns_providers": "Qui c'è un <0>elenco di fornitori DNS noti</0> da cui scegliere.",
@@ -405,8 +405,8 @@
     "update_failed": "Aggiornamento automatico non riuscito. Ti suggeriamo di <a>seguire questi passaggi</a> per aggiornare manualmente.",
     "manual_update": "Ti invitiamo a <a>seguire questi passaggi</a> per aggiornare manualmente.",
     "processing_update": "Perfavore aspetta, AdGuard Home si sta aggiornando",
-    "clients_title": "Client",
-    "clients_desc": "Configura i dispositivi connessi ad AdGuard Home",
+    "clients_title": "Client persistenti",
+    "clients_desc": "Configura le registrazioni dei client persistenti per i dispositivi connessi ad AdGuard Home.",
     "settings_global": "Globale",
     "settings_custom": "Personalizzato",
     "table_client": "Client",
@@ -417,7 +417,7 @@
     "client_edit": "Modifica Client",
     "client_identifier": "Identificatore",
     "ip_address": "Indirizzo IP",
-    "client_identifier_desc": "I client possono essere identificati dall'indirizzo IP, CIDR, indirizzo MAC o un ID speciale (che può essere utilizzato per DoT/DoH/DoQ). <0>Qui</0> potrai saperne di più sui metodi per identificarli.",
+    "client_identifier_desc": "I client possono essere identificati attraverso il loro indirizzo IP, CIDR, indirizzo MAC o ClientID (che può essere utilizzato per DoT/DoH/DoQ). <0>Qui</0> potrai saperne di più sui metodi per identificarli.",
     "form_enter_ip": "Inserisci IP",
     "form_enter_subnet_ip": "Inserisci un indirizzo IP nella subnet \"{{cidr}}\"",
     "form_enter_mac": "Inserisci MAC",
@@ -432,14 +432,14 @@
     "clients_not_found": "Nessun client trovato",
     "client_confirm_delete": "Sei sicuro di voler eliminare il client \"{{key}}\"?",
     "list_confirm_delete": "Sei sicuro di voler eliminare questo elenco?",
-    "auto_clients_title": "Clienti (tempo di esecuzione)",
-    "auto_clients_desc": "Dati dei clienti che utilizzano AdGuard Home, ma che non sono salvati nella configurazione",
+    "auto_clients_title": "Client in tempo reale",
+    "auto_clients_desc": "Dispositivi non presenti nell'elenco dei client Persistenti che possono ancora utilizzare AdGuard Home.",
     "access_title": "Impostazioni di accesso",
     "access_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home.",
     "access_allowed_title": "Client permessi",
-    "access_allowed_desc": "Un elenco di CIDR, indirizzi IP o ID client. Se configurata AdGuard Home accetterà richieste solo da questi client.",
+    "access_allowed_desc": "Un elenco di CIDR, indirizzi IP, o <a>ClientID</a>. Se l'elenco conterrà elementi, AdGuard Home accetterà richieste solo da questi client.",
     "access_disallowed_title": "Client non permessi",
-    "access_disallowed_desc": "Un elenco di CIDR, indirizzi IP o ID client. Se configurata, AdGuard Home rifiuterà richieste da questi client. Se i client consentiti risulteranno configurati, questo campo verrà ignorato.",
+    "access_disallowed_desc": "Un elenco di CIDR, indirizzi IP o <a>ClientID</a>. Se l'elenco conterrà degli elementi, AdGuard Home rifiuterà richieste da questi client. Questo campo verrà ignorato se ci saranno elementi nei client Consentiti.",
     "access_blocked_title": "Domini bloccati",
     "access_blocked_desc": "Da non confondere con i filtri. AdGuard Home eliminerà le richieste DNS corrispondenti a questi domini e queste richieste non verranno visualizzate nel relativo registro. Puoi specificare nomi di dominio esatti, caratteri jolly o regole di filtraggio URL, ad esempio \"esempio.org\", \"*.esempio.org\" o \"||esempio.org^\".",
     "access_settings_saved": "Impostazioni di accesso salvate correttamente",
@@ -450,7 +450,7 @@
     "setup_dns_privacy_1": "<0>DNS su TLS:</0> Utilizza la stringa <1>{{address}}</1>.",
     "setup_dns_privacy_2": "<0>DNS su HTTPS:</0> Utilizza la stringa <1>{{address}}</1>.",
     "setup_dns_privacy_3": "<0>Ecco un elenco di software che è possibile utilizzare.</0>",
-    "setup_dns_privacy_4": "Si usa un dispositivo iOS 14 o macOS Big Sur puoi scaricare uno file speciale.mobileconfig' che aggiunge i server <highlight>DNS su HTTPS</highlight> or <highlight>DNS su TLS</highlight> alle configurazioni DNS.",
+    "setup_dns_privacy_4": "Su un dispositivo iOS 14 o macOS Big Sur puoi scaricare uno speciale file '.mobileconfig' che aggiunge server <highlight>DNS su HTTPS</highlight> o <highlight>DNS su TLS</highlight> alle configurazioni DNS.",
     "setup_dns_privacy_android_1": "Android 9 supporta DNS su TLS in modo nativo. Per configurarlo, vai su Impostazioni → Rete e Internet → Avanzate → DNS privato e inserisci qui il tuo nome di dominio.",
     "setup_dns_privacy_android_2": "<0>AdGuard per Android</0> supporta <1>DNS su HTTPS</1> e <1>DNS su TLS</1>.",
     "setup_dns_privacy_android_3": "<0>Intra</0> aggiunge <1>DNS su HTTPS</1> il supporto ad Android.",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Riscrittura DNS",
     "form_domain": "Inserisci il dominio",
     "form_answer": "Inserisci l'indirizzo IP o il nome del dominio",
-    "form_error_domain_format": "Formato del dominio non valido",
-    "form_error_answer_format": "Formato di risposta non valido",
+    "form_error_domain_format": "Formato del dominio non valido.",
+    "form_error_answer_format": "Formato di risposta non valido.",
     "configure": "Configura",
     "main_settings": "Impostazioni principali",
     "block_services": "Blocca servizi specifici",
@@ -507,7 +507,7 @@
     "filter_updated": "L'elenco è stato aggiornato correttamente",
     "statistics_configuration": "Configurazione delle statistiche",
     "statistics_retention": "Conservazione delle statistiche",
-    "statistics_retention_desc": "Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi",
+    "statistics_retention_desc": "Se dovessi diminuire il valore di intervallo, alcuni dati andranno persi.",
     "statistics_clear": "Azzera statistiche",
     "statistics_clear_confirm": "Sei sicuro di voler azzerare le statistiche?",
     "statistics_retention_confirm": "Sei sicuro di voler modificare la conservazione delle statistiche? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi",
@@ -532,7 +532,7 @@
     "netname": "Nome Network",
     "network": "Rete",
     "descr": "Descrizione",
-    "whois": "Chi è",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Leggi altro</0> su come creare i tuoi elenchi host.",
     "blocked_by_response": "Bloccato per CNAME o IP in risposta",
     "blocked_by_cname_or_ip": "Bloccato da CNAME o IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Eseguirà queste attività: <0> Disattiva DNSStubListener di sistema </0> <0> Imposta l'indirizzo del server DNS su 127.0.0.1 </0> <0> Sostituisci la destinazione del collegamento simbolico di /etc/resolv.conf su / run / systemd /resolve/resolv.conf </0> <0> Arresta DNSStubListener (ricarica il servizio systemd-resolved) </0>",
     "autofix_warning_result": "Di conseguenza, tutte le richieste DNS dal sistema verranno elaborate da AdGuardHome per impostazione predefinita.",
     "tags_title": "Tag",
-    "tags_desc": "È possibile selezionare i tag che corrispondono al client. I tag possono essere inclusi nelle regole dei filtri e consentono di applicarli in modo più accurato. <0> Ulteriori informazioni </0>",
+    "tags_desc": "Puoi selezionare i tag che corrispondono al client. È possibile includere tag nelle regole di filtraggio per applicarli in modo più accurato. <0>Per saperne di più</0>.",
     "form_select_tags": "Seleziona i tag client",
     "check_title": "Controlla il filtro",
-    "check_desc": "Controlla se il nome host è filtrato",
+    "check_desc": "Verifica che il nome host sia filtrato.",
     "check": "Controlla",
     "form_enter_host": "Inserisci un nome per l'host",
     "filtered_custom_rules": "Filtrato dalle regole filtro personalizzate",
@@ -594,19 +594,19 @@
     "allowed": "Consentito",
     "filtered": "Filtrato",
     "rewritten": "Riscritto",
-    "safe_search": "Ricerca sicura",
+    "safe_search": "Ricerca Sicura",
     "blocklist": "Lista nera",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Dimensioni cache",
-    "cache_size_desc": "Dimensioni cache DNS (in byte)",
+    "cache_size_desc": "Dimensioni cache DNS (in byte).",
     "cache_ttl_min_override": "Sovrascrivi TTL minimo",
     "cache_ttl_max_override": "Sovrascrivi TTL massimo",
     "enter_cache_size": "Immetti dimensioni cache (in byte)",
     "enter_cache_ttl_min_override": "Immetti TTL minimo (in secondi)",
     "enter_cache_ttl_max_override": "Immetti TTL massimo (in secondi)",
-    "cache_ttl_min_override_desc": "Estende i valori brevi (in secondi) ricevuti dal server upstream durante la memorizzazione nella cache delle risposte DNS",
-    "cache_ttl_max_override_desc": "Imposta un periodo massimo di attivazione (in secondi) per le voci nella cache DNS",
-    "ttl_cache_validation": "Il valore minimo della cache TTL deve essere inferiore o uguale al valore massimo",
+    "cache_ttl_min_override_desc": "Estende i valori di breve durata (in secondi) ricevuti dal server upstream durante la memorizzazione nella cache delle risposte DNS.",
+    "cache_ttl_max_override_desc": "Imposta un valore di durata massima (secondi) per le voci nella cache DNS.",
+    "ttl_cache_validation": "La sovrascrittura del valore TTL minimo della cache deve essere inferiore o uguale a quello massimo.",
     "cache_optimistic": "Optimistic caching",
     "cache_optimistic_desc": "Fai in modo che AdGuard Home risponda dalla cache anche quando le voci risultano scadute e prova anche ad aggiornarle.",
     "filter_category_general": "Generali",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home eliminerà tutte le richieste DNS da questo client.",
     "filter_allowlist": "ATTENZIONE: Quest'azione escluderà anche la regola \"{{disallowed_rule}}\" dall'elenco di clienti consentiti.",
     "last_rule_in_allowlist": "Impossibile bloccare questo client perché escludere la regola \"{{disallowed_rule}}\" DISATIVERÁ l'elenco \"Clienti consentiti\".",
-    "experimental": "Sperimentale",
     "use_saved_key": "Utilizza la chiave salvata in precedenza",
     "parental_control": "Controllo Parentale",
     "safe_browsing": "Navigazione Sicura",
-    "served_from_cache": "{{value}} <i>(fornito dalla cache)</i>"
+    "served_from_cache": "{{value}} <i>(fornito dalla cache)</i>",
+    "form_error_password_length": "La password deve essere lunga almeno {{value}} caratteri."
 }
diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json
index bc85b3e6..7c2f8ba1 100644
--- a/client/src/__locales/ja.json
+++ b/client/src/__locales/ja.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "クライアント設定",
     "example_upstream_reserved": "<0>特定のドメイン</0>に対してDNSアップストリームを指定できます。",
-    "example_upstream_comment": "コメントを指定できます。",
+    "example_upstream_comment": "コメントを追加できます。",
     "upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)",
     "parallel_requests": "並列リクエスト",
     "load_balancing": "ロードバランシング",
@@ -35,7 +35,7 @@
     "dhcp_config_saved": "DHCP構成が無事に保存されました。",
     "dhcp_ipv4_settings": "DHCP IPv4 設定",
     "dhcp_ipv6_settings": "DHCP IPv6 設定",
-    "form_error_required": "必須項目",
+    "form_error_required": "必須項目です",
     "form_error_ip4_format": "IPv4アドレスが無効です",
     "form_error_ip4_range_start_format": "範囲開始のIPv4アドレスが無効です",
     "form_error_ip4_range_end_format": "範囲終了のIPv4アドレスが無効です",
@@ -43,14 +43,14 @@
     "form_error_ip6_format": "IPv6アドレスが無効です",
     "form_error_ip_format": "IPアドレスが無効です",
     "form_error_mac_format": "MACアドレスが無効です",
-    "form_error_client_id_format": "クライアントIDには、数字、小文字、ハイフンのみが使われている必要があります",
-    "form_error_server_name": "サーバ名が無効です",
+    "form_error_client_id_format": "ClientIDには、数字、小文字、ハイフン以外は使用できません",
+    "form_error_server_name": "サーバー名が無効です",
     "form_error_subnet": "IPアドレス「{{ip}}」はサブネット「{{cidr}}」に含まれていません",
-    "form_error_positive": "0より大きい必要があります",
+    "form_error_positive": "0より大きい値でなければなりません",
     "out_of_range_error": "\"{{start}}\"-\"{{end}}\" の範囲外である必要があります",
     "lower_range_start_error": "範囲開始よりも低い値である必要があります",
-    "greater_range_start_error": "範囲開始より大きい必要があります",
-    "greater_range_end_error": "範囲終了より大きい必要があります",
+    "greater_range_start_error": "範囲開始値より大きい値でなければなりません",
+    "greater_range_end_error": "範囲終了値より大きい値でなければなりません",
     "subnet_error": "アドレスは1つのサブネット内にある必要があります",
     "gateway_or_subnet_invalid": "サブネットマスクが無効です",
     "dhcp_form_gateway_input": "ゲートウェイIP",
@@ -143,7 +143,7 @@
     "use_adguard_browsing_sec_hint": "AdGuard Homeは、ブラウジング・セキュリティ・ウェブサービスによってドメインがブロックされているかを確認します。 確認は、プライバシーに配慮したルックアップAPIを使用して行います(ドメイン名のSHA256ハッシュの短いプレフィックスのみがサーバーに送信されます)。",
     "use_adguard_parental": "AdGuardペアレンタルコントロール・ウェブサービスを使用する",
     "use_adguard_parental_hint": "AdGuard Homeは、ドメインにアダルトコンテンツが含まれているかどうかを確認します。 ブラウジングセキュリティ・ウェブサービスと同じプライバシーに優しいAPIを使用します。",
-    "enforce_safe_search": "セーフサーチを強制する",
+    "enforce_safe_search": "セーフサーチを使用する",
     "enforce_save_search_hint": "AdGuard Homeは、次の検索エンジンでセーフサーチを強制適用します: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay",
     "no_servers_specified": "サーバが指定されていません",
     "general_settings": "一般設定",
@@ -165,10 +165,10 @@
     "enabled_filtering_toast": "フィルタリングを有効にしました",
     "disabled_safe_browsing_toast": "セーフブラウジングを無効にしました",
     "enabled_safe_browsing_toast": "セーフブラウジングを有効にしました",
-    "disabled_parental_toast": "ペアレンタルコントロールを無効にしました",
-    "enabled_parental_toast": "ペアレンタルコントロールを有効にしました",
-    "disabled_safe_search_toast": "セーフサーチを無効にしました",
-    "enabled_save_search_toast": "セーフサーチを有効にしました",
+    "disabled_parental_toast": "ペアレンタルコントロールが無効になりました",
+    "enabled_parental_toast": "ペアレンタルコントロールが有効になりました",
+    "disabled_safe_search_toast": "セーフサーチが無効になりました",
+    "enabled_save_search_toast": "セーフサーチが有効になりました",
     "enabled_table_header": "有効",
     "name_table_header": "名称",
     "list_url_table_header": "URLリスト",
@@ -196,25 +196,25 @@
     "choose_allowlist": "許可リストの選択",
     "enter_valid_blocklist": "ブロックリストへ有効なURLを入力してください。",
     "enter_valid_allowlist": "許可リストへ有効なURLを入力してください。",
-    "form_error_url_format": "URLフォーマットが間違っています",
+    "form_error_url_format": "URLフォーマットが無効です",
     "form_error_url_or_path_format": "リストのURLまたは絶対パスが無効です",
     "custom_filter_rules": "カスタム・フィルタリングルール",
     "custom_filter_rules_hint": "1つの行に1つのルールを入力してください。 広告ブロックルールやhostsファイル構文を使用できます。",
     "system_host_files": "システムのhostsファイル",
     "examples_title": "例",
-    "example_meaning_filter_block": "example.orgドメインとそのすべてのサブドメインへのアクセスをブロックする",
-    "example_meaning_filter_whitelist": "example.orgドメインとそのすべてのサブドメインへのアクセスのブロックを解除する",
+    "example_meaning_filter_block": "example.orgドメインとそのすべてのサブドメインへのアクセスをブロックします。",
+    "example_meaning_filter_whitelist": "example.orgドメインとそのすべてのサブドメインへのアクセスのブロックを解除します。",
     "example_meaning_host_block": "AdGuard Homeは、example.orgドメイン(サブドメインを除く)に対して127.0.0.1のアドレスを返すようになります。",
-    "example_comment": "! ここにはコメントが入ります",
-    "example_comment_meaning": "ただのコメントです",
-    "example_comment_hash": "# ここもコメントです",
-    "example_regex_meaning": "指定の正規表現に一致するドメインへのアクセスをブロックします",
-    "example_upstream_regular": "通常のDNS(UDPでの問い合わせ)",
-    "example_upstream_dot": "暗号化されている <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "暗号化されている <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "暗号化されている <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "<1>DNSCrypt</1> または <2>DNS-over-HTTPS</2> リゾルバのために <0>DNS Stamps</0> を使えます",
-    "example_upstream_tcp": "通常のDNS(TCPでの問い合わせ)",
+    "example_comment": "! コメント本文",
+    "example_comment_meaning": "コメントが入ります。",
+    "example_comment_hash": "# これもコメントです",
+    "example_regex_meaning": "指定の正規表現に一致するドメインへのアクセスをブロックします。",
+    "example_upstream_regular": "通常のDNS(over UDP)。",
+    "example_upstream_dot": "暗号化されている <0>DNS-over-TLS</0>。",
+    "example_upstream_doh": "暗号化されている <0>DNS-over-HTTPS</0>。",
+    "example_upstream_doq": "暗号化 <0>DNS-over-QUIC</0>(実験的)。",
+    "example_upstream_sdns": "<1>DNSCrypt</1> または <2>DNS-over-HTTPS</2> リゾルバのための <0>DNS Stamps</0>。",
+    "example_upstream_tcp": "通常のDNS(over TCP)。",
     "all_lists_up_to_date_toast": "すべてのリストは既に最新です",
     "updated_upstream_dns_toast": "上流DNSサーバを保存しました。",
     "dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています",
@@ -259,7 +259,7 @@
     "query_log_strict_search": "完全一致検索には二重引用符を使用します",
     "query_log_retention_confirm": "クエリ・ログの保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます",
     "anonymize_client_ip": "クライアントIPを匿名化する",
-    "anonymize_client_ip_desc": "ログと統計にクライアントの完全なIPアドレスを保存しない",
+    "anonymize_client_ip_desc": "ログと統計にクライアントのフルPアドレスを保存しません。",
     "dns_config": "DNSサーバ設定",
     "dns_cache_config": "DNSキャッシュ設定",
     "dns_cache_config_desc": "ここでDNSキャッシュを設定できます。",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "Client ID(クライアントID)",
-    "client_id_placeholder": "クライアントIDを入力してください",
-    "client_id_desc": "それぞれのクライアントは、特別なクライアントIDで識別できます。 <a>ここ</a>では、クライアントを特定する方法について詳しく知ることができます。",
+    "client_id": "ClientID(クライアントID)",
+    "client_id_placeholder": "ClientIDを入力してください",
+    "client_id_desc": "それぞれのクライアントは、ClinetIDで識別できます。 <a>こちら</a>では、クライアントを識別する方法について詳しく知ることができます。",
     "download_mobileconfig_doh": "DNS-over-HTTPS用の .mobileconfig をダウンロード",
     "download_mobileconfig_dot": "DNS-over-TLS用の .mobileconfig をダウンロード",
     "download_mobileconfig": "設定ファイルをダウンロードする",
@@ -334,11 +334,11 @@
     "install_devices_router_list_4": "一部のルーターでは、カスタムDNSサーバーを設定できません。この場合、AdGuard Homeを<0>DHCPサーバ</0>として設定してみることがおすすめです。それ以外の場合は、特定のルータモデルにおいて、DNSサーバーをカスタマイズする方法に関するマニュアル等をご確認ください。",
     "install_devices_windows_list_1": "「スタート」メニューまたはWindowsの検索から「設定」を開きます。",
     "install_devices_windows_list_2": "「ネットワークとインターネット」カテゴリに移動し、さらに「ネットワークと共有センター」へ移動します。",
-    "install_devices_windows_list_3": "画面の左側にある「アダプターの設定を変更」を見つけてクリックします。",
-    "install_devices_windows_list_4": "動作中の接続を選択して右クリックし、「プロパティ」を選択します。",
+    "install_devices_windows_list_3": "左パネルにある「アダプターの設定を変更」をクリックします。",
+    "install_devices_windows_list_4": "動作中の接続を右クリックし、「プロパティ」を選択します。",
     "install_devices_windows_list_5": "一覧から「インターネット プロトコル バージョン4(TCP/IPv4)」(もしくはIPv6の場合「インターネット プロトコル バージョン6(TCP/IPv6)」)を見つけ、それを選択してから、もう一度「プロパティ」をクリックします。",
     "install_devices_windows_list_6": "「次のDNSサーバーアドレスを使う」を選択して、お使いのAdGuard Homeサーバーアドレスを入力します。",
-    "install_devices_macos_list_1": "Apple アイコンをクリックして「システム環境設定」へ行きます。",
+    "install_devices_macos_list_1": "Apple アイコンをクリックして「システム環境設定」へ移動します。",
     "install_devices_macos_list_2": "「ネットワーク」をクリックします。",
     "install_devices_macos_list_3": "一覧の最初の接続を選択して「詳細...」をクリックします。",
     "install_devices_macos_list_4": "「DNS」タブを選択して、AdGuard Homeサーバのアドレスを入力します。",
@@ -356,7 +356,7 @@
     "open_dashboard": "ダッシュボードを開きます",
     "install_saved": "保存に成功しました",
     "encryption_title": "暗号化",
-    "encryption_desc": "DNSと管理ウェブインターフェースの両方に対する暗号化(HTTPS/TLS)をサポートします",
+    "encryption_desc": "DNSと管理者ウェブインターフェースの両方に対する暗号化(HTTPS/TLS)サポート。",
     "encryption_config_saved": "暗号化構成が保存されました。",
     "encryption_server": "サーバ名",
     "encryption_server_enter": "ドメイン名を入力してください",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "HTTPSポートが設定されていると、AdGuard Home 管理インターフェースはHTTPS経由でアクセス可能になり、そして「/dns-query」の場所にDNS-over-HTTPSも提供されます。",
     "encryption_dot": "DNS-over-TLS ポート",
     "encryption_dot_desc": "このポートが設定されていると、AdGuard HomeはこのポートでDNS-over-TLSサーバを実行します。",
-    "encryption_doq": "DNS-over-QUIC ポート",
+    "encryption_doq": "DNS-over-QUIC ポート (実験的)",
     "encryption_doq_desc": "このポートが設定されていると、AdGuard HomeはこのポートにてDNS-over-QUICサーバーを実行します。これは実験的なものであり、頼りにならない可能性があります。また、現時点ではこのサーバーをサポートするクライアントも少ないです。",
     "encryption_certificates": "証明書",
     "encryption_certificates_desc": "暗号化を使用するには、ドメインに有効なSSL証明書チェーンを提供する必要があります。無料の証明書は<0> {{link}} </0>で入手できます。または、信頼できる認証局のいずれかから購入することもできます。",
@@ -378,10 +378,10 @@
     "encryption_key_input": "ここに証明書のためのPEM形式の秘密鍵をコピー/ペーストしてください。",
     "encryption_enable": "暗号化を有効にする(HTTPS、DNS-over-HTTPS、DNS-over-TLS)",
     "encryption_enable_desc": "暗号化が有効になっていると、AdGuard Home 管理インターフェースはHTTPS経由で動作し、DNSサーバはDNS-over-HTTPSおよびDNS-over-TLS経由で要求を待ち受けます。",
-    "encryption_chain_valid": "証明書チェーンは有効です",
+    "encryption_chain_valid": "証明書チェーンは有効です。",
     "encryption_chain_invalid": "証明書チェーンは無効です",
-    "encryption_key_valid": "これは有効な{{type}}秘密鍵です",
-    "encryption_key_invalid": "これは無効な{{type}}秘密鍵です",
+    "encryption_key_valid": "これは有効な{{type}}プライベートキーです。",
+    "encryption_key_invalid": "これは無効な{{type}}プライベートキーです",
     "encryption_subject": "件名",
     "encryption_issuer": "発行者",
     "encryption_hostnames": "ホスト名",
@@ -389,15 +389,15 @@
     "topline_expiring_certificate": "SSL証明書は期限切れになります。<0>暗号化設定</0>を更新します。",
     "topline_expired_certificate": "SSL証明書は期限切れです。<0>暗号化設定</0>を更新します。",
     "form_error_port_range": "80〜65535 の範囲でポート番号を入力してください",
-    "form_error_port_unsafe": "これは危険なポートです",
+    "form_error_port_unsafe": "これは不安全なポートです",
     "form_error_equal": "同じ値であってはなりません",
-    "form_error_password": "パスワードが不一致です",
+    "form_error_password": "パスワードが一致しません",
     "reset_settings": "設定をリセットする",
     "update_announcement": "AdGuard Home {{version}}がリリースされました。詳しくは<0>こちらをクリック</0>してください。",
     "setup_guide": "セットアップガイド",
     "dns_addresses": "DNSアドレス",
     "dns_start": "DNSサーバが起動処理中です",
-    "dns_status_error": "DNSサーバ・ステータスの取得エラー",
+    "dns_status_error": "DNSサーバ・ステータスの確認エラー",
     "down": "ダウン",
     "fix": "改善",
     "dns_providers": "こちらは、選択可能な<0>既知のDNSプロバイダの一覧</0>です。",
@@ -405,8 +405,8 @@
     "update_failed": "自動更新に失敗しました。手動で更新するには、<a>手順に従って</a>ください。",
     "manual_update": "手動でアップデートするには、<a>こちらの手順</a>を使ってください。",
     "processing_update": "AdGuard Homeを更新しています。しばらくお待ちください",
-    "clients_title": "クライアント",
-    "clients_desc": "AdGuard Homeに接続されているデバイスを設定します",
+    "clients_title": "永速的クライアント",
+    "clients_desc": "AdGuard Homeに接続されているデバイスの永続的クライアント記録を設定できます。",
     "settings_global": "グローバル",
     "settings_custom": "カスタム",
     "table_client": "クライアント",
@@ -417,7 +417,7 @@
     "client_edit": "クライアントの編集",
     "client_identifier": "識別子",
     "ip_address": "IPアドレス",
-    "client_identifier_desc": "クライアントは、IPアドレス、CIDR、MACアドレス、または特別なクライアントID(DoT/DoH/DoQで使用可能)によって識別することができます。<0>ここ</0>では、クライアントの識別方法についてより詳しくご確認いただけます。",
+    "client_identifier_desc": "クライアントは、IPアドレス、CIDR、MACアドレス、またはClientID(DoT/DoH/DoQに使用可能)によって識別することができます。<0>こちら</0>にて、クライアントの識別方法についてより詳しくご確認いただけます。",
     "form_enter_ip": "IPアドレスを入力してください",
     "form_enter_subnet_ip": "サブネット「{{cidr}}」内のIPアドレスを入力してください",
     "form_enter_mac": "MACアドレスを入力してください",
@@ -432,14 +432,14 @@
     "clients_not_found": "クライアント情報はありません",
     "client_confirm_delete": "クライアント \"{{key}}\" を削除してもよろしいですか?",
     "list_confirm_delete": "このリストを削除してもよろしいですか?",
-    "auto_clients_title": "クライアント(実行時)",
-    "auto_clients_desc": "AdGuard Homeで使用しているが設定に保存されていないクライアント上のデータ",
+    "auto_clients_title": "ランタイムクライアント",
+    "auto_clients_desc": "永続的クライアントのリストに未登録で、AdGuard Homeを使用する場合があるデバイスのリスト。",
     "access_title": "アクセス設定",
     "access_desc": "ここで、AdGuard Home DNSサーバのアクセスルールを設定できます。",
     "access_allowed_title": "許可されたクライアント",
-    "access_allowed_desc": "CIDR、IPアドレス、またはクライアントIDのリスト。設定されている場合、AdGuard HomeはこれらのIPアドレスからのリクエストのみを受け入れます。",
+    "access_allowed_desc": "CIDR、IPアドレス、または<a>ClientID</a>のリスト。このリストに入力がある場合、AdGuard Homeはリストに入っているクライアントからのみリクエストを受け入れます。",
     "access_disallowed_title": "拒否するクライアント",
-    "access_disallowed_desc": "CIDR、IPアドレス、またはクライアントIDのリスト。設定されている場合、AdGuard HomeはこれらのIPアドレスからのリクエストを破棄します。「許可されたクライアント」欄が設定されている場合、この欄は無視されます。",
+    "access_disallowed_desc": "CIDR、IPアドレス、または<a>ClientID</a>のリスト。リストに入力がある場合、AdGuard Homeはリストに入力されているクライアントからのリクエストを破棄します。※「許可されたクライアント」リストに入力項目がある場合、この「拒否するクライアント」設定は無視されます。",
     "access_blocked_title": "拒否するドメイン",
     "access_blocked_desc": "こちらをフィルタと混同しないでください。AdGuard Homeは、ここで入力されたドメインに一致するDNSクエリをドロップし、そういったクエリはクエリログにも表示されません。ここでは、「example.org」、「*.example.org」、「 ||example.org^ 」など、特定のドメイン名、ワイルドカード、URLフィルタルールを入力できます。",
     "access_settings_saved": "アクセス設定の保存に成功しました",
@@ -475,8 +475,8 @@
     "dns_rewrites": "DNS書き換え",
     "form_domain": "ドメイン名を入力してください",
     "form_answer": "IPアドレスかドメイン名を入力",
-    "form_error_domain_format": "ドメイン名のフォーマットが間違っています",
-    "form_error_answer_format": "応答フォーマットが間違っています",
+    "form_error_domain_format": "ドメイン名のフォーマットが無効です",
+    "form_error_answer_format": "応答のフォーマットが無効です",
     "configure": "保存",
     "main_settings": "メイン設定",
     "block_services": "特定のサービスをブロックする",
@@ -507,7 +507,7 @@
     "filter_updated": "フィルタの更新に成功しました",
     "statistics_configuration": "統計設定",
     "statistics_retention": "統計保持",
-    "statistics_retention_desc": "期間を短くすると、一部のデータが失われます",
+    "statistics_retention_desc": "※保持期間を短くすると、一部のデータが失われます。",
     "statistics_clear": "統計を消去する",
     "statistics_clear_confirm": "統計を消去してもよろしいですか?",
     "statistics_retention_confirm": "統計の保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます",
@@ -532,7 +532,7 @@
     "netname": "ネットワーク名",
     "network": "ネットワーク",
     "descr": "説明",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "独自ホストリストの作成についての<0>詳細はこちら</0>。",
     "blocked_by_response": "応答されたCNAMEかIPアドレスによるブロック",
     "blocked_by_cname_or_ip": "CNAMEもしくはIPアドレスによってブロック済み",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "次のタスクを実行します:<0>システムDNSStubListenerを非アクティブ化します</0> <0>DNSサーバのアドレスを127.0.0.1に設定します</0> <0>/etc/resolv.confのシンボリックリンクの対象を/run/systemd/resolve/resolv.confに置換します</0> <0>DNSStubListenerを停止します(systemd-resolvedサービスをリロードします)</0>",
     "autofix_warning_result": "その結果、システムからのすべてのDNSリクエストは、デフォルトでAdGuard Homeによって処理されます。",
     "tags_title": "タグ",
-    "tags_desc": "クライアントに対応するタグを選択できます。タグはフィルタリングルールに含めることができ、より正確に適用できます。 <0>詳細</0>",
+    "tags_desc": "クライアントに対応するタグを選択できます。フィルタリングルールにタグを含めることで、ルールをより正確に適用できます。 <0>詳細はこちら</0>",
     "form_select_tags": "クライアントのタグを選択する",
     "check_title": "フィルタのチェック",
-    "check_desc": "ホスト名がフィルタで処理されるかをチェックします",
+    "check_desc": "ホスト名がフィルタリングされているかを確認できます。",
     "check": "チェックする",
     "form_enter_host": "ホスト名を入力してください",
     "filtered_custom_rules": "カスタム・フィルタリングルールによる処理されました",
@@ -604,9 +604,9 @@
     "enter_cache_size": "キャッシュサイズ(バイト単位)を入力してください",
     "enter_cache_ttl_min_override": "最小TTL(秒単位)を入力してください",
     "enter_cache_ttl_max_override": "最大TTL(秒単位)を入力してください",
-    "cache_ttl_min_override_desc": "DNS応答をキャッシュするとき、上流サーバから受信した短いTTL(秒単位)を延長します",
-    "cache_ttl_max_override_desc": "DNSキャッシュ内のエントリの最大TTL(秒単位)を設定します",
-    "ttl_cache_validation": "最小キャッシュTTL値は最大値以下にする必要があります",
+    "cache_ttl_min_override_desc": "DNS応答をキャッシュするとき、上流サーバから受信した短いTTL(秒単位)を延長します。",
+    "cache_ttl_max_override_desc": "DNSキャッシュ内のエントリの最大TTL(秒単位)を設定します。",
+    "ttl_cache_validation": "最小キャッシュTTL上書きは最大値以下にする必要があります",
     "cache_optimistic": "Optimistic cashing (オプティミスティック・キャッシュ)",
     "cache_optimistic_desc": "エントリの有効期限が切れた場合でも、AdGuard Homeがキャッシュから応答するようにし、エントリの更新も試みます。",
     "filter_category_general": "一般",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Homeは、このクライアントからすべてのDNSクエリを落とします。",
     "filter_allowlist": "【注意】このアクションは、許可されたクライアントのリストから「{{disallowed_rule}}」というルールも除外します。",
     "last_rule_in_allowlist": "ルール「{{disallowed_rule}}」を除外すると「許可されたクライアント」リストが無効になるため、このクライアントを拒否することはできません。",
-    "experimental": "実験用",
     "use_saved_key": "以前に保存したキーを使用する",
     "parental_control": "ペアレンタルコントロール",
     "safe_browsing": "セーフブラウジング",
-    "served_from_cache": "{{value}} <i>(キャッシュから応答)</i>"
+    "served_from_cache": "{{value}} <i>(キャッシュから応答)</i>",
+    "form_error_password_length": "パスワードは{{value}}文字以上にしてください。"
 }
diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json
index ba40f48c..ff52d178 100644
--- a/client/src/__locales/ko.json
+++ b/client/src/__locales/ko.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "클라이언트 설정",
-    "example_upstream_reserved": "<0>특정 도메인에 대한</0> DNS 업스트림을 지정할 수 있습니다.",
-    "example_upstream_comment": "설명을 맞춤 지정할 수 있습니다.",
+    "example_upstream_reserved": "<0>특정 도메인에 대한</0> 업스트림;",
+    "example_upstream_comment": "댓글.",
     "upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.",
     "parallel_requests": "병렬 처리 요청",
     "load_balancing": "로드 밸런싱",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP 구성이 성공적으로 저장되었습니다",
     "dhcp_ipv4_settings": "DHCP IPv4 설정",
     "dhcp_ipv6_settings": "DHCP IPv6 설정",
-    "form_error_required": "필수 필드",
-    "form_error_ip4_format": "잘못된 IPv4 형식",
-    "form_error_ip4_range_start_format": "잘못된 범위 시작 IPv4 형식",
-    "form_error_ip4_range_end_format": "잘못된 범위 종료 IPv4 형식",
-    "form_error_ip4_gateway_format": "잘못된 게이트웨이 IPv4 형식",
-    "form_error_ip6_format": "잘못된 IPv6 형식",
-    "form_error_ip_format": "잘못된 IP 형식",
-    "form_error_mac_format": "잘못된 MAC 형식",
-    "form_error_client_id_format": "클라이언트 ID는 숫자, 소문자 및 하이픈만 포함해야 합니다",
-    "form_error_server_name": "유효하지 않은 서버 이름입니다",
-    "form_error_subnet": "서브넷 \"{{cidr}}\"에 \"{{ip}}\" IP 주소가 없습니다",
-    "form_error_positive": "0보다 커야 합니다",
-    "out_of_range_error": "\"{{start}}\"-\"{{end}}\" 범위 밖이어야 합니다",
-    "lower_range_start_error": "범위 시작보다 작은 값이어야 합니다",
-    "greater_range_start_error": "범위 시작보다 큰 값이어야 합니다",
-    "greater_range_end_error": "범위 종료보다 큰 값이어야 합니다",
-    "subnet_error": "주소는 하나의 서브넷 아래에 있어야 합니다",
-    "gateway_or_subnet_invalid": "잘못된 서브넷 마스크",
+    "form_error_required": "필수 필드.",
+    "form_error_ip4_format": "잘못된 IPv4 주소.",
+    "form_error_ip4_range_start_format": "잘못된 범위 시작 IPv4 주소.",
+    "form_error_ip4_range_end_format": "잘못된 범위 종료 IPv4 주소.",
+    "form_error_ip4_gateway_format": "잘못된 게이트웨이 IPv4 주소.",
+    "form_error_ip6_format": "잘못된 IPv6 주소.",
+    "form_error_ip_format": "잘못된 IP 주소.",
+    "form_error_mac_format": "잘못된 MAC 주소.",
+    "form_error_client_id_format": "ClientID는 숫자, 소문자 및 하이픈만 포함해야 합니다.",
+    "form_error_server_name": "유효하지 않은 서버 이름입니다.",
+    "form_error_subnet": "서브넷 '{{cidr}}'에 '{{ip}}' IP 주소가 없습니다.",
+    "form_error_positive": "0보다 커야 합니다.",
+    "out_of_range_error": "'{{start}}'-'{{end}}' 범위 밖이어야 합니다.",
+    "lower_range_start_error": "범위 시작보다 작은 값이어야 합니다.",
+    "greater_range_start_error": "범위 시작보다 큰 값이어야 합니다.",
+    "greater_range_end_error": "범위 종료보다 큰 값이어야 합니다.",
+    "subnet_error": "주소는 하나의 서브넷에 있어야 합니다.",
+    "gateway_or_subnet_invalid": "잘못된 서브넷 마스크.",
     "dhcp_form_gateway_input": "게이트웨이 IP",
     "dhcp_form_subnet_input": "서브넷 마스크",
     "dhcp_form_range_title": "IP 주소 범위",
@@ -133,7 +133,7 @@
     "number_of_dns_query_blocked_24_hours": "광고 차단 필터 및 호스트 차단 목록에 의해 차단된 DNS 요청 수",
     "number_of_dns_query_blocked_24_hours_by_sec": "AdGuard 브라우징 보안 모듈에 의해 차단된 DNS 요청 수",
     "number_of_dns_query_blocked_24_hours_adult": "차단된 성인 웹 사이트의 수",
-    "enforced_save_search": "세이프 서치 강제",
+    "enforced_save_search": "세이프서치 강제",
     "number_of_dns_query_to_safe_search": "세이프서치가 적용된 검색 엔진에 대해 DNS 요청 수",
     "average_processing_time": "평균처리 시간",
     "average_processing_time_hint": "DNS 요청 처리시 평균 시간(밀리초)",
@@ -196,25 +196,25 @@
     "choose_allowlist": "허용 목록 선택",
     "enter_valid_blocklist": "차단 목록에 유효한 URL을 입력해주세요.",
     "enter_valid_allowlist": "허용 목록에 유효한 URL을 입력해주세요.",
-    "form_error_url_format": "잘못된 URL 형식",
-    "form_error_url_or_path_format": "올바른 URL 또는 목록의 절대 경로가 아닙니다",
+    "form_error_url_format": "잘못된 URL 형식.",
+    "form_error_url_or_path_format": "목록의 URL 또는 절대 경로가 잘못되었습니다.",
     "custom_filter_rules": "커스텀 필터링 규칙",
     "custom_filter_rules_hint": "한 라인에 한 규칙만 입력하세요. 광고 차단 규칙과 호스트 파일 문법 중 하나를 사용할 수 있습니다",
     "system_host_files": "시스템 호스트 파일",
     "examples_title": "예시",
-    "example_meaning_filter_block": "example.org 을 포함한 모든 서브 도메인 접근을 차단합니다",
+    "example_meaning_filter_block": "example.org 및 모든 하위 도메인에 대한 접근 차단;",
     "example_meaning_filter_whitelist": "example.org 을 포함한 모든 서브 도메인 접근을 차단 해제합니다.",
-    "example_meaning_host_block": "AdGuard Home은 example.org 접속 시 127.0.0.1으로 이동합니다. (서브 도메인은 포함되지 않습니다)",
-    "example_comment": "! 여기는 주석이 올 수 있습니다",
-    "example_comment_meaning": "말 그대로의 의미입니다",
-    "example_comment_hash": "# 이것 또한 주석입니다",
-    "example_regex_meaning": "<0>특정 정규 표현식</0>에 맞는 도메인 접근을 차단합니다",
-    "example_upstream_regular": "사용자 지정 DNS (UDP을 통한 접속)",
-    "example_upstream_dot": "암호화 된 <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "암호화 된 <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "암호화된 <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "<1>DNSCrypt</1>나 <2>DNS-over-HTTPS</2> 리졸버를 위해 <0>DNS 스탬프</0>를 사용할 수 있습니다",
-    "example_upstream_tcp": "사용자 지정 DNS (TCP를 통한 접속)",
+    "example_meaning_host_block": "example.org에 대해 127.0.0.1로 응답합니다 (하위 도메인은 아님);",
+    "example_comment": "! 댓글을 추가하는 방법",
+    "example_comment_meaning": "이것은 단지 댓글입니다;",
+    "example_comment_hash": "# 이것 또한 댓글입니다.",
+    "example_regex_meaning": "특정 정규 표현식에 맞는 도메인 접근을 차단합니다.",
+    "example_upstream_regular": "일반 DNS (UDP을 통한 접속);",
+    "example_upstream_dot": "암호화된 <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "암호화된 <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "암호화된 <0>DNS-over-QUIC</0> (실험);",
+    "example_upstream_sdns": "<1>DNSCrypt</1> 또는 <2>DNS-over-HTTPS</2> 리졸버를 위한 <0>DNS 스탬프</0>;",
+    "example_upstream_tcp": "일반 DNS (TCP를 통한 접속);",
     "all_lists_up_to_date_toast": "모든 리스트가 이미 최신입니다",
     "updated_upstream_dns_toast": "업스트림 서버가 성공적으로 저장되었습니다",
     "dns_test_ok_toast": "특정 DNS 서버들은 정상적으로 동작 중입니다",
@@ -262,7 +262,7 @@
     "anonymize_client_ip_desc": "클라이언트의 전체 IP 주소를 로그와 통계에 저장하지 않습니다.",
     "dns_config": "DNS 서버 설정",
     "dns_cache_config": "DNS 캐시 구성",
-    "dns_cache_config_desc": "여기에서 DNS 캐시를 구성 할 수 있습니다",
+    "dns_cache_config_desc": "여기에서 DNS 캐시를 구성할 수 있습니다.",
     "blocking_mode": "차단 모드",
     "default": "기본",
     "nxdomain": "NXDOMAIN",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "클라이언트 ID",
-    "client_id_placeholder": "클라이언트 ID 입력",
-    "client_id_desc": "클라이언트는 특별한 클라이언트 ID를 기반으로 구분됩니다. <a>여기</a>에서 클라이언트를 구분하는 방법을 자세히 알아보세요.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "ClientID 입력",
+    "client_id_desc": "클라이언트는 ClientID로 식별할 수 있습니다. <a>여기</a>에서 클라이언트를 식별하는 방법을 자세히 알아보세요.",
     "download_mobileconfig_doh": "DNS-over-HTTPS용 .mobileconfig 다운로드",
     "download_mobileconfig_dot": "DNS-over-TLS용 .mobileconfig 다운로드",
     "download_mobileconfig": "설정 파일 내려받기",
@@ -309,7 +309,7 @@
     "install_settings_listen": "네트워크 인터페이스",
     "install_settings_port": "포트",
     "install_settings_interface_link": "AdGuard Home 관리자 웹 인터페이스는 다음 주소로 제공됨:",
-    "form_error_port": "유효한 포트 번호를 입력하십시오",
+    "form_error_port": "유효한 포트 번호를 입력하세요.",
     "install_settings_dns": "DNS 서버",
     "install_settings_dns_desc": "다음 주소의 DNS 서버를 사용하도록 장치 또는 라우터를 구성해야 합니다.",
     "install_settings_all_interfaces": "모든 인터페이스",
@@ -334,7 +334,7 @@
     "install_devices_router_list_4": "일부 라우터 유형에서는 사용자 정의 DNS 서버를 설정할 수 없습니다. 이 경우에는 AdGuard Home을 <0>DHCP 서버</0>로 설정할 수 있습니다. 그렇지 않으면 특정 라우터 모델에 맞게 DNS 서버를 설정하는 방법을 찾아야 합니다.",
     "install_devices_windows_list_1": "시작 메뉴 또는 윈도우 검색을 통해 제어판을 엽니다.",
     "install_devices_windows_list_2": "네트워크 및 인터넷 카테고리로 이동한 다음 네트워크 및 공유 센터로 이동합니다.",
-    "install_devices_windows_list_3": "화면 왼쪽에서 '어댑터 설정 변경'을 찾아 클릭합니다.",
+    "install_devices_windows_list_3": "화면 왼쪽에서 '어댑터 설정 변경'을 클릭합니다.",
     "install_devices_windows_list_4": "활성 연결을 선택한 후 우클릭으로 속성을 선택합니다.",
     "install_devices_windows_list_5": "목록에서 '인터넷 프로토콜 버전 4(TCP/IP)' (또는 IPv6의 경우 '인터넷 프로토콜 버전 6(TCP/IPv6)')를 찾아 선택하고 속성을 클릭합니다.",
     "install_devices_windows_list_6": "'DNS 서버 주소 사용'을 선택하고 AdGuard Home 서버 주소 입력합니다.",
@@ -356,7 +356,7 @@
     "open_dashboard": "대시보드 열기",
     "install_saved": "성공적으로 저장되었습니다",
     "encryption_title": "암호화",
-    "encryption_desc": "DNS 및 관리자 웹 인터페이스에 대한 암호화 (HTTPS/TLS) 지원입니다.",
+    "encryption_desc": "DNS 및 관리 웹 인터페이스에 대한 암호화(HTTPS/TLS)를 지원합니다.",
     "encryption_config_saved": "암호화 구성이 저장되었습니다",
     "encryption_server": "서버 이름",
     "encryption_server_enter": "도메인 이름을 입력하세요.",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "HTTPS 포트가 구성되면 HTTPS를 통해 AdGuard Home 관리자 인터페이스에 액세스할 수 있으며, '/dns-query' 위치에 DNS-over-HTTPS도 제공합니다.",
     "encryption_dot": "DNS-over-TLS 포트",
     "encryption_dot_desc": "이 포트가 구성된 경우 AdGuard Home 이 포트에서 DNS-over-TLS 서버를 실행합니다.",
-    "encryption_doq": "DNS-over-QUIC 포트",
+    "encryption_doq": "DNS-over-QUIC 포트 (실험)",
     "encryption_doq_desc": "이 포트가 설정된 경우 AdGuard Home은 해당 포트에서 DNS-over-QUIC 서버를 실행합니다. 이것은 실험적이며 신뢰할 수 없습니다. 또한 현재 이를 지원하는 클라이언트가 많지 않습니다.",
     "encryption_certificates": "인증서",
     "encryption_certificates_desc": "암호화를 사용하려면 도메인에 대해 올바른 SSL 인증서 체인을 제공해야 합니다. <0>{{link}}</0>에서 무료 증명서를 받을 수도 있고, 신뢰할 수있는 인증 기관에서 구입할 수 있습니다.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "PEM으로 인코딩된 개인 키를 여기에 복사/붙여넣기하세요.",
     "encryption_enable": "암호화 활성화 (HTTPS, DNS-over-HTTPS 및 DNS-over-TLS)",
     "encryption_enable_desc": "암호화가 활성화 된 경우 AdGuard Home 관리자 인터페이스는 HTTPS를 통해 작동하고 DNS 서버는 DNS-over-HTTPS 및 DNS-over-TLS를 통해 요청을 수신합니다.",
-    "encryption_chain_valid": "인증서 체인이 유효합니다",
-    "encryption_chain_invalid": "인증서 체인이 유효하지 않습니다",
-    "encryption_key_valid": "유효한 {{type}} 개인 키 입니다.",
-    "encryption_key_invalid": "유효하지 않는 {{type}} 개인 키 입니다.",
+    "encryption_chain_valid": "인증서 체인이 유효합니다.",
+    "encryption_chain_invalid": "인증서 체인이 유효하지 않습니다.",
+    "encryption_key_valid": "유효한 {{type}} 개인 키입니다.",
+    "encryption_key_invalid": "유효하지 않는 {{type}} 개인 키입니다.",
     "encryption_subject": "대상",
     "encryption_issuer": "발행자",
     "encryption_hostnames": "호스트 이름",
     "encryption_reset": "암호화 설정을 재설정하시겠습니까?",
     "topline_expiring_certificate": "SSL 인증서가 곧 만료됩니다. 업데이트<0> 암호화 설정</0>.",
     "topline_expired_certificate": "SSL 인증서가 만료되었습니다. 업데이트<0> 암호화 설정</0>.",
-    "form_error_port_range": "80-65535 범위의 포트 번호를 입력하십시오",
-    "form_error_port_unsafe": "안전하지 않은 포트입니다",
-    "form_error_equal": "동일하지 않아야 함",
-    "form_error_password": "비밀번호 불일치",
+    "form_error_port_range": "80-65535 범위의 포트 번호를 입력하세요.",
+    "form_error_port_unsafe": "안전하지 않은 포트입니다.",
+    "form_error_equal": "동일하지 않아야 함.",
+    "form_error_password": "비밀번호 불일치.",
     "reset_settings": "설정 초기화",
     "update_announcement": "AdGuard Home {{version}} 사용 가능합니다! <0>이곳</0>을 클릭하여 더 많은 정보를 확인하세요.",
     "setup_guide": "설치 안내",
     "dns_addresses": "DNS 주소",
     "dns_start": "DNS 서버를 시작하고 있습니다",
-    "dns_status_error": "DNS 서버 상태를 가져오는 도중 오류가 발생했습니다",
+    "dns_status_error": "DNS 서버 상태를 확인하는 동안 오류가 발생했습니다.",
     "down": "다운로드",
     "fix": "수정",
     "dns_providers": "다음은 선택할 수 있는 <0>알려진 DNS 공급자 목록</0>입니다.",
@@ -405,8 +405,8 @@
     "update_failed": "자동 업데이트 실패 되었습니다. <a> 단계를 따라 수동으로 업데이트하세요</a>",
     "manual_update": "<a>절차를 따라</a> 수동으로 업데이트하십시오.",
     "processing_update": "잠시만 기다려주세요, AdGuard Home가 업데이트 중입니다.",
-    "clients_title": "클라이언트",
-    "clients_desc": "AdGuard Home에 연결할 기기들을 설정",
+    "clients_title": "영구 클라이언트",
+    "clients_desc": "AdGuard Home에 연결된 기기에 대한 영구 클라이언트 레코드를 설정합니다.",
     "settings_global": "글로벌",
     "settings_custom": "사용자",
     "table_client": "클라이언트",
@@ -417,7 +417,7 @@
     "client_edit": "클라이언트 수정",
     "client_identifier": "식별자",
     "ip_address": "IP 주소",
-    "client_identifier_desc": "클라이언트는 IP 주소, CIDR, MAC 주소 또는 특수 클라이언트 ID로 식별할 수 있습니다 (DoT/DoH/DoQ에 사용 가능). <0>여기에서</0> 클라이언트를 식별하는 방법에 대한 자세한 내용은 확인하실 수 있습니다.",
+    "client_identifier_desc": "클라이언트는 IP 주소, CIDR, MAC 주소 또는 ClientID(DoT/DoH/DoQ에 사용 가능)로 식별할 수 있습니다. <0>여기에서</0> 클라이언트를 식별하는 방법에 대한 자세한 내용은 확인하실 수 있습니다.",
     "form_enter_ip": "IP 입력",
     "form_enter_subnet_ip": "서브넷 \"{{cidr}}\" 내의 IP 주소 입력",
     "form_enter_mac": "MAC 입력",
@@ -432,14 +432,14 @@
     "clients_not_found": "클라이언트 없음",
     "client_confirm_delete": "정말 클라이언트 \"{{key}}\" 삭제하시겠습니까?",
     "list_confirm_delete": "정말로 이 목록을 제거하시겠습니까?",
-    "auto_clients_title": "클라이언트 (런타임)",
-    "auto_clients_desc": "AdGuard Home을 사용하지만 구성에 저장되지 않은 클라이언트의 데이터입니다.",
+    "auto_clients_title": "런타임 클라이언트",
+    "auto_clients_desc": "AdGuard Home을 계속 사용할 수 있는 영구 클라이언트 목록에 없는 디바이스입니다.",
     "access_title": "접근 설정",
     "access_desc": "여기에서 AdGuard Home DNS 서버에 대한 액세스 규칙을 구성할 수 있습니다.",
     "access_allowed_title": "허용된 클라이언트",
-    "access_allowed_desc": "CIDR, IP 주소 또는 클라이언트 ID 목록입니다. 허용된 클라이언트가 구성된 경우, AdGuard Home은 이 클라이언트의 요청만 수락합니다.",
+    "access_allowed_desc": "CIDR, IP 주소 또는 <a>ClientID</a> 목록입니다. 이 목록에 항목이 있는 경우, AdGuard Home은 이러한 클라이언트의 요청만 수락합니다.",
     "access_disallowed_title": "차단된 클라이언트",
-    "access_disallowed_desc": "CIDR, IP 주소 또는 클라이언트 ID 목록입니다. 차단된 클라이언트가 구성된 경우, AdGuard Home은 이 클라이언트의 요청을 무시합니다. 허용된 클라이언트가 구성된 경우, 이 필드는 무시됩니다.",
+    "access_disallowed_desc": "CIDR, IP 주소 또는 <a>ClientID</a> 목록입니다. 이 목록에 항목이 있는 경우, AdGuard Home은 이러한 클라이언트의 요청을 무시합니다. 허용된 클라이언트에 항목이 있는 경우, 이 필드는 무시됩니다.",
     "access_blocked_title": "차단된 도메인",
     "access_blocked_desc": "이 기능을 필터와 혼동하지 마세요. AdGuard Home은 이 도메인에 대한 DNS 요청을 무시합니다. 여기에서는 'example.org' '*. example.org', '|| example.org ^'와 같은 특정 도메인 이름, 와일드 카드, URL 필터 규칙을 지정할 수 있습니다.",
     "access_settings_saved": "액세스 설정이 성공적으로 저장되었습니다.",
@@ -475,7 +475,7 @@
     "dns_rewrites": "DNS 변경",
     "form_domain": "도메인 이름 또는 와일드카드를 입력합니다",
     "form_answer": "IP 주소 또는 도메인 이름을 입력하세요",
-    "form_error_domain_format": "도메인 형식이 잘못되었습니다",
+    "form_error_domain_format": "도메인 형식이 잘못되었습니다.",
     "form_error_answer_format": "답변 형식이 잘못되었습니다. ",
     "configure": "설정하기",
     "main_settings": "기본 설정",
@@ -507,7 +507,7 @@
     "filter_updated": "필터가 성공적으로 업데이트됨",
     "statistics_configuration": "통계 구성",
     "statistics_retention": "통계 저장 기간",
-    "statistics_retention_desc": "값을 줄이면 설정한 값보다 오래된 데이터가 소멸됩니다.",
+    "statistics_retention_desc": "간격 값을 줄이면 일부 데이터가 손실됩니다.",
     "statistics_clear": "통계 초기화",
     "statistics_clear_confirm": "통계를 정말로 초기화하시겠습니까?",
     "statistics_retention_confirm": "정말로 통계 저장 기간을 변경하시겠습니까? 저장 주기를 낮출 경우, 일부 데이터가 손실됩니다",
@@ -532,7 +532,7 @@
     "netname": "네트워크 이름",
     "network": "네트워크",
     "descr": "설명",
-    "whois": "후이즈",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "차단 리스트를 직접 호스트하는 법을 <0>알아보세요</0>.",
     "blocked_by_response": "응답 중 차단된 CNAME 또는 IP",
     "blocked_by_cname_or_ip": "CNAME 또는 IP에 의해 차단됨",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "다음 작업을 진행합니다: <0>DNSStubListener 시스템 비활성화</0> <0>DNS 서버 주소를 127.0.0.1로 설정</0> <0>/etc/resolv.conf의 심볼릭 링크 타겟을 /run/systemd/resolve/resolv.conf로 변경</0> <0>DNSStubListener 중지 (systemd-resolved 서비스 새로고침)</0>",
     "autofix_warning_result": "결과적으로 시스템의 모든 DNS 요청은 기본적으로 AdGuard Home에 의해 처리됩니다.",
     "tags_title": "태그",
-    "tags_desc": "클라이언트에 해당하는 태그를 선택할 수 있습니다. 필터링 규칙에 태그를 포함시키면 더 정확하게 적용시킬 수 있습니다. <0>자세히 알아보기</0>",
+    "tags_desc": "클라이언트에 해당하는 태그를 선택할 수 있습니다. 필터링 규칙에 태그를 포함시키면 더 정확하게 적용시킬 수 있습니다. <0>자세히 알아보기</0>.",
     "form_select_tags": "클라이언트 태그 선택",
     "check_title": "필터링 확인",
-    "check_desc": "호스트 이름이 필터링되는지 확인",
+    "check_desc": "호스트 이름이 필터링되는지 확인합니다.",
     "check": "확인",
     "form_enter_host": "호스트 이름을 입력해주세요",
     "filtered_custom_rules": "사용자 정의 필터링 규칙으로 필터링됨",
@@ -598,14 +598,14 @@
     "blocklist": "차단 목록",
     "milliseconds_abbreviation": "ms",
     "cache_size": "캐시 크기",
-    "cache_size_desc": "DNS 캐시 크기 (바이트)",
+    "cache_size_desc": "DNS 캐시 크기 (바이트).",
     "cache_ttl_min_override": "최소 TTL (초) 무시",
     "cache_ttl_max_override": "최대 TTL (초) 무시",
     "enter_cache_size": "캐시 크기를 입력하세요",
     "enter_cache_ttl_min_override": "최소 TTL을 입력하세요",
     "enter_cache_ttl_max_override": "최대 TTL을 입력하세요",
-    "cache_ttl_min_override_desc": "업스트림 서버에서 수신한 TTL 값(최소)을 무시합니다",
-    "cache_ttl_max_override_desc": "업스트림 서버에서 수신한 TTL 값(최대)을 무시합니다",
+    "cache_ttl_min_override_desc": "DNS 응답을 캐싱할 때 업스트림 서버에서 수신한 짧은 TTL 값(초)을 확장합니다.",
+    "cache_ttl_max_override_desc": "DNS 캐시의 항목에 대한 최대 TTL 값(초)을 설정합니다.",
     "ttl_cache_validation": "최소 캐시 TTL 값은 최대 값보다 이하여야 합니다",
     "cache_optimistic": "옵티미스틱 캐시",
     "cache_optimistic_desc": "세션이 만료되었거나 새로고침을 시도하는 경우에도 AdGuard Home이 캐시를 기반으로 응답하도록 합니다.",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home은 이 클라이언트에서 모든 DNS 쿼리를 삭제합니다.",
     "filter_allowlist": "경고: 이 경우 허용된 클라이언트 목록에서 '{{disallowed_rule}}' 규칙 또한 제외됩니다.",
     "last_rule_in_allowlist": "'{{disallowed_rule}}' 규칙을 제외하면 '허용된 클라이언트' 목록이 꺼지므로 해당 클라이언트를 제외할 수 없습니다.",
-    "experimental": "실험",
     "use_saved_key": "이전에 저장했던 키 사용하기",
     "parental_control": "자녀 보호",
     "safe_browsing": "세이프 브라우징",
-    "served_from_cache": "{{value}} <i>(캐시에서 제공)</i>"
+    "served_from_cache": "{{value}} <i>(캐시에서 제공)</i>",
+    "form_error_password_length": "비밀번호는 {{value}}자 이상이어야 합니다."
 }
diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json
index b6933045..36478928 100644
--- a/client/src/__locales/nl.json
+++ b/client/src/__locales/nl.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Cliëntinstellingen",
-    "example_upstream_reserved": "Je kan een DNS-upstream opgeven <0>voor specifieke domein(en)</0>",
-    "example_upstream_comment": "Je kan je commentaar specifiëren",
+    "example_upstream_reserved": "een upstream <0>voor specifieke domeinen</0>;",
+    "example_upstream_comment": "een commentaar.",
     "upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.",
     "parallel_requests": "Parallelle verzoeken",
     "load_balancing": "Volume balanceren",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP configuratie succesvol opgeslagen",
     "dhcp_ipv4_settings": "DHCP IPv4 instellingen",
     "dhcp_ipv6_settings": "DHCP IPv6 instellingen",
-    "form_error_required": "Vereist veld",
-    "form_error_ip4_format": "Ongeldig IPv4-adres",
-    "form_error_ip4_range_start_format": "Ongeldig IPv4-adres start bereik",
-    "form_error_ip4_range_end_format": "Ongeldig IPv4-adres einde bereik",
-    "form_error_ip4_gateway_format": "Ongeldig IPv4-adres van de gateway",
-    "form_error_ip6_format": "Ongeldig IPv6-adres",
-    "form_error_ip_format": "Ongeldig IP-adres",
-    "form_error_mac_format": "Ongeldig MAC-adres",
-    "form_error_client_id_format": "Client-ID mag alleen cijfers, kleine letters en koppeltekens bevatten",
-    "form_error_server_name": "Ongeldige servernaam",
-    "form_error_subnet": "Subnet “{{cidr}}” bevat niet het IP-adres “{{ip}}”",
-    "form_error_positive": "Moet groter zijn dan 0",
-    "out_of_range_error": "Moet buiten bereik zijn \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Moet lager zijn dan begin reeks",
-    "greater_range_start_error": "Moet groter zijn dan begin reeks",
-    "greater_range_end_error": "Moet groter zijn dan einde reeks",
-    "subnet_error": "Adressen moeten in één subnet vallen",
-    "gateway_or_subnet_invalid": "Subnetmasker ongeldig",
+    "form_error_required": "Vereist veld.",
+    "form_error_ip4_format": "Ongeldig IPv4-adres.",
+    "form_error_ip4_range_start_format": "Ongeldig IPv4-adres start bereik.",
+    "form_error_ip4_range_end_format": "Ongeldig IPv4-adres einde bereik.",
+    "form_error_ip4_gateway_format": "Ongeldig IPv4-adres van de gateway.",
+    "form_error_ip6_format": "Ongeldig IPv6-adres.",
+    "form_error_ip_format": "Ongeldig IP-adres.",
+    "form_error_mac_format": "Ongeldig MAC-adres.",
+    "form_error_client_id_format": "Client-ID mag alleen cijfers, kleine letters en koppeltekens bevatten.",
+    "form_error_server_name": "Ongeldige servernaam.",
+    "form_error_subnet": "Subnet “{{cidr}}” bevat niet het IP-adres “{{ip}}”.",
+    "form_error_positive": "Moet groter zijn dan 0.",
+    "out_of_range_error": "Moet buiten bereik zijn \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Moet lager zijn dan begin reeks.",
+    "greater_range_start_error": "Moet groter zijn dan begin reeks.",
+    "greater_range_end_error": "Moet groter zijn dan einde reeks.",
+    "subnet_error": "Adressen moeten in één subnet vallen.",
+    "gateway_or_subnet_invalid": "Subnetmasker ongeldig.",
     "dhcp_form_gateway_input": "Gateway IP",
     "dhcp_form_subnet_input": "Subnet mask",
     "dhcp_form_range_title": "Bereik van IP adressen",
@@ -165,10 +165,10 @@
     "enabled_filtering_toast": "Filters ingeschakeld",
     "disabled_safe_browsing_toast": "Veilig browsen uitgeschakeld",
     "enabled_safe_browsing_toast": "Veilig browsen ingeschakeld",
-    "disabled_parental_toast": "Ouderlijk toezicht uitgeschakeld",
-    "enabled_parental_toast": "Ouderlijk toezicht ingeschakeld",
-    "disabled_safe_search_toast": "Veilig zoeken uitgeschakeld",
-    "enabled_save_search_toast": "Veilig zoeken ingeschakeld",
+    "disabled_parental_toast": "Uitgeschakeld ouderlijk toezicht",
+    "enabled_parental_toast": "Ingeschakeld Ouderlijk toezicht",
+    "disabled_safe_search_toast": "Uitgeschakeld Veilig zoeken",
+    "enabled_save_search_toast": "Ingeschakeld Veilig zoeken",
     "enabled_table_header": "Ingeschakeld",
     "name_table_header": "Naam",
     "list_url_table_header": "URL lijst",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Toestemmingslijsten selecteren",
     "enter_valid_blocklist": "Voer een geldige URL in voor de blokkeerlijst.",
     "enter_valid_allowlist": "Voer een geldige URL in voor de toestemmingslijst.",
-    "form_error_url_format": "Ongeldig URL formaat",
-    "form_error_url_or_path_format": "Ongeldig URL of pad van de lijst",
+    "form_error_url_format": "Ongeldig URL-opmaak.",
+    "form_error_url_or_path_format": "Ongeldig URL of pad van de lijst.",
     "custom_filter_rules": "Aangepaste filterregels",
     "custom_filter_rules_hint": "Voer één regel op een regel in. U kunt adblock-regels gebruiken of de syntaxis van hosts-bestanden gebruiken.",
     "system_host_files": "Systeem host-bestanden",
     "examples_title": "Voorbeelden",
-    "example_meaning_filter_block": "blokkeer toegang tot het example.org domein en alle subdomeinen",
-    "example_meaning_filter_whitelist": "deblokkering van toegang tot het example.org-domein en alle bijbehorende subdomeinen",
-    "example_meaning_host_block": "AdGuard Home zal nu het adres 127.0.0.1 voor het domein example.org retourneren (maar niet de subdomeinen).",
-    "example_comment": "! Hier komt een opmerking",
-    "example_comment_meaning": "zomaar een opmerking",
-    "example_comment_hash": "# Nog een opmerking",
-    "example_regex_meaning": "blokkeer de toegang tot de domeinen die overeenkomen met de opgegeven reguliere expressie",
-    "example_upstream_regular": "standaard DNS (over UDP)",
-    "example_upstream_dot": "versleutelde <0>DNS-via-TLS</0>",
-    "example_upstream_doh": "versleutelde <0>DNS-via-HTTPS</0>",
-    "example_upstream_doq": "versleutelde <0>DNS-via-QUIC</0>",
-    "example_upstream_sdns": "je kunt <0>DNS Stamps</0> voor <1>DNSCrypt</1> of <2>DNS-via-HTTPS</2> oplossingen gebruiken",
-    "example_upstream_tcp": "standaard DNS (over TCP)",
+    "example_meaning_filter_block": "blokkeer toegang tot example.org en alle subdomeinen ervan;",
+    "example_meaning_filter_whitelist": "deblokkeer toegang tot example.org en alle subdomeinen ervan;",
+    "example_meaning_host_block": "127.0.0.1 voor het domein example.org retourneren (maar niet diens subdomeinen);",
+    "example_comment": "! Hier komt een opmerking.",
+    "example_comment_meaning": "zomaar een opmerking;",
+    "example_comment_hash": "# Ook een opmerking.",
+    "example_regex_meaning": "toegang blokkeren tot de domeinen die overeenkomen met de opgegeven reguliere expressie.",
+    "example_upstream_regular": "standaard DNS (over UDP);",
+    "example_upstream_dot": "versleutelde <0>DNS-via-TLS</0>;",
+    "example_upstream_doh": "versleutelde <0>DNS-via-HTTPS</0>;",
+    "example_upstream_doq": "versleutelde <0>DNS-via-QUIC</0> (experimenteel);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> voor <1>DNSCrypt</1> of <2>DNS-via-HTTPS</2> oplossingen;",
+    "example_upstream_tcp": "standaard DNS (over TCP);",
     "all_lists_up_to_date_toast": "Alle lijsten zijn reeds actueel",
     "updated_upstream_dns_toast": "Upstream-servers succesvol opgeslagen",
     "dns_test_ok_toast": "Opgegeven DNS-servers werken correct",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Gebruik dubbele aanhalingstekens voor strikt zoeken",
     "query_log_retention_confirm": "Weet u zeker dat u de bewaartermijn van het query logboek wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren",
     "anonymize_client_ip": "Cliënt IP anonimiseren",
-    "anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in logboeken en statistiekbestanden",
+    "anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in logboeken en statistiekbestanden.",
     "dns_config": "DNS-server configuratie",
     "dns_cache_config": "DNS cache configuratie",
-    "dns_cache_config_desc": "Hier kan de DNS cache geconfigureerd worden",
+    "dns_cache_config_desc": "Hier kan de DNS cache geconfigureerd worden.",
     "blocking_mode": "Blocking modus",
     "default": "Standaard",
     "nxdomain": "NXDOMAIN",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-via-HTTPS",
     "dns_over_tls": "DNS-via-TLS",
     "dns_over_quic": "DNS-via-QUIC",
-    "client_id": "Apparaat-ID",
-    "client_id_placeholder": "Apparaat-ID invoeren",
-    "client_id_desc": "Verschillende apparaten kunnen worden geïdentificeerd door hun specifiek apparaat-ID. <a>Hier</a> vind je meer informatie over het identificeren van apparaten.",
+    "client_id": "Client-ID",
+    "client_id_placeholder": "Client-ID invoeren",
+    "client_id_desc": "Clients kunnen worden geïdentificeerd door hun Client-ID. <a>Hier</a> vind je meer informatie over het identificeren van clienten.",
     "download_mobileconfig_doh": ".mobileconfig voor DNS-via-HTTPS downloaden",
     "download_mobileconfig_dot": ".mobileconfig voor DNS-via-TLS downloaden",
     "download_mobileconfig": "Configuratiebestand downloaden",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Luister interface",
     "install_settings_port": "Poort",
     "install_settings_interface_link": "De webinterface van AdGuard Home admin is beschikbaar op de volgende adressen:",
-    "form_error_port": "Geldige poortwaarde invoeren",
+    "form_error_port": "Geldig poortnummer invoeren.",
     "install_settings_dns": "DNS-server",
     "install_settings_dns_desc": "Je moet jouw apparaten of router configureren om de DNS-server te gebruiken op de volgende adressen:",
     "install_settings_all_interfaces": "Alle interfaces",
@@ -334,8 +334,8 @@
     "install_devices_router_list_4": "Je kan een DNS-server niet instellen op sommige routers. In dat geval kan het een oplossing zijn om AdGuard Home te definiëren als een <0>DHCP-server</0>. Je kan ook in de handleiding van je router kijken hoe je een DNS-server aanpast.",
     "install_devices_windows_list_1": "Open het Configuratiescherm via het menu Start of Windows zoeken.",
     "install_devices_windows_list_2": "Ga naar de categorie Netwerk en Internet en vervolgens naar Netwerkcentrum.",
-    "install_devices_windows_list_3": "Zoek aan de linkerkant van het scherm \"Adapter-instellingen wijzigen\" en klik erop.",
-    "install_devices_windows_list_4": "Selecteer jouw actieve verbinding, klik er met de rechtermuisknop op en kies Eigenschappen.",
+    "install_devices_windows_list_3": "Aan de linkerkant van het scherm, klik op \"Adapter-instellingen wijzigen\".",
+    "install_devices_windows_list_4": "Klik met de rechtermuisknop op jouw actieve verbinding en kies Eigenschappen.",
     "install_devices_windows_list_5": "Zoek \"Internet Protocol versie 4 (TCP/IPv4)\" (of, voor IPv6, \"Internet Protocol versie 6 (TCP/IPv6)\") in de lijst, selecteer het en klik vervolgens opnieuw op Eigenschappen.",
     "install_devices_windows_list_6": "Kies \"Gebruik de volgende DNS-serveradressen\" en voer jouw AdGuard Home serveradressen in.",
     "install_devices_macos_list_1": "Klik op het Apple-pictogram en ga naar Systeemvoorkeuren.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Open Dashboard",
     "install_saved": "Succesvol opgeslagen",
     "encryption_title": "Encryptie",
-    "encryption_desc": "Encryptie (HTTPS/TLS) ondersteuning voor DNS en admin web interface",
+    "encryption_desc": "Encryptie (HTTPS/TLS) ondersteuning voor DNS en admin web interface.",
     "encryption_config_saved": "Versleuteling configuratie opgeslagen",
     "encryption_server": "Server naam",
     "encryption_server_enter": "Voer domein naam in",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Als de HTTPS-poort is geconfigureerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en biedt deze ook DNS-via-HTTPS op de locatie '/ dns-query'.",
     "encryption_dot": "DNS-via-TLS poort",
     "encryption_dot_desc": "Indien deze poort is geconfigureerd, zal AdGuard Home gebruik maken van een DNS-via-TLS server via deze poort.",
-    "encryption_doq": "DNS-via-QUIC poort",
+    "encryption_doq": "DNS-via-QUIC poort (experimenteel)",
     "encryption_doq_desc": "Als deze poort is geconfigureerd, zal AdGuard Home een DNS-via-QUIC server gebruiken via deze poort. Dit is experimenteel en kan onbetrouwbaar zijn. Er zijn overigens nog niet veel systemen die dit nu al ondersteunen.",
     "encryption_certificates": "Certificaten",
     "encryption_certificates_desc": "Om encryptie te gebruiken, moet u een geldige SSL certificaat voor uw domein opgeven. U kunt een gratis certificaat krijgen op <0> {{link}} </0> of u kunt het kopen bij een van de vertrouwde certificaatautoriteiten.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Kopieër en plak je PEM-gecodeerde prive sleutel voor je certificaat hier.",
     "encryption_enable": "Activeer encryptie (HTTPS, DNS-via-HTTPS, en DNS-via-TLS)",
     "encryption_enable_desc": "Als encryptie is geactiveerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en de DNS-server zal luisteren naar aanvragen via DNS-via-HTTPS en DNS-via-TLS.",
-    "encryption_chain_valid": "certificaatketen is geldig",
-    "encryption_chain_invalid": "certificaatketen is ongeldig",
-    "encryption_key_valid": "Dit is een geldig {{type}} privé sleutel",
-    "encryption_key_invalid": "Dit is een ongeldig {{type}} privé sleutel",
+    "encryption_chain_valid": "Certificaatketen is geldig.",
+    "encryption_chain_invalid": "Certificaatketen is ongeldig.",
+    "encryption_key_valid": "Dit is een geldige {{type}} privésleutel.",
+    "encryption_key_invalid": "Dit is een ongeldige {{type}} privésleutel.",
     "encryption_subject": "Onderwerp",
     "encryption_issuer": "Uitgever",
     "encryption_hostnames": "Hostnamen",
     "encryption_reset": "Ben je zeker dat je de encryptie instellingen wil resetten?",
     "topline_expiring_certificate": "Jouw SSL-certificaat vervalt binnenkort. Werk de <0>encryptie-instellingen</0> bij.",
     "topline_expired_certificate": "Jouw SSL-certificaat is vervallen. Werk de <0>encryptie-instellingen</0> bij.",
-    "form_error_port_range": "Poort nummer invoeren tussen 80 en 65535",
-    "form_error_port_unsafe": "Dit is een onveilige poort",
-    "form_error_equal": "Mag niet gelijk zijn",
-    "form_error_password": "Wachtwoord komt niet overeen",
+    "form_error_port_range": "Poortnummer invoeren tussen 80 en 65535.",
+    "form_error_port_unsafe": "Dit is een onveilige poort.",
+    "form_error_equal": "Mag niet gelijk zijn.",
+    "form_error_password": "Wachtwoord komt niet overeen.",
     "reset_settings": "Reset Instellingen",
     "update_announcement": "AdGuard Home{{version}} is nu beschikbaar! <0>klik hier</0> voor meer info.",
     "setup_guide": "Installatie gids",
     "dns_addresses": "DNS adressen",
     "dns_start": "DNS-server aan het opstarten",
-    "dns_status_error": "Fout bij het oproepen van de DNS-server status",
+    "dns_status_error": "Fout bij het controleren van de DNS-server status.",
     "down": "Uitgeschakeld",
     "fix": "Los op",
     "dns_providers": "hier is een <0>lijst of gekende DNS providers</0> waarvan je kan kiezen.",
@@ -405,8 +405,8 @@
     "update_failed": "Automatisch bijwerken is mislukt. <a>Volg deze stappen</a> om handmatig bij te werken.",
     "manual_update": "<a>Volg deze stappen</a> om handmatig bij te werken.",
     "processing_update": "Even geduld, AdGuard Home wordt bijgewerkt",
-    "clients_title": "Gebruikers",
-    "clients_desc": "Configureer apparaten die gebruik maken van AdGuard Home",
+    "clients_title": "Permanente clients",
+    "clients_desc": "Permanente client-records configureren voor apparaten verboden met AdGuard Home.",
     "settings_global": "Globaal",
     "settings_custom": "Aangepast",
     "table_client": "Gebruiker",
@@ -417,7 +417,7 @@
     "client_edit": "Wijzig gebruiker",
     "client_identifier": "Identificeer via",
     "ip_address": "IP adres",
-    "client_identifier_desc": "Apparaten kunnen worden geïdentificeerd door hun IP-adres, CIDR, MAC-adres of een speciaal apparaat-ID (kan gebruikt worden voor DoT/DoH/DoQ). <0>Hier</0> kan je meer lezen over het identificeren van apparaten.",
+    "client_identifier_desc": "Cliënten kunnen worden geïdentificeerd door hun IP-adres, CIDR, MAC-adres of Client-ID (kan gebruikt worden voor DoT/DoH/DoQ). <0>Hier</0> kan je meer lezen over het identificeren van cliënten.",
     "form_enter_ip": "Vul IP in",
     "form_enter_subnet_ip": "Voer een IP-adres in voor het subnet “{{cidr}}”",
     "form_enter_mac": "Vul MAC in",
@@ -432,14 +432,14 @@
     "clients_not_found": "Geen gebruikers gevonden",
     "client_confirm_delete": "Ben je zeker dat je deze gebruiker \"{{key}}\" wilt verwijderen?",
     "list_confirm_delete": "Ben je zeker om deze lijst te verwijderen?",
-    "auto_clients_title": "Gebruikers (runtime)",
-    "auto_clients_desc": "Data over gebruikers die AdGuard Home gebruiken, maar niet geconfigureerd zijn",
+    "auto_clients_title": "Runtime-clients",
+    "auto_clients_desc": "Apparaten die niet op de lijst van permanente clients staan die mogelijk nog steeds AdGuard Home gebruiken.",
     "access_title": "Toegangs instellingen",
     "access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen.",
     "access_allowed_title": "Toegestane gebruikers",
-    "access_allowed_desc": "Een lijst met CIDR's, IP-adressen of client-ID's. Indien geconfigureerd, accepteert AdGuard Home alleen verzoeken van deze cliënts.",
+    "access_allowed_desc": "Een lijst met CIDR's, IP-adressen of <a>Client-ID's</a>. Indien geconfigureerd, accepteert AdGuard Home alleen verzoeken van deze cliënts.",
     "access_disallowed_title": "Verworpen gebruikers",
-    "access_disallowed_desc": "Een lijst met CIDR's, IP-adressen of client-ID's. Indien geconfigureerd, zal AdGuard Home verzoeken van deze klanten verwerpen. Als toegestane cliënts zijn geconfigureerd, wordt dit veld genegeerd.",
+    "access_disallowed_desc": "Een lijst met CIDR's, IP-adressen of <a>Client-ID's</a>. Indien geconfigureerd, zal AdGuard Home verzoeken van deze klanten verwerpen. Als toegestane cliënts zijn geconfigureerd, wordt dit veld genegeerd.",
     "access_blocked_title": "Niet toegelaten domeinen",
     "access_blocked_desc": "Verwar dit niet met filters. AdGuard Home zal deze DNS-zoekopdrachten niet uitvoeren die deze domeinen in de zoekopdracht bevatten. Hier kan je de exacte domeinnamen, wildcards en URL-filter-regels specifiëren, bijv. \"example.org\", \"*.example.org\" of \"||example.org^\".",
     "access_settings_saved": "Toegangsinstellingen succesvol opgeslagen",
@@ -475,8 +475,8 @@
     "dns_rewrites": "DNS herschrijvingen",
     "form_domain": "Vul domein of wildcard in",
     "form_answer": "Vul IP adres of domeinnaam in",
-    "form_error_domain_format": "Ongeldige domeinnaam",
-    "form_error_answer_format": "Ongeldig antwoord",
+    "form_error_domain_format": "Ongeldige opmaak domein.",
+    "form_error_answer_format": "Ongeldig opmaak antwoord.",
     "configure": "Bewerk",
     "main_settings": "Algemene instellingen",
     "block_services": "Specifieke services blokkeren",
@@ -507,7 +507,7 @@
     "filter_updated": "De lijst is succesvol geüpdatet",
     "statistics_configuration": "Statistieken configuratie",
     "statistics_retention": "Statistieken retentie",
-    "statistics_retention_desc": "Als je de interval waarde vermindert, zullen sommige gegevens verloren gaan",
+    "statistics_retention_desc": "Als je de intervalwaarde vermindert, zullen sommige gegevens verloren gaan.",
     "statistics_clear": "Statistieken wissen",
     "statistics_clear_confirm": "Alle statistieken werkelijk wissen?",
     "statistics_retention_confirm": "Weet u zeker dat u de bewaartermijn van de statistieken wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "De volgende taken worden uitgevoerd: <0> Deactiveren van Systeem DNSStubListener</0> <0> DNS-serveradres instellen op 127.0.0.1 </0> <0> Symbolisch koppelingsdoel van /etc/resolv.conf vervangen door /run/systemd/resolve/resolv.conf </0> <0> Stop DNSStubListener (herlaad systemd-resolved service) </0>",
     "autofix_warning_result": "Als gevolg hiervan worden alle DNS-verzoeken van je systeem standaard door AdGuard Home verwerkt.",
     "tags_title": "Labels",
-    "tags_desc": "Je kunt tags selecteren die overeenkomen met de client. Tags kunnen worden opgenomen in de filterregels en je kunt ze dan nauwkeuriger toepassen. <0> Meer informatie </0>",
+    "tags_desc": "Je kunt labels selecteren die overeenkomen met de client. Labels kunnen worden opgenomen in de filterregels om ze \n nauwkeuriger toe te passen. <0>Meer informatie</0>.",
     "form_select_tags": "Client tags selecteren",
     "check_title": "Controleer de filtering",
-    "check_desc": "Controleer of de hostnaam wordt gefilterd",
+    "check_desc": "Controleren of een hostnaam wordt gefilterd.",
     "check": "Controleren",
     "form_enter_host": "Voer een hostnaam in",
     "filtered_custom_rules": "Gefilterd door aangepaste filterregels",
@@ -598,15 +598,15 @@
     "blocklist": "Blokkeerlijst",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Cache grootte",
-    "cache_size_desc": "DNS cache grootte (in bytes)",
+    "cache_size_desc": "DNS-cache grootte (in bytes).",
     "cache_ttl_min_override": "Minimale TTL overschrijven",
     "cache_ttl_max_override": "Maximale TTL overschrijven",
     "enter_cache_size": "Cache grootte invoeren (bytes)",
     "enter_cache_ttl_min_override": "Minimum TTL invoeren (seconden)",
     "enter_cache_ttl_max_override": "Maximum TTL invoeren (seconden)",
-    "cache_ttl_min_override_desc": "Uitbreiden van korte Time-To-Live waardes (seconden) ontvangen van de upstream server bij het cachen van DNS antwoorden",
-    "cache_ttl_max_override_desc": "Instellen van maximum time-to-live waarde (seconden) voor opslag in de DNS cache",
-    "ttl_cache_validation": "Minimale waarde TTL-cache moet kleiner dan of gelijk zijn aan de maximale waarde",
+    "cache_ttl_min_override_desc": "Uitbreiden van korte Time-To-Live waardes (seconden) ontvangen van de upstream server bij het cachen van DNS antwoorden.",
+    "cache_ttl_max_override_desc": "Instellen van maximum time-to-live waarde (seconden) voor opslag in de DNS cache.",
+    "ttl_cache_validation": "Minimale waarde TTL-cache moet kleiner dan of gelijk zijn aan de maximale waarde.",
     "cache_optimistic": "Optimistisch cachen",
     "cache_optimistic_desc": "Laat AdGuard Home reageren vanuit de cache, zelfs als de vermeldingen zijn verlopen en probeer deze ook te vernieuwen.",
     "filter_category_general": "Algemeen",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home zal alle DNS verzoeken van deze toepassing/dit systeem negeren.",
     "filter_allowlist": "WAARSCHUWING: Deze actie zal ook de regel \"{{disallowed_rule}}\" uitsluiten van de lijst met toegestane clients.",
     "last_rule_in_allowlist": "Kan deze client niet weigeren omdat het uitsluiten van de regel \"{{disallowed_rule}}\" de lijst \"Toegestane clients\" zal UITSCHAKELEN.",
-    "experimental": "Experimenteel",
     "use_saved_key": "De eerder opgeslagen sleutel gebruiken",
     "parental_control": "Ouderlijk toezicht",
     "safe_browsing": "Veilig browsen",
-    "served_from_cache": "{{value}} <i>(geleverd vanuit cache)</i>"
+    "served_from_cache": "{{value}} <i>(geleverd vanuit cache)</i>",
+    "form_error_password_length": "Wachtwoord moet minimaal {{value}} tekens lang zijn."
 }
diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json
index 4bac8cee..b8f8bc63 100644
--- a/client/src/__locales/no.json
+++ b/client/src/__locales/no.json
@@ -544,7 +544,7 @@
     "allowed": "Unntak",
     "filtered": "Filtrert",
     "rewritten": "Omskrevet",
-    "safe_search": "Trygge søk",
+    "safe_search": "Aktivert sikkert søk",
     "blocklist": "Blokkeringsliste",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Mellomlagerstørrelse",
@@ -564,14 +564,12 @@
     "filter_category_regional": "Regional",
     "filter_category_other": "Andre",
     "filter_category_general_desc": "Lister som blokkerer sporing og reklamer på de fleste enheter",
-    "filter_category_security_desc": "Lister som spesialiserer seg på å blokkere skadevare-, phishing- eller svindeldomener",
     "filter_category_regional_desc": "Lister som fokuserer på regionale reklamer og sporingstjenere",
     "filter_category_other_desc": "Andre blokkeringslister",
     "original_response": "Opprinnelig svar",
     "click_to_view_queries": "Klikk for å vise forespørsler",
     "port_53_faq_link": "Port 53 er ofte opptatt av «DNSStubListener»- eller «systemd-resolved»-tjenestene. Vennligst les <0>denne instruksjonen</0> om hvordan man løser dette.",
     "adg_will_drop_dns_queries": "AdGuard Home vil droppe alle DNS-forespørsler fra denne klienten.",
-    "experimental": "Eksperimentell",
     "use_saved_key": "Bruk den tidligere lagrede nøkkelen",
     "parental_control": "Foreldrekontroll",
     "served_from_cache": "{{value}} <i>(formidlet fra mellomlageret)</i>"
diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json
index 19cf6aba..253a8cac 100644
--- a/client/src/__locales/pl.json
+++ b/client/src/__locales/pl.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Ustawienia klienta",
-    "example_upstream_reserved": "Możesz określić nadrzędny serwer DNS <0>dla określonych domen</0>",
-    "example_upstream_comment": "Możesz podać komentarz",
+    "example_upstream_reserved": "upstream <0>dla określonych domen</0>;",
+    "example_upstream_comment": "komentarz.",
     "upstream_parallel": "Użyj zapytań równoległych, aby przyspieszyć rozwiązywanie przez jednoczesne wysyłanie zapytań do wszystkich serwerów nadrzędnych.",
     "parallel_requests": "Równoległe żądania",
     "load_balancing": "Równoważenie obciążenia",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Konfiguracja DHCP została pomyślnie zapisana",
     "dhcp_ipv4_settings": "Ustawienia serwera DHCP IPv4",
     "dhcp_ipv6_settings": "Ustawienia serwera DHCP IPv6",
-    "form_error_required": "Pole jest wymagane",
-    "form_error_ip4_format": "Nieprawidłowy adres IPv4",
-    "form_error_ip4_range_start_format": "Nieprawidłowy adres IPv4 początku zakresu",
-    "form_error_ip4_range_end_format": "Nieprawidłowy adres IPv4 końca zakresu",
-    "form_error_ip4_gateway_format": "Nieprawidłowy adres IPv4 bramy",
-    "form_error_ip6_format": "Nieprawidłowy adres IPv6",
-    "form_error_ip_format": "Nieprawidłowy adres IP",
-    "form_error_mac_format": "Nieprawidłowy adres MAC",
-    "form_error_client_id_format": "ID klienta musi zawierać tylko cyfry, małe litery i myślniki",
-    "form_error_server_name": "Nieprawidłowa nazwa serwera",
-    "form_error_subnet": "Podsieć \"{{cidr}}\" nie zawiera adresu IP \"{{ip}}\"",
-    "form_error_positive": "Musi być większa niż 0",
-    "out_of_range_error": "Musi być spoza zakresu \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Musi być niższy niż początek zakresu",
-    "greater_range_start_error": "Musi być większy niż początek zakresu",
-    "greater_range_end_error": "Musi być większy niż koniec zakresu",
-    "subnet_error": "Adresy muszą należeć do jednej podsieci",
-    "gateway_or_subnet_invalid": "Nieprawidłowa maska podsieci",
+    "form_error_required": "Pole wymagane.",
+    "form_error_ip4_format": "Nieprawidłowy adres IPv4.",
+    "form_error_ip4_range_start_format": "Nieprawidłowy adres IPv4 początku zakresu.",
+    "form_error_ip4_range_end_format": "Nieprawidłowy adres IPv4 końca zakresu.",
+    "form_error_ip4_gateway_format": "Nieprawidłowy adres IPv4 bramy.",
+    "form_error_ip6_format": "Nieprawidłowy adres IPv6.",
+    "form_error_ip_format": "Nieprawidłowy adres IP.",
+    "form_error_mac_format": "Nieprawidłowy adres MAC.",
+    "form_error_client_id_format": "ClientID musi zawierać tylko cyfry, małe litery i myślniki.",
+    "form_error_server_name": "Nieprawidłowa nazwa serwera.",
+    "form_error_subnet": "Podsieć \"{{cidr}}\" nie zawiera adresu IP \"{{ip}}\".",
+    "form_error_positive": "Musi być większa niż 0.",
+    "out_of_range_error": "Musi być spoza zakresu \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Musi być niższy niż początek zakresu.",
+    "greater_range_start_error": "Musi być większy niż początek zakresu.",
+    "greater_range_end_error": "Musi być większy niż koniec zakresu.",
+    "subnet_error": "Adresy muszą należeć do jednej podsieci.",
+    "gateway_or_subnet_invalid": "Nieprawidłowa maska podsieci.",
     "dhcp_form_gateway_input": "Adres IP bramy",
     "dhcp_form_subnet_input": "Maska podsieci",
     "dhcp_form_range_title": "Zakres adresów IP",
@@ -167,8 +167,8 @@
     "enabled_safe_browsing_toast": "Włączone Bezpieczne przeglądanie",
     "disabled_parental_toast": "Wyłączona Kontrola Rodzicielska",
     "enabled_parental_toast": "Włączona Kontrola Rodzicielska",
-    "disabled_safe_search_toast": "Bezpieczne wyszukiwanie zostało włączone",
-    "enabled_save_search_toast": "Bezpieczne wyszukiwanie zostało włączone",
+    "disabled_safe_search_toast": "Wyłączone bezpieczne wyszukiwanie",
+    "enabled_save_search_toast": "Włączone bezpieczne wyszukiwanie",
     "enabled_table_header": "Włączone",
     "name_table_header": "Nazwa",
     "list_url_table_header": "Adres URL listy",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Wybierz listy dozwolonych",
     "enter_valid_blocklist": "Wpisz prawidłowy adres URL do listy zablokowanych.",
     "enter_valid_allowlist": "Wpisz prawidłowy adres URL do listy dozwolonych.",
-    "form_error_url_format": "Format adresu URL jest nieprawidłowy",
-    "form_error_url_or_path_format": "Adres URL lub bezwzględna ścieżka listy jest nieprawidłowa",
+    "form_error_url_format": "Nieprawidłowy format URL.",
+    "form_error_url_or_path_format": "Nieprawidłowy adres URL lub bezwzględna ścieżka listy.",
     "custom_filter_rules": "Niestandardowe reguły filtrowania",
     "custom_filter_rules_hint": "Wpisz jedną regułę w jednej linii. Możesz użyć reguł adblock lub składni plików hostów.",
     "system_host_files": "Pliki hosts systemu",
     "examples_title": "Przykłady",
-    "example_meaning_filter_block": "zablokuj dostęp do domeny example.org i wszystkich jej subdomen",
-    "example_meaning_filter_whitelist": "odblokuj dostęp do domeny example.org i wszystkich jej subdomen",
-    "example_meaning_host_block": "AdGuard Home zwróci adres 127.0.0.1 dla domeny example.org (ale nie jej subdomen).",
-    "example_comment": "! Tutaj jest komentarz",
-    "example_comment_meaning": "komentarz",
-    "example_comment_hash": "# Również komentarz",
-    "example_regex_meaning": "zablokuj dostęp do domen pasujących do określonego wyrażenia regularnego",
-    "example_upstream_regular": "normalny DNS (przez UDP)",
-    "example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "zaszyfrowany <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "możesz użyć adresu <0>DNS Stamps</0> dla protokołu <1>DNSCrypt</1> lub <2>DNS-over-HTTPS</2>",
-    "example_upstream_tcp": "zwykły DNS (przez TCP)",
+    "example_meaning_filter_block": "zablokuj dostęp do domeny example.org i wszystkich jej subdomen;",
+    "example_meaning_filter_whitelist": "odblokuj dostęp do domeny example.org i wszystkich jej subdomen;",
+    "example_meaning_host_block": "odpowiedz 127.0.0.1 na example.org (ale nie dla jego subdomen);",
+    "example_comment": "! Tutaj jest komentarz.",
+    "example_comment_meaning": "komentarz;",
+    "example_comment_hash": "# Również komentarz.",
+    "example_regex_meaning": "zablokuj dostęp do domen pasujących do określonego wyrażenia regularnego.",
+    "example_upstream_regular": "normalny DNS (przez UDP);",
+    "example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "zaszyfrowany <0>DNS-over-QUIC</0> (eksperymentalny);",
+    "example_upstream_sdns": "<0>Stempel DNS</0> dla resolwerów <1>DNSCrypt</1> lub <2>DNS-over-HTTPS</2>;",
+    "example_upstream_tcp": "zwykły DNS (przez TCP);",
     "all_lists_up_to_date_toast": "Wszystkie listy są już aktualne",
     "updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane",
     "dns_test_ok_toast": "Określone serwery DNS działają poprawnie",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Używaj podwójnych cudzysłowów do ścisłego wyszukiwania",
     "query_log_retention_confirm": "Czy na pewno chcesz zmienić sposób przechowywania dziennika zapytań? Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone",
     "anonymize_client_ip": "Anonimizuj adres IP klienta",
-    "anonymize_client_ip_desc": "Nie zapisuj pełnego adresu IP w dziennikach i statystykach",
+    "anonymize_client_ip_desc": "Nie zapisuj pełnego adresu IP w dziennikach i statystykach.",
     "dns_config": "Konfiguracja serwera DNS",
     "dns_cache_config": "Konfiguracja pamięci podręcznej DNS",
-    "dns_cache_config_desc": "Tutaj możesz skonfigurować pamięć podręczną DNS",
+    "dns_cache_config_desc": "Tutaj możesz skonfigurować pamięć podręczną DNS.",
     "blocking_mode": "Tryb blokowania",
     "default": "Domyślny",
     "nxdomain": "NXDOMAIN",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "ID klienta",
-    "client_id_placeholder": "Wpisz ID klienta",
-    "client_id_desc": "Różnych klientów można zidentyfikować za pomocą specjalnego ID klienta. <a>Tutaj</a> możesz dowiedzieć się więcej o tym, jak identyfikować klientów.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Wpisz ClientID",
+    "client_id_desc": "Klienci mogą być identyfikowani przez ClientID. Dowiedz się więcej o tym, jak identyfikować klientów <a>tutaj</a>.",
     "download_mobileconfig_doh": "Pobierz plik .mobileconfig dla DNS-over-HTTPS",
     "download_mobileconfig_dot": "Pobierz plik .mobileconfig dla DNS-over-TLS",
     "download_mobileconfig": "Pobierz plik konfiguracyjny",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Interfejs sieciowy",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Twój interfejs www AdGuard Home Admin będzie dostępny pod następującymi adresami:",
-    "form_error_port": "Wprowadź prawidłowy numer portu",
+    "form_error_port": "Wprowadź prawidłowy numer portu.",
     "install_settings_dns": "Serwer DNS",
     "install_settings_dns_desc": "Konieczne będzie skonfigurowanie urządzenia lub routera do korzystania z serwera DNS pod następującymi adresami:",
     "install_settings_all_interfaces": "Wszystkie interfejsy",
@@ -334,8 +334,8 @@
     "install_devices_router_list_4": "Na niektórych typach routerów nie można skonfigurować własnego serwera DNS. W takim przypadku pomocne może być skonfigurowanie AdGuard Home jako <0>serwera DHCP</0>. W przeciwnym razie należy sprawdzić w instrukcji obsługi routera, jak dostosować serwery DNS do konkretnego modelu routera.",
     "install_devices_windows_list_1": "Otwórz panel Ustawienia w menu Start lub w Windows.",
     "install_devices_windows_list_2": "Przejdź do kategorii Sieć i Internet, a następnie do Centrum sieci i udostępniania.",
-    "install_devices_windows_list_3": "Po lewej stronie ekranu znajdź \"Zmień ustawienia adaptera\" i kliknij na niego.",
-    "install_devices_windows_list_4": "Wybierz aktywne połączenie, kliknij je prawym przyciskiem myszy i wybierz Właściwości.",
+    "install_devices_windows_list_3": "W lewym panelu kliknij \"Zmień ustawienia adaptera\".",
+    "install_devices_windows_list_4": "Kliknij prawym przyciskiem myszy aktywne połączenie i wybierz Właściwości.",
     "install_devices_windows_list_5": "Znajdź na liście \"Protokół internetowy w wersji 4 (TCP/IPv4)\" (lub w przypadku IPv6 \"Protokół internetowy w wersji 6 (TCP/IPv6)\"), zaznacz go i ponownie kliknij na Właściwości.",
     "install_devices_windows_list_6": "Wybierz opcję \"Użyj następujących adresów serwerów DNS\" i wprowadź adresy serwerów AdGuard Home.",
     "install_devices_macos_list_1": "Kliknij ikonę Apple i przejdź do Preferencje systemowe.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Otwórz panel sterowania",
     "install_saved": "Pomyślnie zapisany",
     "encryption_title": "Szyfrowanie",
-    "encryption_desc": "Obsługa szyfrowania (HTTPS/TLS) dla interfejsu sieciowego DNS i administratora",
+    "encryption_desc": "Obsługa szyfrowania (HTTPS/TLS) dla interfejsu sieciowego DNS i administratora.",
     "encryption_config_saved": "Konfiguracja szyfrowania została zapisana",
     "encryption_server": "Nazwa serwera",
     "encryption_server_enter": "Wpisz swoją nazwę domeny",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Jeśli port HTTPS jest skonfigurowany, interfejs administratora AdGuard Home będzie dostępny za pośrednictwem protokołu HTTPS i zapewni DNS przez HTTPS w lokalizacji zapytania '/dns-query'.",
     "encryption_dot": "Port DNS-over-TLS",
     "encryption_dot_desc": "Jeśli ten port jest skonfigurowany, AdGuard Home uruchomi serwer DNS-over-TLS na tym porcie.",
-    "encryption_doq": "Port DNS-over-QUIC",
+    "encryption_doq": "Port DNS-over-QUIC (eksperymentalny)",
     "encryption_doq_desc": "Jeśli ten port jest skonfigurowany, AdGuard Home uruchomi serwer DNS-over-QUIC na tym porcie. Jest to funkcja eksperymentalna i może nie być stabilna. Ponadto, w tej chwili nie ma zbyt wielu klientów, którzy go obsługują.",
     "encryption_certificates": "Certyfikaty",
     "encryption_certificates_desc": "Aby korzystać z szyfrowania, musisz podać prawidłowy łańcuch certyfikatów SSL dla swojej domeny. Możesz uzyskać bezpłatny certyfikat na  <0>{{link}}</0> lub możesz go kupić od jednego z zaufanych urzędów certyfikacji.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Tutaj kopiuj/wklej klucze prywatne zakodowane w PEM do swojego certyfikatu.",
     "encryption_enable": "Włącz szyfrowanie (HTTPS, DNS-over-HTTPS i DNS-over-TLS)",
     "encryption_enable_desc": "Jeśli szyfrowanie jest włączone, interfejs administracyjny AdGuard Home będzie działał przez HTTPS, a serwer DNS będzie nasłuchiwał żądań przez DNS-over-HTTPS i DNS-over-TLS.",
-    "encryption_chain_valid": "Łańcuch certyfikatów jest prawidłowy",
-    "encryption_chain_invalid": "Łańcuch certyfikatu jest nieprawidłowy",
+    "encryption_chain_valid": "Łańcuch certyfikatów jest prawidłowy.",
+    "encryption_chain_invalid": "Łańcuch certyfikatu jest nieprawidłowy.",
     "encryption_key_valid": "Poprawny {{type}} klucz prywatny.",
-    "encryption_key_invalid": "Klucz prywatny {{type}} jest nieprawidłowy",
+    "encryption_key_invalid": "Nieprawidłowy {{type}} klucz prywatny.",
     "encryption_subject": "Temat",
     "encryption_issuer": "Zgłaszający",
     "encryption_hostnames": "Nazwy hostów",
     "encryption_reset": "Czy na pewno chcesz zresetować ustawienia szyfrowania?",
     "topline_expiring_certificate": "Twój certyfikat SSL wkrótce wygaśnie. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
     "topline_expired_certificate": "Twój certyfikat SSL wygasł. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
-    "form_error_port_range": "Wpisz numer portu z zakresu 80-65535",
-    "form_error_port_unsafe": "To jest niebezpieczny port",
-    "form_error_equal": "Nie mogą być równe",
-    "form_error_password": "Hasło nie pasuje",
+    "form_error_port_range": "Wpisz numer portu z zakresu 80-65535.",
+    "form_error_port_unsafe": "To jest niebezpieczny port.",
+    "form_error_equal": "Nie mogą być równe.",
+    "form_error_password": "Niezgodne hasło.",
     "reset_settings": "Resetowanie ustawień",
     "update_announcement": "AdGuard Home {{version}} jest już dostępny! <0>Kliknij tutaj</0> aby uzyskać więcej informacji.",
     "setup_guide": "Przewodnik instalacji",
     "dns_addresses": "Adresy DNS",
     "dns_start": "Serwer DNS uruchamia się",
-    "dns_status_error": "Błąd uzyskania statusu serwera DNS",
+    "dns_status_error": "Błąd podczas sprawdzania stanu serwera DNS.",
     "down": "Utrata połączenia",
     "fix": "Napraw",
     "dns_providers": "Oto lista <0>znanych dostawców DNS</0> do wyboru.",
@@ -405,8 +405,8 @@
     "update_failed": "Automatyczna aktualizacja nie powiodła się. Proszę <a>wykonaj kroki</a> aby zaktualizować ręcznie.",
     "manual_update": "Proszę <a>wykonać te czynności</a>, aby zaktualizować ręcznie.",
     "processing_update": "Poczekaj, trwa aktualizacja AdGuard Home",
-    "clients_title": "Klienci",
-    "clients_desc": "Skonfiguruj urządzenia podłączone do AdGuard Home",
+    "clients_title": "Trwali klienci",
+    "clients_desc": "Skonfiguruj trwałe rekordy klienta dla urządzeń podłączonych do AdGuard Home.",
     "settings_global": "Globalny",
     "settings_custom": "Własne",
     "table_client": "Klient",
@@ -417,7 +417,7 @@
     "client_edit": "Edytuj klienta",
     "client_identifier": "Identyfikator",
     "ip_address": "Adres IP",
-    "client_identifier_desc": "Klientów można zidentyfikować po adresie IP, CIDR, adresie MAC lub specjalnym identyfikatorze klienta (może służyć do DoT/DoH/DoQ). <0>Tutaj</0> możesz dowiedzieć się więcej o tym, jak identyfikować klientów.",
+    "client_identifier_desc": "Klienci mogą być identyfikowani na podstawie ich adresu IP, CIDR, adresu MAC lub ClientID (może być używany do DoT/DoH/DoQ). Dowiedz się więcej o tym, jak identyfikować klientów <0>tutaj</0>.",
     "form_enter_ip": "Wpisz adres IP",
     "form_enter_subnet_ip": "Wprowadź adres IP w podsieci \"{{cidr}}\"",
     "form_enter_mac": "Wpisz adres MAC",
@@ -432,14 +432,14 @@
     "clients_not_found": "Nie znaleziono klientów",
     "client_confirm_delete": "Czy na pewno chcesz usunąć klienta \"{{key}}\"?",
     "list_confirm_delete": "Czy na pewno chcesz usunąć tę listę?",
-    "auto_clients_title": "Klienci (czas uruchamiania)",
-    "auto_clients_desc": "Dane klientów, które używają AdGuard Home, ale nie są przechowywane w konfiguracji",
+    "auto_clients_title": "Uruchomieni klienci",
+    "auto_clients_desc": "Urządzenia, których nie ma na liście stałych klientów, które mogą nadal korzystać z AdGuard Home.",
     "access_title": "Ustawienia dostępu",
     "access_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home.",
     "access_allowed_title": "Dozwoleni klienci",
-    "access_allowed_desc": "Lista CIDR-ów, adresów IP lub identyfikatorów klientów. Jeśli zostanie skonfigurowana, AdGuard Home będzie przyjmował żądania tylko od tych klientów.",
+    "access_allowed_desc": "Lista identyfikatorów CIDR, adresów IP lub <a>identyfikatorów klienta</a>. Jeśli ta lista zawiera wpisy, AdGuard Home zaakceptuje żądania tylko od tych klientów.",
     "access_disallowed_title": "Niedozwoleni klienci",
-    "access_disallowed_desc": "Lista CIDR-ów, adresów IP lub identyfikatorów klientów. Jeśli jest skonfigurowana, AdGuard Home będzie odrzucał żądania od tych klientów. Jeśli skonfigurowano dozwolonych klientów, pole to jest ignorowane.",
+    "access_disallowed_desc": "Lista identyfikatorów CIDR, adresów IP lub <a>identyfikatorów klienta</a>. Jeśli ta lista zawiera wpisy, AdGuard Home odrzuci żądania od tych klientów. To pole jest ignorowane, jeśli istnieją wpisy w Dozwolonych klientach.",
     "access_blocked_title": "Niedozwolone domeny",
     "access_blocked_desc": "Nie należy ich mylić z filtrami. AdGuard Home usuwa zapytania DNS pasujące do tych domen, a zapytania te nie pojawiają się nawet w dzienniku zapytań. Możesz określić dokładne nazwy domen, symbole wieloznaczne lub reguły filtrowania adresów URL, np. \"example.org\", \"*.example.org\" lub \"||example.org^\".",
     "access_settings_saved": "Ustawienia dostępu zostały pomyślnie zapisane",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Przepisywanie DNS",
     "form_domain": "Wpisz nazwę domeny lub symbol wieloznaczny",
     "form_answer": "Wpisz adres IP lub nazwę domeny",
-    "form_error_domain_format": "Niepoprawny format domeny",
-    "form_error_answer_format": "Nieprawidłowy format odpowiedzi",
+    "form_error_domain_format": "Niepoprawny format domeny.",
+    "form_error_answer_format": "Nieprawidłowy format odpowiedzi.",
     "configure": "Skonfiguruj",
     "main_settings": "Ustawienia główne",
     "block_services": "Zablokuj określone usługi",
@@ -507,7 +507,7 @@
     "filter_updated": "Filtr został pomyślnie zaktualizowany",
     "statistics_configuration": "Konfiguracja statystyk",
     "statistics_retention": "Przechowywanie statystyk",
-    "statistics_retention_desc": "Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone",
+    "statistics_retention_desc": "Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone.",
     "statistics_clear": "Wyczyść statystyki",
     "statistics_clear_confirm": "Czy na pewno chcesz wyczyścić statystyki?",
     "statistics_retention_confirm": "Czy chcesz zmienić sposób przechowania statystyk? Jeżeli obniżysz wartość interwału, niektóre dane będą utracone",
@@ -532,7 +532,7 @@
     "netname": "Nazwa sieci",
     "network": "Sieć",
     "descr": "Opis",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Dowiedz się więcej</0> o tworzeniu własnych list blokowania hostów.",
     "blocked_by_response": "W odpowiedzi zablokowany przez CNAME lub IP",
     "blocked_by_cname_or_ip": "Zablokowany przez rekord CNAME lub adres IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Wykona następujące zadania: <0>Dezaktywuj system DNSStubListener</0> <0>Ustaw adres serwera DNS na 127.0.0.1</0> <0>Zamień symboliczny cel łącza z /etc/resolv.conf na /run/systemd/resolve/resolv.conf</0> <0>Zatrzymaj DNSStubListener (przeładuj usługę systemową)</0>",
     "autofix_warning_result": "W rezultacie wszystkie żądania DNS z Twojego systemu będą domyślnie przetwarzane przez AdGuardHome.",
     "tags_title": "Tagi",
-    "tags_desc": "Możesz wybrać tagi odpowiadające klientowi. Tagi mogą być uwzględnione w regułach filtrowania i umożliwiają ich dokładniejsze stosowanie. <0>Dowiedz się więcej </0>",
+    "tags_desc": "Możesz wybrać tagi, które odpowiadają klientowi. Uwzględnij tagi w regułach filtrowania, aby zastosować je dokładniej. <0>Dowiedz się więcej</0>.",
     "form_select_tags": "Wybierz tagi klienta",
     "check_title": "Sprawdź filtrowanie",
-    "check_desc": "Sprawdź, czy nazwa hosta jest filtrowana",
+    "check_desc": "Sprawdź, czy nazwa hosta jest filtrowana.",
     "check": "Sprawdź",
     "form_enter_host": "Wpisz nazwę hosta",
     "filtered_custom_rules": "Filtrowane według niestandardowych reguł filtrowania",
@@ -598,15 +598,15 @@
     "blocklist": "Lista zablokowanych",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Rozmiar pamięci podręcznej",
-    "cache_size_desc": "Rozmiar pamięci podręcznej DNS (w bajtach)",
+    "cache_size_desc": "Rozmiar pamięci podręcznej DNS (w bajtach).",
     "cache_ttl_min_override": "Nadpisz minimalną wartość TTL",
     "cache_ttl_max_override": "Nadpisz maksymalną wartość TTL",
     "enter_cache_size": "Wpisz rozmiar pamięci podręcznej (w bajtach)",
     "enter_cache_ttl_min_override": "Wpisz minimalną wartość TTL (w sekundach)",
     "enter_cache_ttl_max_override": "Wpisz maksymalną wartość TTL (w sekundach)",
-    "cache_ttl_min_override_desc": "Zastąp wartość TTL (w sekundach) otrzymaną z serwera nadrzędnego podczas buforowania odpowiedzi DNS",
-    "cache_ttl_max_override_desc": "Ustaw maksymalną wartość TTL (w sekundach) dla wpisów w pamięci podręcznej DNS",
-    "ttl_cache_validation": "Minimalna pamięć podręczna wartości TTL musi być mniejsza lub równa maksymalnej wartości",
+    "cache_ttl_min_override_desc": "Przedłuż najkrótszą wartość TTL (w sekundach) otrzymaną od serwera wychodzącego podczas buforowania odpowiedzi DNS.",
+    "cache_ttl_max_override_desc": "Ustaw maksymalną wartość czasu życia (w sekundach) dla wpisów w pamięci podręcznej DNS.",
+    "ttl_cache_validation": "Minimalne nadpisanie pamięci podręcznej TTL musi być mniejsze lub równe maksimum.",
     "cache_optimistic": "Optymistyczne buforowanie",
     "cache_optimistic_desc": "Spraw, aby AdGuard Home odpowiadał z pamięci podręcznej, nawet gdy wpisy wygasły, a także spróbuj je odświeżyć.",
     "filter_category_general": "Ogólne",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home odrzuci zapytanie DNS od tego klienta.",
     "filter_allowlist": "OSTRZEŻENIE: To działanie spowoduje również wykluczenie reguły \"{{disallowed_rule}}\" z listy dozwolonych klientów.",
     "last_rule_in_allowlist": "Nie można odrzucić tego klienta, ponieważ wykluczenie reguły \"{{disallowed_rule}}\" spowoduje WYŁĄCZENIE listy „Dozwolonych klientów”.",
-    "experimental": "Funkcja eksperymentalna",
     "use_saved_key": "Użyj wcześniej zapisanego klucza",
     "parental_control": "Kontrola rodzicielska",
     "safe_browsing": "Bezpieczne przeglądanie",
-    "served_from_cache": "{{value}} <i>(podawane z pamięci podręcznej)</i>"
+    "served_from_cache": "{{value}} <i>(podawane z pamięci podręcznej)</i>",
+    "form_error_password_length": "Hasło musi mieć przynajmniej {{value}} znaków."
 }
diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json
index c71e619a..56c91880 100644
--- a/client/src/__locales/pt-br.json
+++ b/client/src/__locales/pt-br.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Configurações do cliente",
-    "example_upstream_reserved": "Você pode especificar o DNS primário <0>para o domínio(s) especifico</0>",
-    "example_upstream_comment": "Você pode especificar o comentário",
+    "example_upstream_reserved": "um DNS primário <0>para o domínios especificos</0>;",
+    "example_upstream_comment": "um comentário.",
     "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos oss servidores DNS primário",
     "parallel_requests": "Solicitações paralelas",
     "load_balancing": "Balanceamento de carga",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Configurações DHCP salvas com sucesso",
     "dhcp_ipv4_settings": "Configurações DHCP IPv4",
     "dhcp_ipv6_settings": "Configurações DHCP IPv6",
-    "form_error_required": "Campo obrigatório",
-    "form_error_ip4_format": "Endereço de IPv4 inválido",
-    "form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido",
-    "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido",
-    "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido",
-    "form_error_ip6_format": "Endereço de IPv6 inválido",
-    "form_error_ip_format": "Endereço de IP inválido",
-    "form_error_mac_format": "Endereço de MAC inválido",
+    "form_error_required": "Campo obrigatório.",
+    "form_error_ip4_format": "Endereço de IPv4 inválido.",
+    "form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido.",
+    "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido.",
+    "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido.",
+    "form_error_ip6_format": "Endereço de IPv6 inválido.",
+    "form_error_ip_format": "Endereço de IP inválido.",
+    "form_error_mac_format": "Endereço de MAC inválido.",
     "form_error_client_id_format": "O ID do cliente deve conter apenas números, letras minúsculas e hifens",
-    "form_error_server_name": "Nome de servidor inválido",
-    "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
-    "form_error_positive": "Deve ser maior que 0",
-    "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Deve ser inferior ao início do intervalo",
-    "greater_range_start_error": "Deve ser maior que o início do intervalo",
-    "greater_range_end_error": "Deve ser maior que o fim do intervalo",
-    "subnet_error": "Endereços devem estar em uma sub-rede",
-    "gateway_or_subnet_invalid": "Máscara de sub-rede inválida",
+    "form_error_server_name": "Nome de servidor inválido.",
+    "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\".",
+    "form_error_positive": "Deve ser maior que 0.",
+    "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Deve ser inferior ao início do intervalo.",
+    "greater_range_start_error": "Deve ser maior que o início do intervalo.",
+    "greater_range_end_error": "Deve ser maior que o fim do intervalo.",
+    "subnet_error": "Endereços devem estar em uma sub-rede.",
+    "gateway_or_subnet_invalid": "Máscara de sub-rede inválida.",
     "dhcp_form_gateway_input": "IP do gateway",
     "dhcp_form_subnet_input": "Máscara de sub-rede",
     "dhcp_form_range_title": "Faixa de endereços IP",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Escolher as listas de permissões",
     "enter_valid_blocklist": "Digite um URL válido para a lista de bloqueio.",
     "enter_valid_allowlist": "Digite uma URL válida para a lista de permissões.",
-    "form_error_url_format": "Formato da URL inválida",
-    "form_error_url_or_path_format": "URL ou local da lista inválida",
+    "form_error_url_format": "Formato da URL inválida.",
+    "form_error_url_or_path_format": "URL ou local da lista inválida.",
     "custom_filter_rules": "Regras de filtragem personalizadas",
     "custom_filter_rules_hint": "Digite uma regra por linha. Você pode usar regras de bloqueio de anúncios ou a sintaxe de arquivos de hosts.",
     "system_host_files": "Arquivos hosts do sistema",
     "examples_title": "Exemplos",
-    "example_meaning_filter_block": "bloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios",
-    "example_meaning_filter_whitelist": "desbloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios",
-    "example_meaning_host_block": "O AdGuard Home irá retornar o endereço 127.0.0.1 para o domínio exemplo.org (exceto seus subdomínios).",
-    "example_comment": "! Aqui vai um comentário",
-    "example_comment_meaning": "apenas um comentário",
-    "example_comment_hash": "# Também um comentário",
-    "example_regex_meaning": "bloqueia o acesso aos domínios que correspondem à expressão regular especificada",
-    "example_upstream_regular": "DNS regular (através do UDP)",
-    "example_upstream_dot": "<0>DNS-sobre-TLS</0> criptografado",
-    "example_upstream_doh": "<0>DNS-sobre-HTTPS</0> criptografado",
-    "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado",
-    "example_upstream_sdns": "você pode usar <0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>",
-    "example_upstream_tcp": "DNS regular (através do TCP)",
+    "example_meaning_filter_block": "bloqueia o acesso ao exemplo.org e a todos os seus subdomínios;",
+    "example_meaning_filter_whitelist": "desbloqueia o acesso ao exemplo.org e a todos os seus subdomínios;",
+    "example_meaning_host_block": "responde o endereço 127.0.0.1 para o exemplo.org (exceto seus subdomínios);",
+    "example_comment": "! Aqui vai um comentário.",
+    "example_comment_meaning": "apenas um comentário;",
+    "example_comment_hash": "# Também um comentário.",
+    "example_regex_meaning": "bloqueia o acesso aos domínios que correspondem à expressão regular especificada.",
+    "example_upstream_regular": "dNS regular (através do UDP);",
+    "example_upstream_dot": "<0>DNS-sobre-TLS</0> criptografado;",
+    "example_upstream_doh": "<0>DNS-sobre-HTTPS</0> criptografado;",
+    "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado (experimental);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>;",
+    "example_upstream_tcp": "DNS regular (através do TCP);",
     "all_lists_up_to_date_toast": "Todas as listas já estão atualizadas",
     "updated_upstream_dns_toast": "Servidores DNS primário salvos com sucesso",
     "dns_test_ok_toast": "Os servidores DNS especificados estão funcionando corretamente",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Use aspas duplas para uma pesquisa mais criteriosa",
     "query_log_retention_confirm": "Você tem certeza de que deseja alterar o arquivamento do registro de consulta? Se diminuir o valor de intervalo, alguns dados serão perdidos",
     "anonymize_client_ip": "Tornar anônimo o IP do cliente",
-    "anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros e estatísticas",
+    "anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros ou estatísticas.",
     "dns_config": "Configuração do servidor DNS",
     "dns_cache_config": "Configuração de cache DNS",
-    "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS",
+    "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS.",
     "blocking_mode": "Modo de bloqueio",
     "default": "Padrão",
     "nxdomain": "NXDOMAIN",
@@ -277,7 +277,7 @@
     "dns_over_quic": "DNS-sobre-QUIC",
     "client_id": "ID do cliente",
     "client_id_placeholder": "Digite o ID do cliente",
-    "client_id_desc": "Diferentes clientes podem ser identificados por um ID de cliente especial. <a>Aqui</a> você pode aprender mais sobre como identificar clientes.",
+    "client_id_desc": "Os clientes podem ser identificados por um ID de cliente especial. Saiba mais como identificar clientes <a>aqui</a>.",
     "download_mobileconfig_doh": "BAixar .mobileconfig para DNS-sobre-HTTPS",
     "download_mobileconfig_dot": "BAixar .mobileconfig para DNS-sobre-TLS",
     "download_mobileconfig": "Baixar arquivo de configuração",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Interface de escuta",
     "install_settings_port": "Porta",
     "install_settings_interface_link": "A interface web de administrador do AdGuard estará disponível nos seguintes endereços:",
-    "form_error_port": "Digite um numero de porta válida",
+    "form_error_port": "Digite um numero de porta válida.",
     "install_settings_dns": "Servidor DNS",
     "install_settings_dns_desc": "Você precisa configurar seu dispositivo ou roteador para usar o servidor DNS nos seguintes endereços:",
     "install_settings_all_interfaces": "Todas interfaces",
@@ -334,11 +334,11 @@
     "install_devices_router_list_4": "Em alguns tipos de roteador, um servidor DNS personalizado não pode ser configurado. Nesse caso, configurar o AdGuard Home como um <0>Servidor DHCP</0> pode ajudar. Caso contrário, você deve verificar o manual do roteador sobre como personalizar os servidores DNS em seu modelo de roteador específico.",
     "install_devices_windows_list_1": "Abra o Painel de Controle pelo Menu Iniciar ou pela Pesquisa do Windows.",
     "install_devices_windows_list_2": "Entre na categoria Rede e Internet e depois clique em Central de Rede e Compartilhamento.",
-    "install_devices_windows_list_3": "No lado esquerdo da janela clique em \"Alterar as configurações do adaptador\".",
-    "install_devices_windows_list_4": "Selecione sua atual conexão, clique nela com o botão direito do mouse e depois clique em Propriedades.",
+    "install_devices_windows_list_3": "No painel esquerdo, clique em \"Alterar configurações do adaptador\".",
+    "install_devices_windows_list_4": "Clique com o botão direito do mouse em sua conexão ativa e selecione Propriedades.",
     "install_devices_windows_list_5": "Procure na lista por \"Internet Protocol Version 4 (TCP/IP)\" (ou por IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), selecione e clique em Propriedades novamente.",
     "install_devices_windows_list_6": "Marque \"usar os seguintes endereços de servidor DNS\" e digite os endereços do servidores do AdGuard Home.",
-    "install_devices_macos_list_1": "Clique na ícone da Apple e depois em Preferências do Sistema.",
+    "install_devices_macos_list_1": "Clique no ícone da Apple e depois em Preferências do Sistema.",
     "install_devices_macos_list_2": "Clique em Rede.",
     "install_devices_macos_list_3": "Selecione a primeira conexão da lista e clique em Avançado.",
     "install_devices_macos_list_4": "Selecione a guia DNS e digite os endereços dos servidores do AdGuard Home.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Abrir painel",
     "install_saved": "Salvo com sucesso",
     "encryption_title": "Criptografia",
-    "encryption_desc": "Suporte a criptografia (HTTPS/TLS) para DNS e interface de administração web",
+    "encryption_desc": "Suporte a criptografia (HTTPS/TLS) para DNS e interface de administração web.",
     "encryption_config_saved": "Configuração de criptografia salva",
     "encryption_server": "Nome do servidor",
     "encryption_server_enter": "Digite seu nome de domínio",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home será acessível via HTTPS e também fornecerá o DNS-sobre-HTTPS no local '/dns-query'.",
     "encryption_dot": "Porta DNS-sobre-TLS",
     "encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home irá executar o servidor DNS-sobre- TSL nesta porta.",
-    "encryption_doq": "Porta DNS-sobre-QUIC",
+    "encryption_doq": "Porta DNS-sobre-QUIC (experimental)",
     "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. É experimental e pode não ser confiável. Além disso, não há muitos clientes que ofereçam suporte no momento.",
     "encryption_certificates": "Certificados",
     "encryption_certificates_desc": "Para usar criptografia, você precisa fornecer uma cadeia de certificados SSL válida para seu domínio. Você pode obter um certificado gratuito em <0> {{link}}</0> ou pode comprá-lo de uma das autoridades de certificação confiáveis.",
@@ -379,25 +379,25 @@
     "encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
     "encryption_enable_desc": "Se a criptografia estiver ativada, a interface administrativa do AdGuard Home funcionará em HTTPS, o servidor DNS irá capturar as solicitações por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.",
     "encryption_chain_valid": "Cadeia de chave válida.",
-    "encryption_chain_invalid": "A cadeia de certificado é inválida",
-    "encryption_key_valid": "Esta é uma chave privada {{type}} válida",
-    "encryption_key_invalid": "Esta é uma chave privada {{type}} inválida",
+    "encryption_chain_invalid": "A cadeia de certificado é inválida.",
+    "encryption_key_valid": "Esta é uma chave privada {{type}} válida.",
+    "encryption_key_invalid": "Esta é uma chave privada {{type}} inválida.",
     "encryption_subject": "Assunto",
     "encryption_issuer": "Emissor",
     "encryption_hostnames": "Nomes dos servidores",
     "encryption_reset": "Você tem certeza de que deseja redefinir a configuração de criptografia?",
     "topline_expiring_certificate": "Seu certificado SSL está prestes a expirar. Atualize suas <0>configurações de criptografia</]0>",
     "topline_expired_certificate": "Seu certificado SSL está expirado. Atualize suas <0>configurações de criptografia</0>",
-    "form_error_port_range": "Digite um número de porta entre 80 e 65535",
-    "form_error_port_unsafe": "Esta porta não é segura",
-    "form_error_equal": "Não deve ser igual",
-    "form_error_password": "Senhas não coincidem",
+    "form_error_port_range": "Digite um número de porta entre 80 e 65535.",
+    "form_error_port_unsafe": "Esta porta não é segura.",
+    "form_error_equal": "Não deve ser igual.",
+    "form_error_password": "Senhas não coincidem.",
     "reset_settings": "Redefinir configurações",
     "update_announcement": "AdGuard Home {{version}} está disponível!<0>Clique aqui</0> para mais informações.",
     "setup_guide": "Guia de configuração",
     "dns_addresses": "Endereços DNS",
     "dns_start": "O servidor DNS está iniciando",
-    "dns_status_error": "Ocorreu um erro ao obter o status do servidor DNS",
+    "dns_status_error": "Ocorreu um erro ao verificar o status do servidor DNS.",
     "down": "Caiu",
     "fix": "Corrigido",
     "dns_providers": "Aqui está uma <0>lista de provedores de DNS conhecidos</0> para escolher.",
@@ -405,8 +405,8 @@
     "update_failed": "A atualização automática falhou. Por favor, <a>siga estes passos</a> para atualizar manualmente.",
     "manual_update": "Por favor, <a>siga estes passos</a> para atualizar manualmente.",
     "processing_update": "Por favor, aguarde enquanto o AdGuard Home está sendo atualizado",
-    "clients_title": "Clientes",
-    "clients_desc": "Configure dispositivos conectados ao AdGuard",
+    "clients_title": "Clientes persistentes",
+    "clients_desc": "Configure registros de cliente persistentes para dispositivos conectados ao AdGuard Home.",
     "settings_global": "Global",
     "settings_custom": "Personalizado",
     "table_client": "Cliente",
@@ -417,7 +417,7 @@
     "client_edit": "Editar cliente",
     "client_identifier": "Identificador",
     "ip_address": "Endereço de IP",
-    "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). <0>Aqui</0> você pode aprender mais sobre como identificar clientes.",
+    "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). Saiba mais sobre como identificar clientes <0>aqui</0>.",
     "form_enter_ip": "Digite o endereço de IP",
     "form_enter_subnet_ip": "Digite um endereço IP na sub-rede \"{{cidr}}\"",
     "form_enter_mac": "Digite o endereço MAC",
@@ -432,14 +432,14 @@
     "clients_not_found": "Nenhum cliente foi encontrado",
     "client_confirm_delete": "Você tem certeza de que deseja excluir o cliente \"{{key}}\"?",
     "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?",
-    "auto_clients_title": "Clientes (tempo de execução)",
-    "auto_clients_desc": "Dados dos clientes que usam o AdGuard Home, que não são armazenados na configuração",
+    "auto_clients_title": "Clientes ativos",
+    "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home.",
     "access_title": "Configurações de acessos",
     "access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home.",
     "access_allowed_title": "Clientes permitidos",
-    "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se configurado, o AdGuard Home do aceitará solicitações apenas desses clientes.",
+    "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou <a>IDs de cliente</a>. Se esta lista tiver entradas, o AdGuard Home do aceitará solicitações apenas desses clientes.",
     "access_disallowed_title": "Clientes não permitidos",
-    "access_disallowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se configurado, o AdGuard Home descartará as solicitações desses clientes. Se clientes permitidos estiverem configurados, este campo será ignorado.",
+    "access_disallowed_desc": "Uma lista de CIDRs, endereços IP ou <a>IDs de cliente</a>. Se essa lista tiver entradas, o AdGuard Home descartará as solicitações desses clientes. Este campo é ignorado se houver entradas em clientes permitidos.",
     "access_blocked_title": "Domínios bloqueados",
     "access_blocked_desc": "Não deve ser confundido com filtros. O AdGuard Home elimina as consultas DNS que correspondem a esses domínios, e essas consultas nem aparecem no registro de consultas. Você pode especificar nomes de domínio exatos, caracteres curinga ou regras de filtro de URL, por exemplo \"exemplo.org\", \"*.exemplo.org\", ou \"||exemplo.org^\" correspondentemente.",
     "access_settings_saved": "Configurações de acesso foram salvas com sucesso",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Reescritas de DNS",
     "form_domain": "Digite o nome do domínio ou wildcard",
     "form_answer": "Digite o endereço de IP ou nome de domínio",
-    "form_error_domain_format": "Formato de domínio inválido",
-    "form_error_answer_format": "Formato de resposta inválido",
+    "form_error_domain_format": "Formato de domínio inválido.",
+    "form_error_answer_format": "Formato de resposta inválido.",
     "configure": "Configurar",
     "main_settings": "Configurações principais",
     "block_services": "Bloquear serviços específicos",
@@ -507,7 +507,7 @@
     "filter_updated": "O filtro atualizado com sucesso",
     "statistics_configuration": "Configurações de estatísticas",
     "statistics_retention": "Permanência das estatísticas",
-    "statistics_retention_desc": "Se você diminuir o valor do intervalo, alguns dados serão perdidos",
+    "statistics_retention_desc": "Se você diminuir o valor do intervalo, alguns dados serão perdidos.",
     "statistics_clear": " Limpar estatísticas",
     "statistics_clear_confirm": "Você tem certeza de que deseja limpar as estatísticas?",
     "statistics_retention_confirm": "Você tem certeza que quer alterar o arquivamento das estatísticas? Se diminuir o valor do intervalo, alguns dados serão perdidos",
@@ -532,7 +532,7 @@
     "netname": "Nome da rede",
     "network": "Rede",
     "descr": "Descrição",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Saiba mais</0> sobre como criar as suas próprias listas negras de servidores.",
     "blocked_by_response": "Bloqueado por CNAME ou IP na resposta",
     "blocked_by_cname_or_ip": "Bloqueado por CNAME ou IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Ele irá realizar estas tarefas: <0>Desativar sistema DNSStubListener</0> <0>Definir endereço do servidor DNS para 127.0.0.1</0> <0>Substituir o alvo simbólico do link /etc/resolv.conf para /run/systemd/resolv.conf</0> <0>Parar DNSStubListener (recarregar serviço resolvido pelo sistema)</0>",
     "autofix_warning_result": "Como resultado, todos as solicitações DNS do seu sistema serão processadas pelo AdGuard Home por padrão.",
     "tags_title": "Marcadores",
-    "tags_desc": "Você pode selecionar as tags que correspondem ao cliente. As tags podem ser incluídas nas regras de filtragem e permitir que você as aplique com mais precisão. <0>Saiba mais</0>",
+    "tags_desc": "Você pode selecionar tags que correspondam ao cliente. Inclua tags nas regras de filtragem para aplicá-las com mais precisão. <0>Saber mais</0>.",
     "form_select_tags": "Selecione as tags do cliente",
     "check_title": "Verifique a filtragem",
-    "check_desc": "Verificar se o nome do host está sendo filtrado",
+    "check_desc": "Verificar se um nome do host está sendo filtrado.",
     "check": "Verificar",
     "form_enter_host": "Digite o nome do host",
     "filtered_custom_rules": "Filtrado pelas regras de filtragem personalizadas",
@@ -598,15 +598,15 @@
     "blocklist": "Lista de bloqueio",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Tamanho do cache",
-    "cache_size_desc": "Tamanho do cache do DNS (em bytes)",
+    "cache_size_desc": "Tamanho do cache do DNS (em bytes).",
     "cache_ttl_min_override": "Sobrepor o TTL mínimo",
     "cache_ttl_max_override": "Sobrepor o TTL máximo",
     "enter_cache_size": "Digite o tamanho do cache (bytes)",
     "enter_cache_ttl_min_override": "Digite o TTL máximo (segundos)",
     "enter_cache_ttl_max_override": "Digite o TTL máximo (segundos)",
-    "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS",
-    "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS",
-    "ttl_cache_validation": "O valor TTL mínimo do cache deve ser menor ou igual ao valor máximo",
+    "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS.",
+    "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS.",
+    "ttl_cache_validation": "O substituto mínimo de cache TTL deve ser menor ou igual ao máximo.",
     "cache_optimistic": "Cache otimista",
     "cache_optimistic_desc": "Faz o AdGuard Home responder a partir do cache mesmo quando as entradas expirarem e também tenta atualizá-las.",
     "filter_category_general": "Geral",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "O AdGuard Home descartará todas as consultas DNS deste cliente.",
     "filter_allowlist": "AVISO: Esta ação também excluirá a regra \"{{disallowed_rule}}\" da lista de clientes permitidos.",
     "last_rule_in_allowlist": "Não é possível desautorizar este cliente porque excluir a regra \"{{disallowed_rule}}\" DESATIVARÁ a lista de \"Clientes permitidos\".",
-    "experimental": "Experimental",
     "use_saved_key": "Use a chave salva anteriormente",
     "parental_control": "Controle parental",
     "safe_browsing": "Navegação segura",
-    "served_from_cache": "{{value}} <i>(servido do cache)</i>"
+    "served_from_cache": "{{value}} <i>(servido do cache)</i>",
+    "form_error_password_length": "A senha deve ter pelo menos {{value}} caracteres."
 }
diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json
index 0405f5c1..068ebc03 100644
--- a/client/src/__locales/pt-pt.json
+++ b/client/src/__locales/pt-pt.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Definições do cliente",
     "example_upstream_reserved": "Podes especificar o DNS primário <0>para domínio(s) especifico(s)</0>",
-    "example_upstream_comment": "Tu podes especificar o comentário",
+    "example_upstream_comment": "um comentário.",
     "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS",
     "parallel_requests": "Solicitações paralelas",
     "load_balancing": "Balanceamento de carga",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Definições DHCP guardadas com sucesso",
     "dhcp_ipv4_settings": "Definições DHCP IPv4",
     "dhcp_ipv6_settings": "Definições DHCP IPv6",
-    "form_error_required": "Campo obrigatório",
-    "form_error_ip4_format": "Endereço de IPv4 inválido",
-    "form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido",
-    "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido",
-    "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido",
-    "form_error_ip6_format": "Endereço de IPv6 inválido",
-    "form_error_ip_format": "Endereço de IP inválido",
-    "form_error_mac_format": "Endereço de MAC inválido",
+    "form_error_required": "Campo obrigatório.",
+    "form_error_ip4_format": "Endereço de IPv4 inválido.",
+    "form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido.",
+    "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido.",
+    "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido.",
+    "form_error_ip6_format": "Endereço de IPv6 inválido.",
+    "form_error_ip_format": "Endereço de IP inválido.",
+    "form_error_mac_format": "Endereço de MAC inválido.",
     "form_error_client_id_format": "O ID do cliente deve conter apenas números, letras minúsculas e hifens",
-    "form_error_server_name": "Nome de servidor inválido",
-    "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
-    "form_error_positive": "Deve ser maior que 0",
-    "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Deve ser inferior ao início do intervalo",
-    "greater_range_start_error": "Deve ser maior que o início do intervalo",
-    "greater_range_end_error": "Deve ser maior que o fim do intervalo",
-    "subnet_error": "Os endereços devem estar em uma sub-rede",
-    "gateway_or_subnet_invalid": "Máscara de sub-rede inválida",
+    "form_error_server_name": "Nome de servidor inválido.",
+    "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\".",
+    "form_error_positive": "Deve ser maior que 0.",
+    "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Deve ser inferior ao início do intervalo.",
+    "greater_range_start_error": "Deve ser maior que o início do intervalo.",
+    "greater_range_end_error": "Deve ser maior que o fim do intervalo.",
+    "subnet_error": "Os endereços devem estar em uma sub-rede.",
+    "gateway_or_subnet_invalid": "Máscara de sub-rede inválida.",
     "dhcp_form_gateway_input": "IP do gateway",
     "dhcp_form_subnet_input": "Máscara de sub-rede",
     "dhcp_form_range_title": "Faixa de endereços IP",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Escolher as listas de permissões",
     "enter_valid_blocklist": "Digite uma URL válida para a lista de bloqueio.",
     "enter_valid_allowlist": "Digite uma URL válida para a lista de permissões.",
-    "form_error_url_format": "Formato da URL inválida",
-    "form_error_url_or_path_format": "URL ou local da lista inválida",
+    "form_error_url_format": "Formato da URL inválida.",
+    "form_error_url_or_path_format": "URL ou local da lista inválida.",
     "custom_filter_rules": "Regras de filtragem personalizadas",
     "custom_filter_rules_hint": "Insira uma regra por linha. Pode usar regras de bloqueio de anúncios ou a sintaxe de ficheiros de hosts.",
     "system_host_files": "Arquivos hosts do sistema",
     "examples_title": "Exemplos",
-    "example_meaning_filter_block": "bloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios",
-    "example_meaning_filter_whitelist": "desbloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios",
-    "example_meaning_host_block": "O AdGuard Home irá retornar o endereço 127.0.0.1 para o domínio exemplo.org (excepto os seus subdomínios).",
-    "example_comment": "! Aqui vai um comentário",
-    "example_comment_meaning": "apenas um comentário",
-    "example_comment_hash": "# Também um comentário",
-    "example_regex_meaning": "bloquear o acesso aos domínios que correspondam à expressão regular especificada",
+    "example_meaning_filter_block": "bloqueia o acesso ao exemplo.org e a todos os seus subdomínios;",
+    "example_meaning_filter_whitelist": "desbloqueia o acesso ao exemplo.org e a todos os seus subdomínios;",
+    "example_meaning_host_block": "retorna o endereço 127.0.0.1 para o exemplo.org (exceto seus subdomínios);",
+    "example_comment": "! Aqui vai um comentário.",
+    "example_comment_meaning": "apenas um comentário;",
+    "example_comment_hash": "# Também um comentário.",
+    "example_regex_meaning": "bloquear o acesso aos domínios que correspondam à expressão regular especificada.",
     "example_upstream_regular": "DNS regular (através do UDP)",
-    "example_upstream_dot": "<0>DNS-sobre-TLS</0> criptografado",
-    "example_upstream_doh": "<0>DNS-sobre-HTTPS</0> criptografado",
-    "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado",
-    "example_upstream_sdns": "pode usar <0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>",
-    "example_upstream_tcp": "dNS regular (através do TCP)",
+    "example_upstream_dot": "<0>DNS-sobre-TLS</0> criptografado;",
+    "example_upstream_doh": "<0>DNS-sobre-HTTPS</0> criptografado;",
+    "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado (experimental);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>;",
+    "example_upstream_tcp": "DNS regular (através do TCP);",
     "all_lists_up_to_date_toast": "Todas as listas já estão atualizadas",
     "updated_upstream_dns_toast": "Servidores DNS primário guardados com sucesso",
     "dns_test_ok_toast": "Os servidores DNS especificados estão a funcionar corretamente",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Usar aspas duplas para uma pesquisa rigorosa",
     "query_log_retention_confirm": "Tem a certeza de que deseja alterar a retenção do registo de consulta? Se diminuir o valor do intervalo, alguns dados serão perdidos",
     "anonymize_client_ip": "Tornar anónimo o IP do cliente",
-    "anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros e estatísticas",
+    "anonymize_client_ip_desc": "Não gurda o endereço de IP completo do cliente em registros ou estatísticas.",
     "dns_config": "Definição do servidor DNS",
     "dns_cache_config": "Definição de cache DNS",
-    "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS",
+    "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS.",
     "blocking_mode": "Modo de bloqueio",
     "default": "Predefinido",
     "nxdomain": "NXDOMAIN",
@@ -277,7 +277,7 @@
     "dns_over_quic": "DNS-sobre-QUIC",
     "client_id": "ID do cliente",
     "client_id_placeholder": "Insira o ID do cliente",
-    "client_id_desc": "Diferentes clientes podem ser identificados por um ID de cliente especial. <a>Aqui</a> você pode aprender mais sobre como identificar clientes.",
+    "client_id_desc": "Os clientes podem ser identificados por um ID de cliente especial. Saiba mais como identificar clientes <a>aqui</a>.",
     "download_mobileconfig_doh": "Transferir .mobileconfig para DNS-sobre-HTTPS",
     "download_mobileconfig_dot": "Transferir .mobileconfig para DNS-sobre-TLS",
     "download_mobileconfig": "Transferir ficheiro de configuração",
@@ -334,11 +334,11 @@
     "install_devices_router_list_4": "Em alguns tipos de roteador, um servidor DNS personalizado não pode ser configurado. Nesse caso, configurar o AdGuard Home como um <0>Servidor DHCP</0> pode ajudar. Caso contrário, tu deve verificar o manual do router sobre como personalizar os servidores DNS em seu modelo de router específico.",
     "install_devices_windows_list_1": "Abra o Painel de Controlo através do Menu Iniciar ou pela Pesquisa do Windows.",
     "install_devices_windows_list_2": "Entre na categoria Rede e Internet e depois clique em Central de Rede e Partilha.",
-    "install_devices_windows_list_3": "No lado esquerdo da janela clique em \"Alterar as definições do adaptador\".",
-    "install_devices_windows_list_4": "Selecione sua atual ligação, clique nela com o botão direito do rato e depois clique em Propriedades.",
+    "install_devices_windows_list_3": "No painel esquerdo, clique em \"Alterar configurações do adaptador\".",
+    "install_devices_windows_list_4": "Clique com o botão direito do mouse em sua conexão ativa e selecione Propriedades.",
     "install_devices_windows_list_5": "Procure na lista por \"Internet Protocol Version 4 (TCP/IP)\" (ou por IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), selecione e clique em Propriedades novamente.",
     "install_devices_windows_list_6": "Marque \"Usar os seguintes endereços de servidor DNS\" e insira os endereços do servidores do AdGuard Home.",
-    "install_devices_macos_list_1": "Clique na ícone da Apple e depois em Preferências do Sistema.",
+    "install_devices_macos_list_1": "Clique no ícone da Apple e depois em Preferências do Sistema.",
     "install_devices_macos_list_2": "Clique em Rede.",
     "install_devices_macos_list_3": "Selecione a primeira ligação da lista e clique em Avançado.",
     "install_devices_macos_list_4": "Selecione a guia DNS e insira os endereços dos servidores do AdGuard Home.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Abrir Painel",
     "install_saved": "Guardado com sucesso",
     "encryption_title": "Encriptação",
-    "encryption_desc": "Suporta a criptografia (HTTPS/TLS) para DNS e interface de administração web",
+    "encryption_desc": "Suporta a criptografia (HTTPS/TLS) para DNS e interface de administração web.",
     "encryption_config_saved": "Definição de criptografia guardada",
     "encryption_server": "Nome do servidor",
     "encryption_server_enter": "Insira o seu nome de domínio",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home será acessível via HTTPS e também fornecerá o DNS-sobre-HTTPS no local '/dns-query'.",
     "encryption_dot": "Porta DNS-sobre-TLS",
     "encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home irá executar o servidor DNS-sobre- TSL nesta porta.",
-    "encryption_doq": "Porta DNS-sobre-QUIC",
+    "encryption_doq": "Porta DNS-sobre-QUIC (experimental)",
     "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. É experimental e pode não ser confiável. Além disso, não há demasiados clientes que ofereçam suporte no momento.",
     "encryption_certificates": "Certificados",
     "encryption_certificates_desc": "Para usar criptografia, precisa de fornecer uma cadeia de certificados SSL válida para o seu domínio. Pode obter um certificado gratuito em <0> {{link}}</0> ou pode comprá-lo numa das autoridades de certificação confiáveis.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Copie/cole aqui a chave privada codificada em PEM para o seu certificado.",
     "encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
     "encryption_enable_desc": "Se a criptografia estiver ativada, a interface administrativa do AdGuard Home funcionará em HTTPS, o servidor DNS irá capturar as solicitações por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.",
-    "encryption_chain_valid": "Cadeia de certificado válida",
-    "encryption_chain_invalid": "A cadeia de certificado é inválida",
-    "encryption_key_valid": "Esta é uma chave privada {{type}} válida",
-    "encryption_key_invalid": "Esta é uma chave privada {{type}} inválida",
+    "encryption_chain_valid": "Cadeia de certificado válida.",
+    "encryption_chain_invalid": "A cadeia de certificado é inválida.",
+    "encryption_key_valid": "Esta é uma chave privada {{type}} válida.",
+    "encryption_key_invalid": "Esta é uma chave privada {{type}} inválida.",
     "encryption_subject": "Assunto",
     "encryption_issuer": "Emissor",
     "encryption_hostnames": "Nomes dos servidores",
     "encryption_reset": "Tem a certeza de que deseja repor a definição de criptografia?",
     "topline_expiring_certificate": "O seu certificado SSL está prestes a expirar. Atualize as suas <0>definições de criptografia</0>.",
     "topline_expired_certificate": "O seu certificado SSL está expirado. Atualize as suas <0>definições de criptografia</0>.",
-    "form_error_port_range": "Digite um numero de porta entre 80 e 65535",
-    "form_error_port_unsafe": "Esta porta não é segura",
-    "form_error_equal": "Não deve ser igual",
-    "form_error_password": "As palavras-passe não coincidem",
+    "form_error_port_range": "Digite um numero de porta entre 80 e 65535.",
+    "form_error_port_unsafe": "Esta porta não é segura.",
+    "form_error_equal": "Não deve ser igual.",
+    "form_error_password": "As palavras-passe não coincidem.",
     "reset_settings": "Repor definições",
     "update_announcement": "AdGuard Home {{version}} está disponível!<0>Clique aqui</0> para mais informações.",
     "setup_guide": "Guia de instalação",
     "dns_addresses": "Endereços DNS",
     "dns_start": "O servidor DNS está a iniciar",
-    "dns_status_error": "Erro ao obter o estado do servidor DNS",
+    "dns_status_error": "Ocorreu um erro ao verificar o estado do servidor DNS.",
     "down": "Caiu",
     "fix": "Corrigido",
     "dns_providers": "Aqui está uma <0>lista de provedores de DNS conhecidos</0> para escolher.",
@@ -405,8 +405,8 @@
     "update_failed": "A atualização automática falhou. Por favor, <a>siga estes passos</a> para atualizar manualmente.",
     "manual_update": "Por favor, <a>siga estes passos</a> para atualizar manualmente.",
     "processing_update": "Por favor espere, o AdGuard Home está a atualizar-se",
-    "clients_title": "Clientes",
-    "clients_desc": "Configure os dispositivos ligados ao AdGuard",
+    "clients_title": "Clientes persistentes",
+    "clients_desc": "Configure registros de cliente persistentes para dispositivos conectados ao AdGuard Home.",
     "settings_global": "Global",
     "settings_custom": "Personalizar",
     "table_client": "Cliente",
@@ -417,7 +417,7 @@
     "client_edit": "Editar cliente",
     "client_identifier": "Identificador",
     "ip_address": "Endereço de IP",
-    "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). <0>Aqui</0> você pode aprender mais sobre como identificar clientes.",
+    "client_identifier_desc": "Os clientes podem ser identificados pelo endereço IP, CIDR, Endereço MAC ou um ID de cliente especial (pode ser usado para DoT/DoH/DoQ). Saiba mais sobre como identificar clientes <0>aqui</0>.",
     "form_enter_ip": "Insira IP",
     "form_enter_subnet_ip": "Digite um endereço IP na sub-rede \"{{cidr}}\"",
     "form_enter_mac": "Insira o endereço MAC",
@@ -432,14 +432,14 @@
     "clients_not_found": "Nenhum cliente foi encontrado",
     "client_confirm_delete": "Tem a certeza de que deseja excluir o cliente \"{{key}}\"?",
     "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?",
-    "auto_clients_title": "Clientes (tempo de execução)",
-    "auto_clients_desc": "Dados dos clientes que usam o AdGuard Home, que não são armazenados na definição",
+    "auto_clients_title": "Clientes ativos",
+    "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home.",
     "access_title": "Definições de acesso",
     "access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home.",
     "access_allowed_title": "Clientes permitidos",
-    "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se configurado, o AdGuard Home do aceitará solicitações apenas desses clientes.",
+    "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou <a>IDs de cliente</a>. Se esta lista tiver entradas, o AdGuard Home do aceitará solicitações apenas desses clientes.",
     "access_disallowed_title": "Clientes não permitidos",
-    "access_disallowed_desc": "Uma lista de CIDRs, endereços IP ou IDs de cliente. Se configurado, o AdGuard Home descartará as solicitações desses clientes. Se clientes permitidos estiverem configurados, este campo será ignorado.",
+    "access_disallowed_desc": "Uma lista de CIDRs, endereços IP ou <a>IDs de cliente</a>. Se essa lista tiver entradas, o AdGuard Home descartará as solicitações desses clientes. Este campo é ignorado se houver entradas em clientes permitidos.",
     "access_blocked_title": "Domínios bloqueados",
     "access_blocked_desc": "Não deve ser confundido com filtros. O AdGuard Home elimina as consultas DNS que correspondem a esses domínios, e essas consultas nem aparecem no registro de consultas. Você pode especificar nomes de domínio exatos, caracteres curinga ou regras de filtro de URL, por exemplo \"exemplo.org\", \"*.exemplo.org\", ou \"||exemplo.org^\" correspondentemente.",
     "access_settings_saved": "Definições de acesso foram guardadas com sucesso",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Reescritas de DNS",
     "form_domain": "Inserir domínio",
     "form_answer": "Insira o endereço de IP ou nome de domínio",
-    "form_error_domain_format": "Formato de domínio inválido",
-    "form_error_answer_format": "Formato de resposta inválido",
+    "form_error_domain_format": "Formato de domínio inválido.",
+    "form_error_answer_format": "Formato de resposta inválido.",
     "configure": "Configurar",
     "main_settings": "Definições principais",
     "block_services": "Bloquear serviços específicos",
@@ -507,7 +507,7 @@
     "filter_updated": "O filtro atualizado com sucesso",
     "statistics_configuration": "Definição das estatísticas",
     "statistics_retention": "Retenção de estatísticas",
-    "statistics_retention_desc": "Se diminuir o valor do intervalo, alguns dados serão perdidos",
+    "statistics_retention_desc": "Se diminuir o valor do intervalo, alguns dados serão perdidos.",
     "statistics_clear": "Limpar estatísticas",
     "statistics_clear_confirm": "Tem a certeza de que deseja limpar as estatísticas?",
     "statistics_retention_confirm": "Tem a certeza que quer alterar a retenção de estatísticas? Se diminuir o valor do intervalo, alguns dados serão perdidos",
@@ -532,7 +532,7 @@
     "netname": "Nome da rede",
     "network": "Rede",
     "descr": "Descrição",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Saiba mais</0>sobre como criar as suas próprias listas negras de servidores.",
     "blocked_by_response": "Bloqueado por CNAME ou IP em resposta",
     "blocked_by_cname_or_ip": "Bloqueado por CNAME ou IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Irá realizar estas tarefas: <0>Desativar sistema DNSStubListener</0> <0>Definir endereço do servidor DNS para 127.0.0.1</0> <0>Substituir o alvo simbólico do link /etc/resolv.conf para /run/systemd/resolv.conf</0> <0>Parar DNSStubListener (recarregar serviço resolvido pelo sistema)</0>",
     "autofix_warning_result": "Como resultado, todos as solicitações DNS do seu sistema serão processadas pelo AdGuard Home por predefinição.",
     "tags_title": "Etiquetas",
-    "tags_desc": "Tu podes selecionar as etiquetas que correspondem ao cliente. As etiquetas podem ser incluídas nas regras de filtragem e permitir que tu as aplique com mais precisão. <0>Saiba mais</0>",
+    "tags_desc": "Você pode selecionar tags que correspondam ao cliente. Inclua tags nas regras de filtragem para aplicá-las com mais precisão. <0>Saber mais</0>.",
     "form_select_tags": "Selecione as tags do cliente",
     "check_title": "Verifique a filtragem",
-    "check_desc": "Verificar se o nome do host está sendo filtrado",
+    "check_desc": "Verificar se um nome do host está sendo filtrado.",
     "check": "Verificar",
     "form_enter_host": "Insira o hostname",
     "filtered_custom_rules": "Filtrado pelas regras de filtragem personalizadas",
@@ -598,15 +598,15 @@
     "blocklist": "Lista de bloqueio",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Tamanho do cache",
-    "cache_size_desc": "Tamanho do cache do DNS (em bytes)",
+    "cache_size_desc": "Tamanho do cache do DNS (em bytes).",
     "cache_ttl_min_override": "Sobrepor o TTL mínimo",
     "cache_ttl_max_override": "Sobrepor o TTL máximo",
     "enter_cache_size": "Digite o tamanho do cache (bytes)",
     "enter_cache_ttl_min_override": "Digite o TTL máximo (segundos)",
     "enter_cache_ttl_max_override": "Digite o TTL máximo (segundos)",
-    "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS",
-    "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS",
-    "ttl_cache_validation": "O valor TTL mínimo do cache deve ser menor ou igual ao valor máximo",
+    "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS.",
+    "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS.",
+    "ttl_cache_validation": "O substituto mínimo de cache TTL deve ser menor ou igual ao máximo.",
     "cache_optimistic": "Cache otimista",
     "cache_optimistic_desc": "Faz o AdGuard Home responder a partir do cache mesmo quando as entradas expirarem e também tenta atualizá-las.",
     "filter_category_general": "Geral",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "O AdGuard Home descartará todas as consultas DNS deste cliente.",
     "filter_allowlist": "AVISO: Esta ação também excluirá a regra \"{{disallowed_rule}}\" da lista de clientes permitidos.",
     "last_rule_in_allowlist": "Não é possível desautorizar este cliente porque excluir a regra \"{{disallowed_rule}}\" DESATIVARÁ a lista de \"Clientes permitidos\".",
-    "experimental": "Experimental",
     "use_saved_key": "Use a chave guardada anteriormente",
     "parental_control": "Controlo parental",
     "safe_browsing": "Navegação segura",
-    "served_from_cache": "{{value}} <i>(servido do cache)</i>"
+    "served_from_cache": "{{value}} <i>(servido do cache)</i>",
+    "form_error_password_length": "A palavra-passe deve ter pelo menos {{value}} caracteres."
 }
diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json
index 0e959ee7..c4b304b2 100644
--- a/client/src/__locales/ro.json
+++ b/client/src/__locales/ro.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Setări client",
-    "example_upstream_reserved": "Puteți specifica un DNS în amonte <0>pentru domeniul (domeniile) specific(e)</0>",
-    "example_upstream_comment": "Puteți specifica un comentariu",
+    "example_upstream_reserved": "un flux în amonte <0>pentru domenii specifice</0>;",
+    "example_upstream_comment": "un comentariu.",
     "upstream_parallel": "Folosiți interogări paralele pentru a accelera rezolvarea, interogând simultan toate serverele în amonte.",
     "parallel_requests": "Solicitări paralele",
     "load_balancing": "Echilibrare-sarcini",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Configurare DHCP salvată cu succes",
     "dhcp_ipv4_settings": "Setări DHCP IPv4",
     "dhcp_ipv6_settings": "Setări DHCP IPv6",
-    "form_error_required": "Câmp necesar",
-    "form_error_ip4_format": "Adresă IPv4 nevalidă",
-    "form_error_ip4_range_start_format": "Adresă IPv4 de început al intervalului nevalidă",
-    "form_error_ip4_range_end_format": "Adresa IPv4 de sfârșit al intervalului nevalidă",
-    "form_error_ip4_gateway_format": "Adresă IPv4 a gateway-ului nevalidă",
-    "form_error_ip6_format": "Adresa IPv6 nevalidă",
-    "form_error_ip_format": "Adresă IP nevalidă",
-    "form_error_mac_format": "Adresă MAC nevalidă",
-    "form_error_client_id_format": "ID-ul clientului trebuie să conțină numai numere, litere minuscule și liniuțe.",
-    "form_error_server_name": "Nume de server nevalid",
-    "form_error_subnet": "Subrețeaua „{{cidr}}” nu conține adresa IP „{{ip}}”",
-    "form_error_positive": "Trebuie să fie mai mare de 0",
-    "out_of_range_error": "Trebuie să fie în afara intervalului „{{start}}”-„{{end}}”",
-    "lower_range_start_error": "Trebuie să fie mai mică decât începutul intervalului",
-    "greater_range_start_error": "Trebuie să fie mai mare decât începutul intervalului",
-    "greater_range_end_error": "Trebuie să fie mai mare decât sfârșitul intervalului",
-    "subnet_error": "Adresele trebuie să fie în aceeași subrețea",
-    "gateway_or_subnet_invalid": "Mască de subrețea nevalidă",
+    "form_error_required": "Câmp obligatoriu.",
+    "form_error_ip4_format": "Adresă IPv4 nevalidă.",
+    "form_error_ip4_range_start_format": "Adresă IPv4 nevalidă pentru începutul intervalului.",
+    "form_error_ip4_range_end_format": "Adresă IPv4 nevalidă a sfârșitului intervalului.",
+    "form_error_ip4_gateway_format": "Adresă IPv4 nevalidă a gateway-ului.",
+    "form_error_ip6_format": "Adresa IPv6 nevalidă.",
+    "form_error_ip_format": "Adresă IP nevalidă.",
+    "form_error_mac_format": "Adresă MAC nevalidă.",
+    "form_error_client_id_format": "ClientID-ul trebuie să conțină numai numere, litere minuscule și cratime.",
+    "form_error_server_name": "Nume de server nevalid.",
+    "form_error_subnet": "Subrețeaua „{{cidr}}” nu conține adresa IP „{{ip}}”.",
+    "form_error_positive": "Trebuie să fie mai mare de 0.",
+    "out_of_range_error": "Trebuie să fie în afara intervalului „{{start}}”-„{{end}}”.",
+    "lower_range_start_error": "Trebuie să fie mai mică decât începutul intervalului.",
+    "greater_range_start_error": "Trebuie să fie mai mare decât începutul intervalului.",
+    "greater_range_end_error": "Trebuie să fie mai mare decât sfârșitul intervalului.",
+    "subnet_error": "Adresele trebuie să fie în aceeași subrețea.",
+    "gateway_or_subnet_invalid": "Mască de subrețea nevalidă.",
     "dhcp_form_gateway_input": "IP Gateway",
     "dhcp_form_subnet_input": "Mască subnet",
     "dhcp_form_range_title": "Interval de adrese IP",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Selectați liste de autorizări",
     "enter_valid_blocklist": "Introduceți un URL valid pentru blocare.",
     "enter_valid_allowlist": "Introduceți un URL valid pentru autorizare.",
-    "form_error_url_format": "Format URL invalid",
-    "form_error_url_or_path_format": "Invalid URL sau o cale absolută a listei",
+    "form_error_url_format": "Format URL nevalid.",
+    "form_error_url_or_path_format": "URL nevalabil sau calea absolută a listei.",
     "custom_filter_rules": "Reguli de filtrare personalizate",
     "custom_filter_rules_hint": "Introduceți o regulă pe linie. Puteți utiliza reguli de blocare sau sintaxa de fișiere hosts.",
     "system_host_files": "Fișiere de sistem hosts",
     "examples_title": "Exemple",
-    "example_meaning_filter_block": "blochează accesul la domeniul exemplu.org și la toate subdomeniile sale",
-    "example_meaning_filter_whitelist": "deblochează accesul la domeniul exemplu.org și la toate subdomeniile sale",
-    "example_meaning_host_block": "AdGuard Home va returna acum adresa 127.0.0.1 pentru domeniul exemplu.org (dar nu și subdomeniile sale).",
-    "example_comment": "! Iată cum se adăugă o descriere",
-    "example_comment_meaning": "comentariu",
-    "example_comment_hash": "# Astfel putem lăsa comentarii",
-    "example_regex_meaning": "blocare acces la domenii care corespund expresiei obișnuite specificate",
-    "example_upstream_regular": "DNS clasic (over UDP)",
-    "example_upstream_dot": "<0>DNS-over-TLS</0> criptat",
-    "example_upstream_doh": "<0>DNS-over-HTTPS</0> criptat",
-    "example_upstream_doq": "<0>DNS-over-QUIC</0> criptat",
-    "example_upstream_sdns": "puteți utiliza <0>DNS Stamps</0> pentru rezolvere <1>DNSCrypt</1> sau <2>DNS-over-HTTPS</2>",
-    "example_upstream_tcp": "DNS clasic (over TCP)",
+    "example_meaning_filter_block": "blochează accesul la domeniul exemplu.org și la toate subdomeniile sale;",
+    "example_meaning_filter_whitelist": "deblochează accesul la domeniul exemplu.org și la toate subdomeniile sale;",
+    "example_meaning_host_block": "răspunde cu 127.0.0.1 pentru domeniul exemplu.org (dar nu și pentru subdomeniile sale);",
+    "example_comment": "! Aici urmează un comentariu.",
+    "example_comment_meaning": "doar un comentariu;",
+    "example_comment_hash": "# De asemenea, un comentariu.",
+    "example_regex_meaning": "blochează accesul la domeniile care corespund expresiei regulate specificate.",
+    "example_upstream_regular": "DNS clasic (over UDP);",
+    "example_upstream_dot": "<0>DNS-over-TLS</0> criptat;",
+    "example_upstream_doh": "<0>DNS-over-HTTPS</0> criptat;",
+    "example_upstream_doq": "<0>DNS-over-QUIC</0> criptat (experimental);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> pentru <1>DNSCrypt</1> sau rezolvere <2>DNS-over-HTTPS</2>;",
+    "example_upstream_tcp": "DNS clasic (over TCP);",
     "all_lists_up_to_date_toast": "Toate listele sunt deja la zi",
     "updated_upstream_dns_toast": "Serverele din amonte au fost salvate cu succes",
     "dns_test_ok_toast": "Serverele DNS specificate funcționează corect",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Utilizați ghilimele duble pentru căutare strictă",
     "query_log_retention_confirm": "Sunteți sigur că doriți să schimbați retenția jurnalului de interogare? Reducând valoarea intervalului, unele date vor fi pierdute",
     "anonymize_client_ip": "Anonimizare client IP",
-    "anonymize_client_ip_desc": "Nu salvați adresa IP completă a clientului în jurnale și statistici",
+    "anonymize_client_ip_desc": "Nu salvați adresa IP completă a clientului în jurnale și statistici.",
     "dns_config": "Configurația serverului DNS",
     "dns_cache_config": "Configurare cache DNS",
-    "dns_cache_config_desc": "Aici puteți configura cache-ul DNS",
+    "dns_cache_config_desc": "Aici puteți configura cache-ul DNS.",
     "blocking_mode": "Modul de blocare",
     "default": "Implicit",
     "nxdomain": "NXDOMAIN",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "ID Client",
-    "client_id_placeholder": "Introduceți ID client",
-    "client_id_desc": "Diferiți clienți pot fi identificați printr-un ID special al clientului. <a>Aici</a> puteți afla mai multe despre cum să identificați clienții.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Introduceți un ClientID",
+    "client_id_desc": "Clienții pot fi identificați prin ClientID. Aflați mai multe despre cum să identificați clienții <a>aici</a>.",
     "download_mobileconfig_doh": "Descărcați .mobileconfig pentru DNS-over-HTTPS",
     "download_mobileconfig_dot": "Descărcați .mobileconfig pentru DNS-over-TLS",
     "download_mobileconfig": "Descărcați fișierul de configurare",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Interfață de ascultare",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Interfața dvs. de administrare AdGuard Home va fi disponibilă pe următoarele adrese:",
-    "form_error_port": "Introduceți un număr de port valid",
+    "form_error_port": "Introduceți un număr de port valid.",
     "install_settings_dns": "Server DNS",
     "install_settings_dns_desc": "Va trebui să configurați aparatele sau routerul pentru a utiliza serverul DNS pe următoarele adrese:",
     "install_settings_all_interfaces": "Toate interfețele",
@@ -334,12 +334,12 @@
     "install_devices_router_list_4": "Unele tipuri de routere, nu permit configurarea unui server DNS personalizat. În acest caz, configurarea AdGuard Home ca un <0>server DHCP</0>vă poate ajuta. Dacă nu, ar trebui verificat manualul routerului dvs. specific, ca să aflați cum se pot personaliza serverele DNS.",
     "install_devices_windows_list_1": "Deschideți panoul de control prin meniul Start sau căutare Windows.",
     "install_devices_windows_list_2": "Accesați categoria \"Rețea și Internet\", apoi la \"Centrul de Rețea și Partajare\".",
-    "install_devices_windows_list_3": "În partea stângă a ecranului găsiți \"Schimbare setări adaptor\" și clicați pe el.",
-    "install_devices_windows_list_4": "Selectați conexiunea activă, faceți clic dreapta pe ea și alegeți \"Proprietăți\".",
+    "install_devices_windows_list_3": "În panoul din stânga, faceți clic pe „Modificare setări adaptor”.",
+    "install_devices_windows_list_4": "Faceți clic dreapta pe conexiunea activă și selectați „Proprietăți”.",
     "install_devices_windows_list_5": "Găsiți Internet Protocol Versiunea 4 (TCP/IPv4) din listă, selectați-l și apoi clicați din nou pe Proprietăți.",
     "install_devices_windows_list_6": "Alegeți „Utilizați următoarele adrese de server DNS” și introduceți adresele serverului dvs. AdGuard Home.",
-    "install_devices_macos_list_1": "Clicați pe icoana Apple și accesați Preferințele Sistemului.",
-    "install_devices_macos_list_2": "Clicați pe Network.",
+    "install_devices_macos_list_1": "Faceți clic pe pictograma „Apple” și accesați „Preferințe de sistem”.",
+    "install_devices_macos_list_2": "Faceți clic pe „Rețea”.",
     "install_devices_macos_list_3": "Selectați prima conexiune din listă și clicați pe Avansat.",
     "install_devices_macos_list_4": "Selectați fila DNS și introduceți adresele serverului dvs. AdGuard Home.",
     "install_devices_android_list_1": "Din ecranul principal al Meniului Android, tapați Setări.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Deschideți Tabloul de bord",
     "install_saved": "Salvat cu succes",
     "encryption_title": "Criptare",
-    "encryption_desc": "Suport de Criptare (HTTPS/TLS) pentru DNS și interfața web administrator",
+    "encryption_desc": "Suport pentru criptare (HTTPS/TLS) atât pentru DNS, cât și pentru interfața web de administrare.",
     "encryption_config_saved": "Configurația de criptare salvată",
     "encryption_server": "Nume de server",
     "encryption_server_enter": "Introduceți numele domeniului",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Dacă portul HTTPS este configurat, interfața administrator AdGuard Home va fi accesibilă prin HTTPS și va oferi de asemenea DNS-over-HTTPS în locația '/DNS-query'.",
     "encryption_dot": "Port DNS-over-TLS",
     "encryption_dot_desc": "Dacă acest port este configurat, AdGuard Home va rula un server DNS-over-TLS pe acest port.",
-    "encryption_doq": "Port DNS-over-QUIC",
+    "encryption_doq": "Port DNS-over-QUIC (experimental)",
     "encryption_doq_desc": "Dacă acest port este configurat, AdGuard Home va rula un server DNS-over-QUIC pe acest port. Este experimental și este posibil să nu fie fiabil. De asemenea, nu există prea mulți clienți care să-l susțină în acest moment.",
     "encryption_certificates": "Certificate",
     "encryption_certificates_desc": "Pentru a utiliza criptarea, trebuie furnizate o serie de certificate SSL valabile pentru domeniul dvs.. Puteți obține un certificat gratuit pe <0>{{link}}</0> sau îl puteți cumpăra de la una din Autoritățile Certificate de încredere.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Copiați/lipiți cheia dvs. privată PEM-codată pentru certificatul dvs. aici.",
     "encryption_enable": "Activați criptarea (HTTPS, DNS-over-HTTPS, și DNS-over-TLS)",
     "encryption_enable_desc": "Dacă este activată criptarea, interfața administrator AdGuard Home va lucra peste HTTPS, și serverul DNS va asculta pentru cereri peste DNS-over-HTTPS și DNS-over-TLS.",
-    "encryption_chain_valid": "Lanț de certificate valid",
-    "encryption_chain_invalid": "Lanț de certificate invalid",
-    "encryption_key_valid": "Aceasta este o cheie privată {{type}} validă",
-    "encryption_key_invalid": "Aceasta este o cheie privată {{type}} invalidă",
+    "encryption_chain_valid": "Lanț de certificate valid.",
+    "encryption_chain_invalid": "Lanț de certificate invalid.",
+    "encryption_key_valid": "Aceasta este o cheie privată {{type}} validă.",
+    "encryption_key_invalid": "Aceasta este o cheie privată {{type}} invalidă.",
     "encryption_subject": "Obiect",
     "encryption_issuer": "Emitent",
     "encryption_hostnames": "Nume de host",
     "encryption_reset": "Sunteți sigur că doriți să resetați setările de criptare?",
     "topline_expiring_certificate": "Certificatul dvs. SSL este pe cale să expire. Actualizați <0>Setările de criptare</0>.",
     "topline_expired_certificate": "Certificatul dvs. SSL a expirat. Actualizați <0>Setările de criptare</0>.",
-    "form_error_port_range": "Introduceți valoarea portului între 80-65535",
-    "form_error_port_unsafe": "Acesta este un port nesigur",
-    "form_error_equal": "Nu trebuie să fie egale",
-    "form_error_password": "Parolele nu corespund",
+    "form_error_port_range": "Introduceți valoarea portului între 80-65535.",
+    "form_error_port_unsafe": "Acesta este un port nesigur.",
+    "form_error_equal": "Nu trebuie să fie egale.",
+    "form_error_password": "Parolele nu corespund.",
     "reset_settings": "Resetare setări",
     "update_announcement": "AdGuard Home {{version}} este disponibil! <0>Clicați aici</0> pentru mai multe informații.",
     "setup_guide": "Ghid de instalare",
     "dns_addresses": "Adrese DNS",
     "dns_start": "Serverul DNS demarează",
-    "dns_status_error": "Eroare la verificare statut server DNS",
+    "dns_status_error": "Eroare la verificare statut server DNS.",
     "down": "Down",
     "fix": "Fix",
     "dns_providers": "Iată o <0>listă de furnizori DNS cunoscuți</0> ce pot fi aleși.",
@@ -405,8 +405,8 @@
     "update_failed": "Auto-actualizarea a eșuat. Vă rugăm să <a>urmați aceste etape</a> pentru a actualiza manual.",
     "manual_update": "Vă rugăm <a>să urmați etapele următoare</a> pentru a actualiza manual.",
     "processing_update": "Vă rugăm să așteptați, AdGuard Home se actualizează...",
-    "clients_title": "Clienți",
-    "clients_desc": "Configură aparatele conectate la AdGuard Home",
+    "clients_title": "Clienți persistenți",
+    "clients_desc": "Configură înregistrările clientului permanent pentru dispozitivele conectate la AdGuard Home.",
     "settings_global": "General",
     "settings_custom": "Personalizat",
     "table_client": "Client",
@@ -417,7 +417,7 @@
     "client_edit": "Editare client",
     "client_identifier": "Identificator",
     "ip_address": "Adresa IP",
-    "client_identifier_desc": "Clienții pot fi identificați prin adresa IP, CIDR, adresa MAC sau un ID client special (poate fi folosit pentru DoT/DoH/DoQ). <0>Aici</0> puteți afla mai multe despre cum să identificați clienții.",
+    "client_identifier_desc": "Clienții pot fi identificați prin adresa lor IP, CIDR, adresa MAC sau ClientID (poate fi utilizat pentru DoT/DoH/DoQ). Aflați mai multe despre cum să identificați clienții <0>aici</0>.",
     "form_enter_ip": "Introduceți IP",
     "form_enter_subnet_ip": "Introduceți o adresă IP în subrețeaua „{{cidr}}”",
     "form_enter_mac": "Introduceți MAC",
@@ -432,14 +432,14 @@
     "clients_not_found": "Nu au fost găsiți clienți",
     "client_confirm_delete": "Sunteți sigur că doriți să ștergeți clientul \"{{key}}\"?",
     "list_confirm_delete": "Sigur doriți să ștergeți această listă?",
-    "auto_clients_title": "Clienți (runtime)",
-    "auto_clients_desc": "Date despre clienții care folosesc AdGuard Home, dar care nu sunt stocate în configurație",
+    "auto_clients_title": "Clienți runtime",
+    "auto_clients_desc": "Dispozitivele care nu se află pe lista de clienți Persistent care pot utiliza în continuare AdGuard Home.",
     "access_title": "Setări de acces",
     "access_desc": "Aici puteți configura regulile de acces pentru serverul DNS AdGuard Home.",
     "access_allowed_title": "Clienți autorizați",
-    "access_allowed_desc": "O listă de adrese CIDR sau IP client. Dacă este configurat, AdGuard Home va accepta cereri numai de la acești clienți.",
+    "access_allowed_desc": "O listă de CIDR-uri, adrese IP sau <a>ClientID-uri</a>. Dacă această listă are intrări, AdGuard Home va accepta cereri numai de la acești clienți.",
     "access_disallowed_title": "Clienți neautorizați",
-    "access_disallowed_desc": "O listă de adrese CIDR sau IP. Dacă este configurat, AdGuard Home va elimina cereri de la acești clienți. Dacă sunt configurați clienți permiși, acest câmp este ignorat.",
+    "access_disallowed_desc": "O listă de CIDR-uri, adrese IP sau <a>ClientID-uri</a>. Dacă această listă are intrări, AdGuard Home va renunța la cererile de la acești clienți. Acest câmp este ignorat dacă există intrări în „Clienți permiși”.",
     "access_blocked_title": "Domenii blocate",
     "access_blocked_desc": "A nu se confunda cu filtrele. AdGuard Home respinge cererile DNS pentru aceste domenii, iar aceste cereri nici măcar nu apar în jurnalul de solicitări. Puteți specifica nume exacte de domenii, metacaractere sau reguli de filtrare URL, cum ar fi \"example.org\", \"*.exemple.org\" sau \"||example.org^\" în mod corespunzător.",
     "access_settings_saved": "Setările de acces au fost salvate cu succes",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Rescrieri DNS",
     "form_domain": "Introduceți un nume de domeniu sau wildcard",
     "form_answer": "Introduceți adresa IP sau numele de domeniu",
-    "form_error_domain_format": "Format de răspuns invalid",
-    "form_error_answer_format": "Format de răspuns invalid",
+    "form_error_domain_format": "Format de domeniu invalid.",
+    "form_error_answer_format": "Format de răspuns invalid.",
     "configure": "Configurați",
     "main_settings": "Setări principale",
     "block_services": "Blochează anumite servicii",
@@ -507,7 +507,7 @@
     "filter_updated": "Filtrul a fost actualizat cu succes",
     "statistics_configuration": "Configurația statisticilor",
     "statistics_retention": "Păstrarea statisticilor",
-    "statistics_retention_desc": "Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
+    "statistics_retention_desc": "Dacă reduceți valoarea intervalului, unele date vor fi pierdute.",
     "statistics_clear": " Șterge statisticile",
     "statistics_clear_confirm": "Sunteți sigur că doriți să ștergeți statisticile?",
     "statistics_retention_confirm": "Sunteți sigur că doriți să schimbați păstrarea statisticilor? Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
@@ -532,7 +532,7 @@
     "netname": "Numele rețelei",
     "network": "Rețea",
     "descr": "Descriere",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Aflați mai multe</0> despre crearea propriilor liste hosts.",
     "blocked_by_response": "Blocat de CNAME sau IP ca răspuns",
     "blocked_by_cname_or_ip": "Blocat de CNAME sau IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Va efectua aceste sarcini: <0>Dezactivare sistem DNSStubListener</0> <0>Setare adresă server DNS la 127.0.0.1</0> <0>Înlocuire link simbolic țintă /etc/resolv.conf cu /run/systemd/resolve/resolv.conf</0> <0>Oprire DNSStubListener (reîncărcare servici rezolvat prin sistem)</0>",
     "autofix_warning_result": "Ca urmare, toate cererile DNS ale sistemul dvs. vor fi procesate în mod implicit de AdGuardHome.",
     "tags_title": "Etichete",
-    "tags_desc": "Puteți selecta etichetele care corespund clientului. Etichetele pot fi incluse în regulile de filtrare și vă permit să le aplicați mai exact. <0>Aflați mai multe</0>",
+    "tags_desc": "Puteți selecta etichetele care corespund clientului. Includeți etichete în regulile de filtrare pentru a le aplica mai precis. <0>Aflați mai multe</0>.",
     "form_select_tags": "Selectați etichete client",
     "check_title": "Verificați filtrarea",
-    "check_desc": "Verificați dacă numele de host este filtrat",
+    "check_desc": "Verifică dacă numele de host este filtrat.",
     "check": "Verificați",
     "form_enter_host": "Introduceți un nume de host",
     "filtered_custom_rules": "Filtrat prin reguli de filtrare personalizate",
@@ -594,19 +594,19 @@
     "allowed": "Permise",
     "filtered": "Filtrate",
     "rewritten": "Rescrise",
-    "safe_search": "Căutare sigură",
+    "safe_search": "Căutarea sigură",
     "blocklist": "Lista de blocări",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Mărime cache",
-    "cache_size_desc": "Mărime cache DNS (în octeți)",
+    "cache_size_desc": "Mărime cache DNS (în octeți).",
     "cache_ttl_min_override": "Suprascrieți minimum TTL",
     "cache_ttl_max_override": "Suprascrieți maximum TTL",
     "enter_cache_size": "Introduceți mărimea cache-ului (bytes)",
     "enter_cache_ttl_min_override": "Introduceți minimum TTL (secunde)",
     "enter_cache_ttl_max_override": "Introduceți maximum TTL (secunde)",
-    "cache_ttl_min_override_desc": "Extinde valorile timp-de-viață scurte (secunde) primite de la serverul din amonte la stocarea în cache a răspunsurilor DNS",
-    "cache_ttl_max_override_desc": "Setează o valoare maximă a timpului-de-viață (secunde) pentru intrările din memoria cache DNS",
-    "ttl_cache_validation": "Valoarea TTL cache minimă trebuie să fie mai mică sau egală cu valoarea maximă",
+    "cache_ttl_min_override_desc": "Extinde valorile timp-de-viață scurte (secunde) primite de la serverul din amonte la stocarea în cache a răspunsurilor DNS.",
+    "cache_ttl_max_override_desc": "Setează o valoare maximă a timpului-de-viață (secunde) pentru intrările din memoria cache DNS.",
+    "ttl_cache_validation": "Valoarea TTL cache minimă trebuie să fie mai mică sau egală cu valoarea maximă.",
     "cache_optimistic": "Caching optimistic",
     "cache_optimistic_desc": "Face ca AdGuard Home să răspundă din cache chiar și atunci când intrările au expirate și de asemenea, încearcă să le reîmprospăteze.",
     "filter_category_general": "General",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home va renunța la toate interogările DNS de la acest client.",
     "filter_allowlist": "AVERTISMENT: Această acțiune va exclude și regula „{{disallowed_rule}}” din lista de clienți permiși.",
     "last_rule_in_allowlist": "Acest client nu poate fi exclus deoarece excluderea regulii „{{disallowed_rule}}” va DEZACTIVA lista „Clienți acceptați”.",
-    "experimental": "Experimental",
     "use_saved_key": "Folosiți cheia salvată anterior",
     "parental_control": "Control Parental",
     "safe_browsing": "Navigare în siguranță",
-    "served_from_cache": "{{value}} <i>(furnizat din cache)</i>"
+    "served_from_cache": "{{value}} <i>(furnizat din cache)</i>",
+    "form_error_password_length": "Parola trebuie să aibă cel puțin {{value}} caractere."
 }
diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json
index cd1e8091..dc73fded 100644
--- a/client/src/__locales/ru.json
+++ b/client/src/__locales/ru.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Настройки клиентов",
-    "example_upstream_reserved": "Вы можете указать DNS-сервер <0>для конкретного домена(-ов)</0>",
-    "example_upstream_comment": "Вы можете указать комментарий",
+    "example_upstream_reserved": "DNS-сервер <0>для конкретных доменов</0>;",
+    "example_upstream_comment": "комментарий.",
     "upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.",
     "parallel_requests": "Параллельные запросы",
     "load_balancing": "Распределение нагрузки\n",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Конфигурация DHCP-сервера успешно сохранена",
     "dhcp_ipv4_settings": "Настройки DHCP IPv4",
     "dhcp_ipv6_settings": "Настройки DHCP IPv6",
-    "form_error_required": "Обязательное поле",
-    "form_error_ip4_format": "Некорректный IPv4-адрес",
-    "form_error_ip4_range_start_format": "Некорректный IPv4-адрес начала диапазона",
-    "form_error_ip4_range_end_format": "Некорректный IPv4-адрес конца диапазона",
-    "form_error_ip4_gateway_format": "Некорректный IPv4-адрес шлюза",
-    "form_error_ip6_format": "Некорректный IPv6-адрес",
-    "form_error_ip_format": "Некорректный IP-адрес",
-    "form_error_mac_format": "Некорректный MAC-адрес",
-    "form_error_client_id_format": "ID клиента может содержать только цифры, строчные латинские буквы и дефисы",
-    "form_error_server_name": "Некорректное имя сервера",
-    "form_error_subnet": "Подсеть «{{cidr}}» не содержит IP-адрес «{{ip}}»",
-    "form_error_positive": "Должно быть больше 0",
-    "out_of_range_error": "Должно быть вне диапазона «{{start}}»-«{{end}}»",
-    "lower_range_start_error": "Должно быть меньше начала диапазона",
-    "greater_range_start_error": "Должно быть больше начала диапазона",
-    "greater_range_end_error": "Должно быть больше конца диапазона",
-    "subnet_error": "Адреса должны быть внутри одной подсети",
-    "gateway_or_subnet_invalid": "Некорректная маска подсети",
+    "form_error_required": "Обязательное поле.",
+    "form_error_ip4_format": "Некорректный IPv4-адрес.",
+    "form_error_ip4_range_start_format": "Некорректный IPv4-адрес начала диапазона.",
+    "form_error_ip4_range_end_format": "Некорректный IPv4-адрес конца диапазона.",
+    "form_error_ip4_gateway_format": "Некорректный IPv4-адрес шлюза.",
+    "form_error_ip6_format": "Некорректный IPv6-адрес.",
+    "form_error_ip_format": "Некорректный IP-адрес.",
+    "form_error_mac_format": "Некорректный MAC-адрес.",
+    "form_error_client_id_format": "ClientID может содержать только цифры, строчные латинские буквы и дефисы.",
+    "form_error_server_name": "Некорректное имя сервера.",
+    "form_error_subnet": "Подсеть «{{cidr}}» не содержит IP-адрес «{{ip}}».",
+    "form_error_positive": "Должно быть больше 0.",
+    "out_of_range_error": "Должно быть вне диапазона «{{start}}»-«{{end}}».",
+    "lower_range_start_error": "Должно быть меньше начала диапазона.",
+    "greater_range_start_error": "Должно быть больше начала диапазона.",
+    "greater_range_end_error": "Должно быть больше конца диапазона.",
+    "subnet_error": "Адреса должны быть внутри одной подсети.",
+    "gateway_or_subnet_invalid": "Некорректная маска подсети.",
     "dhcp_form_gateway_input": "IP-адрес шлюза",
     "dhcp_form_subnet_input": "Маска подсети",
     "dhcp_form_range_title": "Диапазон IP-адресов",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Выберите списки разрешённых",
     "enter_valid_blocklist": "Добавьте действующий URL-адрес в чёрный список.",
     "enter_valid_allowlist": "Добавьте действующий URL-адрес в белый список.",
-    "form_error_url_format": "Некорректный URL",
-    "form_error_url_or_path_format": "Некорректный URL или абсолютный путь к списку",
+    "form_error_url_format": "Неверный формат URL.",
+    "form_error_url_or_path_format": "Неверный URL или абсолютный путь к списку.",
     "custom_filter_rules": "Пользовательское правило фильтрации",
     "custom_filter_rules_hint": "Вводите по одному правилу на строчку. Вы можете использовать правила блокировки или синтаксис файлов hosts.",
     "system_host_files": "Системные hosts-файлы",
     "examples_title": "Примеры",
-    "example_meaning_filter_block": "заблокировать доступ к домену example.org и всем его поддоменам",
-    "example_meaning_filter_whitelist": "разблокировать доступ к домену example.org и всем его поддоменам",
-    "example_meaning_host_block": "Теперь AdGuard Home вернёт 127.0.0.1 для домена example.org (но не для его поддоменов).",
-    "example_comment": "! Так можно добавлять описание",
-    "example_comment_meaning": "комментарий",
-    "example_comment_hash": "# И вот так тоже",
-    "example_regex_meaning": "блокирует доступ к доменам, соответствующим <0>заданному регулярному выражению</0>",
-    "example_upstream_regular": "обычный DNS (поверх UDP)",
-    "example_upstream_dot": "зашифрованный <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "зашифрованный <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "зашифрованный <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "вы можете использовать <0>DNS Stamps</0> для <1>DNSCrypt</1> или <2>DNS-over-HTTPS</2> резолверов",
-    "example_upstream_tcp": "обычный DNS (поверх TCP)",
+    "example_meaning_filter_block": "заблокировать доступ к домену example.org и всем его поддоменам;",
+    "example_meaning_filter_whitelist": "разблокировать доступ к домену example.org и всем его поддоменам;",
+    "example_meaning_host_block": "отвечать адресом 127.0.0.1 для домена example.org (но не для его поддоменов);",
+    "example_comment": "! Так можно добавлять комментарии.",
+    "example_comment_meaning": "комментарий;",
+    "example_comment_hash": "# И вот так тоже.",
+    "example_regex_meaning": "блокировать доступ к доменам, соответствующим заданному регулярному выражению.",
+    "example_upstream_regular": "обычный DNS (поверх UDP);",
+    "example_upstream_dot": "зашифрованный <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "зашифрованный <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "зашифрованный <0>DNS-over-QUIC</0> (эксперементальный);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt</1> или <2>DNS-over-HTTPS</2> серверов;",
+    "example_upstream_tcp": "обычный DNS (поверх TCP);",
     "all_lists_up_to_date_toast": "Все списки уже обновлены",
     "updated_upstream_dns_toast": "DNS-серверы успешно обновлены",
     "dns_test_ok_toast": "Указанные серверы DNS работают корректно",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Используйте двойные кавычки для строгого поиска",
     "query_log_retention_confirm": "Вы уверены, что хотите изменить срок хранения запросов? При сокращении интервала данные могут быть утеряны",
     "anonymize_client_ip": "Анонимизировать IP-адрес клиента",
-    "anonymize_client_ip_desc": "Не сохранять полный IP-адрес клиента в журналах и статистике",
+    "anonymize_client_ip_desc": "Не сохранять полный IP-адрес клиента в журналах и статистике.",
     "dns_config": "Настройки DNS-сервера",
     "dns_cache_config": "Настройка кеша DNS",
-    "dns_cache_config_desc": "Здесь можно настроить кеш DNS",
+    "dns_cache_config_desc": "Здесь можно настроить кеш DNS.",
     "blocking_mode": "Режим блокировки",
     "default": "Стандартный",
     "nxdomain": "NXDOMAIN",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "Идентификатор клиента",
-    "client_id_placeholder": "Введите идентификатор клиента",
-    "client_id_desc": "Различные клиенты могут идентифицироваться по специальному идентификатору клиента. <a>Здесь</a> вы можете узнать больше об идентификации клиентов.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Введите ClientID",
+    "client_id_desc": "Клиенты могут идентифицироваться по ClientID. <a>Здесь</a> вы можете узнать больше об идентификации клиентов.",
     "download_mobileconfig_doh": "Скачать .mobileconfig для DNS-over-HTTPS",
     "download_mobileconfig_dot": "Скачать .mobileconfig для DNS-over-TLS",
     "download_mobileconfig": "Загрузить файл конфигурации",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Сетевой интерфейс",
     "install_settings_port": "Порт",
     "install_settings_interface_link": "Ваш веб-интерфейс администрирования AdGuard Home будет доступен по следующим адресам:",
-    "form_error_port": "Введите корректный порт",
+    "form_error_port": "Введите корректный порт.",
     "install_settings_dns": "DNS-сервер",
     "install_settings_dns_desc": "Вам будет нужно настроить свои устройства или роутер на использование DNS-сервера на одном из следующих адресов:",
     "install_settings_all_interfaces": "Все интерфейсы",
@@ -334,12 +334,12 @@
     "install_devices_router_list_4": "Вы не можете установить собственный DNS-сервер на некоторых типах маршрутизаторов. В этом случае может помочь настройка AdGuard Home в качестве <0>DHCP-сервера</0>. В противном случае вам следует обратиться к руководству по настройке DNS-серверов для вашей конкретной модели маршрутизатора.",
     "install_devices_windows_list_1": "Откройте Панель управления через меню «Пуск» или через поиск Windows.",
     "install_devices_windows_list_2": "Перейдите в «Сеть и интернет», а затем в «Центр управления сетями и общим доступом»",
-    "install_devices_windows_list_3": "В левой стороне экрана найдите «Изменение параметров адаптера» и кликните по нему.",
-    "install_devices_windows_list_4": "Выделите ваше активное подключение, затем кликните по нему правой клавишей мыши и выберите «Свойства».",
+    "install_devices_windows_list_3": "В левой панели кликните на «Изменение параметров адаптера».",
+    "install_devices_windows_list_4": "Кликните на ваше активное подключение правой кнопкой мыши и выберите «Свойства».",
     "install_devices_windows_list_5": "Найдите в списке пункт «IP версии 4 (TCP/IPv4)» (или «IP версии 6 (TCP/IPv6)» для IPv6), выделите его и затем снова нажмите «Свойства».",
     "install_devices_windows_list_6": "Выберите «Использовать следующие адреса DNS-серверов» и введите адреса серверов AdGuard Home.",
-    "install_devices_macos_list_1": "Кликните по иконке Apple и перейдите в «Системные настройки».",
-    "install_devices_macos_list_2": "Кликните по иконке «Сеть».",
+    "install_devices_macos_list_1": "Кликните на иконку Apple и перейдите в «Системные настройки».",
+    "install_devices_macos_list_2": "Кликните на иконку «Сеть».",
     "install_devices_macos_list_3": "Выберите первое подключение в списке и нажмите кнопку «Дополнительно».",
     "install_devices_macos_list_4": "Выберите вкладку «DNS» и добавьте адреса AdGuard Home.",
     "install_devices_android_list_1": "В меню управления нажмите иконку «Настройки».",
@@ -356,7 +356,7 @@
     "open_dashboard": "Открыть Панель управления",
     "install_saved": "Успешно сохранено",
     "encryption_title": "Шифрование",
-    "encryption_desc": "Поддержка шифрования (HTTPS/TLS) для DNS и веб-интерфейса администрирования",
+    "encryption_desc": "Поддержка шифрования (HTTPS/TLS) для DNS и веб-интерфейса администрирования.",
     "encryption_config_saved": "Настройки шифрования сохранены",
     "encryption_server": "Имя сервера",
     "encryption_server_enter": "Введите ваше доменное имя",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Если порт HTTPS настроен, веб-интерфейс администрирования AdGuard Home будет доступен через HTTPS, а также DNS-over-HTTPS сервер будет доступен по пути '/dns-query'.",
     "encryption_dot": "Порт DNS-over-TLS",
     "encryption_dot_desc": "Если этот порт настроен, AdGuard Home запустит DNS-over-TLS-сервер на этому порту.",
-    "encryption_doq": "Порт DNS-over-QUIC",
+    "encryption_doq": "Порт DNS-over-QUIC (экспериментальный)",
     "encryption_doq_desc": "Если этот порт настроен, AdGuard Home запустит сервер DNS-over-QUIC на этом порте. Это экспериментально и может быть ненадёжно. Кроме того, не так много клиентов поддерживает этот способ в настоящий момент.",
     "encryption_certificates": "Сертификаты",
     "encryption_certificates_desc": "Для использования шифрования вам необходимо предоставить корректную цепочку SSL-сертификатов для вашего домена. Вы можете получить бесплатный сертификат на <0>{{link}}</0> или вы можете купить его у одного из доверенных Центров Сертификации.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Скопируйте сюда приватный ключ в PEM-кодировке.",
     "encryption_enable": "Включить шифрование (HTTPS, DNS-over-HTTPS и DNS-over-TLS)",
     "encryption_enable_desc": "Если шифрование включено, веб-интерфейс AdGuard Home будет работать по HTTPS, а DNS-сервер будет также работать по DNS-over-HTTPS и DNS-over-TLS.",
-    "encryption_chain_valid": "Цепочка сертификатов прошла проверку",
-    "encryption_chain_invalid": "Цепочка сертификатов не прошла проверку",
-    "encryption_key_valid": "Корректный {{type}} приватный ключ",
-    "encryption_key_invalid": "Некорректный {{type}} приватный ключ",
+    "encryption_chain_valid": "Цепочка сертификатов прошла проверку.",
+    "encryption_chain_invalid": "Цепочка сертификатов не прошла проверку.",
+    "encryption_key_valid": "Корректный {{type}} приватный ключ.",
+    "encryption_key_invalid": "Некорректный {{type}} приватный ключ.",
     "encryption_subject": "Субъект",
     "encryption_issuer": "Издатель",
     "encryption_hostnames": "Имена хостов",
     "encryption_reset": "Вы уверены, что хотите сбросить настройки шифрования?",
     "topline_expiring_certificate": "Ваш SSL-сертификат скоро истекает. Обновите <0>Настройки шифрования</0>.",
     "topline_expired_certificate": "Ваш SSL-сертификат истёк. Обновите <0>Настройки шифрования</0>.",
-    "form_error_port_range": "Введите номер порта из интервала 80-65535",
-    "form_error_port_unsafe": "Это небезопасный порт",
-    "form_error_equal": "Не должны быть равны",
-    "form_error_password": "Пароли не совпадают",
+    "form_error_port_range": "Введите номер порта из интервала 80-65535.",
+    "form_error_port_unsafe": "Это небезопасный порт.",
+    "form_error_equal": "Не должны быть равны.",
+    "form_error_password": "Пароли не совпадают.",
     "reset_settings": "Сбросить настройки",
     "update_announcement": "AdGuard Home {{version}} уже доступна! <0>Нажмите сюда</0>, чтобы узнать больше.",
     "setup_guide": "Инструкция по настройке",
     "dns_addresses": "Адреса DNS",
     "dns_start": "DNS-сервер запускается",
-    "dns_status_error": "Ошибка при получении состояния DNS-сервера",
+    "dns_status_error": "Ошибка при получении состояния DNS-сервера.",
     "down": "Вниз",
     "fix": "Исправить",
     "dns_providers": "<0>Список известных DNS-провайдеров</0> на выбор.",
@@ -405,8 +405,8 @@
     "update_failed": "Ошибка авто-обновления. Пожалуйста, <a>следуйте инструкции</a> для обновления вручную.",
     "manual_update": "Пожалуйста, <a>следуйте инструкции</a> для обновления вручную.",
     "processing_update": "Пожалуйста, подождите, AdGuard Home обновляется",
-    "clients_title": "Клиенты",
-    "clients_desc": "Настройте устройства, использующие AdGuard Home",
+    "clients_title": "Сохранённые клиенты",
+    "clients_desc": "Настройте устройства, использующие AdGuard Home.",
     "settings_global": "Глобальные",
     "settings_custom": "Свои",
     "table_client": "Клиент",
@@ -417,7 +417,7 @@
     "client_edit": "Редактировать клиента",
     "client_identifier": "Идентификатор",
     "ip_address": "IP-адрес",
-    "client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу, CIDR или MAC-адресу или специальному ID (можно использовать для DoT/DoH/DoQ). <0>Здесь</0> вы можете узнать больше об идентификации клиентов.",
+    "client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу, CIDR, MAC-адресу или ClientID (можно использовать для DoT/DoH/DoQ). <0>Здесь</0> вы можете узнать больше об идентификации клиентов.",
     "form_enter_ip": "Введите IP",
     "form_enter_subnet_ip": "Введите IP-адрес в подсети «{{cidr}}»",
     "form_enter_mac": "Введите MAC",
@@ -433,13 +433,13 @@
     "client_confirm_delete": "Вы уверены, что хотите удалить клиента «{{key}}»?",
     "list_confirm_delete": "Вы уверены, что хотите удалить этот список?",
     "auto_clients_title": "Клиенты (runtime)",
-    "auto_clients_desc": "Данные о клиентах, которые используют AdGuard Home, но не хранятся в настройках",
+    "auto_clients_desc": "Несохранённые клиенты, которые могут пользоваться AdGuard Home.",
     "access_title": "Настройки доступа",
     "access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home.",
     "access_allowed_title": "Разрешённые клиенты",
-    "access_allowed_desc": "Список CIDR, IP-адресов или ID клиентов. Если он настроен, AdGuard Home будет принимать запросы только от этих клиентов.",
+    "access_allowed_desc": "Список CIDR, IP-адресов или <a>ClientID</a>. Если в списке есть записи, AdGuard Home будет принимать запросы только от этих клиентов.",
     "access_disallowed_title": "Запрещённые клиенты",
-    "access_disallowed_desc": "Список CIDR, IP-адресов или ID клиентов. Если он настроен, AdGuard Home будет игнорировать запросы от этих клиентов. Если настроены разрешённые клиенты, это поле игнорируется.",
+    "access_disallowed_desc": "Список CIDR, IP-адресов или <a>ClientID</a>. Если в списке есть записи, AdGuard Home будет игнорировать запросы от этих клиентов. Это поле игнорируется, если список разрешённых клиентов содержит записи.",
     "access_blocked_title": "Неразрешённые домены",
     "access_blocked_desc": "Не путать с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами. Здесь вы можете уточнить точные имена доменов, шаблоны, правила URL-фильтрации, например, «example.org», «*.example.org» или «||example.org».",
     "access_settings_saved": "Настройки доступа успешно сохранены",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Перезапись DNS-запросов",
     "form_domain": "Введите домен",
     "form_answer": "Введите IP адрес или домен",
-    "form_error_domain_format": "Некорректный домен",
-    "form_error_answer_format": "Некорректный ответ",
+    "form_error_domain_format": "Некорректный домен.",
+    "form_error_answer_format": "Некорректный ответ.",
     "configure": "Настроить",
     "main_settings": "Основные настройки",
     "block_services": "Выбрать заблокированные сервисы",
@@ -507,7 +507,7 @@
     "filter_updated": "Список успешно обновлён",
     "statistics_configuration": "Конфигурация статистики",
     "statistics_retention": "Сохранение статистики",
-    "statistics_retention_desc": "Если вы уменьшите значение интервала, некоторые данные могут быть утеряны",
+    "statistics_retention_desc": "Если вы уменьшите значение интервала, некоторые данные могут быть потеряны.",
     "statistics_clear": "Очистить статистику",
     "statistics_clear_confirm": "Вы уверены, что хотите очистить статистику?",
     "statistics_retention_confirm": "Вы уверены, что хотите изменить срок хранения статистики? При сокращении интервала данные могут быть утеряны",
@@ -532,7 +532,7 @@
     "netname": "Название сети",
     "network": "Сеть",
     "descr": "Описание",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Узнайте больше</0> о создании собственных списков блокировки хостов.",
     "blocked_by_response": "Заблокировано по CNAME или IP в ответе",
     "blocked_by_cname_or_ip": "Заблокировано с помощью CNAME или IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Будут выполняться следующие задачи: <0>Деактивировать системный DNSStubListener</0> <0>Установить адрес сервера DNS на 127.0.0.1</0> <0>Создать символическую ссылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Остановить DNSStubListener (перезагрузить системную службу)</0>.",
     "autofix_warning_result": "В результате все DNS-запросы от вашей системы будут по умолчанию обрабатываться AdGuard Home.\n",
     "tags_title": "Теги",
-    "tags_desc": "Вы можете выбрать теги, которые соответствуют клиенту. Теги могут быть включены в правила фильтрации и позволят вам применять их более точно. <0>Узнать больше</0>.",
+    "tags_desc": "Вы можете выбрать теги, которые соответствуют клиенту. Теги могут быть включены в правила фильтрации, чтобы применять их более точно. <0>Узнать больше</0>.",
     "form_select_tags": "Выбрать теги клиента",
     "check_title": "Проверить фильтрацию",
-    "check_desc": "Проверить фильтрацию имени хоста",
+    "check_desc": "Проверить фильтрацию имени хоста.",
     "check": "Проверить",
     "form_enter_host": "Введите имя хоста",
     "filtered_custom_rules": "Отфильтрованы с помощью пользовательских правил фильтрации",
@@ -598,15 +598,15 @@
     "blocklist": "Чёрный список",
     "milliseconds_abbreviation": "мс",
     "cache_size": "Размер кеша",
-    "cache_size_desc": "Размера кеша DNS (в байтах)",
+    "cache_size_desc": "Размера кеша DNS (в байтах).",
     "cache_ttl_min_override": "Переопределить минимальный TTL (в секундах)",
     "cache_ttl_max_override": "Переопределить максимальный TTL (в секундах)",
     "enter_cache_size": "Введите размер кеша (в байтах)",
     "enter_cache_ttl_min_override": "Введите минимальный TTL (в секундах)",
     "enter_cache_ttl_max_override": "Введите максимальный TTL (в секундах)",
-    "cache_ttl_min_override_desc": "Расширить короткие TTL-значения (в секундах), полученные с upstream-сервера при кешировании DNS-ответов",
-    "cache_ttl_max_override_desc": "Установить максимальное TTL-значение (в секундах) для записей в DNS-кэше",
-    "ttl_cache_validation": "Минимальное значение TTL-кеша должно быть меньше или равно максимальному значению",
+    "cache_ttl_min_override_desc": "Расширить короткие TTL-значения (в секундах), полученные с upstream-сервера при кешировании DNS-ответов.",
+    "cache_ttl_max_override_desc": "Установить максимальное TTL-значение (в секундах) для записей в DNS-кеше.",
+    "ttl_cache_validation": "Минимальное значение TTL-кеша должно быть меньше или равно максимальному значению.",
     "cache_optimistic": "Оптимистическое кеширование",
     "cache_optimistic_desc": "AdGuard Home будет отвечать из кеша, даже если ответы в нём неактуальны, и попытается обновить их.",
     "filter_category_general": "Общие",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home AdGuard Home сбросит все DNS-запросы от этого клиента.",
     "filter_allowlist": "ВНИМАНИЕ: Это действие также исключит правило «{{disallowed_rule}}» из списка разрешённых клиентов.",
     "last_rule_in_allowlist": "Нельзя заблокировать этого клиента, так как исключение правила «{{disallowed_rule}}» ОТКЛЮЧИТ режим белого списка.",
-    "experimental": "Экспериментальный",
     "use_saved_key": "Использовать сохранённый ранее ключ",
     "parental_control": "Родительский контроль",
     "safe_browsing": "Безопасный интернет",
-    "served_from_cache": "{{value}} <i>(получено из кеша)</i>"
+    "served_from_cache": "{{value}} <i>(получено из кеша)</i>",
+    "form_error_password_length": "Пароль должен быть длиной не меньше {{value}} символов."
 }
diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json
index e9f02cb2..d2a4f5b9 100644
--- a/client/src/__locales/si-lk.json
+++ b/client/src/__locales/si-lk.json
@@ -541,7 +541,6 @@
     "click_to_view_queries": "විමසුම් බැලීමට ඔබන්න",
     "port_53_faq_link": "53 වන කෙවෙනිය බොහෝ විට \"DNSStubListener\" හෝ \"systemd-resolved\" සේවා භාවිතයට ගනු ලැබේ. කරුණාකර මෙය විසඳන්නේ කෙසේද යන්න පිළිබඳ <0>මෙම උපදෙස්</0> කියවන්න.",
     "adg_will_drop_dns_queries": "ඇඩ්ගාර්ඩ් හෝම් මෙම අනුග්‍රාහකයේ සියළුම ව.නා.ප. විමසුම් අතහැර දමනු ඇත.",
-    "experimental": "පරීක්‍ෂාත්මක",
     "use_saved_key": "පෙර සුරැකි යතුර භාවිතා කරන්න",
     "parental_control": "දෙමාපිය පාලනය",
     "safe_browsing": "ආරක්‍ෂිත පිරික්සුම",
diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json
index 7b4596f3..da174fda 100644
--- a/client/src/__locales/sk.json
+++ b/client/src/__locales/sk.json
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home zruší všetky DNS dopyty od tohto klienta.",
     "filter_allowlist": "UPOZORNENIE: Táto akcia tiež vylúči pravidlo \"\"{{disallowed_rule}}\"\" zo zoznamu povolených klientov.",
     "last_rule_in_allowlist": "Nemôžete zakázať tohto klienta, pretože vylúčenie pravidla \"{{disallowed_rule}}\" zakáže zoznam \"povolených klientov\".",
-    "experimental": "Experimentálne",
     "use_saved_key": "Použiť predtým uložený kľúč",
     "parental_control": "Rodičovská kontrola",
     "safe_browsing": "Bezpečné prehliadanie",
-    "served_from_cache": "{{value}} <i>(prevzatá z cache pamäte)</i>"
+    "served_from_cache": "{{value}} <i>(prevzatá z cache pamäte)</i>",
+    "form_error_password_length": "Heslo musí mať dĺžku aspoň {{value}} znakov."
 }
diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json
index a9c5fed6..8df19766 100644
--- a/client/src/__locales/sl.json
+++ b/client/src/__locales/sl.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Nastavitve odjemalca",
-    "example_upstream_reserved": "Lahko določite gorvodni DNS <0>za določene domene</0>",
-    "example_upstream_comment": "Lahko določite komentar",
+    "example_upstream_reserved": "gorvodni <0>za določene domene</0>;",
+    "example_upstream_comment": "komentar.",
     "upstream_parallel": "Uporabite vzporedne zahteve za pospešitev reševanja s hkratnim poizvedovanjem vseh gorvodnih strežnikov.",
     "parallel_requests": "Vzporedne zahteve",
     "load_balancing": "Uravnavanje obremenitve",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Nastavitve DHCP so bile uspešno shranjena",
     "dhcp_ipv4_settings": "Nastavitve DHCP IPv4",
     "dhcp_ipv6_settings": "Nastavitve DHCP IPv6",
-    "form_error_required": "Zahtevano polje",
-    "form_error_ip4_format": "Neveljaven naslov IPv4",
-    "form_error_ip4_range_start_format": "Neveljaven začetek oblike razpona IPv4",
-    "form_error_ip4_range_end_format": "Neveljaven konec oblike razpona IPv4",
-    "form_error_ip4_gateway_format": "Neveljaven naslov IPv4 prehoda",
-    "form_error_ip6_format": "Neveljaven naslov IPv6",
-    "form_error_ip_format": "Neveljaven naslov IP",
-    "form_error_mac_format": "Neveljaven naslov MAC",
-    "form_error_client_id_format": "ID odjemalca mora vsebovati samo številke, male črke in vezaje",
-    "form_error_server_name": "Neveljavno ime strežnika",
-    "form_error_subnet": "Podomrežje \"{{cidr}}\" ne vsebuje naslova IP \"{{ip}}\"",
-    "form_error_positive": "Mora biti večja od 0",
-    "out_of_range_error": "Mora biti izven razpona \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Mora biti manjši od začetka razpona",
-    "greater_range_start_error": "Mora biti večji od začetka razpona",
-    "greater_range_end_error": "Mora biti večji od konca razpona",
-    "subnet_error": "Naslovi morajo biti v enem podomrežju",
-    "gateway_or_subnet_invalid": "Maska podomrežja ni veljavna",
+    "form_error_required": "Zahtevano polje.",
+    "form_error_ip4_format": "Neveljaven naslov IPv4.",
+    "form_error_ip4_range_start_format": "Neveljaven naslov IPv4 začetka razpona.",
+    "form_error_ip4_range_end_format": "Neveljaven naslov IPv4 konca razpona.",
+    "form_error_ip4_gateway_format": "Neveljaven naslov IPv4 prehoda.",
+    "form_error_ip6_format": "Neveljaven naslov IPv6.",
+    "form_error_ip_format": "Neveljaven naslov IP.",
+    "form_error_mac_format": "Neveljaven naslov MAC.",
+    "form_error_client_id_format": "ID odjemalca mora vsebovati samo številke, male črke in vezaje.",
+    "form_error_server_name": "Neveljavno ime strežnika.",
+    "form_error_subnet": "Podomrežje \"{{cidr}}\" ne vsebuje naslova IP \"{{ip}}\".",
+    "form_error_positive": "Mora biti večja od 0.",
+    "out_of_range_error": "Mora biti izven razpona \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Mora biti manjši od začetka razpona.",
+    "greater_range_start_error": "Mora biti večji od začetka razpona.",
+    "greater_range_end_error": "Mora biti večji od konca razpona.",
+    "subnet_error": "Naslovi morajo biti v enem podomrežju.",
+    "gateway_or_subnet_invalid": "Maska podomrežja ni veljavna.",
     "dhcp_form_gateway_input": "IP prehoda",
     "dhcp_form_subnet_input": "Maska podomrežja",
     "dhcp_form_range_title": "Razpon naslovov IP",
@@ -143,7 +143,7 @@
     "use_adguard_browsing_sec_hint": "AdGuard Home bo preveril ali je domena onemogočena s spletno storitivijo 'Varnost brskanja'ovoljenih. Za izvedbo preverjanja bo uporabil API za iskanje, ki je prijazen do zasebnosti: strežniku se pošlje le kratka predpona zgoščenke domenskega imena SHA256.",
     "use_adguard_parental": "Uporabi AdGuardovo spletno storitev 'Starševski nadzor'",
     "use_adguard_parental_hint": "AdGuard Home bo preveril, če domena vsebuje  vsebine za odrasle. Uporablja enako, za zasebnost prijazen API, kot spletno storitev za varnost brskanja.",
-    "enforce_safe_search": "Uporabi varno iskanje",
+    "enforce_safe_search": "Uporabi Varno iskanje",
     "enforce_save_search_hint": "AdGuard Home bo vsilil varno iskanje v naslednjih iskalnikih: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
     "no_servers_specified": "Ni določenih strežnikov",
     "general_settings": "Splošne nastavitve",
@@ -167,8 +167,8 @@
     "enabled_safe_browsing_toast": "Omogočeno varno brskanje",
     "disabled_parental_toast": "Onemogočen starševski nadzor",
     "enabled_parental_toast": "Omogočen starševski nadzor",
-    "disabled_safe_search_toast": "Onemogočeno varno iskanje",
-    "enabled_save_search_toast": "Omogočeno varno iskanje",
+    "disabled_safe_search_toast": "Onemogočeno Varno iskanje",
+    "enabled_save_search_toast": "Omogočeno Varno iskanje",
     "enabled_table_header": "Omogočeno",
     "name_table_header": "Ime",
     "list_url_table_header": "Seznam URL naslovov",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Izberite sezname dovoljenih",
     "enter_valid_blocklist": "Vnesite veljaven URL naslov seznama nedovoljenih.",
     "enter_valid_allowlist": "Vnesite veljaven URL naslov seznama dovoljenih.",
-    "form_error_url_format": "Neveljaven format URL naslova",
+    "form_error_url_format": "Neveljaven format URL naslova.",
     "form_error_url_or_path_format": "Neveljaven URL ali absolutna pot seznama",
     "custom_filter_rules": "Pravila filtriranja po meri",
     "custom_filter_rules_hint": "V vrstico vnesite eno pravilo. Uporabite lahko pravila zaviranja oglasov ali sintakso gostiteljskih datotek.",
     "system_host_files": "Sistemske gostiteljske datooteke",
     "examples_title": "Primeri",
-    "example_meaning_filter_block": "onemogoči dostop do domene example.org in vseh njenih poddomen",
-    "example_meaning_filter_whitelist": "omogoči dostop do domene example.org in vseh njenih poddomen",
-    "example_meaning_host_block": "AdGuard Home bo zdaj vrnil  naslov  127.0.0.1 za domeno example.org (ne pa tudi njunih poddomen).",
-    "example_comment": "! Tukaj je komentar",
-    "example_comment_meaning": "samo komentar",
-    "example_comment_hash": "# Tudi komentar",
-    "example_regex_meaning": "onemogoča dostop do domen, ki se ujemajo z določenim regularnim izrazom",
-    "example_upstream_regular": "redni DNS (nad UDP)",
-    "example_upstream_dot": "šifriran <0>DNS-prek-TLS</0>",
-    "example_upstream_doh": "šifriran <0>DNS-prek-HTTPS</0>",
-    "example_upstream_doq": "šifriran <0>DNS-prek-QUIC</0>",
-    "example_upstream_sdns": "lahko uporabite <0>DNS Žige</0> za reševalce <1>DNSCrypt</1> ali <2>DNS-prek-HTTPS</2>",
-    "example_upstream_tcp": "redni DNS (nad TCP)",
+    "example_meaning_filter_block": "onemogoči dostop do domene example.org in vseh njenih poddomen;",
+    "example_meaning_filter_whitelist": "omogoči dostop do domene example.org in vseh njenih poddomen;",
+    "example_meaning_host_block": "odgovori z 127.0.0.1 na primer.org (vendar ne za njegove poddomene);",
+    "example_comment": "! Tukaj je komentar.",
+    "example_comment_meaning": "samo komentar;",
+    "example_comment_hash": "# Tudi komentar.",
+    "example_regex_meaning": "onemogoča dostop do domen, ki se ujemajo z določenim regularnim izrazom.",
+    "example_upstream_regular": "redni DNS (nad UDP);",
+    "example_upstream_dot": "šifriran <0>DNS-prek-TLS</0>;",
+    "example_upstream_doh": "šifriran <0>DNS-prek-HTTPS</0>;",
+    "example_upstream_doq": "šifriran <0>DNS-prek-QUIC</0> (eksperimentalno);",
+    "example_upstream_sdns": "lahko uporabite <0>DNS Žige</0> za reševalce <1>DNSCrypt</1> ali <2>DNS-prek-HTTPS</2>;",
+    "example_upstream_tcp": "redni DNS (nad TCP);",
     "all_lists_up_to_date_toast": "Vsi seznami so že posodobljeni",
     "updated_upstream_dns_toast": "Gorvodni trežniki so uspešno shranjeni",
     "dns_test_ok_toast": "Navedeni strežniki DNS delujejo pravilno",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Za strogo iskanje uporabite dvojne narekovaje",
     "query_log_retention_confirm": "Ali ste prepričani, da želite spremeniti zadrževanje dnevnika poizvedb? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
     "anonymize_client_ip": "Anonimiziraj odjemalca IP",
-    "anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih in statistiki",
+    "anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih ali statistiki.",
     "dns_config": "Konfiguracija strežnika DNS",
     "dns_cache_config": "Konfiguracija strežnika DNS",
-    "dns_cache_config_desc": "Tu lahko konfigurirate predpomnilnik DNS",
+    "dns_cache_config_desc": "Tu lahko nastavite predpomnilnik DNS.",
     "blocking_mode": "Način zaviranja",
     "default": "Privzeto",
     "nxdomain": "NXDOMAIN",
@@ -277,7 +277,7 @@
     "dns_over_quic": "DNS-prek-QIUC",
     "client_id": "ID odjemalca",
     "client_id_placeholder": "Vnesite ID odjemalca",
-    "client_id_desc": "Različne odjemalce je mogoče prepoznati s posebnim ID-jem odjemalca. <a>Tukaj</a> lahko izveste več o prepoznavanju odjemalcev.",
+    "client_id_desc": "Odjemalce je mogoče identificirati s ClientID. Več o tem, kako prepoznati odjemalce, preberite <a>tukaj</a>.",
     "download_mobileconfig_doh": "Prenos .mobileconfig za DNS-preko-HTTPS",
     "download_mobileconfig_dot": "Prenos .mobileconfig za DNS-preko-TLS",
     "download_mobileconfig": "Prenesi nastavitveno datoteko",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Poslušaj vmesnik",
     "install_settings_port": "Vrata",
     "install_settings_interface_link": "Vaš AdGuard Home Skrbniški spletni vmesnik bo na voljo na naslednjih naslovih:",
-    "form_error_port": "Vnesite veljavno številko vrat",
+    "form_error_port": "Vnesite veljavno številko vrat.",
     "install_settings_dns": "DNS strežnik",
     "install_settings_dns_desc": "Vaše naprave ali usmerjevalnik boste morali konfigurirati za uporabo strežnika DNS na naslednjih naslovih:",
     "install_settings_all_interfaces": "Vsi vmesniki",
@@ -334,12 +334,12 @@
     "install_devices_router_list_4": "Pri nekaterih vrstah usmerjevalnikov strežnika DNS po meri ni mogoče nastaviti. V tem primeru vam lahko pomaga nastavitev AdGuard Home kot <0>strežnika DHCP</0>. V nasprotnem primeru bi morali v priročniku usmerjevalnika preveriti, kako prilagodite strežnike DNS na vašem določenem modelu usmerjevalnika.",
     "install_devices_windows_list_1": "Odprite 'Nadzorno ploščo' prek menija 'Začetek' ali 'Iskanja v sistemu Windows'.",
     "install_devices_windows_list_2": "Pojdite v 'Omrežje' in 'Kategorija interneta' in nato v 'Omrežje' in 'Središče za skupno rabo'.",
-    "install_devices_windows_list_3": "Na levi strani zaslona poiščite 'Spremeni nastavitve kartice' in kliknite nanjo.",
-    "install_devices_windows_list_4": "Izberite aktivno povezavo, kliknite na njo z desno miškino tipko in izberite 'Lastnosti'.",
+    "install_devices_windows_list_3": "V levem podoknu kliknite 'Spremeni nastavitve kartice'\".",
+    "install_devices_windows_list_4": "Z desno tipko miške kliknite svojo aktivno povezavo in izberite Lastnosti.",
     "install_devices_windows_list_5": "Na seznamu poiščite 'Internet protokol različica 4 (TCP/IPv4)' (ali, za IPv6, 'Internet protokol različica 6 (TCP/IPv6)'), jo izberite in nato še enkrat kliknite 'Lastnosti'.",
     "install_devices_windows_list_6": "Izberite 'Uporabi naslednje naslove DNS strežnikov' in vnesite vaše naslove strežnika AdGuard Home.",
-    "install_devices_macos_list_1": "Kliknite ikono Apple in pojdite na 'Nastavitve sistema'.",
-    "install_devices_macos_list_2": "Kliknite na 'Omrežje'",
+    "install_devices_macos_list_1": "Kkliknite ikono Apple in pojdite na Sistemske nastavitve.",
+    "install_devices_macos_list_2": "Kliknite na 'Omrežje'.",
     "install_devices_macos_list_3": "Izberite prvo povezavo na seznamu in kliknite na 'Napredno'.",
     "install_devices_macos_list_4": "Izberite zavihek DNS in vnesite vaše naslove AdGuard Home strežnika.",
     "install_devices_android_list_1": "Na začetnem zaslonu menija Android tapnite 'Nastavitve'.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Odpri nadzorno ploščo",
     "install_saved": "Shranjeno uspešno",
     "encryption_title": "Šifriranje",
-    "encryption_desc": "Podpora za šifriranje (HTTPS/TLS) za DNS in skrbniški spletni vmesnik",
+    "encryption_desc": "Podpora za šifriranje (HTTPS/TLS) za DNS in skrbniški spletni vmesnik.",
     "encryption_config_saved": "Nastavitve šifriranja so shranjene",
     "encryption_server": "Ime strežnika",
     "encryption_server_enter": "Vnesite ime vaše domene",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Če so vrata HTTPS konfigurirana, bo skrbniški vmesnik AdGuard Home dostopen prek protokola HTTPS, prav tako pa bo zagotovil DNS-prek-HTTPS na mestu '/dns-query'.",
     "encryption_dot": "Vrata DNS-prek-TLS",
     "encryption_dot_desc": "Če so ta vrata konfigurirana, bo AdGuard Home na teh vratih zagnal DNS-prek-TLS strežnika.",
-    "encryption_doq": "DNS-prek-vrat QUIC",
+    "encryption_doq": "DNS-prek-vrat QUIC (eksperimentalno)",
     "encryption_doq_desc": "Če so nastavljena ta vrata bo AdGuard Home na teh vratih zagnal strežnik DNS-prek-QUIC. To je eksperimentalno in morda ni zanesljivo. Prav tako trenutno ni preveč odjemalcev, ki to podpirajo.",
     "encryption_certificates": "Digitalna potrdila",
     "encryption_certificates_desc": "Za uporabo šifriranja morate za svojo domeno zagotoviti veljavno verigo potrdil SSL. Brezplačno digitalno potrdilo lahko dobite na <0>{{link}}</0> ali pa ga kupite pri enem od   zaupanja vrednih overiteljev.\n\n",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Tukaj kopirajte/prilepite PEM-kodiran zasebni ključ za vaše digitalno potrdilo.",
     "encryption_enable": "Omogoči šifriranje (HTTPS, DNS-prek-HTTPS in DNS-prek-TLS)",
     "encryption_enable_desc": "Če je omogočeno šifriranje, bo skrbniški vmesnik AdGuard Home deloval prek HTTPS, strežnik DNS pa bo poslušal zahteve prek DNS-prek-HTTPS in DNS-prek-TLS.",
-    "encryption_chain_valid": "Veriga digitalih potrdil je veljavna",
-    "encryption_chain_invalid": "Veriga digitalih potrdil ni veljavna",
-    "encryption_key_valid": "To je veljaven zasebni ključ {{type}}",
-    "encryption_key_invalid": "To je neveljaven zasebni ključ {{type}}",
+    "encryption_chain_valid": "Veriga digitalih potrdil je veljavna.",
+    "encryption_chain_invalid": "Veriga digitalih potrdil ni veljavna.",
+    "encryption_key_valid": "To je veljaven zasebni ključ {{type}}.",
+    "encryption_key_invalid": "To je neveljaven zasebni ključ {{type}}.",
     "encryption_subject": "Predmet",
     "encryption_issuer": "Izdajatelj",
     "encryption_hostnames": "Imena gostiteljev",
     "encryption_reset": "Ali ste prepričani, da želite ponastaviti nastavitve šifriranja?",
     "topline_expiring_certificate": "Vaš e digitalno potrdilo SSL bo kmalu poteklol. Posodobite <0>Nastavitve šifriranja</0>.",
     "topline_expired_certificate": "Vaše digitalno potrdilo SSL je poteklo. Posodobi <0>Nastavitve šifriranja</0>.",
-    "form_error_port_range": "Vnesite vrednost vrat v razponu med 80-65535",
-    "form_error_port_unsafe": "To so nevarna vrata",
-    "form_error_equal": "Ne sme biti enako",
-    "form_error_password": "Geslo se ne ujema",
+    "form_error_port_range": "Vnesite številko vrat v razponu med 80-65535.",
+    "form_error_port_unsafe": "To so nevarna vrata.",
+    "form_error_equal": "Ne sme biti enako.",
+    "form_error_password": "Geslo se ne ujema.",
     "reset_settings": "Ponastavi nastavitve",
     "update_announcement": "Zdaj je na voljo AdGuard Home {{version}}! <0>Klinite tukaj</0> za več informacij.",
     "setup_guide": "Navodila za nastavitev",
     "dns_addresses": "DNS naslovi",
     "dns_start": "Zaganja se strežnik DNS",
-    "dns_status_error": "Napaka pri pridobivanju stanja strežnika DNS",
+    "dns_status_error": "Napaka pri preverjanju stanja strežnika DNS.",
     "down": "Navzdol",
     "fix": "Popravi",
     "dns_providers": "Tukaj je  <0>seznam znanih ponudnikov DNS</0>, med katerimi lahko izbirate.",
@@ -405,8 +405,8 @@
     "update_failed": "Samodejna posodobitev ni uspela. Prosimo <a>sledite korakom</a>, da ročno posodobite.",
     "manual_update": "Za ročno posodobitev <a>sledite tem korakom</a>.",
     "processing_update": "Prosimo, počakajte. AdGuard Home se posodablja!",
-    "clients_title": "Odjemalci",
-    "clients_desc": "Konfigurirajte naprave, ki so povezane z AdGuard Home",
+    "clients_title": "Trajni odjemalci",
+    "clients_desc": "Nastavite trajne zapise odjemalca za povezane naprave z AdGuard Home.",
     "settings_global": "Splošno",
     "settings_custom": "Po meri",
     "table_client": "Odjemalec",
@@ -417,7 +417,7 @@
     "client_edit": "Uredi odjemalca",
     "client_identifier": "Identifikator",
     "ip_address": "IP naslov",
-    "client_identifier_desc": "Odjemalce je mogoče prepoznati po naslovu IP, CIDR, naslovu MAC ali posebnem ID-ju odjemalca (lahko se uporablja za DoT/DoH/DoQ). <0>Tukaj</0> lahko izveste več o prepoznavanju odjemalcev.",
+    "client_identifier_desc": "Odjemalce je mogoče prepoznati po naslovu IP, CIDR, naslovu MAC ali ID-ju (lahko se uporablja za DoT/DoH/DoQ). <0>Tukaj</0> lahko izveste več o prepoznavanju odjemalcev.",
     "form_enter_ip": "Vnesite IP",
     "form_enter_subnet_ip": "V podomrežje \"{{cidr}}\" vnesite naslov IP",
     "form_enter_mac": "Vnesite MAC",
@@ -432,14 +432,14 @@
     "clients_not_found": "Odjemalcev ni bilo mogoče najti",
     "client_confirm_delete": "Ali ste prepričani, da želite izbrisati odjemalca \"{{key}}\"?",
     "list_confirm_delete": "Ali ste prepričani, da želite izbrisati ta seznam?",
-    "auto_clients_title": "Odjemalci (čas izvajanja)",
-    "auto_clients_desc": "Podatki o odjemalcih, ki uporabljajo AdGuard Home, vendar niso shranjeni v konfiguraciji",
+    "auto_clients_title": "Odjemalci izvajanja",
+    "auto_clients_desc": "Naprave, ki niso na seznamu trajnih odjemalcev, ki morda še vedno uporabljajo AdGuard Home.",
     "access_title": "Nastavitve dostopa",
     "access_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home.",
     "access_allowed_title": "Dovoljeni odjemalci",
-    "access_allowed_desc": "Seznam naslovov CIDR ali IP. Če je nastavljen, bo AdGuard Home sprejel zahteve samo od teh teh naslovov IP.",
+    "access_allowed_desc": "Seznam CIDR-jev, naslovov IP ali <a>ID-jev odjemalcev</a>. Če ta seznam vsebuje vnose, bo AdGuard Home sprejel zahteve samo teh odjemalcev.",
     "access_disallowed_title": "Zavrnjeni odjemalci",
-    "access_disallowed_desc": "Seznam naslovov CIDR ali IP. Če je nastavljen, bo AdGuard Home spustil zahteve iz teh naslovov IP.",
+    "access_disallowed_desc": "Seznam CIDR-jev, naslovov IP ali <a>ID-jev odjemalcev</a>. Če ta seznam vsebuje vnose, bo AdGuard Home zavrnil zahteve teh odjemalcev. To polje je prezrto, če so vnosi v dovoljenih odjemalcih.",
     "access_blocked_title": "Prepovedane domene",
     "access_blocked_desc": "Ne gre zamenjati s filtri. AdGuard Home spusti poizvedbe DNS, ki se ujemajo s temi domenami, in te poizvedbe se niti ne pojavijo v dnevniku poizvedb. Določite lahko natančna imena domen, nadomestne znake ali pravila filtriranja URL-jev, npr. ustrezno \"example.org\", \"*.example.org\" ali \"|| example.org ^\".",
     "access_settings_saved": "Nastavitve dostopa so uspešno shranjene",
@@ -475,8 +475,8 @@
     "dns_rewrites": "Prepisovanja NDS",
     "form_domain": "Vnesite domeno ali nadomestni znak",
     "form_answer": "Vnesite IP naslov ali ime domene",
-    "form_error_domain_format": "Neveljavna oblika domene",
-    "form_error_answer_format": "Neveljavna oblika odgovora",
+    "form_error_domain_format": "Neveljavna oblika domene.",
+    "form_error_answer_format": "Neveljavna oblika odgovora.",
     "configure": "Konfiguriraj",
     "main_settings": "Glavne nastavitve",
     "block_services": "Onemogoči določene storitve",
@@ -507,7 +507,7 @@
     "filter_updated": "Filter je bil uspešno posodobljen",
     "statistics_configuration": "Nastavitve statistike",
     "statistics_retention": "Statistika zadrževanja",
-    "statistics_retention_desc": "Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
+    "statistics_retention_desc": "Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni.",
     "statistics_clear": " Počisti statistiko",
     "statistics_clear_confirm": "Ali ste prepričani, da želite počistiti statistiko?",
     "statistics_retention_confirm": "Ali ste prepričani, da želite spremeniti zadrževanje statistike? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
@@ -532,7 +532,7 @@
     "netname": "Ime omrežja",
     "network": "Omrežje",
     "descr": "Opis",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Več o</0> ustvarjanju lastnih seznamov gostiteljev.",
     "blocked_by_response": "Onemogočeno s CNAME ali IP v odgovoru",
     "blocked_by_cname_or_ip": "Onemogočeno s CNAME ali IP naslovom",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "To bo izvedlo naslednja opravila: <0>Deaktiviraj sistemski DNSStubListener</0> <0>Nastavi naslov strežnika DNS na 127.0.0.1</0> <0>Zamenjaj cilj simbolične povezave /etc/resolv.conf with /run/systemd/resolve/resolv.conf</0> <0>Zaustavi DNSStubListener (znova naloži storitev systemd-resolved)",
     "autofix_warning_result": "Kot rezultat, bo vse zahteve DNS iz vašega sistema privzeto obdelal AdGuard Home.",
     "tags_title": "Oznake",
-    "tags_desc": "Izberete lahko oznake, ki ustrezajo odjemalcu. Oznake lahko vključite v pravila filtriranja in vam omogočajo, da jih natančneje uporabite. <0>Več o tem</0>",
+    "tags_desc": "Izberete lahko oznake, ki ustrezajo odjemalcu. Oznake lahko vključite v pravila filtriranja in vam omogočajo, da jih natančneje uporabite. <0>Več o tem</0>.",
     "form_select_tags": "Izberite odjemalske oznake",
     "check_title": "Preveri filtriranje",
-    "check_desc": "Preverite, ali je ime gostitelja filtrirano",
+    "check_desc": "Preverite, ali je ime gostitelja filtrirano.",
     "check": "Preveri",
     "form_enter_host": "Vnesite ime gostitelja",
     "filtered_custom_rules": "Filtrirano s pravili filtriranja po meri",
@@ -598,15 +598,15 @@
     "blocklist": "Seznam nedovoljenih",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Velikost predpomnilnika",
-    "cache_size_desc": "Velikost predpomnilnika DNS (v bajtih)",
+    "cache_size_desc": "Velikost predpomnilnika DNS (v bajtih).",
     "cache_ttl_min_override": "Preglasi najmanjši TTL",
     "cache_ttl_max_override": "Preglasi največji TTL",
     "enter_cache_size": "Vnesite velikost predpomnilnika (v bajtih)",
     "enter_cache_ttl_min_override": "Vnesite najmanjši TTL (v sekundah)",
     "enter_cache_ttl_max_override": "Vnesite največji TTL (v sekundah)",
-    "cache_ttl_min_override_desc": "Razširite kratke vrednosti časa v živo (v sekundah), ki jih prejme strežnik za predpomnjenje, ko predpomni odzive DNS",
-    "cache_ttl_max_override_desc": "Nastavi največjo vrednost časa v živo (v sekundah) za vnose v predpomnilnik DNS",
-    "ttl_cache_validation": "Najmanjša vrednost predpomnilnika TTL mora biti manjša ali enaka največji vrednosti",
+    "cache_ttl_min_override_desc": "Podaljšajte kratke življenjske vrednosti (sekunde), prejete od gorvodnega strežnika pri predpomnjenju odgovorov DNS.",
+    "cache_ttl_max_override_desc": "Nastavite največjo vrednost življenjske dobe (sekunde) za vnose v predpomnilniku DNS.",
+    "ttl_cache_validation": "Najmanjša preglasitev TTL predpomnilnika mora biti manjša ali enaka najvišji.",
     "cache_optimistic": "Optimistično predpomnjenje",
     "cache_optimistic_desc": "Poskrbi, da se AdGuard Home odzove iz predpomnilnika, tudi ko vnosi potečejo, in jih tudi poskusi osvežiti.",
     "filter_category_general": "Splošno",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home bo izpustil vse poizvedbe DNS iz tega odjemalca.",
     "filter_allowlist": "OPOZORILO: S to akcijo bo pravilo \"{{disallowed_rule}}\" izključeno s seznama dovoljenih odjemalcev.",
     "last_rule_in_allowlist": "Tega odjemalca ni mogoče onemogočiti, ker izključitev pravila \"{{disallowed_rule}}\" bo ONEMOGOČILO seznam 'Dovoljeni odjemalci'.",
-    "experimental": "Eksperimentalno",
     "use_saved_key": "Uporabi prej shranjeni ključ",
     "parental_control": "Starševski nadzor",
     "safe_browsing": "Varno brskanje",
-    "served_from_cache": "{{value}} <i>(postreženo iz predpomnilnika)</i>"
+    "served_from_cache": "{{value}} <i>(postreženo iz predpomnilnika)</i>",
+    "form_error_password_length": "Geslo mora vsebovati najmanj {{value}} znakov."
 }
diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json
index 59aad3a0..3c6a19f2 100644
--- a/client/src/__locales/sr-cs.json
+++ b/client/src/__locales/sr-cs.json
@@ -524,7 +524,7 @@
     "allowed": "Dozvoljeno",
     "filtered": "Filtrirano",
     "rewritten": "Prepisano",
-    "safe_search": "Sigurna pretraga",
+    "safe_search": "uključi sigurno pretraživanje",
     "blocklist": "Lista blokiranih",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Veličina predmemorije",
@@ -549,6 +549,5 @@
     "click_to_view_queries": "Kliknite da pogledate zahteve",
     "port_53_faq_link": "Port 53 je najčešće zauzet od \"DNSStubListener\" ili \"systemd-resolved\" usluga. Pročitajte <0>ovo uputstvo</0> kako da to rešite.",
     "adg_will_drop_dns_queries": "AdGuard Home će odbacivati sve DNS unose od ovog klijenta.",
-    "experimental": "Eksperimentalno",
     "parental_control": "Roditeljska kontrola"
 }
diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json
index 195a651f..8ab55d81 100644
--- a/client/src/__locales/sv.json
+++ b/client/src/__locales/sv.json
@@ -36,15 +36,15 @@
     "dhcp_ipv4_settings": "DHCP IPv4 inställningar",
     "dhcp_ipv6_settings": "DHCP IPv6 inställningar",
     "form_error_required": "Obligatoriskt fält",
-    "form_error_ip4_format": "Ogiltig IPv4-adress",
+    "form_error_ip4_format": "Ogiltig IPv4-adress.",
     "form_error_ip4_range_start_format": "Ogiltig IPv4-adress för starten av intervallet",
     "form_error_ip4_range_end_format": "Ogiltig IPv4-adress för slutet av intervallet",
     "form_error_ip4_gateway_format": "Ogiltig IPv4 adress för gatewayen",
-    "form_error_ip6_format": "Ogiltig IPv6-adress",
-    "form_error_ip_format": "Ogiltig IP-adress",
-    "form_error_mac_format": "Ogiltig MAC-adress",
+    "form_error_ip6_format": "Ogiltig IPv6-adress.",
+    "form_error_ip_format": "Ogiltig IP-adress.",
+    "form_error_mac_format": "Ogiltig MAC-adress.",
     "form_error_client_id_format": "Ogiltigt klient-ID",
-    "form_error_server_name": "Ogiltigt servernamn",
+    "form_error_server_name": "Ogiltigt servernamn.",
     "form_error_subnet": "Subnätet \"{{cidr}}\" innehåller inte IP-adressen \"{{ip}}\"",
     "form_error_positive": "Måste vara större än noll",
     "out_of_range_error": "Måste vara utanför intervallet \"{{start}}\"-\"{{end}}\"",
@@ -94,7 +94,7 @@
     "filters": "Filter",
     "filter": "Filter",
     "query_log": "Förfrågningslogg",
-    "compact": "Komprimera",
+    "compact": "Kompakt",
     "nothing_found": "Inget hittades",
     "faq": "FAQ",
     "version": "version",
@@ -532,7 +532,7 @@
     "netname": "Nätverksnamn",
     "network": "Nätverk",
     "descr": "Beskrivning",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Mer info</0> om att skapa dina egna blockeringslistor för värdar.",
     "blocked_by_response": "Blockerad av CNAME eller IP i svaret",
     "blocked_by_cname_or_ip": "Blockerad av CNAME eller IP",
@@ -562,7 +562,7 @@
     "choose_from_list": "Välj från listan",
     "add_custom_list": "Lägg till en anpassad lista",
     "host_whitelisted": "Värden är tillåten",
-    "check_ip": "IP adresser: {{ip}}",
+    "check_ip": "IP-adresser: {{ip}}",
     "check_cname": "CNAME: {{cname}}",
     "check_reason": "Anledning: {{reason}}",
     "check_service": "Service namn: {{service}}",
@@ -594,7 +594,7 @@
     "allowed": "Vitlistade",
     "filtered": "Filtrerad",
     "rewritten": "Omskriven",
-    "safe_search": "Säker surf",
+    "safe_search": "Säker sökning",
     "blocklist": "Blocklista",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Cachestorlek",
@@ -624,7 +624,6 @@
     "adg_will_drop_dns_queries": "AdGuard Home kommer att kasta alla DNS-frågor från den här klienten.",
     "filter_allowlist": "VARNING: Denna åtgärd kommer också att utesluta regeln \"{{disallowed_rule}}\" från listan över tillåtna klienter.",
     "last_rule_in_allowlist": "Det går inte att avvisa den här klienten eftersom att utesluta regeln \"{{disallowed_rule}}\" kommer att INAKTIVERA listan \"Tillåtna klienter\".",
-    "experimental": "Experimentell",
     "use_saved_key": "Använd den tidigare sparade nyckeln",
     "parental_control": "Föräldrakontroll",
     "safe_browsing": "Säker surfning",
diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json
index bf0431f7..86683857 100644
--- a/client/src/__locales/tr.json
+++ b/client/src/__locales/tr.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "İstemci ayarları",
-    "example_upstream_reserved": "<0>Belirli alan adları için</0> DNS üst sunucusu tanımlayabilirsiniz.",
-    "example_upstream_comment": "Bir yorum belirtebilirsiniz",
+    "example_upstream_reserved": "<0>belirli alan adları</0> için bir üst sunucusu;",
+    "example_upstream_comment": "bir yorum.",
     "upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.",
     "parallel_requests": "Paralel istekler",
     "load_balancing": "Yük dengeleme",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP yapılandırması başarıyla kaydedildi",
     "dhcp_ipv4_settings": "DHCP IPv4 Ayarları",
     "dhcp_ipv6_settings": "DHCP IPv6 Ayarları",
-    "form_error_required": "Gerekli alan",
-    "form_error_ip4_format": "IPv4 adresi geçersiz",
-    "form_error_ip4_range_start_format": "Başlangıç aralığı IPv4 adresi geçersiz",
-    "form_error_ip4_range_end_format": "Bitiş aralığı IPv4 adresi geçersiz",
-    "form_error_ip4_gateway_format": "Ağ geçidi IPv4 adresi geçersiz",
-    "form_error_ip6_format": "IPv6 adresi geçersiz",
-    "form_error_ip_format": "IP adresi geçersiz",
-    "form_error_mac_format": "MAC adresi geçersiz",
-    "form_error_client_id_format": "İstemci kimliği yalnızca sayılar, küçük harfler ve kısa çizgiler içermelidir",
-    "form_error_server_name": "Sunucu adı geçersiz",
-    "form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor",
-    "form_error_positive": "0'dan büyük olmalıdır",
-    "out_of_range_error": "\"{{start}}\"-\"{{end}}\" aralığının dışında olmalıdır",
-    "lower_range_start_error": "Başlangıç aralığından daha düşük olmalıdır",
-    "greater_range_start_error": "Başlangıç aralığından daha büyük olmalıdır",
-    "greater_range_end_error": "Bitiş aralığından daha büyük olmalıdır",
-    "subnet_error": "Adresler bir alt ağda olmalıdır",
-    "gateway_or_subnet_invalid": "Alt ağ maskesi geçersiz",
+    "form_error_required": "Gerekli alan.",
+    "form_error_ip4_format": "IPv4 adresi geçersiz.",
+    "form_error_ip4_range_start_format": "Başlangıç aralığı IPv4 adresi geçersiz.",
+    "form_error_ip4_range_end_format": "Bitiş aralığı IPv4 adresi geçersiz.",
+    "form_error_ip4_gateway_format": "Ağ geçidi IPv4 adresi geçersiz.",
+    "form_error_ip6_format": "IPv6 adresi geçersiz.",
+    "form_error_ip_format": "IP adresi geçersiz.",
+    "form_error_mac_format": "MAC adresi geçersiz.",
+    "form_error_client_id_format": "İstemci Kimliği yalnızca sayılar, küçük harfler ve kısa çizgiler içermelidir.",
+    "form_error_server_name": "Sunucu adı geçersiz.",
+    "form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor.",
+    "form_error_positive": "0'dan büyük olmalıdır.",
+    "out_of_range_error": "\"{{start}}\"-\"{{end}}\" aralığının dışında olmalıdır.",
+    "lower_range_start_error": "Başlangıç aralığından daha düşük olmalıdır.",
+    "greater_range_start_error": "Başlangıç aralığından daha büyük olmalıdır.",
+    "greater_range_end_error": "Bitiş aralığından daha büyük olmalıdır.",
+    "subnet_error": "Adresler bir alt ağda olmalıdır.",
+    "gateway_or_subnet_invalid": "Alt ağ maskesi geçersiz.",
     "dhcp_form_gateway_input": "Ağ geçidi IP",
     "dhcp_form_subnet_input": "Alt ağ maskesi",
     "dhcp_form_range_title": "IP adresi aralığı",
@@ -143,7 +143,7 @@
     "use_adguard_browsing_sec_hint": "AdGuard Home, alan adının gezinti koruması web hizmeti tarafından engellenip engellenmediğini kontrol eder. Kontrolü gerçekleştirmek için gizlilik dostu arama API'sini kullanır: sunucuya yalnızca SHA256 karma alan adının kısa bir ön eki gönderilir.",
     "use_adguard_parental": "AdGuard ebeveyn denetimi web hizmetini kullan",
     "use_adguard_parental_hint": "AdGuard Home, alan adının yetişkin içerik bulundurup bulundurmadığını kontrol eder. Gezinti koruması web hizmeti ile kullandığımız aynı gizlilik dostu API'yi kullanır.",
-    "enforce_safe_search": "Güvenli aramayı kullan",
+    "enforce_safe_search": "Güvenli Aramayı kullan",
     "enforce_save_search_hint": "AdGuard Home, şu arama motorlarında güvenli aramayı uygular: Google, YouTube, Bing, DuckDuckGo, Yandex ve Pixabay.",
     "no_servers_specified": "Sunucu belirtilmedi",
     "general_settings": "Genel ayarlar",
@@ -165,10 +165,10 @@
     "enabled_filtering_toast": "Filtreleme etkin",
     "disabled_safe_browsing_toast": "Güvenli Gezinti devre dışı bırakıldı",
     "enabled_safe_browsing_toast": "Güvenli Gezinti etkinleştirildi",
-    "disabled_parental_toast": "Ebeveyn denetimi devre dışı bırakıldı",
-    "enabled_parental_toast": "Ebeveyn denetimi etkinleştirildi",
-    "disabled_safe_search_toast": "Güvenli arama devre dışı bırakıldı",
-    "enabled_save_search_toast": "Güvenli arama etkinleştirildi",
+    "disabled_parental_toast": "Ebeveyn Denetimi devre dışı bırakıldı",
+    "enabled_parental_toast": "Ebeveyn Denetimi etkinleştirildi",
+    "disabled_safe_search_toast": "Güvenli Arama devre dışı bırakıldı",
+    "enabled_save_search_toast": "Güvenli Arama etkinleştirildi",
     "enabled_table_header": "Etkin",
     "name_table_header": "İsim",
     "list_url_table_header": "Liste URL'si",
@@ -196,25 +196,25 @@
     "choose_allowlist": "İzin listelerini seçin",
     "enter_valid_blocklist": "Engel listesine geçerli bir URL girin.",
     "enter_valid_allowlist": "İzin listesine geçerli bir URL girin.",
-    "form_error_url_format": "URL biçimi geçersiz",
-    "form_error_url_or_path_format": "Listenin URL adresi veya dosya konumu geçersiz",
+    "form_error_url_format": "URL biçimi geçersiz.",
+    "form_error_url_or_path_format": "Listenin URL adresi veya dosya konumu geçersiz.",
     "custom_filter_rules": "Özel filtreleme kuralları",
     "custom_filter_rules_hint": "Her satıra bir kural girin. Reklam engelleme kuralı veya ana bilgisayar dosyası söz dizimi kullanabilirsiniz.",
     "system_host_files": "Sistem ana bilgisayar dosyaları",
     "examples_title": "Örnekler",
-    "example_meaning_filter_block": "example.org alan adına ve tüm alt alan adlarına olan erişimi engeller",
-    "example_meaning_filter_whitelist": "example.org alan adına ve tüm alt alan adlarına olan erişim engelini kaldırır",
-    "example_meaning_host_block": "AdGuard Home, example.org adresi için 127.0.0.1 adresine yönlendirme yapacaktır (alt alan adları için geçerli değildir)",
-    "example_comment": "! Buraya bir yorum ekledim",
-    "example_comment_meaning": "sadece bir yorum",
-    "example_comment_hash": "# Bir yorum daha ekledim",
-    "example_regex_meaning": "belirtilen düzenli ifadelerle eşleşen alan adlarına erişimi engelle",
-    "example_upstream_regular": "normal DNS (UDP üzerinden)",
-    "example_upstream_dot": "şifrelenmiş <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "şifrelenmiş <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "şifrelenmiş <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "<1>DNSCrypt</1> veya <2>DNS-over-HTTPS</2> çözümleyicileri için <0>DNS Damgaları</0> kullanabilirsiniz",
-    "example_upstream_tcp": "normal DNS (TCP üzerinden)",
+    "example_meaning_filter_block": "example.org'a ve tüm alt alanlarına erişimi engeller;",
+    "example_meaning_filter_whitelist": "example.org'a ve tüm alt alanlarına erişimin engelini kaldırır;",
+    "example_meaning_host_block": "example.org için 127.0.0.1 ile yanıt verin (ancak alt alanları için değil);",
+    "example_comment": "! Buraya bir yorum gelir.",
+    "example_comment_meaning": "sadece bir yorum;",
+    "example_comment_hash": "# Ayrıca bir yorum.",
+    "example_regex_meaning": "belirtilen düzenli ifadelerle eşleşen alan adlarına erişimi engelle.",
+    "example_upstream_regular": "normal DNS (UDP üzerinden);",
+    "example_upstream_dot": "şifrelenmiş <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "şifrelenmiş <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "şifrelenmiş <0>DNS-over-QUIC</0> (deneysel);",
+    "example_upstream_sdns": "<1>DNSCrypt</1> veya <2>DNS-over-HTTPS</2> çözümleyicileri için <0>DNS Damgaları</0>;",
+    "example_upstream_tcp": "normal DNS (TCP üzerinden);",
     "all_lists_up_to_date_toast": "Tüm listeler güncel durumda",
     "updated_upstream_dns_toast": "Üst sunucular başarıyla kaydedildi",
     "dns_test_ok_toast": "Belirtilen DNS sunucuları düzgün çalışıyor",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Tam arama için çift tırnak işareti kullanın",
     "query_log_retention_confirm": "Sorgu günlüğü saklama süresini değiştirmek istediğinize emin misiniz? Aralık değerini azaltırsanız, bazı veriler kaybolacaktır",
     "anonymize_client_ip": "İstemcinin IP adresini gizle",
-    "anonymize_client_ip_desc": "İstemcinin IP adresini günlüklere ve istatistiklere kaydetmeyin",
+    "anonymize_client_ip_desc": "İstemcinin tam IP adresini günlüklere veya istatistiklere kaydetmeyin.",
     "dns_config": "DNS sunucu yapılandırması",
     "dns_cache_config": "DNS önbellek yapılandırması",
-    "dns_cache_config_desc": "Burada DNS önbelleğini yapılandırabilirsiniz",
+    "dns_cache_config_desc": "Burada DNS önbelleğini yapılandırabilirsiniz.",
     "blocking_mode": "Engelleme modu",
     "default": "Varsayılan",
     "nxdomain": "NXDOMAIN",
@@ -276,8 +276,8 @@
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
     "client_id": "İstemci Kimliği",
-    "client_id_placeholder": "İstemci kimliği girin",
-    "client_id_desc": "Farklı istemciler, özel bir istemci kimliği ile tanımlanabilir. <a>Burada</a> istemcileri nasıl tanımlayacağınız hakkında daha fazla bilgi edinebilirsiniz.",
+    "client_id_placeholder": "İstemci Kimliği girin",
+    "client_id_desc": "İstemciler, İstemci Kimliği ile tanımlanabilir. İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiyi <a>buradan</a> öğrenin.",
     "download_mobileconfig_doh": "DNS-over-HTTPS için .mobileconfig dosyasını indir",
     "download_mobileconfig_dot": "DNS-over-TLS için .mobileconfig dosyasını indir",
     "download_mobileconfig": "Yapılandırma dosyasını indir",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Dinleme arayüzü",
     "install_settings_port": "Bağlantı noktası",
     "install_settings_interface_link": "AdGuard Home yönetici web arayüzü sayfanız şu adresten erişilebilir olacaktır:",
-    "form_error_port": "Geçerli bir bağlantı noktası değeri girin",
+    "form_error_port": "Geçerli bir bağlantı noktası değeri girin.",
     "install_settings_dns": "DNS sunucusu",
     "install_settings_dns_desc": "Cihazlarınızı veya yönlendiricinizi şu adresteki DNS sunucusunu kullanması için ayarlamanız gerekecek:",
     "install_settings_all_interfaces": "Tüm arayüzler",
@@ -338,7 +338,7 @@
     "install_devices_windows_list_4": "Kullandığınız aktif bağlantının üzerine sağ tıklayın ve Özellikler öğesine tıklayın.",
     "install_devices_windows_list_5": "Listede \"İnternet Protokolü Sürüm 4 (TCP/IPv4)\" (veya IPv6 için \"İnternet Protokolü Sürüm 6 (TCP/IPv6)\") öğesini bulun, seçin ve ardından tekrar Özellikler'e tıklayın.",
     "install_devices_windows_list_6": "\"Aşağıdaki DNS sunucu adreslerini kullan\"ı seçin ve AdGuard Home sunucu adreslerinizi girin.",
-    "install_devices_macos_list_1": "Apple simgesinde bulunan Sistem Tercihleri'ne tıklayın.",
+    "install_devices_macos_list_1": "Apple simgesine tıklayın ve Sistem Tercihleri'ne gidin.",
     "install_devices_macos_list_2": "Ağ'a tıklayın.",
     "install_devices_macos_list_3": "Listedeki ilk bağlantıyı seçin ve Gelişmiş öğesine tıklayın.",
     "install_devices_macos_list_4": "DNS sekmesini seçin ve AdGuard Home sunucunuzun adreslerini girin.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Ana Sayfayı Aç",
     "install_saved": "Başarıyla kaydedildi",
     "encryption_title": "Şifreleme",
-    "encryption_desc": "DNS ve yönetici web arayüzü için şifreleme (HTTPS/TLS) desteği",
+    "encryption_desc": "DNS ve yönetici web arayüzü için şifreleme (HTTPS/TLS) desteği.",
     "encryption_config_saved": "Şifreleme yapılandırması kaydedildi",
     "encryption_server": "Sunucu adı",
     "encryption_server_enter": "Alan adınızı girin",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "HTTPS bağlantı noktası yapılandırılırsa, AdGuard Home yönetici arayüzüne HTTPS aracılığıyla erişilebilir olacak ve ayrıca '/dns-query' üzerinden DNS-over-HTTPS bağlantısı sağlanır.",
     "encryption_dot": "DNS-over-TLS bağlantı noktası",
     "encryption_dot_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, DNS-over-TLS sunucusunu bu bağlantı noktası üzerinden çalıştıracaktır.",
-    "encryption_doq": "DNS-over-QUIC bağlantı noktası",
+    "encryption_doq": "DNS-over-QUIC bağlantı noktası (deneysel)",
     "encryption_doq_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, DNS-over-QUIC sunucusunu bu bağlantı noktası üzerinden çalıştıracaktır. Bu özellik deneme aşamasındadır ve güvenilir olmayabilir. Ayrıca, şu anda bu özelliği destekleyen çok fazla istemci yok.",
     "encryption_certificates": "Sertifikalar",
     "encryption_certificates_desc": "Şifrelemeyi kullanmak için alan adınıza geçerli bir SSL sertifika zinciri sağlamanız gerekir. <0>{{link}}</0> adresinden ücretsiz bir sertifika alabilir veya güvenilir Sertifika Yetkililerinden satın alabilirsiniz.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Sertifikanızın PEM biçimli özel anahtarını kopyalayıp buraya yapıştırın.",
     "encryption_enable": "Şifrelemeyi etkinleştir (HTTPS, DNS-over-HTTPS ve DNS-over-TLS)",
     "encryption_enable_desc": "Şifrelemeyi etkinleştirirseniz, AdGuard Home yönetici arayüzü HTTPS üzerinden çalışır ve DNS sunucusu, DNS-over-HTTPS ve DNS-over-TLS üzerinden gelen istekleri dinler.",
-    "encryption_chain_valid": "Sertifika zinciri geçerli",
-    "encryption_chain_invalid": "Sertifika zinciri geçersiz",
-    "encryption_key_valid": "Bu geçerli bir {{type}} özel anahtar",
-    "encryption_key_invalid": "Bu geçersiz bir {{type}} özel anahtar",
+    "encryption_chain_valid": "Sertifika zinciri geçerli.",
+    "encryption_chain_invalid": "Sertifika zinciri geçersiz.",
+    "encryption_key_valid": "Bu geçerli bir {{type}} özel anahtar.",
+    "encryption_key_invalid": "Bu geçersiz bir {{type}} özel anahtar.",
     "encryption_subject": "Konu",
     "encryption_issuer": "Sağlayan",
     "encryption_hostnames": "Ana bilgisayar adları",
     "encryption_reset": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?",
     "topline_expiring_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını</0> güncelleyin.",
     "topline_expired_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını</0> güncelleyin.",
-    "form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin",
-    "form_error_port_unsafe": "Bu bağlantı noktası güvenli değil",
-    "form_error_equal": "Aynı olmamalı",
-    "form_error_password": "Şifreler uyuşmuyor",
+    "form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin.",
+    "form_error_port_unsafe": "Bu bağlantı noktası güvenli değil.",
+    "form_error_equal": "Aynı olmamalı.",
+    "form_error_password": "Parolalar uyuşmuyor.",
     "reset_settings": "Ayarları sıfırla",
     "update_announcement": "AdGuard Home {{version}} sürümü mevcut! Daha fazla bilgi için <0>buraya tıklayın.</0>",
     "setup_guide": "Kurulum Rehberi",
     "dns_addresses": "DNS adresleri",
     "dns_start": "DNS sunucusu başlatılıyor",
-    "dns_status_error": "DNS sunucusunun durumu denetlenirken bir hata oluştu",
+    "dns_status_error": "DNS sunucusunun durumu denetlenirken bir hata oluştu.",
     "down": "Kapalı",
     "fix": "Düzelt",
     "dns_providers": "Aralarından seçim yapabileceğiniz, bilinen <0>DNS sağlayıcıların listesi</0>.",
@@ -405,8 +405,8 @@
     "update_failed": "Otomatik güncelleme başarısız oldu. Elle güncellemek için lütfen <a>bu adımları uygulayın</a>.",
     "manual_update": "Elle güncellemek için lütfen <a>bu adımları uygulayın</a>.",
     "processing_update": "Lütfen bekleyin, AdGuard Home güncelleniyor",
-    "clients_title": "İstemciler",
-    "clients_desc": "AdGuard Home'a bağlı cihazları yapılandırın",
+    "clients_title": "Kalıcı istemciler",
+    "clients_desc": "AdGuard Home'a bağlı cihazlar için kalıcı istemci kayıtlarını yapılandırın.",
     "settings_global": "Genel",
     "settings_custom": "Özel",
     "table_client": "İstemci",
@@ -417,7 +417,7 @@
     "client_edit": "İstemciyi Düzenle",
     "client_identifier": "Tanımlayıcı",
     "ip_address": "IP adresi",
-    "client_identifier_desc": "İstemciler IP adresi, CIDR, MAC adresi veya özel bir istemci kimliği ile tanımlanabilir (DoT/DoH/DoQ için kullanılabilir). İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiyi <0>burada</0> bulabilirsiniz.",
+    "client_identifier_desc": "İstemciler IP adresleri, CIDR, MAC adresleri veya ClientID (DoT/DoH/DoQ için kullanılabilir) ile tanımlanabilir. İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiyi <0>buradan</0> edinebilirsiniz.",
     "form_enter_ip": "IP girin",
     "form_enter_subnet_ip": "\"{{cidr}}\" alt ağına bir IP adresi girin",
     "form_enter_mac": "MAC adresi girin",
@@ -432,14 +432,14 @@
     "clients_not_found": "İstemci bulunamadı",
     "client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?",
     "list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?",
-    "auto_clients_title": "İstemciler (çalışma zamanı)",
-    "auto_clients_desc": "AdGuard Home'u kullanan ancak yapılandırmada depolanmayan istemcilerle ilgili veriler",
+    "auto_clients_title": "Çalışma zamanı istemcileri",
+    "auto_clients_desc": "Henüz AdGuard Home'u kullanabilecek Kalıcı istemciler listesinde olmayan cihazlar.",
     "access_title": "Erişim ayarları",
     "access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz.",
     "access_allowed_title": "İzin verilen istemciler",
-    "access_allowed_desc": "CIDR'lerin, IP adreslerinin veya istemci kimliklerinin listesi. Yapılandırılırsa, AdGuard Home yalnızca bu istemcilerden gelen istekleri kabul eder.",
+    "access_allowed_desc": "CIDR'lerin, IP adreslerinin veya <a>İstemci Kimliklerin</a> listesi. Bu listede girişler varsa, AdGuard Home yalnızca bu istemcilerden gelen istekleri kabul eder.",
     "access_disallowed_title": "İzin verilmeyen istemciler",
-    "access_disallowed_desc": "CIDR'lerin, IP adreslerinin veya istemci kimliklerinin listesi. Yapılandırılırsa, AdGuard Home bu istemcilerden gelen istekleri keser. İzin verilen istemciler yapılandırılırsa, bu alan yok sayılır.",
+    "access_disallowed_desc": "CIDR'lerin, IP adreslerinin veya <a>İstemci Kimliklerin</a> listesi. Bu listede girişler varsa, AdGuard Home bu istemcilerden gelen istekleri keser. İzin verilen istemcilerde girişler varsa, bu alan yok sayılır.",
     "access_blocked_title": "İzin verilmeyen alan adları",
     "access_blocked_desc": "Bu işlem filtrelerle ilgili değildir. AdGuard Home, bu alan adlarından gelen DNS sorgularını yanıtsız bırakır ve bu sorgular sorgu günlüğünde görünmez. Tam alan adlarını, joker karakterleri veya URL filtre kurallarını belirtebilirsiniz, ör. \"example.org\", \"*.example.org\" veya \"||example.org^\".",
     "access_settings_saved": "Erişim ayarları başarıyla kaydedildi!",
@@ -453,7 +453,7 @@
     "setup_dns_privacy_4": "Bir iOS 14 veya macOS Big Sur cihazında, DNS ayarlarına <highlight>DNS-over-HTTPS</highlight> veya <highlight>DNS-over-TLS</highlight> sunucuları ekleyen özel '.mobileconfig' dosyasını indirebilirsiniz.",
     "setup_dns_privacy_android_1": "Android 9, yerel olarak DNS-over-TLS protokolünü destekler. Yapılandırmak için Ayarlar → Ağ ve İnternet → Gelişmiş → Özel DNS seçeneğine gidin ve alan adınızı girin.",
     "setup_dns_privacy_android_2": "<0>Android için AdGuard</0>, <1>DNS-over-HTTPS</1> ve <1>DNS-over-TLS</1> protokolünü destekler.",
-    "setup_dns_privacy_android_3": "<0>Intra</0> Android'e <1>DNS-over-HTTPS</1> desteğini ekler.",
+    "setup_dns_privacy_android_3": "<0>Intra</0> Android'e <1>DNS-over-HTTPS</1> protokol desteğini ekler.",
     "setup_dns_privacy_ios_1": "<0>DNSCloak</0>, <1>DNS-over-HTTPS</1> protokolünü destekler, ancak kendi sunucunuzu kullanacak şekilde yapılandırmak için bir <2>DNS Damgası</2> oluşturmanız gerekir.",
     "setup_dns_privacy_ios_2": "<0>iOS için AdGuard</0>, <1>DNS-over-HTTPS</1> ve <1>DNS-over-TLS</1> protokolünü destekler.",
     "setup_dns_privacy_other_title": "Diğer kullanım alanları",
@@ -475,8 +475,8 @@
     "dns_rewrites": "DNS yeniden yazımları",
     "form_domain": "Alan adı veya joker karakter girin",
     "form_answer": "IP adresi veya alan adı girin",
-    "form_error_domain_format": "Alan adı biçimi geçersiz",
-    "form_error_answer_format": "Yanıt biçimi geçersiz",
+    "form_error_domain_format": "Alan adı biçimi geçersiz.",
+    "form_error_answer_format": "Yanıt biçimi geçersiz.",
     "configure": "Yapılandır",
     "main_settings": "Ana ayarlar",
     "block_services": "Belirli hizmetleri engelle",
@@ -507,7 +507,7 @@
     "filter_updated": "Liste başarıyla güncellendi",
     "statistics_configuration": "İstatistik yapılandırması",
     "statistics_retention": "İstatistikleri sakla",
-    "statistics_retention_desc": "Zaman değerini azaltırsanız, bazı veriler kaybolacaktır",
+    "statistics_retention_desc": "Zaman değerini azaltırsanız, bazı veriler kaybolacaktır.",
     "statistics_clear": " İstatistikleri temizle",
     "statistics_clear_confirm": "İstatistikleri temizlemek istediğinizden emin misiniz?",
     "statistics_retention_confirm": "İstatistik saklama süresini değiştirmek istediğinizden emin misiniz? Aralık değerini azaltırsanız, bazı veriler kaybolacaktır",
@@ -532,7 +532,7 @@
     "netname": "Ağ adı",
     "network": "Ağ",
     "descr": "Açıklama",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "Kendi ana bilgisayar listelerinizi oluşturma hakkında <0>daha fazla bilgi edinin</0>.",
     "blocked_by_response": "Yanıt olarak CNAME veya IP tarafından engellendi",
     "blocked_by_cname_or_ip": "CNAME veya IP tarafından engellendi",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Bu görevleri gerçekleştirecek: <0>Sistem DNSStubListener'ı devre dışı bırakın</0> <0>DNS sunucusu adresini 127.0.0.1 olarak ayarlayın</0> <0>/etc/resolv.conf'un sembolik bağlantı hedefini /run/systemd/resolve/resolv.conf ile değiştirin<0> <0>DNSStubListener'ı durdurun (systemd çözümlenmiş hizmeti yeniden yükleyin)</0>",
     "autofix_warning_result": "Sonuç olarak, sisteminizden gelen tüm DNS istekleri varsayılan olarak AdGuard Home tarafından işlenecektir.",
     "tags_title": "Etiketler",
-    "tags_desc": "İstemciyi tanımlayan etiketleri seçebilirsiniz. Etiketler, filtreleme kurallarına dahil edilebilir ve bunları daha doğru bir şekilde uygulamanıza olanak tanır. <0>Daha fazla bilgi edinin</0>",
+    "tags_desc": "İstemciye karşılık gelen etiketleri seçebilirsiniz. Etiketleri daha kesin olarak uygulamak için filtreleme kurallarına dahil edin. <0>Daha fazla bilgi edinin</0>.",
     "form_select_tags": "İstemci etiketlerini seçin",
     "check_title": "Filtrelemeyi denetleyin",
-    "check_desc": "Ana bilgisayar adının filtreleme durumunu kontrol edin",
+    "check_desc": "Ana bilgisayar adının filtreleme durumunu kontrol edin.",
     "check": "Denetle",
     "form_enter_host": "Ana bilgisayar adı girin",
     "filtered_custom_rules": "Özel filtreleme kurallarına göre filtrelendi",
@@ -594,19 +594,19 @@
     "allowed": "İzin verilen",
     "filtered": "Filtrelenen",
     "rewritten": "Yeniden yazılan",
-    "safe_search": "Güvenli arama",
+    "safe_search": "Güvenli Arama",
     "blocklist": "Engel listesi",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Önbellek boyutu",
-    "cache_size_desc": "DNS önbellek boyutu (bayt cinsinden)",
+    "cache_size_desc": "DNS önbellek boyutu (bayt cinsinden).",
     "cache_ttl_min_override": "Minimum TTL'i değiştir",
     "cache_ttl_max_override": "Maksimum TTL'i değiştir",
     "enter_cache_size": "Önbellek boyutunu girin (bayt)",
     "enter_cache_ttl_min_override": "Minimum TTL değerini girin (saniye olarak)",
     "enter_cache_ttl_max_override": "Maksimum TTL değerini girin (saniye olarak)",
-    "cache_ttl_min_override_desc": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan kullanım süresi değerini uzatın (saniye olarak)",
-    "cache_ttl_max_override_desc": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak)",
-    "ttl_cache_validation": "Minimum önbellek TTL değeri, maksimum değerden küçük veya bu değere eşit olmalıdır",
+    "cache_ttl_min_override_desc": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan kullanım süresi değerini uzatın (saniye olarak).",
+    "cache_ttl_max_override_desc": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak).",
+    "ttl_cache_validation": "Minimum önbellek TTL geçersiz kılma, maksimuma eşit veya bundan küçük olmalıdır.",
     "cache_optimistic": "İyimser önbelleğe alma",
     "cache_optimistic_desc": "Girişlerin süresi dolduğunda bile AdGuard Home'un önbellekten yanıt vermesini sağlayın ve bunları yenilemeye çalışın.",
     "filter_category_general": "Genel",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home, bu istemciden gelen tüm DNS sorgularını yok sayar.",
     "filter_allowlist": "UYARI: Bu işlem ayrıca \"{{disallowed_rule}}\" kuralını izin verilen istemciler listesinden hariç tutacaktır.",
     "last_rule_in_allowlist": "\"{{disallowed_rule}}\" kuralı hariç tutulduğunda \"İzin verilen istemciler\" listesi DEVRE DIŞI bırakılacağı için bu istemciye izin verilemez.",
-    "experimental": "Deneysel",
     "use_saved_key": "Önceden kaydedilmiş anahtarı kullan",
     "parental_control": "Ebeveyn Denetimi",
     "safe_browsing": "Güvenli Gezinti",
-    "served_from_cache": "{{value}} <i>(önbellekten kullanıldı)</i>"
+    "served_from_cache": "{{value}} <i>(önbellekten kullanıldı)</i>",
+    "form_error_password_length": "Parola en az {{value}} karakter uzunluğunda olmalı."
 }
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index 42db7636..eb721af4 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Налаштування клієнта",
-    "example_upstream_reserved": "Ви можете вказати DNS-сервер <0>для певних доменів</0>",
-    "example_upstream_comment": "Ви можете вказати коментар",
+    "example_upstream_reserved": "DNS-сервер <0>для певних доменів</0>;",
+    "example_upstream_comment": "коментар.",
     "upstream_parallel": "Використовувати паралельні запити, щоб пришвидшити вирішення одночасною чергою всіх оригінальних серверів.",
     "parallel_requests": "Паралельні запити",
     "load_balancing": "Балансування навантаження",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Конфігурацію DHCP-сервера успішно збережено",
     "dhcp_ipv4_settings": "Налаштування DHCP IPv4",
     "dhcp_ipv6_settings": "Налаштування DHCP IPv6",
-    "form_error_required": "Обов'язкове поле",
-    "form_error_ip4_format": "Неправильна IPv4-адреса",
-    "form_error_ip4_range_start_format": "Неправильна IPv4-адреса для початку діапазону",
-    "form_error_ip4_range_end_format": "Неправильна IPv4-адреса для кінця діапазону",
-    "form_error_ip4_gateway_format": "Неправильна IPv4-адреса для шлюзу",
-    "form_error_ip6_format": "Неправильна IPv6-адреса",
-    "form_error_ip_format": "Неправильна IP-адреса",
-    "form_error_mac_format": "Неправильна MAC-адреса",
-    "form_error_client_id_format": "ID клієнта має містити лише цифри, малі букви та дефіси",
-    "form_error_server_name": "Неправильна назва сервера",
-    "form_error_subnet": "Підмережа «{{cidr}}» не містить IP-адресу «{{ip}}»",
-    "form_error_positive": "Повинно бути більше 0",
-    "out_of_range_error": "Не повинна бути в діапазоні «{{start}}»−«{{end}}»",
-    "lower_range_start_error": "Має бути меншим за початкову адресу",
-    "greater_range_start_error": "Має бути більшим за початкову адресу",
-    "greater_range_end_error": "Має бути більшим за кінцеву адресу",
-    "subnet_error": "Адреси повинні бути в одній підмережі",
-    "gateway_or_subnet_invalid": "Неправильна маска підмережі",
+    "form_error_required": "Обов'язкове поле.",
+    "form_error_ip4_format": "Неправильна IPv4-адреса.",
+    "form_error_ip4_range_start_format": "Неправильна IPv4-адреса початку діапазону.",
+    "form_error_ip4_range_end_format": "Неправильна IPv4-адреса кінця діапазону.",
+    "form_error_ip4_gateway_format": "Неправильна IPv4-адреса шлюзу.",
+    "form_error_ip6_format": "Неправильна IPv6-адреса.",
+    "form_error_ip_format": "Неправильна IP-адреса.",
+    "form_error_mac_format": "Неправильна MAC-адреса.",
+    "form_error_client_id_format": "ClientID має містити лише цифри, малі латинські букви та дефіси.",
+    "form_error_server_name": "Неправильна назва сервера.",
+    "form_error_subnet": "Підмережа «{{cidr}}» не містить IP-адресу «{{ip}}».",
+    "form_error_positive": "Повинно бути більше ніж 0.",
+    "out_of_range_error": "Не повинна бути в діапазоні «{{start}}»−«{{end}}».",
+    "lower_range_start_error": "Має бути меншим за початкову адресу.",
+    "greater_range_start_error": "Має бути більшим за початкову адресу.",
+    "greater_range_end_error": "Має бути більшим за кінцеву адресу.",
+    "subnet_error": "Адреси повинні бути в одній підмережі.",
+    "gateway_or_subnet_invalid": "Неправильна маска підмережі.",
     "dhcp_form_gateway_input": "IP-адреса шлюзу",
     "dhcp_form_subnet_input": "Маска підмережі",
     "dhcp_form_range_title": "Діапазон IP-адрес",
@@ -196,20 +196,20 @@
     "choose_allowlist": "Обрати списки дозволених сайтів",
     "enter_valid_blocklist": "Введіть дійсну URL-адресу в список блокування.",
     "enter_valid_allowlist": "Введіть дійсну URL-адресу в список дозволів.",
-    "form_error_url_format": "Неправильний формат URL",
-    "form_error_url_or_path_format": "Помилкова URL-адреса чи абсолютний шлях до списку",
+    "form_error_url_format": "Неправильний формат URL.",
+    "form_error_url_or_path_format": "Неправильна URL-адреса або абсолютний шлях до списку.",
     "custom_filter_rules": "Власні правила фільтрування",
     "custom_filter_rules_hint": "Вводьте одне правило на рядок. Ви можете використовувати правила блокування чи синтаксис файлів hosts.",
     "system_host_files": "Системні hosts-файли",
     "examples_title": "Зразки",
-    "example_meaning_filter_block": "блокує доступ до домену example.org та всіх його піддоменів",
-    "example_meaning_filter_whitelist": "розблоковує доступ до домену example.org та всіх його піддоменів",
-    "example_meaning_host_block": "AdGuard Home повертатиме адресу 127.0.0.1 для домену example.org (але не його піддоменів).",
-    "example_comment": "! Так можна додавати коментар",
-    "example_comment_meaning": "просто коментар",
-    "example_comment_hash": "# Це також коментар",
-    "example_regex_meaning": "блокує доступ до доменів, що відповідають вказаному звичайному виразу",
-    "example_upstream_regular": "звичайний DNS (через UDP)",
+    "example_meaning_filter_block": "блокувати доступ до домену example.org та всіх його піддоменів;",
+    "example_meaning_filter_whitelist": "розблоковвати доступ до домену example.org та всіх його піддоменів;",
+    "example_meaning_host_block": "повертати адресу 127.0.0.1 для домену example.org, але не його піддоменів;",
+    "example_comment": "! Так можна додавати коментар.",
+    "example_comment_meaning": "просто коментар;",
+    "example_comment_hash": "# Також коментар.",
+    "example_regex_meaning": "блокувати доступ до доменів, що відповідають вказаному регулярному виразу.",
+    "example_upstream_regular": "звичайний DNS (через UDP);",
     "example_upstream_dot": "зашифрований <0>DNS-over-TLS</0>",
     "example_upstream_doh": "зашифрований <0>DNS-over-HTTPS</0>",
     "example_upstream_doq": "зашифрований <0>DNS-over-QUIC</0>",
@@ -431,7 +431,7 @@
     "clients_not_found": "Клієнтів не знайдено",
     "client_confirm_delete": "Ви впевнені, що хочете видалити клієнта «{{key}}»?",
     "list_confirm_delete": "Ви впевнені, що хочете видалити цей список?",
-    "auto_clients_title": "Клієнти (поза налаштуванням)",
+    "auto_clients_title": "Runtime-клієнти",
     "auto_clients_desc": "Дані про клієнтів, які використовують AdGuard Home, але не зберігаються в конфігурації",
     "access_title": "Налаштування доступу",
     "access_desc": "Тут ви можете налаштувати правила доступу для DNS-сервера AdGuard Home.",
@@ -531,7 +531,7 @@
     "netname": "Назва мережі",
     "network": "Мережа",
     "descr": "Опис",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Як створити власні списки блокування</0>.",
     "blocked_by_response": "У відповідь заблоковано по CNAME або IP",
     "blocked_by_cname_or_ip": "Заблоковано по CNAME або IP",
@@ -551,10 +551,10 @@
     "autofix_warning_list": "Це виконає наступні завдання: <0>Деактивує систему DNSStubListener</0> <0>Змінить адресу DNS сервера на 127.0.0.1</0> <0>Замінить символічне посилання /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Зупинить DNSStubListener (перезапустить сервіс systemd-resolved)</0>",
     "autofix_warning_result": "В результаті буде усталено, що усі DNS-запити вашої системи будуть опрацьовані AdGuard Home.",
     "tags_title": "Теги",
-    "tags_desc": "Ви можете вибрати теги, які відповідають клієнту. Теги можна використати в правилах фільтрування, щоб точніше застосовувати їх. <0>Докладніше</0>",
+    "tags_desc": "Ви можете вибрати теги, які відповідають клієнту. Теги можна використати в правилах фільтрування, щоб точніше застосовувати їх. <0>Докладніше</0>.",
     "form_select_tags": "Виберіть теги клієнта",
     "check_title": "Перевірте фільтрування",
-    "check_desc": "Перевірте чи фільтрується назва вузла",
+    "check_desc": "Перевірити чи фільтрується назва вузла.",
     "check": "Перевірити",
     "form_enter_host": "Введіть назву вузла",
     "filtered_custom_rules": "Відфільтровано за власними правилами фільтрування",
@@ -597,7 +597,7 @@
     "blocklist": "Список блокування",
     "milliseconds_abbreviation": "мс",
     "cache_size": "Розмір кешу",
-    "cache_size_desc": "Розмір кешу DNS (у байтах)",
+    "cache_size_desc": "Розмір кешу DNS (у байтах).",
     "cache_ttl_min_override": "Замінити мінімальний TTL",
     "cache_ttl_max_override": "Замінити максимальний TTL",
     "enter_cache_size": "Введіть розмір кешу (байт)",
@@ -623,9 +623,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home буде видаляти всі запити DNS із цього клієнта.",
     "filter_allowlist": "ПОПЕРЕДЖЕННЯ: Таким чином ви також виключите правило «{{disallowed_rule}}» зі списку дозволених клієнтів.",
     "last_rule_in_allowlist": "Неможливо заблокувати цього клієнта, тому що правило «{{disallowed_rule}}» ВИМКНЕ режим списку дозволів.",
-    "experimental": "Експериментальний",
     "use_saved_key": "Використати раніше збережений ключ",
     "parental_control": "Батьківський контроль",
     "safe_browsing": "Безпечний перегляд",
-    "served_from_cache": "{{value}} <i>(отримано з кешу)</i>"
+    "served_from_cache": "{{value}} <i>(отримано з кешу)</i>",
+    "form_error_password_length": "Пароль мусить мати принаймні {{value}} символів."
 }
diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json
index 394801f1..28ec3b1b 100644
--- a/client/src/__locales/vi.json
+++ b/client/src/__locales/vi.json
@@ -583,7 +583,7 @@
     "allowed": "Được phép",
     "filtered": "Đã lọc",
     "rewritten": "Đã viết lại",
-    "safe_search": "Tìm kiếm an toàn",
+    "safe_search": "Kích hoạt Tìm kiếm An toàn",
     "blocklist": "Danh sách chặn",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Kích thước cache",
@@ -611,6 +611,5 @@
     "adg_will_drop_dns_queries": "AdGuard Home sẽ loại bỏ tất cả các truy vấn DNS từ ứng dụng khách này.",
     "filter_allowlist": "CẢNH BÁO: Hành động này cũng sẽ loại trừ quy tắc \"{{disallowed_rule}}\" khỏi danh sách các ứng dụng khách được phép.",
     "last_rule_in_allowlist": "Không thể không cho phép ứng dụng khách này vì việc loại trừ quy tắc \"{{disallowed_rule}}\" sẽ TẮT danh sách \"Ứng dụng khách được phép\".",
-    "experimental": "Thử nghiệm",
     "parental_control": "Quản lý của phụ huynh"
 }
diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json
index b24dc0c4..dce4be31 100644
--- a/client/src/__locales/zh-cn.json
+++ b/client/src/__locales/zh-cn.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "客户端设置",
-    "example_upstream_reserved": "您可以<0>为特定域名</0>指定上游 DNS 服务器",
-    "example_upstream_comment": "您可以指定注解",
+    "example_upstream_reserved": "指定为<0>特定域名</0>的上游服务器;",
+    "example_upstream_comment": "注释。",
     "upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。",
     "parallel_requests": "并行请求",
     "load_balancing": "负载均衡",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "已成功保存 DHCP 服务器配置",
     "dhcp_ipv4_settings": "DHCP IPv4设置",
     "dhcp_ipv6_settings": "DHCP IPv6设置",
-    "form_error_required": "必填字段",
-    "form_error_ip4_format": "无效的 IPv4 地址",
-    "form_error_ip4_range_start_format": "范围起始值的 IPv4 地址无效",
-    "form_error_ip4_range_end_format": "范围终值的 IPv4 地址无效",
-    "form_error_ip4_gateway_format": "网关 IPv4 地址无效",
-    "form_error_ip6_format": "无效的 IPv6 地址",
-    "form_error_ip_format": "无效的 IP 地址",
-    "form_error_mac_format": "无效的 MAC 地址",
-    "form_error_client_id_format": "无效的客户端 ID 格式客户 ID 必须只包含数字、小写字母和连字符",
-    "form_error_server_name": "无效的服务器名",
-    "form_error_subnet": "子网 \"{{cidr}}\" 不包含 IP 地址 \"{{ip}}\"",
-    "form_error_positive": "必须大于 0",
-    "out_of_range_error": "必定超出了范围 \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "必须小于范围起始值",
-    "greater_range_start_error": "必须大于范围起始值",
-    "greater_range_end_error": "必须大于范围终值",
-    "subnet_error": "地址必须在一个子网内",
-    "gateway_or_subnet_invalid": "子网掩码无效",
+    "form_error_required": "必填字段。",
+    "form_error_ip4_format": "无效的 IPv4 地址。",
+    "form_error_ip4_range_start_format": "范围起始值的 IPv4 地址无效。",
+    "form_error_ip4_range_end_format": "范围终值的 IPv4 地址无效。",
+    "form_error_ip4_gateway_format": "网关 IPv4 格式无效。",
+    "form_error_ip6_format": "无效的 IPv6 地址。",
+    "form_error_ip_format": "无效的 IP 地址。",
+    "form_error_mac_format": "无效的 MAC 地址。",
+    "form_error_client_id_format": "客户端 ID 必须只包含数字、小写字母和连字符。",
+    "form_error_server_name": "无效的服务器名。",
+    "form_error_subnet": "子网 \"{{cidr}}\" 不包含 IP 地址 \"{{ip}}\"。",
+    "form_error_positive": "必须大于 0。",
+    "out_of_range_error": "必定超出了范围 \"{{start}}\"-\"{{end}}\"。",
+    "lower_range_start_error": "必须小于范围起始值。",
+    "greater_range_start_error": "必须大于范围起始值。",
+    "greater_range_end_error": "必须大于范围终值。",
+    "subnet_error": "地址必须在一个子网内。",
+    "gateway_or_subnet_invalid": "子网掩码无效。",
     "dhcp_form_gateway_input": "网关 IP",
     "dhcp_form_subnet_input": "子网掩码",
     "dhcp_form_range_title": "IP 地址范围",
@@ -196,25 +196,25 @@
     "choose_allowlist": "选择允许列表",
     "enter_valid_blocklist": "输入有效的阻止列表URL",
     "enter_valid_allowlist": "输入有效的允许列表URL",
-    "form_error_url_format": "无效的URL格式",
-    "form_error_url_or_path_format": "无效的URL或列表的绝对路径",
+    "form_error_url_format": "无效的 URL 格式。",
+    "form_error_url_or_path_format": "无效的 URL 或列表的绝对路径。",
     "custom_filter_rules": "自定义过滤器规则",
     "custom_filter_rules_hint": "请确保每行只输入一条规则。你可以输入符合 adblock 语法或 Hosts 语法的规则。",
     "system_host_files": "系统主机文件",
     "examples_title": "范例",
-    "example_meaning_filter_block": "拦截 example.org 域名及其所有子域名",
-    "example_meaning_filter_whitelist": "放行 example.org 及其所有子域名",
-    "example_meaning_host_block": "AdGuard Home 现在将会把 example.org(但不包括它的子域名)解析到 127.0.0.1。",
-    "example_comment": "! 这是一行注释",
-    "example_comment_meaning": "只是一条注释",
-    "example_comment_hash": "# 这也是一行注释",
-    "example_regex_meaning": "阻止访问与<0>指定的正则表达式</0>匹配的域",
-    "example_upstream_regular": "常规 DNS(基于 UDP)",
-    "example_upstream_dot": "加密 <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "加密 <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "加密的<0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "你可以使用 <1>DNSCrypt</1> 的 <0>DNS Stamps</0> 或者 <2>DNS-over-HTTPS</2> 解析器",
-    "example_upstream_tcp": "常规 DNS(基于 TCP )",
+    "example_meaning_filter_block": "阻止 example.org 域名及其所有子域名;",
+    "example_meaning_filter_whitelist": "解除 example.org 及其所有子域名的封锁;",
+    "example_meaning_host_block": "对 example.org(不包括它的子域名)以 127.0.0.1 作为响应;",
+    "example_comment": "! 这是一行注释。",
+    "example_comment_meaning": "只是一条注释;",
+    "example_comment_hash": "# 这也是一行注释。",
+    "example_regex_meaning": "阻止访问与指定的正则表达式匹配的域名。",
+    "example_upstream_regular": "常规 DNS(基于 UDP);",
+    "example_upstream_dot": "加密 <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "加密 <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "加密 <0>DNS-over-QUIC</0>(实验性的);",
+    "example_upstream_sdns": "<1>DNSCrypt</1> 的 <0>DNS Stamps</0> 或者 <2>DNS-over-HTTPS</2> 解析器;",
+    "example_upstream_tcp": "常规 DNS(基于 TCP );",
     "all_lists_up_to_date_toast": "所有列表都是最新的",
     "updated_upstream_dns_toast": "上游服务器保存成功",
     "dns_test_ok_toast": "指定的 DNS 服务器现已正常运行",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "使用双引号进行严谨搜索",
     "query_log_retention_confirm": "您确定要更改查询记录保留时间吗? 如果您减少间隔时间的值, 某些数据可能会丢失。",
     "anonymize_client_ip": "匿名化客户端IP",
-    "anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整IP地址",
+    "anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整 IP 地址。",
     "dns_config": "DNS 服务配置",
     "dns_cache_config": "DNS缓存配置",
-    "dns_cache_config_desc": "你可以在此处配置 DNS缓存",
+    "dns_cache_config_desc": "您可以在此处配置 DNS 缓存。",
     "blocking_mode": "拦截模式",
     "default": "默认",
     "nxdomain": "无效域名",
@@ -277,7 +277,7 @@
     "dns_over_quic": "DNS-over-QUIC",
     "client_id": "客户端 ID",
     "client_id_placeholder": "输入客户端 ID",
-    "client_id_desc": "可根据一个特殊的客户端 ID 识别不同客户端。在 <a>这里</a>你可以了解到更多关于如何识别客户端的信息。",
+    "client_id_desc": "可根据一个特殊的客户端 ID 识别不同客户端。在<a>这里</a>您可以了解到更多关于如何识别客户端的信息。",
     "download_mobileconfig_doh": "下载适用于 DNS-over-HTTPS 的 .mobileconfig",
     "download_mobileconfig_dot": "下载适用于 DNS-over-TLS 的 .mobileconfig",
     "download_mobileconfig": "下载配置文件",
@@ -309,7 +309,7 @@
     "install_settings_listen": "监听接口",
     "install_settings_port": "端口",
     "install_settings_interface_link": "您可以通过以下地址访问您的 AdGuard Home 网页管理界面:",
-    "form_error_port": "输入有效的端口值",
+    "form_error_port": "输入有效的端口值。",
     "install_settings_dns": "DNS 服务器",
     "install_settings_dns_desc": "您将需要使用以下地址来设置您的设备或路由器的 DNS 服务器:",
     "install_settings_all_interfaces": "所有接口",
@@ -334,12 +334,12 @@
     "install_devices_router_list_4": "在某些类型的路由器上无法设置自定义 DNS 服务器。在此情况下将 AdGuard Home 设置为 <0>DHCP 服务器</0>,可能会有所帮助。否则您应该查找如何根据特定路由器型号设置 DNS 服务器的使用手册。",
     "install_devices_windows_list_1": "通过开始菜单或 Windows 搜索功能打开控制面板。",
     "install_devices_windows_list_2": "点击进入 ”网络和 Internet“ 后,再次点击进入 “网络和共享中心”",
-    "install_devices_windows_list_3": "在窗口的左侧找到 ”更改适配器设置“ 并点击进入。",
-    "install_devices_windows_list_4": "选择您正在连接的网络设备,右击它并选择 ”属性“ 。",
+    "install_devices_windows_list_3": "在窗口的左侧点击「更改适配器设置」。",
+    "install_devices_windows_list_4": "选择您正在连接的网络设备,右击它并选择「属性”」。",
     "install_devices_windows_list_5": "在列表中找到 ”Internet 协议版本 4 (TCP/IPv4)“ ,选择并再次点击 ”属性“ 。",
     "install_devices_windows_list_6": "选择“使用下面的 DNS 服务器地址”,并输入您的 AdGuard Home 服务器地址。",
-    "install_devices_macos_list_1": "点击苹果图标,进入 ”系统首选项“。",
-    "install_devices_macos_list_2": "点击 ”网络“ 。",
+    "install_devices_macos_list_1": "点击苹果图标,进入「系统首选项」。",
+    "install_devices_macos_list_2": "点击「网络」。",
     "install_devices_macos_list_3": "选择在列表中的第一个连接,并点击 ”高级“ 。",
     "install_devices_macos_list_4": "选择 ”DNS“ 选项卡,并输入您的 AdGuard Home 服务器地址。",
     "install_devices_android_list_1": "在安卓主屏幕菜单中点击设置。",
@@ -356,7 +356,7 @@
     "open_dashboard": "打开仪表盘",
     "install_saved": "保存成功",
     "encryption_title": "加密",
-    "encryption_desc": "为 DNS 与网页管理界面启用加密(HTTPS/TLS)",
+    "encryption_desc": "为 DNS 与网页管理界面启用加密(HTTPS/TLS)。",
     "encryption_config_saved": "加密配置已保存",
     "encryption_server": "服务器名称",
     "encryption_server_enter": "输入您的域名",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "如果配置了 HTTPS 端口,AdGuard Home 管理界面将可以通过 HTTPS 访问,它还将在在 '/dns-query' 位置提供 DNS-over-HTTPS 。",
     "encryption_dot": "DNS-over-TLS 端口",
     "encryption_dot_desc": "如果配置了此端口,AdGuard Home 将在此端口上运行一个 DNS-over-TLS 服务器。",
-    "encryption_doq": "DNS-over-QUIC 端口",
+    "encryption_doq": "DNS-over-QUIC 端口(实验性的)",
     "encryption_doq_desc": "如果配置了此端口,AdGuard Home将在此端口上运行一个DNS-over-QUIC服务器。这是实验性的,可能不可靠。而且,支持此特性的客户端并不多。",
     "encryption_certificates": "证书",
     "encryption_certificates_desc": "为了使用加密,您需要为域提供有效的 SSL 证书链。您可以在 <0>{{link}}</0> 上获得免费证书,也可以从受信任的证书颁发机构购买证书。",
@@ -378,26 +378,26 @@
     "encryption_key_input": "将您以 PEM 格式编码的证书私钥复制粘贴到此处。",
     "encryption_enable": "启用加密(HTTPS、DNS-over-HTTPS、DNS-over-TLS)",
     "encryption_enable_desc": "如果启用加密选项,AdGuard Home 的网页管理界面将通过 HTTPS 连接访问,同时 DNS 服务器将监听通过 DNS-over-HTTPS 与 DNS-over-TLS 发送的请求。",
-    "encryption_chain_valid": "证书链验证有效",
-    "encryption_chain_invalid": "证书链验证无效",
-    "encryption_key_valid": "该 {{type}} 私钥验证有效",
-    "encryption_key_invalid": "该 {{type}} 私钥验证无效",
+    "encryption_chain_valid": "证书链有效。",
+    "encryption_chain_invalid": "证书链无效。",
+    "encryption_key_valid": "该 {{type}} 私钥有效。",
+    "encryption_key_invalid": "该 {{type}} 私钥无效。",
     "encryption_subject": "使用者",
     "encryption_issuer": "颁发者",
     "encryption_hostnames": "主机名",
     "encryption_reset": "您确定想要重置加密设置?",
     "topline_expiring_certificate": "您的 SSL 证书即将过期。请更新 <0>加密设置</0> 。",
     "topline_expired_certificate": "您的 SSL 证书已过期。请更新 <0>加密设置</0> 。",
-    "form_error_port_range": "输入 80 - 65535 范围内的端口值",
-    "form_error_port_unsafe": "这是一个不安全的端口",
-    "form_error_equal": "不可相同",
-    "form_error_password": "密码不匹配",
+    "form_error_port_range": "输入 80 - 65535 范围内的端口值。",
+    "form_error_port_unsafe": "这是一个不安全的端口。",
+    "form_error_equal": "不可相同。",
+    "form_error_password": "密码不匹配。",
     "reset_settings": "重置设置",
     "update_announcement": "AdGuard Home {{version}} 现已发布! <0>点击此处</0> 以获取详细信息。",
     "setup_guide": "设置指导",
     "dns_addresses": "DNS 地址",
     "dns_start": "正在启动DNS服务",
-    "dns_status_error": "检查DNS服务器状态时出错",
+    "dns_status_error": "检查 DNS 服务器状态时出错。",
     "down": "下移",
     "fix": "修复",
     "dns_providers": "此为可从中选择的<0>已知 DNS 提供商列表</0>。",
@@ -405,8 +405,8 @@
     "update_failed": "自动更新失败。请<a>跟随这些步骤</a>以手动更新。",
     "manual_update": "请跟随<a>此步骤</a>以进行手动更新。",
     "processing_update": "正在更新 AdGuard Home,请稍侯",
-    "clients_title": "客户端",
-    "clients_desc": "配置已连接到 AdGuard Home 的设备",
+    "clients_title": "持久客户端",
+    "clients_desc": "配置已连接到 AdGuard Home 的设备的持久客户端记录。",
     "settings_global": "全局",
     "settings_custom": "自定义",
     "table_client": "客户端",
@@ -417,7 +417,7 @@
     "client_edit": "编辑客户端",
     "client_identifier": "标识符",
     "ip_address": "IP 地址",
-    "client_identifier_desc": "客户端可通过 IP 、MAC 地址、CIDR 或特殊 ID(可用于 DoT/DoH/DoQ)被识别。<0>这里</0>您可多了解如何识别客户端。",
+    "client_identifier_desc": "客户端可通过 IP 、MAC 地址、CIDR 或客户端 ID(可用于 DoT/DoH/DoQ)被识别。<0>这里</0>您可多了解如何识别客户端。",
     "form_enter_ip": "输入 IP",
     "form_enter_subnet_ip": "输入一个 IP 地址,其须位于子网\"{{cidr}}\"",
     "form_enter_mac": "输入 MAC",
@@ -433,13 +433,13 @@
     "client_confirm_delete": "您确定要删除客户端 \"{{key}}\"?",
     "list_confirm_delete": "您确定要删除此列表吗?",
     "auto_clients_title": "客户端(运行时间)",
-    "auto_clients_desc": "使用 Adguard Home 但未存储在配置中的客户端上的数据",
+    "auto_clients_desc": "不在可继续使用 AdGuard Home 的持久客户端列表中的设备。",
     "access_title": "访问设置",
     "access_desc": "您可在此处配置 AdGuard Home DNS 服务器的访问规则。",
     "access_allowed_title": "允许的客户端",
-    "access_allowed_desc": "CIDR、IP 地址或客户端 ID 的列表。如已配置,则 AdGuard Home 将仅接受来自这些客户端的请求。",
+    "access_allowed_desc": "CIDR、IP 地址或<a>客户端 ID</a> 的列表。如已配置,则 AdGuard Home 将仅接受来自这些客户端的请求。",
     "access_disallowed_title": "不允许的客户端",
-    "access_disallowed_desc": "CIDR、IP 地址或客户端 ID 的列表。如果已配置,则 AdGuard Home 将丢弃来自这些 IP 地址的请求。如果允许的客户端已配置,此字段将会被忽略。",
+    "access_disallowed_desc": "CIDR、IP 地址或<a>客户端 ID</a> 的列表。如果已配置,则 AdGuard Home 将丢弃来自这些客户端的请求。如果允许的客户端已配置,此字段将会被忽略。",
     "access_blocked_title": "不允许的域名",
     "access_blocked_desc": "不要将此功能与过滤器混淆。AdGuard Home 将排除匹配这些网域的 DNS 查询,并且这些查询将不会在查询日志中显示。在此可以明确指定域名、通配符(wildcard)和网址过滤的规则,例如 \"example.org\"、\"*.example.org\" 或 \"||example.org^\"。",
     "access_settings_saved": "访问设置保存成功",
@@ -475,8 +475,8 @@
     "dns_rewrites": "DNS 重写",
     "form_domain": "输入域",
     "form_answer": "输入 IP 地址或域名",
-    "form_error_domain_format": "无效的域格式",
-    "form_error_answer_format": "无效的响应格式",
+    "form_error_domain_format": "无效的网域格式。",
+    "form_error_answer_format": "无效的响应格式。",
     "configure": "配置",
     "main_settings": "主要设置",
     "block_services": "阻止特定服务",
@@ -507,7 +507,7 @@
     "filter_updated": "成功更新过滤器",
     "statistics_configuration": "统计配置",
     "statistics_retention": "统计保留",
-    "statistics_retention_desc": "如果您减少该间隔的数值, 某些数据可能会丢失",
+    "statistics_retention_desc": "如果您减少该间隔的数值, 某些数据可能会丢失。",
     "statistics_clear": " 清除统计数据",
     "statistics_clear_confirm": "您确定要清除统计数据?",
     "statistics_retention_confirm": "您确定要更改统计记录保留时间吗? 如果您减少间隔时间的值, 某些数据可能会丢失。",
@@ -532,7 +532,7 @@
     "netname": "网络名称",
     "network": "网络",
     "descr": "描述",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>了解更多</0>关于创建自己的hosts清单。",
     "blocked_by_response": "因响应的CNAME或IP被屏蔽",
     "blocked_by_cname_or_ip": "按CNAME或IP拦截",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "其将会进行如下工作:<0>停用系统DNSStubListener</0><0>设置DNS服务器地址为127.0.0.1</0><0>将/etc/resolv.conf的符号链接目标替换为/run/systemd/resolv/resolv.conf</0><0>停止DNSStubListener(重新加载系统解析服务)</0>",
     "autofix_warning_result": "因此,默认情况下所有来自系统的DNS请求都将由AdGuardHome处理。",
     "tags_title": "标签",
-    "tags_desc": "您可以选择与客户端对应的标记。标签可以包含在过滤规则中,并允许您更准确地应用它们。<0>了解更多</0>",
+    "tags_desc": "您可以选择与客户端对应的标记。标签可以包含在过滤规则中,并允许您更准确地应用它们。<0>了解更多</0>。",
     "form_select_tags": "选择客户端标签",
     "check_title": "检查过滤",
-    "check_desc": "检查主机名是否被过滤",
+    "check_desc": "检查主机名是否被过滤。",
     "check": "检查",
     "form_enter_host": "输入主机名称",
     "filtered_custom_rules": "被自定义过滤规则过滤",
@@ -598,15 +598,15 @@
     "blocklist": "拦截列表",
     "milliseconds_abbreviation": "毫秒",
     "cache_size": "缓存大小",
-    "cache_size_desc": "DNS缓存大小 (单位:字节)",
+    "cache_size_desc": "DNS 缓存大小(单位:字节)。",
     "cache_ttl_min_override": "覆盖最小TTL值",
     "cache_ttl_max_override": "覆盖最大TTL值",
     "enter_cache_size": "输入缓存大小(字节)",
     "enter_cache_ttl_min_override": "输入最小 TTL 值(秒)",
     "enter_cache_ttl_max_override": "输入最大 TTL 值(秒)",
-    "cache_ttl_min_override_desc": "缓存 DNS 响应时,延长从上游服务器接收到的 TTL 值 (秒)",
-    "cache_ttl_max_override_desc": "设定 DNS 缓存条目的最大 TTL 值(秒)",
-    "ttl_cache_validation": "最小缓存TTL值必须小于或等于最大值",
+    "cache_ttl_min_override_desc": "缓存 DNS 响应时,延长从上游服务器接收到的 TTL 值 (秒)。",
+    "cache_ttl_max_override_desc": "设定 DNS 缓存条目的最大 TTL 值(秒)。",
+    "ttl_cache_validation": "最小缓存 TTL 值必须小于或等于最大值。",
     "cache_optimistic": "乐观缓存",
     "cache_optimistic_desc": "即使条目已过期,也让 AdGuard Home 从缓存中响应,并尝试刷新它们。",
     "filter_category_general": "常规",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home 会终止所有来自此客户端的DNS查询。",
     "filter_allowlist": "警告:此操作将把规则 \"{{disallowed_rule}}\" 排除在允许客户端的列表之外。",
     "last_rule_in_allowlist": "无法禁止此客户端,因为排除 “{{disallowed_rule}}” 规则将禁用“允许客户端”的列表。",
-    "experimental": "实验性的",
     "use_saved_key": "使用之前保存的密钥",
     "parental_control": "家长控制",
     "safe_browsing": "安全浏览",
-    "served_from_cache": "{{value}}<i>(由缓存提供)</i>"
+    "served_from_cache": "{{value}}<i>(由缓存提供)</i>",
+    "form_error_password_length": "密码必须至少有 {{value}} 个字符。"
 }
diff --git a/client/src/__locales/zh-hk.json b/client/src/__locales/zh-hk.json
index e0ffb3e3..cae94b69 100644
--- a/client/src/__locales/zh-hk.json
+++ b/client/src/__locales/zh-hk.json
@@ -608,6 +608,5 @@
     "original_response": "原始回應",
     "click_to_view_queries": "按一下以檢視查詢結果",
     "port_53_faq_link": "連接埠 53 經常被「DNSStubListener」或「systemd-resolved」服務佔用。請閱讀下列有關解決<0>這個問題</0>的說明",
-    "adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。",
-    "experimental": "實驗性"
+    "adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。"
 }
diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json
index 0757f08d..52bbc4e0 100644
--- a/client/src/__locales/zh-tw.json
+++ b/client/src/__locales/zh-tw.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "用戶端設定",
-    "example_upstream_reserved": "您可<0>對於特定的網域</0>明確指定 DNS 上游",
-    "example_upstream_comment": "您可明確指定註解",
+    "example_upstream_reserved": "<0>供特定的網域</0>之上游;",
+    "example_upstream_comment": "註解。",
     "upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
     "parallel_requests": "並行的請求",
     "load_balancing": "負載平衡",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "動態主機設定協定(DHCP)配置被成功地儲存",
     "dhcp_ipv4_settings": "DHCP IPv4 設定",
     "dhcp_ipv6_settings": "DHCP IPv6 設定",
-    "form_error_required": "必填的欄位",
-    "form_error_ip4_format": "無效的 IPv4 位址",
-    "form_error_ip4_range_start_format": "無效起始範圍的 IPv4 位址",
-    "form_error_ip4_range_end_format": "無效結束範圍的 IPv4 位址",
-    "form_error_ip4_gateway_format": "無效閘道的 IPv4 位址",
-    "form_error_ip6_format": "無效的 IPv6 位址",
-    "form_error_ip_format": "無效的 IP 位址",
-    "form_error_mac_format": "無效的媒體存取控制(MAC)位址",
-    "form_error_client_id_format": "用戶端 ID 必須只包含數字、小寫字母和連字號",
-    "form_error_server_name": "無效的伺服器名稱",
-    "form_error_subnet": "子網路 \"{{cidr}}\" 不包含該 IP 位址 \"{{ip}}\"",
-    "form_error_positive": "必須大於 0",
-    "out_of_range_error": "必須在\"{{start}}\"-\"{{end}}\"範圍之外",
-    "lower_range_start_error": "必須低於起始範圍",
-    "greater_range_start_error": "必須大於起始範圍",
-    "greater_range_end_error": "必須大於結束範圍",
-    "subnet_error": "位址必須在子網路中",
-    "gateway_or_subnet_invalid": "無效的子網路遮罩",
+    "form_error_required": "必填的欄位。",
+    "form_error_ip4_format": "無效的 IPv4 位址。",
+    "form_error_ip4_range_start_format": "無效起始範圍的 IPv4 位址。",
+    "form_error_ip4_range_end_format": "無效結束範圍的 IPv4 位址。",
+    "form_error_ip4_gateway_format": "無效閘道的 IPv4 位址。",
+    "form_error_ip6_format": "無效的 IPv6 位址。",
+    "form_error_ip_format": "無效的 IP 位址。",
+    "form_error_mac_format": "無效的媒體存取控制(MAC)位址。",
+    "form_error_client_id_format": "用戶端 ID 必須只包含數字、小寫字母和連字號。",
+    "form_error_server_name": "無效的伺服器名稱。",
+    "form_error_subnet": "子網路 \"{{cidr}}\" 不包含該 IP 位址 \"{{ip}}\"。",
+    "form_error_positive": "必須大於 0。",
+    "out_of_range_error": "必須在\"{{start}}\"-\"{{end}}\"範圍之外。",
+    "lower_range_start_error": "必須低於起始範圍。",
+    "greater_range_start_error": "必須大於起始範圍。",
+    "greater_range_end_error": "必須大於結束範圍。",
+    "subnet_error": "位址必須在子網路中。",
+    "gateway_or_subnet_invalid": "無效的子網路遮罩。",
     "dhcp_form_gateway_input": "閘道 IP",
     "dhcp_form_subnet_input": "子網路遮罩",
     "dhcp_form_range_title": "IP 位址範圍",
@@ -196,25 +196,25 @@
     "choose_allowlist": "選擇允許清單",
     "enter_valid_blocklist": "輸入一個到該封鎖清單之有效的網址。",
     "enter_valid_allowlist": "輸入一個到該允許清單之有效的網址。",
-    "form_error_url_format": "無效的網址格式",
-    "form_error_url_or_path_format": "該清單之網址或絕對的路徑為無效的",
+    "form_error_url_format": "無效的網址格式。",
+    "form_error_url_or_path_format": "該清單之無效的網址或絕對的路徑。",
     "custom_filter_rules": "自訂的過濾規則",
     "custom_filter_rules_hint": "於一行上輸入一項規則。您可使用廣告封鎖規則或主機檔案語法。",
     "system_host_files": "系統主機檔案",
     "examples_title": "範例",
-    "example_meaning_filter_block": "封鎖至 example.org 網域及其所有的子網域之存取",
-    "example_meaning_filter_whitelist": "解除封鎖至 example.org 網域及其所有的子網域之存取",
-    "example_meaning_host_block": "AdGuard Home 現在將對 example.org 網域(但非其子網域)返回 127.0.0.1 位址。",
-    "example_comment": "! 看,一個註解",
-    "example_comment_meaning": "只是一個註解",
-    "example_comment_hash": "# 也是一個註解",
-    "example_regex_meaning": "封鎖至與該已明確指定的規則運算式(Regular Expression)相符的網域之存取",
-    "example_upstream_regular": "一般的 DNS(透過 UDP)",
-    "example_upstream_dot": "加密的 <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "加密的 <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "加密的 <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "您可使用關於 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2> 解析器之 <0>DNS 戳記</0>",
-    "example_upstream_tcp": "一般的 DNS(透過 TCP)",
+    "example_meaning_filter_block": "封鎖至 example.org 網域及其所有的子網域之存取;",
+    "example_meaning_filter_whitelist": "解除封鎖至 example.org 網域及其所有的子網域之存取;",
+    "example_meaning_host_block": "對 example.org(但非對其子網域)以 127.0.0.1 回覆;",
+    "example_comment": "! 看,一個註解。",
+    "example_comment_meaning": "只是一個註解;",
+    "example_comment_hash": "# 也是一個註解。",
+    "example_regex_meaning": "封鎖至與該已明確指定的規則運算式(Regular Expression)相符的網域之存取。",
+    "example_upstream_regular": "常規 DNS(透過 UDP);",
+    "example_upstream_dot": "加密的 <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "加密的 <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "加密的 <0>DNS-over-QUIC</0>(實驗性的);",
+    "example_upstream_sdns": "關於 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2> 解析器之 <0>DNS 戳記</0>;",
+    "example_upstream_tcp": "常規 DNS(透過 TCP);",
     "all_lists_up_to_date_toast": "所有的清單已是最新的",
     "updated_upstream_dns_toast": "上游的伺服器被成功地儲存",
     "dns_test_ok_toast": "已明確指定的 DNS 伺服器正在正確地運作",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "使用雙引號於嚴謹的搜尋",
     "query_log_retention_confirm": "您確定您想要更改查詢記錄保留嗎?如果您減少該間隔值,某些資料將被丟失",
     "anonymize_client_ip": "將用戶端 IP 匿名",
-    "anonymize_client_ip_desc": "不要在記錄和統計資料中儲存用戶端之完整的 IP 位址",
+    "anonymize_client_ip_desc": "不要儲存用戶端之完整的 IP 位址到記錄或統計資料裡。",
     "dns_config": "DNS 伺服器配置",
     "dns_cache_config": "DNS 快取配置",
-    "dns_cache_config_desc": "於此您可配置 DNS 快取",
+    "dns_cache_config_desc": "於此您可配置 DNS 快取。",
     "blocking_mode": "封鎖模式",
     "default": "預設",
     "nxdomain": "不存在的網域(NXDOMAIN)",
@@ -277,7 +277,7 @@
     "dns_over_quic": "DNS-over-QUIC",
     "client_id": "用戶端 ID",
     "client_id_placeholder": "輸入用戶端 ID",
-    "client_id_desc": "不同的用戶端可根據特殊的用戶端 ID 被識別。<0>於此</0>,您可了解更多關於如何識別用戶端。",
+    "client_id_desc": "用戶端可根據用戶端 ID 被識別。<a>於此</a>,了解更多關於如何識別用戶端。",
     "download_mobileconfig_doh": "下載用於 DNS-over-HTTPS 的 .mobileconfig",
     "download_mobileconfig_dot": "下載用於 DNS-over-TLS 的 .mobileconfig",
     "download_mobileconfig": "下載配置檔案",
@@ -309,7 +309,7 @@
     "install_settings_listen": "監聽介面",
     "install_settings_port": "連接埠",
     "install_settings_interface_link": "您的 AdGuard Home 管理員網路介面將於下列的位址上為可用的:",
-    "form_error_port": "輸入有效的連接埠號碼",
+    "form_error_port": "輸入有效的連接埠號碼。",
     "install_settings_dns": "DNS 伺服器",
     "install_settings_dns_desc": "您將需要配置您的裝置或路由器以使用於下列的位址上之 DNS 伺服器:",
     "install_settings_all_interfaces": "所有的介面",
@@ -334,12 +334,12 @@
     "install_devices_router_list_4": "於某些路由器機型上,自訂的 DNS 伺服器無法被設置。在這種情況下,設置 AdGuard Home 作為 <0>DHCP</0> 伺服器可能有所幫助。否則,您應查明有關如何對您的特定路由器型號自訂 DNS 伺服器之路由器用法說明。",
     "install_devices_windows_list_1": "通過開始功能表或 Windows 搜尋,開啟控制台。",
     "install_devices_windows_list_2": "去網路和網際網路類別,然後去網路和共用中心。",
-    "install_devices_windows_list_3": "於畫面之左側上找到\"變更介面卡設定\"並向它點擊。",
-    "install_devices_windows_list_4": "選擇您現行的連線,向它點擊滑鼠右鍵,然後選擇內容。",
+    "install_devices_windows_list_3": "在左側面板中,點擊\"變更介面卡設定\"。",
+    "install_devices_windows_list_4": "向您現行的連線點擊滑鼠右鍵,然後選擇內容。",
     "install_devices_windows_list_5": "在清單中找到\"網際網路通訊協定第 4 版(TCP/IPv4)\"[或用於 IPv6,\"網際網路通訊協定第 6 版(TCP/IPv6)\"],選擇它,然後再次向內容點擊。",
     "install_devices_windows_list_6": "選擇\"使用下列的 DNS 伺服器位址\",然後輸入您的 AdGuard Home 伺服器位址。",
-    "install_devices_macos_list_1": "向 Apple 圖像點擊,然後去系統偏好設定。",
-    "install_devices_macos_list_2": "向網路點擊。",
+    "install_devices_macos_list_1": "點擊 Apple 圖像,然後去系統偏好設定。",
+    "install_devices_macos_list_2": "點擊網路。",
     "install_devices_macos_list_3": "選擇在您的清單中之首要的連線,然後點擊進階的。",
     "install_devices_macos_list_4": "選擇該 DNS 分頁,然後輸入您的 AdGuard Home 伺服器位址。",
     "install_devices_android_list_1": "從 Android 選單主畫面中,輕觸設定。",
@@ -356,7 +356,7 @@
     "open_dashboard": "開啟儀表板",
     "install_saved": "被成功地儲存",
     "encryption_title": "加密",
-    "encryption_desc": "供 DNS 和管理員網路介面兩者之加密(HTTPS/TLS)支援",
+    "encryption_desc": "供 DNS 和管理員網路介面兩者之加密(HTTPS/TLS)支援。",
     "encryption_config_saved": "加密配置被儲存",
     "encryption_server": "伺服器名稱",
     "encryption_server_enter": "輸入您的域名",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "如果 HTTPS 連接埠被配置,AdGuard Home 管理員介面透過 HTTPS 將為可存取的,且它也將於 '/dns-query' 位置上提供 DNS-over-HTTPS。",
     "encryption_dot": "DNS-over-TLS 連接埠",
     "encryption_dot_desc": "如果該連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-TLS 伺服器。",
-    "encryption_doq": "DNS-over-QUIC 連接埠",
+    "encryption_doq": "DNS-over-QUIC 連接埠(實驗性的)",
     "encryption_doq_desc": "如果此連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-QUIC 伺服器。它是實驗性的並可能為不可靠的。再者,此刻沒有太多支援它的用戶端。",
     "encryption_certificates": "憑證",
     "encryption_certificates_desc": "為了使用加密,您需要提供有效的安全通訊端層(SSL)憑證鏈結供您的網域。於 <0>{{link}}</0> 上您可取得免費的憑證或您可從受信任的憑證授權單位之一購買它。",
@@ -378,26 +378,26 @@
     "encryption_key_input": "於此複製/貼上您的隱私增強郵件編碼之(PEM-encoded)私密金鑰供您的憑證。",
     "encryption_enable": "啟用加密(HTTPS、DNS-over-HTTPS 和 DNS-over-TLS)",
     "encryption_enable_desc": "如果加密被啟用,AdGuard Home 管理員介面透過 HTTPS 將運作,且該 DNS 伺服器將留心監聽透過 DNS-over-HTTPS 和 DNS-over-TLS 之請求。",
-    "encryption_chain_valid": "憑證鏈結為有效的",
-    "encryption_chain_invalid": "憑證鏈結為無效的",
-    "encryption_key_valid": "此為有效的 {{type}} 私密金鑰",
-    "encryption_key_invalid": "此為無效的 {{type}} 私密金鑰",
+    "encryption_chain_valid": "憑證鏈結為有效的。",
+    "encryption_chain_invalid": "憑證鏈結為無效的。",
+    "encryption_key_valid": "此為有效的 {{type}} 私密金鑰。",
+    "encryption_key_invalid": "此為無效的 {{type}} 私密金鑰。",
     "encryption_subject": "物件",
     "encryption_issuer": "簽發者",
     "encryption_hostnames": "主機名稱",
     "encryption_reset": "您確定您想要重置加密設定嗎?",
     "topline_expiring_certificate": "您的安全通訊端層(SSL)憑證即將到期。更新<0>加密設定</0>。",
     "topline_expired_certificate": "您的安全通訊端層(SSL)憑證為已到期的。更新<0>加密設定</0>。",
-    "form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼",
-    "form_error_port_unsafe": "此為不安全的連接埠",
-    "form_error_equal": "必須為不相等的",
-    "form_error_password": "不相符的密碼",
+    "form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼。",
+    "form_error_port_unsafe": "此為不安全的連接埠。",
+    "form_error_equal": "必須不為相等的。",
+    "form_error_password": "不相符的密碼。",
     "reset_settings": "重置設定",
     "update_announcement": "AdGuard Home {{version}} 現為可用的!關於更多的資訊,<0>點擊這裡</0>。",
     "setup_guide": "設置指南",
     "dns_addresses": "DNS 位址",
     "dns_start": "DNS 伺服器正在啟動",
-    "dns_status_error": "檢查 DNS 伺服器狀態出錯",
+    "dns_status_error": "檢查 DNS 伺服器狀態出錯。",
     "down": "停止運作的",
     "fix": "修復",
     "dns_providers": "這裡是一個從中選擇之<0>已知的 DNS 供應商之清單</0>。",
@@ -405,8 +405,8 @@
     "update_failed": "自動更新已失敗。請<a>遵循這些步驟</a>以手動地更新。",
     "manual_update": "請<a>遵循這些步驟</a>以手動地更新。",
     "processing_update": "請稍候,AdGuard Home 正被更新",
-    "clients_title": "用戶端",
-    "clients_desc": "配置被連線到 AdGuard Home 的裝置",
+    "clients_title": "持續性用戶端",
+    "clients_desc": "配置關於被連線到 AdGuard Home 的裝置之持續性用戶端記錄。",
     "settings_global": "全域的",
     "settings_custom": "自訂的",
     "table_client": "用戶端",
@@ -417,7 +417,7 @@
     "client_edit": "編輯用戶端",
     "client_identifier": "識別碼",
     "ip_address": "IP 位址",
-    "client_identifier_desc": "用戶端可根據 IP 位址、無類別網域間路由(CIDR)、媒體存取控制(MAC)位址或特殊的用戶端 ID(可被用於 DoT/DoH/DoQ)被識別。<0>於此</0>,您可了解更多關於如何識別用戶端。",
+    "client_identifier_desc": "用戶端可根據它們的 IP 位址、無類別網域間路由(CIDR)、媒體存取控制(MAC)位址或用戶端 ID(可被用於 DoT/DoH/DoQ)被識別。<0>於此</0>,了解更多關於如何識別用戶端。",
     "form_enter_ip": "輸入 IP",
     "form_enter_subnet_ip": "在子網路 \"{{cidr}}\" 中輸入一組 IP 位址",
     "form_enter_mac": "輸入媒體存取控制(MAC)",
@@ -432,14 +432,14 @@
     "clients_not_found": "無已發現之用戶端",
     "client_confirm_delete": "您確定您想要刪除用戶端 \"{{key}}\" 嗎?",
     "list_confirm_delete": "您確定您想要刪除該清單嗎?",
-    "auto_clients_title": "用戶端(執行時期)",
-    "auto_clients_desc": "使用 AdGuard Home 但未被儲存在配置中之關於用戶端的資料",
+    "auto_clients_title": "執行時期用戶端",
+    "auto_clients_desc": "未於可能仍然使用 AdGuard Home 的持續性用戶端之清單上的裝置。",
     "access_title": "存取設定",
     "access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則。",
     "access_allowed_title": "已允許的用戶端",
-    "access_allowed_desc": "無類別網域間路由(CIDRs)、IP 位址或用戶端 ID 之清單。如果被配置,AdGuard Home 將接受僅來自這些用戶端的請求。",
+    "access_allowed_desc": "無類別網域間路由(CIDRs)、IP 位址或<a>用戶端 IDs</a> 之清單。如果此清單有項目,AdGuard Home 將接受僅來自這些用戶端的請求。",
     "access_disallowed_title": "未被允許的用戶端",
-    "access_disallowed_desc": "無類別網域間路由(CIDRs)、IP 位址或用戶端 IDs 之清單。如果被配置,AdGuard Home 將排除來自這些用戶端的請求。如果已允許的用戶端被配置,此欄位被忽略。",
+    "access_disallowed_desc": "無類別網域間路由(CIDRs)、IP 位址或<a>用戶端 IDs</a> 之清單。如果此清單有項目,AdGuard Home 將排除來自這些用戶端的請求。如果在已允許的用戶端中有項目,此欄位被忽略。",
     "access_blocked_title": "未被允許的網域",
     "access_blocked_desc": "不要把這個和過濾器混淆。AdGuard Home 排除與這些網域相符的 DNS 查詢,且這些查詢甚至不會出現在查詢記錄中。您可相應地明確指定確切的域名、萬用字元(wildcard)或網址過濾器的規則,例如,\"example.org\"、\"*.example.org\" 或 \"||example.org^\"。",
     "access_settings_saved": "存取設定被成功地儲存",
@@ -475,8 +475,8 @@
     "dns_rewrites": "DNS 改寫",
     "form_domain": "輸入域名或萬用字元(wildcard)",
     "form_answer": "輸入 IP 位址或域名",
-    "form_error_domain_format": "無效的網域格式",
-    "form_error_answer_format": "無效的回應格式",
+    "form_error_domain_format": "無效的網域格式。",
+    "form_error_answer_format": "無效的回應格式。",
     "configure": "配置",
     "main_settings": "主設定",
     "block_services": "封鎖特定的服務",
@@ -507,7 +507,7 @@
     "filter_updated": "該清單已被成功地更新",
     "statistics_configuration": "統計資料配置",
     "statistics_retention": "統計資料保留",
-    "statistics_retention_desc": "如果您減少該間隔值,某些資料將被丟失",
+    "statistics_retention_desc": "如果您減少該間隔值,某些資料將被丟失。",
     "statistics_clear": " 清除統計資料",
     "statistics_clear_confirm": "您確定您想要清除統計資料嗎?",
     "statistics_retention_confirm": "您確定您想要更改統計資料保留嗎?如果您減少該間隔值,某些資料將被丟失",
@@ -532,7 +532,7 @@
     "netname": "網路名稱",
     "network": "網路",
     "descr": "說明",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>了解更多</0>有關創建您自己的主機(hosts)清單。",
     "blocked_by_response": "在回應過程中被正規名稱(CNAME)或 IP 封鎖",
     "blocked_by_cname_or_ip": "被正規名稱(CNAME)或 IP 封鎖",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "它將執行這些任務:<0>撤銷系統 DNSStubListener</0> <0>設定 DNS 伺服器位址為 127.0.0.1</0> <0>用 /run/systemd/resolve/resolv.conf 取代 /etc/resolv.conf 的符號連結目標</0> <0>停止 DNSStubListener(重新載入 systemd-resolved 服務)</0>",
     "autofix_warning_result": "因此,預設下,來自您的系統之所有的 DNS 請求將被 AdGuard Home 處理。",
     "tags_title": "標記",
-    "tags_desc": "您可選擇對應該用戶端的標記。標記可被包括在過濾規則中並允許您更準確地套用它們。<0>了解更多</0>",
+    "tags_desc": "您可選擇對應該用戶端的標記。包括在過濾規則中的標記以更準確地套用它們。<0>了解更多</0>。",
     "form_select_tags": "選擇用戶端標記",
     "check_title": "檢查該過濾",
-    "check_desc": "檢查該主機名稱是否被過濾",
+    "check_desc": "檢查主機名稱是否被過濾。",
     "check": "檢查",
     "form_enter_host": "輸入主機名稱",
     "filtered_custom_rules": "被自訂的過濾規則過濾",
@@ -598,15 +598,15 @@
     "blocklist": "封鎖清單",
     "milliseconds_abbreviation": "ms",
     "cache_size": "快取大小",
-    "cache_size_desc": "DNS 快取大小(以位元組)",
+    "cache_size_desc": "DNS 快取大小(以位元組)。",
     "cache_ttl_min_override": "覆寫最小的存活時間(TTL)",
     "cache_ttl_max_override": "覆寫最大的存活時間(TTL)",
     "enter_cache_size": "輸入快取大小(位元組)",
     "enter_cache_ttl_min_override": "輸入最小的存活時間(秒)",
     "enter_cache_ttl_max_override": "輸入最大的存活時間(秒)",
-    "cache_ttl_min_override_desc": "當快取 DNS 回應時,延長從上游的伺服器收到的短存活時間數值(秒)",
-    "cache_ttl_max_override_desc": "設定最大的存活時間數值(秒)供在 DNS 快取中的項目",
-    "ttl_cache_validation": "最小的快取存活時間(TTL)數值必須小於或等於最大的數值",
+    "cache_ttl_min_override_desc": "當快取 DNS 回應時,延長從上游的伺服器收到的短存活時間數值(秒)。",
+    "cache_ttl_max_override_desc": "設定最大的存活時間數值(秒)供在 DNS 快取中的項目。",
+    "ttl_cache_validation": "最小的快取存活時間(TTL)覆寫必須小於或等於最大的。",
     "cache_optimistic": "樂觀快取",
     "cache_optimistic_desc": "即使當項目為已到期的,從快取使 AdGuard Home 回覆,並還嘗試重新整理它們。",
     "filter_category_general": "一般的",
@@ -624,9 +624,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home 將持續排除來自此用戶端之所有的 DNS 查詢。",
     "filter_allowlist": "警告:此操作將把 \"{{disallowed_rule}}\" 規則排除在已允許用戶端的清單之外。",
     "last_rule_in_allowlist": "無法禁止此用戶端,因為排除 “{{disallowed_rule}}” 規則將禁用“已允許用戶端”的清單。",
-    "experimental": "實驗性的",
     "use_saved_key": "使用該先前已儲存的金鑰",
     "parental_control": "家長控制",
     "safe_browsing": "安全瀏覽",
-    "served_from_cache": "{{value}} <i>(由快取提供)</i>"
+    "served_from_cache": "{{value}} <i>(由快取提供)</i>",
+    "form_error_password_length": "密碼必須為至少長 {{value}} 個字元。"
 }

From b29f320fd466715d02041a4753223ad4f2105f76 Mon Sep 17 00:00:00 2001
From: Peter Dave Hello <hsu@peterdavehello.org>
Date: Wed, 23 Feb 2022 22:42:03 +0800
Subject: [PATCH 003/143] Simplify Dockerfile Alpine Linux apk usage

Remove additional `--update` and manual clean up for apk in Dockerfile
---
 scripts/make/Dockerfile | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/scripts/make/Dockerfile b/scripts/make/Dockerfile
index f5a543a2..a731e3a6 100644
--- a/scripts/make/Dockerfile
+++ b/scripts/make/Dockerfile
@@ -21,8 +21,7 @@ LABEL\
 	org.opencontainers.image.version=$VERSION
 
 # Update certificates.
-RUN apk --no-cache --update add ca-certificates libcap tzdata && \
-	rm -rf /var/cache/apk/* && \
+RUN apk --no-cache add ca-certificates libcap tzdata && \
 	mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
 	chown -R nobody: /opt/adguardhome
 

From ff1e108bfe820c533bd757feb25c10a88f9127b1 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 28 Feb 2022 16:26:45 +0300
Subject: [PATCH 004/143] Pull request: 4213 add bsd syslog

Merge in DNS/adguard-home from 4213-bsd-syslog to master

Updates #4046.
Closes #4213.

Squashed commit of the following:

commit 1e57c75c4184e83b09cfd27456340ca9447791be
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 28 16:20:32 2022 +0300

    home: imp error msg

commit 63059d031153ff9b6dc9aecd9522d2ad4f8448da
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 28 15:36:37 2022 +0300

    all: imp log of changes

commit 682c3c9e8986b6bdf2d0c665c9cad4a71fd2cc83
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 28 15:29:29 2022 +0300

    home: imp code

commit 86c311a71d07823c18521890bea7c898c117466b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 28 15:03:02 2022 +0300

    home: add bsd syslog
---
 CHANGELOG.md             |  3 +++
 internal/home/service.go | 22 +++++++++++++++++-----
 2 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2976e276..a54f8c57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@ and this project adheres to
 
 ### Added
 
+- Logs are now collected by default on FreeBSD and OpenBSD when AdGuard Home is
+  installed as a service ([#4213]).
 - `windows/arm64` support ([#3057]).
 
 ### Changed
@@ -87,6 +89,7 @@ In this release, the schema version has changed from 12 to 13.
 [#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
 [#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381
 [#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
+[#4213]: https://github.com/AdguardTeam/AdGuardHome/issues/4213
 [#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
diff --git a/internal/home/service.go b/internal/home/service.go
index e5d0ba39..081974e2 100644
--- a/internal/home/service.go
+++ b/internal/home/service.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"io/fs"
 	"os"
+	"path/filepath"
 	"runtime"
 	"strconv"
 	"strings"
@@ -82,10 +83,15 @@ func svcStatus(s service.Service) (status service.Status, err error) {
 // On OpenWrt, the service utility may not exist.  We use our service script
 // directly in this case.
 func svcAction(s service.Service, action string) (err error) {
-	if runtime.GOOS == "darwin" &&
-		action == "start" &&
-		!strings.HasPrefix(Context.workDir, "/Applications/") {
-		log.Info("warning: service must be started from within the /Applications directory")
+	if runtime.GOOS == "darwin" && action == "start" {
+		var exe string
+		if exe, err = os.Executable(); err != nil {
+			log.Error("starting service: getting executable path: %s", err)
+		} else if exe, err = filepath.EvalSymlinks(exe); err != nil {
+			log.Error("starting service: evaluating executable symlinks: %s", err)
+		} else if !strings.HasPrefix(exe, "/Applications/") {
+			log.Info("warning: service must be started from within the /Applications directory")
+		}
 	}
 
 	err = service.Control(s, action)
@@ -579,6 +585,9 @@ status() {
 }
 `
 
+// freeBSDScript is the source of the daemon script for FreeBSD.  Keep as close
+// as possible to the https://github.com/kardianos/service/blob/18c957a3dc1120a2efe77beb401d476bade9e577/service_freebsd.go#L204.
+//
 // TODO(a.garipov): Don't use .WorkingDirectory here.  There are currently no
 // guarantees that it will actually be the required directory.
 //
@@ -587,14 +596,16 @@ const freeBSDScript = `#!/bin/sh
 # PROVIDE: {{.Name}}
 # REQUIRE: networking
 # KEYWORD: shutdown
+
 . /etc/rc.subr
+
 name="{{.Name}}"
 {{.Name}}_env="IS_DAEMON=1"
 {{.Name}}_user="root"
 pidfile_child="/var/run/${name}.pid"
 pidfile="/var/run/${name}_daemon.pid"
 command="/usr/sbin/daemon"
-command_args="-P ${pidfile} -p ${pidfile_child} -f -r {{.WorkingDirectory}}/{{.Name}}"
+command_args="-P ${pidfile} -p ${pidfile_child} -T ${name} -r {{.WorkingDirectory}}/{{.Name}}"
 run_rc_command "$1"
 `
 
@@ -604,6 +615,7 @@ const openBSDScript = `#!/bin/sh
 
 daemon="{{.Path}}"
 daemon_flags={{ .Arguments | args }}
+daemon_logger="daemon.info"
 
 . /etc/rc.d/rc.subr
 

From afbc7a72e3844f348c22d59e957c525334576a10 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 28 Feb 2022 19:13:15 +0300
Subject: [PATCH 005/143] Pull request: client: fix link in client form

Updates #4244.

Squashed commit of the following:

commit 20d558e9e6935555a13e1aebc7d364e6f1910e9e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Feb 28 19:01:32 2022 +0300

    client: fix link in client form
---
 client/src/components/Settings/Clients/Form.js | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js
index d5f80c62..353b0bea 100644
--- a/client/src/components/Settings/Clients/Form.js
+++ b/client/src/components/Settings/Clients/Form.js
@@ -280,13 +280,10 @@ let Form = (props) => {
                             </strong>
                         </div>
                         <div className="form__desc mt-0">
-                            <Trans
-                                components={{
-                                    a: <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer">
-                                        text
-                                    </a>,
-                                }}
-                            >
+                            <Trans components={[
+                                <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer"
+                                    key="0">text</a>,
+                            ]}>
                                 client_identifier_desc
                             </Trans>
                         </div>

From 3afe7c3daf39d53fdcf5260b7b3f035f0891c83f Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 1 Mar 2022 15:10:48 +0300
Subject: [PATCH 006/143] Pull request: client: upd i18n

Merge in DNS/adguard-home from 2643-upd-i18n to master

Squashed commit of the following:

commit 1f36b960877ee2c30319e26132db892fb8a2ef71
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Mar 1 15:05:24 2022 +0300

    client: upd i18n
---
 client/src/__locales/hu.json | 148 ++++++++++++++++++-----------------
 client/src/__locales/sk.json | 138 ++++++++++++++++----------------
 client/src/__locales/uk.json |  48 ++++++------
 3 files changed, 168 insertions(+), 166 deletions(-)

diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json
index b49b6506..5ffa9d15 100644
--- a/client/src/__locales/hu.json
+++ b/client/src/__locales/hu.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Kliens beállítások",
     "example_upstream_reserved": "Megadhat egy DNS kiszolgálót <0>egy adott domainhez vagy domainekhez</0>",
-    "example_upstream_comment": "Megadhat egy megjegyzést",
+    "example_upstream_comment": "egy megjegyzés.",
     "upstream_parallel": "Használjon párhuzamos lekéréseket a domainek feloldásának felgyorsításához az összes upstream kiszolgálóra való egyidejű lekérdezéssel.",
     "parallel_requests": "Párhuzamos lekérések",
     "load_balancing": "Terheléselosztás",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP beállítások sikeresen el lettek mentve",
     "dhcp_ipv4_settings": "DHCP IPv4 Beállítások",
     "dhcp_ipv6_settings": "DHCP IPv6 Beállítások",
-    "form_error_required": "Kötelező mező",
-    "form_error_ip4_format": "Érvénytelen IPv4 cím",
-    "form_error_ip4_range_start_format": "Érvénytelen IPv4-cím a tartomány kezdetéhez",
-    "form_error_ip4_range_end_format": "Érvénytelen IPv4-cím a tartomány végén",
-    "form_error_ip4_gateway_format": "Érvénytelen IPv4-cím az átjáró",
-    "form_error_ip6_format": "Érvénytelen IPv6 cím",
-    "form_error_ip_format": "Érvénytelen IP-cím",
-    "form_error_mac_format": "Érvénytelen MAC cím",
-    "form_error_client_id_format": "Az ügyfél-azonosító csak számokat, kisbetűket és kötőjeleket tartalmazhat",
-    "form_error_server_name": "Érvénytelen szervernév",
-    "form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet",
-    "form_error_positive": "0-nál nagyobbnak kell lennie",
-    "out_of_range_error": "A tartományon kívül legyen \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Kisebb legyen, mint a tartomány kezdete",
-    "greater_range_start_error": "Nagyobbbb legyen, mint a tartomány kezdete",
-    "greater_range_end_error": "Nagyobb legyen, mint a tartomány vége",
-    "subnet_error": "A címeknek egy alhálózatban kell lenniük",
-    "gateway_or_subnet_invalid": "Az alhálózati maszk érvénytelen",
+    "form_error_required": "Kötelező mező.",
+    "form_error_ip4_format": "Érvénytelen IPv4 cím.",
+    "form_error_ip4_range_start_format": "Érvénytelen IPv4-cím a tartomány kezdetéhez.",
+    "form_error_ip4_range_end_format": "Érvénytelen IPv4-cím a tartomány végén.",
+    "form_error_ip4_gateway_format": "Az átjáróhoz (gateway) érvénytelen IPv4 cím lett megadva.",
+    "form_error_ip6_format": "Érvénytelen IPv6 cím.",
+    "form_error_ip_format": "Érvénytelen IP-cím.",
+    "form_error_mac_format": "Érvénytelen MAC cím.",
+    "form_error_client_id_format": "A ClientID (kliens azonosító) csak számokat, kisbetűket és kötőjeleket tartalmazhat.",
+    "form_error_server_name": "Érvénytelen szervernév.",
+    "form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet.",
+    "form_error_positive": "0-nál nagyobbnak kell lennie.",
+    "out_of_range_error": "A következő tartományon kívül legyen: \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Kisebb legyen, mint a tartomány kezdete.",
+    "greater_range_start_error": "Nagyobbnak kell lennie, mint a tartomány kezdete.",
+    "greater_range_end_error": "Nagyobb legyen, mint a tartomány vége.",
+    "subnet_error": "A címeknek egy alhálózatban kell lenniük.",
+    "gateway_or_subnet_invalid": "Az alhálózati maszk érvénytelen.",
     "dhcp_form_gateway_input": "Átjáró IP",
     "dhcp_form_subnet_input": "Alhálózati maszk",
     "dhcp_form_range_title": "IP-címek tartománya",
@@ -143,7 +143,7 @@
     "use_adguard_browsing_sec_hint": "Az AdGuard Home ellenőrzi, hogy a böngészési biztonsági modul a domaint tiltólistára tette-e. Az ellenőrzés elvégzéséhez egy adatvédelmet tiszteletben tartó API-t fog használni: a domain név egy rövid előtagját elküldi SHA256 kódolással a szerver felé.",
     "use_adguard_parental": "Használja az AdGuard szülői felügyelet webszolgáltatását",
     "use_adguard_parental_hint": "Az AdGuard Home ellenőrzi, hogy a domain tartalmaz-e felnőtteknek szóló anyagokat. Ugyanazokat az adatvédelmi API-kat használja, mint a böngésző biztonsági webszolgáltatás.",
-    "enforce_safe_search": "Biztonságos keresés kényszerítése",
+    "enforce_safe_search": "Biztonságos keresés használata",
     "enforce_save_search_hint": "Az AdGuard Home a következő keresőmotorokban biztosíthatja a biztonságos keresést: Google, Youtube, Bing, DuckDuckGo, Yandex és Pixabay.",
     "no_servers_specified": "Nincsenek megadott kiszolgálók",
     "general_settings": "Általános beállítások",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Engedélyezési lista választás",
     "enter_valid_blocklist": "Adjon meg egy érvényes URL-t a blokkolási listához.",
     "enter_valid_allowlist": "Adjon meg egy érvényes URL-t az engedélyezési listához.",
-    "form_error_url_format": "Érvénytelen URL formátum",
-    "form_error_url_or_path_format": "Helytelen URL vagy elérési út a listához",
+    "form_error_url_format": "Érvénytelen URL formátum.",
+    "form_error_url_or_path_format": "Helytelen URL vagy abszolút elérési útvonal a listához.",
     "custom_filter_rules": "Egyéni szűrési szabályok",
     "custom_filter_rules_hint": "Adjon meg egy szabályt egy sorban. Használhat egyszerű hirdetésblokkolási szabályokat vagy hosztfájl szintaxist.",
     "system_host_files": "Rendszer hosztfájlok",
     "examples_title": "Példák",
-    "example_meaning_filter_block": "letiltja a hozzáférést az example.org domainhez, valamint annak az összes aldomainjéhez is",
-    "example_meaning_filter_whitelist": "feloldja a hozzáférést az example.org domainhez, valamint annak az összes aldomainjéhez is",
-    "example_meaning_host_block": "Az AdGuard Home mostantol a 127.0.0.1 címre irányítja az example.org domaint (de az aldomaineket nem).",
-    "example_comment": "! Ide írhat egy megjegyzést",
-    "example_comment_meaning": "csak egy megjegyzés",
-    "example_comment_hash": "# Ez is egy megjegyzés",
-    "example_regex_meaning": "megakadályozza a hozzáférést a reguláris kifejezéssel egyező domaineknél",
-    "example_upstream_regular": "hagyományos DNS (UDP felett)",
-    "example_upstream_dot": "titkosított <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "titkosított <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "titkosított <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "használhatja a <0> DNS Stamps</0>-ot a <1>DNSCrypt</1> vagy a <2>DNS-over-HTTPS</2> feloldások érdekében",
-    "example_upstream_tcp": "hagyományos DNS (TCP felett)",
+    "example_meaning_filter_block": "letiltja a hozzáférést az example.org domainhez, valamint annak az összes aldomainjéhez is;",
+    "example_meaning_filter_whitelist": "feloldja a hozzáférést az example.org domainhez, valamint annak az összes aldomainjéhez is;",
+    "example_meaning_host_block": "az example.org-ot a 127.0.0.1-es címre oldja fel (de az aldomainjeit nem);",
+    "example_comment": "! Ide írhat egy megjegyzést.",
+    "example_comment_meaning": "csak egy megjegyzés;",
+    "example_comment_hash": "# Ez is egy megjegyzés.",
+    "example_regex_meaning": "blokkolja a hozzáférést azokhoz a domainekhez, amik illeszkednek a megadott reguláris kifejezésre.",
+    "example_upstream_regular": "hagyományos DNS (UDP felett);",
+    "example_upstream_dot": "titkosított <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "titkosított <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "titkosított <0>DNS-over-QUIC</0> (kísérleti);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> a <1>DNSCrypt</1> vagy <2>DNS-over-HTTPS</2> feloldókhoz;",
+    "example_upstream_tcp": "hagyományos DNS (TCP felett);",
     "all_lists_up_to_date_toast": "Már minden lista naprakész",
     "updated_upstream_dns_toast": "Upstream szerverek sikeresen mentve",
     "dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Használjon \"dupla idézőjelet\" a pontos kereséshez",
     "query_log_retention_confirm": "Biztos benne, hogy megváltoztatja a kérések naplójának megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
     "anonymize_client_ip": "Kliens IP-címének anonimizálása",
-    "anonymize_client_ip_desc": "Ne mentse el a kliens teljes IP-címét a naplókban és a statisztikákban",
+    "anonymize_client_ip_desc": "Ne mentse el a kliens teljes IP-címét a naplókban és a statisztikákban.",
     "dns_config": "DNS szerver beállításai",
     "dns_cache_config": "DNS gyorsítótár beállításai",
-    "dns_cache_config_desc": "Itt tudja konfigurálni a DNS gyorsítótárat",
+    "dns_cache_config_desc": "Itt tudja konfigurálni a DNS gyorsítótárat.",
     "blocking_mode": "Blokkolás módja",
     "default": "Alapértelmezett",
     "nxdomain": "NXDOMAIN",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "Kliens azonosító",
-    "client_id_placeholder": "Kliens azonosító megadása",
-    "client_id_desc": "A különböző klienseket egy speciális kliens azonosító segítségével lehet azonosítani. <a>Itt</a> többet is megtudhat arról, hogyan lehet a klienseket azonosítani.",
+    "client_id": "Kliens azonosító (ClientID)",
+    "client_id_placeholder": "Kliens azonosító (ClientID) megadása",
+    "client_id_desc": "A kliensek a ClientID által kerülnek azonosításra. Tudjon meg többet arról <a>ide kattintva</a>, hogy miként történik a kliensek azonosítása.",
     "download_mobileconfig_doh": ".mobileconfig letöltése DNS-over-HTTPS-hez",
     "download_mobileconfig_dot": ".mobileconfig letöltése DNS-over-TLS-hez",
     "download_mobileconfig": "Konfigurációs fájl letöltése",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Figyelő felület",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Az AdGuard Home webes admin felülete elérhető a következő címe(ke)n:",
-    "form_error_port": "Írja be az érvényes portszámot",
+    "form_error_port": "Adjon meg egy érvényes portot.",
     "install_settings_dns": "DNS szerver",
     "install_settings_dns_desc": "Be kell állítania az eszközeit vagy a routerét, hogy használni tudja a DNS szervert a következő címeken:",
     "install_settings_all_interfaces": "Minden felület",
@@ -334,8 +334,8 @@
     "install_devices_router_list_4": "Bizonyos típusú routereknél nem állíthat be egyéni DNS-kiszolgálót. Ebben az esetben segíthet, ha az AdGuard Home-t DHCP-szerverként állítja be. Ellenkező esetben keresse meg az adott router kézikönyvében a DNS-kiszolgálók testreszabását.",
     "install_devices_windows_list_1": "Nyissa meg a Vezérlőpultot a Start menün vagy a Windows keresőn keresztül.",
     "install_devices_windows_list_2": "Válassza a Hálózat és internet kategóriát, majd pedig a Hálózati és megosztási központot.",
-    "install_devices_windows_list_3": "A képernyő bal oldalán keresse meg az Adapterbeállítások módosítása lehetőséget és kattintson rá.",
-    "install_devices_windows_list_4": "Válassza ki a jelenleg is használt kapcsolatot, majd jobb egérgombbal kattintson rá és a megjelenő menüből válassza a Tulajdonságok elemet.",
+    "install_devices_windows_list_3": "A bal oldali panelben kattintson az \"Adapterbeállítások módosítása\" lehetőségre.",
+    "install_devices_windows_list_4": "Kattintson jobb egérgombbal az aktív kapcsolatra és válassza ki a Tulajdonságokat.",
     "install_devices_windows_list_5": "Keresse meg az Internet Protocol Version 4 (TCP/IPv4) elemet a listában, válassza ki, majd ismét kattintson a Tulajdonságokra.",
     "install_devices_windows_list_6": "Válassza a \"Következő DNS címek használata\" lehetőséget és adja meg az AdGuard Home szerver címeit.",
     "install_devices_macos_list_1": "Kattintson az Apple ikonra és válassza a Rendszerbeállításokat.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Irányítópult megnyitása",
     "install_saved": "Sikeres mentés",
     "encryption_title": "Titkosítás",
-    "encryption_desc": "Titkosítás (HTTPS/TLS) támogatása mind a DNS, mind pedig a webes admin felület számára",
+    "encryption_desc": "Titkosítás (HTTPS/TLS) támogatása mind a DNS, mind pedig a webes admin felület számára.",
     "encryption_config_saved": "Titkosítási beállítások mentve",
     "encryption_server": "Szerver neve",
     "encryption_server_enter": "Adja meg az Ön domain címét",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Ha a HTTPS port konfigurálva van, akkor az AdGuard Home admin felülete elérhető lesz a HTTPS-en keresztül, és ezenkívül DNS-over-HTTPS-t is biztosít a '/dns-query' helyen.",
     "encryption_dot": "DNS-over-TLS port",
     "encryption_dot_desc": "Ha ez a port be van állítva, az AdGuard Home DNS-over-TLS szerverként tud futni ezen a porton.",
-    "encryption_doq": "DNS-over-TLS port",
+    "encryption_doq": "DNS-over-QUIC port (kísérleti)",
     "encryption_doq_desc": "Ha ez a port be van állítva, akkor az AdGuard Home egy DNS-over-QUIC szerverként fog futni ezen a porton. Ez egy kísérleti funkció és nem biztos, hogy megbízható. Emellett nincs sok olyan kliens, ami támogatná ezt jelenleg.",
     "encryption_certificates": "Tanúsítványok",
     "encryption_certificates_desc": "A titkosítás használatához érvényes SSL tanúsítványláncot kell megadnia a domainjéhez. Ingyenes tanúsítványt kaphat a <0>{{link}}</0> webhelyen, vagy megvásárolhatja az egyik megbízható tanúsítványkibocsátó hatóságtól.",
@@ -378,34 +378,35 @@
     "encryption_key_input": "Másolja ki és illessze be ide a tanúsítványa PEM-kódolt privát kulcsát.",
     "encryption_enable": "Titkosítás engedélyezése (HTTPS, DNS-over-HTTPS, és DNS-over-TLS)",
     "encryption_enable_desc": "Ha a titkosítás engedélyezve van, az AdGuard Home admin felülete működik HTTPS-en keresztül, és a DNS szerver is várja a kéréseket DNS-over-HTTPS-en, valamint DNS-over-TLS-en keresztül.",
-    "encryption_chain_valid": "A tanúsítványlánc érvényes",
-    "encryption_chain_invalid": "A tanúsítványlánc érvénytelen",
-    "encryption_key_valid": "Ez egy érvényes {{type}} privát kulcs",
-    "encryption_key_invalid": "Ez egy érvénytelen {{type}} privát kulcs",
+    "encryption_chain_valid": "A tanúsítványlánc érvényes.",
+    "encryption_chain_invalid": "A tanúsítványlánc érvénytelen.",
+    "encryption_key_valid": "Ez egy érvényes {{type}} privát kulcs.",
+    "encryption_key_invalid": "Ez egy érvénytelen {{type}} privát kulcs.",
     "encryption_subject": "Tárgy",
     "encryption_issuer": "Kibocsátó",
     "encryption_hostnames": "Hosztnevek",
     "encryption_reset": "Biztosan visszaállítja a titkosítási beállításokat?",
     "topline_expiring_certificate": "Az SSL-tanúsítványa hamarosan lejár. Frissítse a <0>Titkosítási beállításokat</0>.",
     "topline_expired_certificate": "Az SSL-tanúsítványa lejárt. Frissítse a <0>Titkosítási beállításokat</0>.",
-    "form_error_port_range": "A port értékét a 80-65535 tartományban adja meg",
-    "form_error_port_unsafe": "Ez a port nem biztonságos",
-    "form_error_equal": "Nem egyezhetnek",
-    "form_error_password": "A jelszavak nem egyeznek",
+    "form_error_port_range": "Adjon meg egy portszámot a 80-65535 tartományon belül.",
+    "form_error_port_unsafe": "Ez a port nem biztonságos.",
+    "form_error_equal": "Nem egyezhetnek.",
+    "form_error_password": "A jelszavak nem egyeznek.",
     "reset_settings": "Beállítások visszaállítása",
     "update_announcement": "Az AdGuard Home {{version}} verziója elérhető! <0>Kattintson ide</0> további információkért.",
     "setup_guide": "Beállítási útmutató",
     "dns_addresses": "DNS címek",
     "dns_start": "A DNS szerver indul",
-    "dns_status_error": "Hiba történt a DNS szerver állapotának ellenőrzésekor",
+    "dns_status_error": "Hiba történt a DNS szerver állapotának ellenőrzésekor.",
     "down": "Nem elérhető",
     "fix": "Állandó",
     "dns_providers": "Itt van az <0>ismert DNS szolgáltatók listája</0>, amelyekből választhat.",
     "update_now": "Frissítés most",
     "update_failed": "Az automatikus frissítés nem sikerült. Kérjük, hogy <a>kövesse ezeket a lépéseket</a> a manuális frissítéshez.",
+    "manual_update": "Kérjük, hogy <a>kövesse ezeket a lépéseket</a> a manuális frissítéshez.",
     "processing_update": "Kérjük várjon, az AdGuard Home frissítése folyamatban van",
-    "clients_title": "Kliensek",
-    "clients_desc": "Az AdGuard Home-hoz csatlakozó eszközök kezelése",
+    "clients_title": "Fenntartott kliensek",
+    "clients_desc": "Állítsa be az AdGuard Home-ban fenntartott kliens rekordokat az egyes eszközeihez.",
     "settings_global": "Globális",
     "settings_custom": "Egyéni",
     "table_client": "Kliens",
@@ -416,7 +417,7 @@
     "client_edit": "Kliens módosítása",
     "client_identifier": "Azonosító",
     "ip_address": "IP cím",
-    "client_identifier_desc": "A klienseket az IP-cím, a CIDR, a MAC-cím vagy egy speciális kliens azonosító alapján lehet azonosítani (ez használható DoT/DoH /DoQ esetén). <0>Itt</0> többet is megtudhat a kliensek azonosításáról.",
+    "client_identifier_desc": "A kliensek azonosíthatók az IP cím, CIDR, MAC cím, vagy a ClientID (ami használható DoT/DoH/DoQ esetén) alapján. Tudjon meg többet arról <0>ide kattintva</0>, hogy miként lehet azonosítani a klienseket.",
     "form_enter_ip": "IP-cím megadása",
     "form_enter_subnet_ip": "Adjon meg egy IP címet az alhálózatban \"{{cidr}}\"",
     "form_enter_mac": "MAC-cím megadása",
@@ -431,14 +432,14 @@
     "clients_not_found": "Nem található kliens",
     "client_confirm_delete": "Biztosan törölni szeretné a(z) \"{{key}}\" klienst?",
     "list_confirm_delete": "Biztosan törölni kívánja ezt a listát?",
-    "auto_clients_title": "Kliensek (futási idő)",
-    "auto_clients_desc": "Az AdGuard Home-ot használó, de a konfigurációban nem tárolt kliensek adatai",
+    "auto_clients_title": "Futási idejű kliensek",
+    "auto_clients_desc": "Ezek az eszközök nem szerepelnek a fenntartott kliensek listáján, de használják az AdGuard Home-ot.",
     "access_title": "Hozzáférési beállítások",
     "access_desc": "Itt konfigurálhatja az AdGuard Home DNS-kiszolgáló hozzáférési szabályait.",
     "access_allowed_title": "Engedélyezett kliensek",
-    "access_allowed_desc": "CIDR-ek, IP-címek vagy kliensazonosítók listája. Ha be van állítva, akkor az AdGuard Home csak azokat a lekérdezéseket engedélyezi, amelyek ezektől a kliensektől érkeznek.",
+    "access_allowed_desc": "CIDR-ek, IP-címek vagy <a>ClientID-k</a> listája. Ha be van állítva, akkor az AdGuard Home csak azokat a lekérdezéseket engedélyezi, amelyek ezektől a kliensektől érkeznek.",
     "access_disallowed_title": "Nem engedélyezett kliensek",
-    "access_disallowed_desc": "CIDR-ek, IP-címek vagy kliensazonosítók listája. Ha be van állítva, akkor az AdGuard Home eldobja azokat a lekérdezéseket, amelyek ezektől a kliensektől érkeznek. Ha engedélyezett kliensek vannak ide bekonfigurálva, akkor pedig az a mező ki lesz hagyva.",
+    "access_disallowed_desc": "CIDR-ek, IP-címek vagy <a>ClientID-k</a> listája. Ha be van állítva, akkor az AdGuard Home eldobja azokat a lekérdezéseket, amelyek ezektől a kliensektől érkeznek. Ha engedélyezett kliensek vannak ide bekonfigurálva, akkor pedig az a mező ki lesz hagyva.",
     "access_blocked_title": "Nem engedélyezett domainek",
     "access_blocked_desc": "Ne keverje össze ezt a szűrőkkel. Az AdGuard Home az összes DNS kérést el fogja dobni, ami ezekkel a domainekkel megegyezik, és ezek a lekérések nem is fognak megjelenni a lekérdezési naplóban sem. Megadhatja a pontos domain neveket, a helyettesítő karaktereket vagy az URL szűrési szabályokat, pl. ennek megfelelően \"example.org\", \"*.example.org\", vagy \"||example.org^\".",
     "access_settings_saved": "A hozzáférési beállítások sikeresen mentésre kerültek",
@@ -474,8 +475,8 @@
     "dns_rewrites": "DNS átírások",
     "form_domain": "Adja meg a domain nevet vagy a helyettesítő karaktert",
     "form_answer": "Adjon meg egy IP-címet vagy egy domain nevet",
-    "form_error_domain_format": "Érvénytelen domain formátum",
-    "form_error_answer_format": "Érvénytelen válasz formátum",
+    "form_error_domain_format": "Érvénytelen domain formátum.",
+    "form_error_answer_format": "Érvénytelen válasz formátum.",
     "configure": "Beállítás",
     "main_settings": "Fő beállítások",
     "block_services": "Speciális szolgáltatások blokkolása",
@@ -506,7 +507,7 @@
     "filter_updated": "A lista sikeresen frissítve lett",
     "statistics_configuration": "Statisztikai beállítások",
     "statistics_retention": "Statisztika megőrzése",
-    "statistics_retention_desc": "Ha csökkenti az intervallum értékét, az előtte levő adatok elvesznek",
+    "statistics_retention_desc": "Ha csökkenti az intervallum értékét, az előtte levő adatok elvesznek.",
     "statistics_clear": "Statisztikák visszaállítása",
     "statistics_clear_confirm": "Biztosan vissza akarja állítani a statisztikákat?",
     "statistics_retention_confirm": "Biztos benne, hogy megváltoztatja a statisztika megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
@@ -516,7 +517,7 @@
     "interval_hours_plural": "{{count}} óra",
     "filters_configuration": "Szűrők beállításai",
     "filters_enable": "Szűrők engedélyezése",
-    "filters_interval": "Szűrőfrissítési gyakoriság",
+    "filters_interval": "Szűrők frissítési gyakorisága:",
     "disabled": "Kikapcsolva",
     "username_label": "Felhasználónév",
     "username_placeholder": "Felhasználónév megadása",
@@ -531,7 +532,7 @@
     "netname": "Hálózat neve",
     "network": "Hálózat",
     "descr": "Leírás",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Tudjon meg többet</0> a saját hosztlisták létrehozásáról.",
     "blocked_by_response": "Blokkolva a CNAME vagy a válasz IP-címe alapján",
     "blocked_by_cname_or_ip": "CNAME vagy IP által blokkolva",
@@ -551,10 +552,10 @@
     "autofix_warning_list": "A következő feladatokat hajtja végre: <0>A DNSStubListener rendszer kikapcsolása</0><0>Beállítja a DNS-kiszolgáló címét 127.0.0.1-re.</0><0>Lecseréli az /etc/resolv.conf szimbolikus útvonalat erre: /run/systemd/resolve/resolv.conf</0><0>A DNSStubListener leállítása (a rendszer által feloldott szolgáltatás újratöltése)</0>",
     "autofix_warning_result": "Mindennek eredményeként az Ön rendszeréből származó összes DNS-kérést alapértelmezés szerint az AdGuard Home dolgozza fel.",
     "tags_title": "Címkék",
-    "tags_desc": "Kiválaszthatja a klienseknek megfelelő címkéket. A címkék beilleszthetők a szűrési szabályokba, és lehetővé teszik azok pontosabb alkalmazását. <0>További információ</0>",
+    "tags_desc": "Kiválaszthatja a klienseknek megfelelő címkéket. A címkék beilleszthetők a szűrési szabályokba, és lehetővé teszik azok pontosabb alkalmazását. <0>További információ</0>.",
     "form_select_tags": "Válasszon kliens címkéket",
     "check_title": "Szűrés ellenőrzése",
-    "check_desc": "Ellenőrzi, hogy a hosztnév szűrve van-e",
+    "check_desc": "Ellenőrzi, hogy a hosztnév szűrve van-e.",
     "check": "Ellenőrzés",
     "form_enter_host": "Adja meg a hosztnevet",
     "filtered_custom_rules": "Szűrve van az egyéni szűrési szabályok alapján",
@@ -597,15 +598,15 @@
     "blocklist": "Tiltólista",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Gyorsítótár mérete",
-    "cache_size_desc": "DNS gyorsítótár mérete (bájtokban)",
+    "cache_size_desc": "DNS gyorsítótár mérete (bájtokban).",
     "cache_ttl_min_override": "A minimális TTL felülírása",
     "cache_ttl_max_override": "A maximális TTL felülírása",
     "enter_cache_size": "Adja meg a gyorsítótár méretét",
     "enter_cache_ttl_min_override": "Adja meg a minimális TTL-t (másodpercben)",
     "enter_cache_ttl_max_override": "Adja meg a maximális TTL-t (másodpercben)",
-    "cache_ttl_min_override_desc": "Megnöveli a DNS kiszolgálótól kapott rövid TTL értékeket (másodpercben), ha gyorsítótárazza a DNS kéréseket",
-    "cache_ttl_max_override_desc": "Állítson be egy maximális TTL értéket (másodpercben) a DNS gyorsítótár bejegyzéseihez",
-    "ttl_cache_validation": "A minimális gyorsítótár TTL értéknek kisebbnek vagy egyenlőnek kell lennie a maximum értékkel",
+    "cache_ttl_min_override_desc": "Megnöveli a DNS kiszolgálótól kapott rövid TTL értékeket (másodpercben), ha gyorsítótárazza a DNS kéréseket.",
+    "cache_ttl_max_override_desc": "Állítson be egy maximális TTL értéket (másodpercben) a DNS gyorsítótár bejegyzéseihez.",
+    "ttl_cache_validation": "A minimális gyorsítótár TTL értéknek kisebbnek vagy egyenlőnek kell lennie a maximum értékkel.",
     "cache_optimistic": "Optimista gyorsítótár",
     "cache_optimistic_desc": "Lehetővé teszi, hogy az AdGuard Home a gyorsítótárból válaszoljon, még abban az esetben is, ha az ott lévő bejegyzések lejértak, és próbálja meg frissíteni őket.",
     "filter_category_general": "Általános",
@@ -626,5 +627,6 @@
     "use_saved_key": "Előzőleg mentett kulcs használata",
     "parental_control": "Szülői felügyelet",
     "safe_browsing": "Biztonságos böngészés",
-    "served_from_cache": "{{value}} <i>(gyorsítótárból kiszolgálva)</i>"
+    "served_from_cache": "{{value}} <i>(gyorsítótárból kiszolgálva)</i>",
+    "form_error_password_length": "A jelszó legalább {{value}} karakter hosszú kell, hogy legyen."
 }
diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json
index da174fda..118a0578 100644
--- a/client/src/__locales/sk.json
+++ b/client/src/__locales/sk.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Nastavenie klienta",
-    "example_upstream_reserved": "Môžete zadať DNS upstream <0>pre konkrétnu doménu (domény)</0>",
-    "example_upstream_comment": "Môžete napísať komentár",
+    "example_upstream_reserved": "upstream <0>pre konkrétne domény</0>;",
+    "example_upstream_comment": "komentár.",
     "upstream_parallel": "Používať paralelné dopyty na zrýchlenie súčasným dopytovaním všetkých upstream serverov súčasne.",
     "parallel_requests": "Paralelné dopyty",
     "load_balancing": "Vyrovnávanie záťaže",
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Konfigurácia DHCP servera bola úspešne uložená",
     "dhcp_ipv4_settings": "Nastavenia DHCP IPv4",
     "dhcp_ipv6_settings": "Nastavenia DHCP IPv6",
-    "form_error_required": "Povinná položka",
-    "form_error_ip4_format": "Neplatná IPv4 adresa",
-    "form_error_ip4_range_start_format": "Neplatný začiatok rozsahu IPv4 formátu",
-    "form_error_ip4_range_end_format": "Neplatný koniec rozsahu IPv4 formátu",
-    "form_error_ip4_gateway_format": "Neplatná IPv4 adresa brány",
-    "form_error_ip6_format": "Neplatná IPv6 adresa",
-    "form_error_ip_format": "Neplatná IP adresa",
-    "form_error_mac_format": "Neplatná MAC adresa",
-    "form_error_client_id_format": "ID klienta musí obsahovať iba čísla, malé písmená a spojovníky",
+    "form_error_required": "Povinná položka.",
+    "form_error_ip4_format": "Neplatná IPv4 adresa.",
+    "form_error_ip4_range_start_format": "Neplatný začiatok rozsahu IPv4 formátu.",
+    "form_error_ip4_range_end_format": "Neplatná IPv4 adresa konca rozsahu.",
+    "form_error_ip4_gateway_format": "Neplatná IPv4 adresa brány.",
+    "form_error_ip6_format": "Neplatná IPv6 adresa.",
+    "form_error_ip_format": "Neplatná IP adresa.",
+    "form_error_mac_format": "Neplatná MAC adresa.",
+    "form_error_client_id_format": "Id klienta musí obsahovať iba čísla, malé písmená a spojovníky.",
     "form_error_server_name": "Neplatné meno servera",
-    "form_error_subnet": "Podsieť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
-    "form_error_positive": "Musí byť väčšie ako 0",
-    "out_of_range_error": "Musí byť mimo rozsahu \"{{start}}\"-\"{{end}}\"",
-    "lower_range_start_error": "Musí byť nižšie ako začiatok rozsahu",
-    "greater_range_start_error": "Musí byť väčšie ako začiatok rozsahu",
-    "greater_range_end_error": "Musí byť väčšie ako koniec rozsahu",
-    "subnet_error": "Adresy musia byť v spoločnej podsieti",
-    "gateway_or_subnet_invalid": "Maska podsiete je neplatná",
+    "form_error_subnet": "Podsieť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\".",
+    "form_error_positive": "Musí byť väčšie ako 0.",
+    "out_of_range_error": "Musí byť mimo rozsahu \"{{start}}\"-\"{{end}}\".",
+    "lower_range_start_error": "Musí byť nižšie ako začiatok rozsahu.",
+    "greater_range_start_error": "Musí byť väčšie ako začiatok rozsahu.",
+    "greater_range_end_error": "Musí byť väčšie ako koniec rozsahu.",
+    "subnet_error": "Adresy musia byť v spoločnej podsieti.",
+    "gateway_or_subnet_invalid": "Maska podsiete je neplatná.",
     "dhcp_form_gateway_input": "IP brána",
     "dhcp_form_subnet_input": "Maska podsiete",
     "dhcp_form_range_title": "Rozsah IP adries",
@@ -167,8 +167,8 @@
     "enabled_safe_browsing_toast": "Bezpečné prehliadanie zapnuté",
     "disabled_parental_toast": "Vypnutá Rodičovská kontrola",
     "enabled_parental_toast": "Zapnutá Rodičovská kontrola",
-    "disabled_safe_search_toast": "Vypnuté bezpečné vyhľadávanie",
-    "enabled_save_search_toast": "Zapnuté bezpečné vyhľadávanie",
+    "disabled_safe_search_toast": "Vypnuté Bezpečné vyhľadávanie",
+    "enabled_save_search_toast": "Zapnuté Bezpečné vyhľadávanie",
     "enabled_table_header": "Zapnuté",
     "name_table_header": "Meno",
     "list_url_table_header": "Zoznam URL adries",
@@ -196,25 +196,25 @@
     "choose_allowlist": "Vybrať povolený zoznam",
     "enter_valid_blocklist": "Zadajte platnú URL adresu do zoznamu blokovaných DNS.",
     "enter_valid_allowlist": "Zadajte platnú URL adresu do zoznamu povolených DNS.",
-    "form_error_url_format": "Neplatný URL formát",
-    "form_error_url_or_path_format": "Neplatná URL adresa alebo absolútna adresa zoznamu",
+    "form_error_url_format": "Neplatný URL formát.",
+    "form_error_url_or_path_format": "Neplatná URL adresa alebo absolútna adresa zoznamu.",
     "custom_filter_rules": "Vlastné filtračné pravidlá",
     "custom_filter_rules_hint": "Zadajte na každý riadok jedno pravidlo. Môžete použiť buď adblock pravidlá alebo syntax host súborov.",
     "system_host_files": "Systémové súbory hosts",
     "examples_title": "Príklady",
-    "example_meaning_filter_block": "zablokovať prístup k doméne example.org  a všetkým jej subdoménam",
-    "example_meaning_filter_whitelist": "odblokovať prístup k doméne example.org  a všetkým jej subdoménam",
-    "example_meaning_host_block": "AdGuard Home teraz vráti adresu 127.0.0.1 pre doménu example.org (ale nie pre jej subdomény).",
-    "example_comment": "! Sem sa pridáva komentár",
-    "example_comment_meaning": "len komentár",
-    "example_comment_hash": "# Tiež komentár",
-    "example_regex_meaning": "zablokovať prístup k doménam, ktoré zodpovedajú zadanému regulárnemu výrazu",
-    "example_upstream_regular": "radová DNS (cez UDP)",
-    "example_upstream_dot": "šifrované <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "šifrované <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "šifrované <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "môžete použiť <0>DNS pečiatky</0> pre <1>DNSCrypt</1> alebo <2>DNS-over-HTTPS</2>",
-    "example_upstream_tcp": "radová DNS (cez TCP)",
+    "example_meaning_filter_block": "zablokovať prístup k doméne example.org a všetkým jej subdoménam;",
+    "example_meaning_filter_whitelist": "odblokovať prístup k doméne example.org a všetkým jej subdoménam;",
+    "example_meaning_host_block": "vrátiť IP adresu 127.0.0.1 pre doménu example.org (ale nie pre jej subdomény);",
+    "example_comment": "! Sem sa pridáva komentár.",
+    "example_comment_meaning": "len komentár;",
+    "example_comment_hash": "# Tiež komentár.",
+    "example_regex_meaning": "zablokovať prístup k doménam zodpovedajúcim zadanému regulárnemu výrazu.",
+    "example_upstream_regular": "obyčajná DNS (cez UDP);",
+    "example_upstream_dot": "šifrované <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "šifrované <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "šifrované <0>DNS-over-QUIC</0> (experimentálne);",
+    "example_upstream_sdns": "<0>DNS pečiatky</0> pre <1>DNSCrypt</1> alebo <2>DNS-over-HTTPS</2> rezolvery;",
+    "example_upstream_tcp": "obyčajná DNS (cez TCP);",
     "all_lists_up_to_date_toast": "Všetky zoznamy sú už aktuálne",
     "updated_upstream_dns_toast": "Upstream servery boli úspešne uložené",
     "dns_test_ok_toast": "Špecifikované DNS servery pracujú korektne",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Na prísne vyhľadávanie použite dvojité úvodzovky",
     "query_log_retention_confirm": "Naozaj chcete zmeniť uchovávanie denníku dopytov? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
     "anonymize_client_ip": "Anonymizujte IP klienta",
-    "anonymize_client_ip_desc": "Neukladať úplnú IP adresu klienta do protokolov a štatistík",
+    "anonymize_client_ip_desc": "Neukladať úplnú IP adresu klienta do protokolov a štatistík.",
     "dns_config": "Konfigurácia DNS servera",
     "dns_cache_config": "Konfigurácia DNS cache",
-    "dns_cache_config_desc": "Tu môžete nakonfigurovať DNS cache",
+    "dns_cache_config_desc": "Tu môžete nakonfigurovať DNS cache.",
     "blocking_mode": "Spôsob blokovania",
     "default": "Predvolené",
     "nxdomain": "NXDOMAIN",
@@ -277,7 +277,7 @@
     "dns_over_quic": "DNS-over-QUIC",
     "client_id": "ID klienta",
     "client_id_placeholder": "Zadať ID klienta",
-    "client_id_desc": "Rôznych klientov možno identifikovať podľa špeciálneho ID klienta. <a>Tu</a> sa dozviete viac o tom, ako identifikovať klientov.",
+    "client_id_desc": "Klientov možno identifikovať podľa ClientID. Viac informácií o tom, ako identifikovať klientov, nájdete <a>tu</a>.",
     "download_mobileconfig_doh": "Prevziať .mobileconfig pre DNS-over-HTTPS",
     "download_mobileconfig_dot": "Prevziať .mobileconfig pre DNS-over-TLS",
     "download_mobileconfig": "Stiahnuť konfiguračný súbor",
@@ -334,11 +334,11 @@
     "install_devices_router_list_4": "Na niektorých typoch smerovačov nemôžete nastaviť vlastný DNS server. V takom prípade môže pomôcť, ak nastavíte AdGuard Home ako <0>DHCP server</0>. V opačnom prípade by ste mali vyhľadať príručku, ako prispôsobiť DNS servery konkrétnemu modelu smerovača.",
     "install_devices_windows_list_1": "Otvorte panel Nastavenia cez menu Štart alebo vyhľadávanie Windows.",
     "install_devices_windows_list_2": "Prejdite do kategórie Sieť a internet a potom do Centra sietí a zdieľania.",
-    "install_devices_windows_list_3": "Na ľavej strane obrazovky nájdite položku \"Zmeniť nastavenia adaptéra\" a kliknite na ňu.",
-    "install_devices_windows_list_4": "Zvoľte aktívne pripojený adaptér a pravým klikom otvorte Vlastnosti",
+    "install_devices_windows_list_3": "Na ľavom paneli kliknite na „Zmeniť nastavenia adaptéra“.",
+    "install_devices_windows_list_4": "Kliknite pravým tlačidlom myši na aktívne pripojenie a vyberte Vlastnosti.",
     "install_devices_windows_list_5": "Nájdite v zozname položku \"Internet Protocol verzia 4 (TCP/IPv4)\" (alebo pre IPv6, \"Internet Protocol verzia 6 (TCP/IPv6)\"), vyberte ju a potom znova kliknite na Vlastnosti.",
     "install_devices_windows_list_6": "Zvoľte \"Použiť nasledujúce adresy DNS servera\" a zadajte adresy domáceho AdGuard servera.",
-    "install_devices_macos_list_1": "Kliknite na ikonu Apple a prejdite na položku Systémové predvoľby.",
+    "install_devices_macos_list_1": "Kliknite na ikonu Apple a prejdite na Predvoľby systému.",
     "install_devices_macos_list_2": "Kliknite na Sieť.",
     "install_devices_macos_list_3": "Zvoľte prvé pripojenie vo Vašom zozname a kliknite na Pokročilé.",
     "install_devices_macos_list_4": "Vyberte kartu DNS a zadajte adresy Vašich AdGuard Home serverov.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Otvoriť riadiaci panel",
     "install_saved": "Úspešne uložené",
     "encryption_title": "Šifrovanie",
-    "encryption_desc": "Podpora šifrovania (HTTPS/TLS) pre webové rozhranie DNS aj administrátora",
+    "encryption_desc": "Podpora šifrovania (HTTPS/TLS) pre webové rozhranie DNS aj administrátora.",
     "encryption_config_saved": "Konfigurácia šifrovania uložená",
     "encryption_server": "Meno servera",
     "encryption_server_enter": "Zadajte meno Vašej domény",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Ak je nakonfigurovaný HTTPS port, AdGuard Home administrátorské rozhranie bude prístupné cez HTTPS a bude tiež poskytovať DNS-cez-HTTPS na '/dns-query'.",
     "encryption_dot": "Port DNS-cez-TLS",
     "encryption_dot_desc": "Ak je tento port nakonfigurovaný, AdGuard Home bude na tomto porte spúšťať DNS-cez-TLS server.",
-    "encryption_doq": "Port DNS-cez-QUIC",
+    "encryption_doq": "DNS-over-QUIC (experimentálne)",
     "encryption_doq_desc": "Ak je tento port nakonfigurovaný, AdGuard Home na tomto porte spustí server DNS-over-QUIC. Je to experimentálne a nemusí to byť spoľahlivé. Momentálne tiež nie je príliš veľa klientov, ktorí by ju podporovali.",
     "encryption_certificates": "Certifikáty",
     "encryption_certificates_desc": "Ak chcete používať šifrovanie, musíte pre svoju doménu poskytnúť platný reťazec certifikátov SSL. Certifikát môžete získať bezplatne na adrese <0>{{link}}</0> alebo si ho môžete kúpiť od jedného z dôveryhodných certifikačných orgánov.",
@@ -378,26 +378,26 @@
     "encryption_key_input": "Skopírujte a prilepte sem svoj súkromný kľúč vo formáte PEM pre Váš certifikát.",
     "encryption_enable": "Zapnite šifrovanie (HTTPS, DNS-cez-HTTPS a DNS-cez-TLS)",
     "encryption_enable_desc": "Ak je šifrovanie zapnuté, AdGuard Home administrátorské rozhranie bude pracovať cez HTTPS a DNS server bude počúvať požiadavky cez DNS-cez-HTTPS a DNS-cez-TLS.",
-    "encryption_chain_valid": "Certifikačný reťazec je platný",
-    "encryption_chain_invalid": "Certifikačný reťazec je neplatný",
-    "encryption_key_valid": "Toto je platný {{type}} súkromný kľúč",
-    "encryption_key_invalid": "Toto je neplatný {{type}} súkromný kľúč",
+    "encryption_chain_valid": "Certifikačný reťazec je platný.",
+    "encryption_chain_invalid": "Certifikačný reťazec je neplatný.",
+    "encryption_key_valid": "Toto je platný {{type}} súkromný kľúč.",
+    "encryption_key_invalid": "Toto je neplatný {{type}} súkromný kľúč.",
     "encryption_subject": "Predmet",
     "encryption_issuer": "Vydavateľ",
     "encryption_hostnames": "Názvy hostiteľov",
     "encryption_reset": "Naozaj chcete obnoviť nastavenia šifrovania?",
     "topline_expiring_certificate": "Váš SSL certifikát čoskoro vyprší. Aktualizujte <0>Nastavenia šifrovania</0>.",
     "topline_expired_certificate": "Váš SSL certifikát vypršal. Aktualizujte <0>Nastavenia šifrovania</0>.",
-    "form_error_port_range": "Zadajte číslo portu v rozsahu 80-65535",
-    "form_error_port_unsafe": "Toto nie je bezpečný port",
-    "form_error_equal": "Nesmie byť rovnaká",
-    "form_error_password": "Heslo sa nezhoduje",
+    "form_error_port_range": "Zadajte číslo portu v rozsahu 80-65535.",
+    "form_error_port_unsafe": "Toto nie je bezpečný port.",
+    "form_error_equal": "Nesmie byť rovnaká.",
+    "form_error_password": "Heslo sa nezhoduje.",
     "reset_settings": "Obnoviť nastavenia",
     "update_announcement": "AdGuard Home {{version}} je teraz k dispozícii! <0>Viac informácií nájdete tu</0>.",
     "setup_guide": "Sprievodca nastavením",
     "dns_addresses": "DNS adresy",
     "dns_start": "Spúšťa sa DNS server",
-    "dns_status_error": "Chyba pri zisťovaní stavu DNS servera",
+    "dns_status_error": "Chyba pri zisťovaní stavu DNS servera.",
     "down": "Nadol",
     "fix": "Opraviť",
     "dns_providers": "Tu je <0>zoznam známych poskytovateľov DNS</0>, z ktorého si vyberiete.",
@@ -405,8 +405,8 @@
     "update_failed": "Automatická aktualizácia zlyhala. Prosím <a>sledujte postup</a> pre manuálnu aktualizáciu.",
     "manual_update": "Pre manuálnu aktualizáciu prosím <a>sledujte tento postup</a>.",
     "processing_update": "Čakajte prosím, AdGuard Home sa aktualizuje",
-    "clients_title": "Klienti",
-    "clients_desc": "Konfigurácia zariadení pripojených k AdGuard Home",
+    "clients_title": "Permanentní klienti",
+    "clients_desc": "Nakonfigurujte trvalé záznamy klientov pre zariadenia pripojené k domovskej stránke AdGuard.",
     "settings_global": "Globálne",
     "settings_custom": "Vlastné",
     "table_client": "Klient",
@@ -417,7 +417,7 @@
     "client_edit": "Upraviť klienta",
     "client_identifier": "Identifikátor",
     "ip_address": "IP adresa",
-    "client_identifier_desc": "Klientov je možné identifikovať podľa IP adresy, CIDR a MAC adresy alebo špeciálneho ID klienta (možno použiť pre DoT/DoH/DoQ). <0>Tu</0> sa dozviete viac o tom, ako identifikovať klientov.",
+    "client_identifier_desc": "Klientov možno identifikovať podľa ich IP adresy, CIDR, MAC adresy alebo ClientID (možno použiť pre DoT/DoH/DoQ). Viac informácií o tom, ako identifikovať klientov, nájdete <0>tu</0>.",
     "form_enter_ip": "Zadajte IP adresu",
     "form_enter_subnet_ip": "Zadajte IP adresu do podsiete \"{{cidr}}\"",
     "form_enter_mac": "Zadajte MAC adresu",
@@ -432,14 +432,14 @@
     "clients_not_found": "Nebol nájdený žiaden klient",
     "client_confirm_delete": "Naozaj chcete vymazať \"{{key}}\" klienta?",
     "list_confirm_delete": "Naozaj chcete vymazať tento zoznam?",
-    "auto_clients_title": "Klienti (runtime)",
-    "auto_clients_desc": "Údaje o klientoch, ktorí používajú AdGuard Home, ale nie sú uložení v konfigurácii",
+    "auto_clients_title": "Runtime klienti",
+    "auto_clients_desc": "Zariadenia, ktoré nie sú na zozname trvalých klientov, ktorí môžu stále používať AdGuard Home.",
     "access_title": "Nastavenia prístupu",
     "access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.",
     "access_allowed_title": "Povolení klienti",
-    "access_allowed_desc": "Zoznam CIDR, IP adries alebo ID klientov. Ak je nakonfigurovaný, AdGuard Home akceptuje len dopyty od týchto klientov.",
+    "access_allowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home bude akceptovať požiadavky iba od týchto klientov.",
     "access_disallowed_title": "Nepovolení klienti",
-    "access_disallowed_desc": "Zoznam CIDR, IP adries alebo ID klientov. Ak je nakonfigurovaný, AdGuard Home bude ignorovať dopyty od týchto klientov.",
+    "access_disallowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home zruší požiadavky od týchto klientov. Toto pole sa ignoruje, ak sú v poli Povolení klienti položky.",
     "access_blocked_title": "Nepovolené domény",
     "access_blocked_desc": "Nesmie byť zamieňaná s filtrami. AdGuard Home zruší DNS dopyty, ktoré sa zhodujú s týmito doménami, a tieto dopyty sa nezobrazia ani v denníku dopytov. Môžete určiť presné názvy domén, zástupné znaky alebo pravidlá filtrovania URL adries, napr. \"example.org\", \"*.example.org\" alebo ||example.org^\" zodpovedajúcim spôsobom.",
     "access_settings_saved": "Nastavenia prístupu úspešne uložené",
@@ -475,8 +475,8 @@
     "dns_rewrites": "DNS prepisovanie",
     "form_domain": "Zadajte meno domény alebo zástupný znak",
     "form_answer": "Zadajte IP adresu alebo meno domény",
-    "form_error_domain_format": "Neplatný formát domény",
-    "form_error_answer_format": "Neplatný formát odpovede",
+    "form_error_domain_format": "Neplatný formát domény.",
+    "form_error_answer_format": "Neplatný formát odpovede.",
     "configure": "Konfigurovať",
     "main_settings": "Hlavné nastavenia",
     "block_services": "Blokovať vybrané služby",
@@ -507,7 +507,7 @@
     "filter_updated": "Filter bol úspešne aktualizovaný",
     "statistics_configuration": "Konfigurácia štatistiky",
     "statistics_retention": "Štatistika za obdobie",
-    "statistics_retention_desc": "Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
+    "statistics_retention_desc": "Ak znížite hodnotu intervalu, niektoré údaje sa stratia.",
     "statistics_clear": "Vynulovať štatistiku",
     "statistics_clear_confirm": "Naozaj chcete vynulovať štatistiku?",
     "statistics_retention_confirm": "Naozaj chcete zmeniť uchovávanie štatistík? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
@@ -532,7 +532,7 @@
     "netname": "Meno siete",
     "network": "Sieť",
     "descr": "Popis",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Dozvedieť sa viac</0> o tvorbe vlastných zoznamov hostiteľov.",
     "blocked_by_response": "Blokované pomocou CNAME alebo IP v odpovedi",
     "blocked_by_cname_or_ip": "Zablokované na základe CNAME alebo IP",
@@ -552,10 +552,10 @@
     "autofix_warning_list": "Bude vykonávať tieto úlohy: <0>Deaktivovať systém DNSStubListener</0> <0>Nastaviť adresu servera DNS na 127.0.0.1</0> <0>Nahradiť cieľový symbolický odkaz /etc/resolv.conf na /run/systemd/resolve/resolv.conf</0> <0>Zastaviť službu DNSStubListener (znova načítať službu systemd-resolved)</0>",
     "autofix_warning_result": "Výsledkom bude, že všetky DNS dopyty z Vášho systému budú štandardne spracované službou AdGuard Home.",
     "tags_title": "Tagy",
-    "tags_desc": "Môžete vybrať tagy ktoré zodpovedajú klientovi. Tagy môžu byť súčasťou filtračných pravidiel a umožňujú Vám použiť ich presnejšie. <0>Viac informácií</0>",
+    "tags_desc": "Môžete vybrať značky, ktoré zodpovedajú klientovi. Zahrňte značky do pravidiel filtrovania, aby ste ich použili presnejšie. <0>Viac informácií</0>.",
     "form_select_tags": "Zvoľte tagy klienta",
     "check_title": "Skontrolujte filtráciu",
-    "check_desc": "Skontrolujte, či je názov hostiteľa filtrovaný",
+    "check_desc": "Skontrolujte, či je názov hostiteľa filtrovaný.",
     "check": "Kontrola",
     "form_enter_host": "Zadajte meno hostiteľa",
     "filtered_custom_rules": "Filtrované podľa vlastných filtračných pravidiel",
@@ -604,9 +604,9 @@
     "enter_cache_size": "Zadať veľkosť cache (v bajtoch)",
     "enter_cache_ttl_min_override": "Zadať minimálne TTL (v sekundách)",
     "enter_cache_ttl_max_override": "Zadať maximálne TTL (v sekundách)",
-    "cache_ttl_min_override_desc": "Predĺži krátke hodnoty TTL (v sekundách) prijaté od servera typu upstream pri ukladaní odpovedí DNS do cache pamäte",
-    "cache_ttl_max_override_desc": "Nastaví maximálnu hodnotu TTL (v sekundách) pre záznamy v DNS cache pamäti",
-    "ttl_cache_validation": "Minimálna hodnota TTL cache musí byť menšia alebo rovná maximálnej hodnote",
+    "cache_ttl_min_override_desc": "Predĺži krátke hodnoty TTL (v sekundách) prijaté od servera typu upstream pri ukladaní odpovedí DNS do cache pamäte.",
+    "cache_ttl_max_override_desc": "Nastaví maximálnu hodnotu TTL (v sekundách) pre záznamy v DNS cache pamäti.",
+    "ttl_cache_validation": "Minimálna hodnota TTL cache musí byť menšia alebo rovná maximálnej hodnote.",
     "cache_optimistic": "Optimistické nastavenie",
     "cache_optimistic_desc": "Nechajte AdGuard Home odpovedať z vyrovnávacej pamäte, aj keď už platnosť položiek skončila, a tiež sa pokúste ich obnoviť.",
     "filter_category_general": "Všeobecné",
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index eb721af4..716d784c 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -210,11 +210,11 @@
     "example_comment_hash": "# Також коментар.",
     "example_regex_meaning": "блокувати доступ до доменів, що відповідають вказаному регулярному виразу.",
     "example_upstream_regular": "звичайний DNS (через UDP);",
-    "example_upstream_dot": "зашифрований <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "зашифрований <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "зашифрований <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "ви можете використовувати <0>DNS Stamps</0> для вирішення <1>DNSCrypt</1> або <2>DNS-over-HTTPS</2>",
-    "example_upstream_tcp": "звичайний DNS (через TCP)",
+    "example_upstream_dot": "зашифрований <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "зашифрований <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "зашифрований <0>DNS-over-QUIC</0> (експериментальний);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt</1> або <2>DNS-over-HTTPS</2> серверів;",
+    "example_upstream_tcp": "звичайний DNS (через TCP);",
     "all_lists_up_to_date_toast": "Всі списки вже оновлені",
     "updated_upstream_dns_toast": "DNS-сервери оновлено",
     "dns_test_ok_toast": "Вказані DNS сервери працюють правильно",
@@ -259,10 +259,10 @@
     "query_log_strict_search": "Використовуйте подвійні лапки для точного пошуку",
     "query_log_retention_confirm": "Ви дійсно хочете змінити час зберігання журналу? Якщо ви зменшите значення, деякі дані будуть втрачені",
     "anonymize_client_ip": "Анонімізація IP-адреси клієнта",
-    "anonymize_client_ip_desc": "Не зберігайте повну IP-адресу клієнта в журналах і статистиці",
+    "anonymize_client_ip_desc": "Не зберігати повну IP-адресу клієнта в журналах і статистиці.",
     "dns_config": "Конфігурація DNS-сервера",
     "dns_cache_config": "Конфігурація кешу DNS",
-    "dns_cache_config_desc": "Тут ви можете налаштувати кеш DNS",
+    "dns_cache_config_desc": "Тут ви можете налаштувати кеш DNS.",
     "blocking_mode": "Режим блокування",
     "default": "Типовий",
     "nxdomain": "NXDOMAIN",
@@ -275,9 +275,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "Ідентифікатор клієнта",
-    "client_id_placeholder": "Введіть ідентифікатор клієнта",
-    "client_id_desc": "Різні клієнти можуть бути розпізнані завдяки спеціальному ідентифікатору. <a>Докладніше про ідентифікацію клієнтів</a>.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Введіть ClientID",
+    "client_id_desc": "Різні клієнти можуть бути розпізнані завдяки ClientID. <a>Докладніше про ідентифікацію клієнтів</a>.",
     "download_mobileconfig_doh": "Завантажити .mobileconfig для DNS-over-HTTPS",
     "download_mobileconfig_dot": "Завантажити .mobileconfig для DNS-over-TLS",
     "download_mobileconfig": "Завантажити файл конфігурації",
@@ -309,7 +309,7 @@
     "install_settings_listen": "Мережевий інтерфейс",
     "install_settings_port": "Порт",
     "install_settings_interface_link": "Веб-інтерфейс адміністратора AdGuard Home буде доступний за такими адресами:",
-    "form_error_port": "Уведіть правильне значення порту",
+    "form_error_port": "Уведіть правильне значення порту.",
     "install_settings_dns": "DNS-сервер",
     "install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:",
     "install_settings_all_interfaces": "Усі інтерфейси",
@@ -334,12 +334,12 @@
     "install_devices_router_list_4": "Ви не можете встановити власний DNS-сервер на деяких типах маршрутизаторів. У цьому разі вам може допомогти налаштування AdGuard Home в якості <0>DHCP-сервера</0>. В іншому разі вам потрібно знайти інструкцію щодо налаштування DNS-сервера для вашої конкретної моделі маршрутизатора.",
     "install_devices_windows_list_1": "Відкрийте Панель керування через меню «Пуск» або пошук Windows.",
     "install_devices_windows_list_2": "Перейдіть до категорії Мережа й Інтернет, а потім до Центру мереж і спільного доступу.",
-    "install_devices_windows_list_3": "У лівій частині екрана знайдіть текст «Змінити настройки адаптера» та натисніть на нього.",
-    "install_devices_windows_list_4": "Виберіть своє активне з'єднання, клацніть на ньому правою кнопкою миші та виберіть Властивості.",
+    "install_devices_windows_list_3": "Зліва на екрані натисніть на «Змінити настройки адаптера».",
+    "install_devices_windows_list_4": "Клацніть на активному з'єднанні правою кнопкою миші та виберіть «Властивості».",
     "install_devices_windows_list_5": "Знайдіть у списку пункт «Internet Protocol Version 4 (TCP/IPv4)» або «Internet Protocol Version 6 (TCP/IPv6)», виберіть його та натисніть кнопку Властивості ще раз.",
     "install_devices_windows_list_6": "Виберіть «Використовувати наступні адреси DNS-серверів» та введіть адреси вашого сервера AdGuard Home.",
     "install_devices_macos_list_1": "Клацніть на піктограму Apple і перейдіть до Системних налаштувань.",
-    "install_devices_macos_list_2": "Клацніть на Мережа.",
+    "install_devices_macos_list_2": "Виберіть «Мережа».",
     "install_devices_macos_list_3": "Виберіть перше з'єднання зі списку та натисніть кнопку Додатково.",
     "install_devices_macos_list_4": "Виберіть вкладку DNS і введіть адреси сервера AdGuard Home.",
     "install_devices_android_list_1": "На головному екрані меню Android торкніться Налаштування.",
@@ -356,7 +356,7 @@
     "open_dashboard": "Відкрити інформаційну панель",
     "install_saved": "Збережено успішно",
     "encryption_title": "Шифрування",
-    "encryption_desc": "Підтримка шифрування (HTTPS/TLS) як для DNS так і для веб-інтерфейсу адміністратора",
+    "encryption_desc": "Підтримка шифрування (HTTPS/TLS) як для DNS, так і для вебінтерфейсу адміністратора.",
     "encryption_config_saved": "Конфігурацію шифрування збережено",
     "encryption_server": "Назва сервера",
     "encryption_server_enter": "Введіть ваше доменне ім'я",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Якщо HTTPS-порт налаштовано, інтерфейс адміністратора AdGuard Home буде доступний через HTTPS, а також DNS-over-HTTPS-сервер буде доступний за адресою /dns-query.",
     "encryption_dot": "Порт DNS-over-TLS",
     "encryption_dot_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-TLS.",
-    "encryption_doq": "Порт DNS-over-QUIC",
+    "encryption_doq": "Порт DNS-over-QUIC (експериментальний)",
     "encryption_doq_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-QUIC. Це експериментально і може бути ненадійним. Крім того, зараз не так багато клієнтів, які це підтримують.",
     "encryption_certificates": "Сертифікати",
     "encryption_certificates_desc": "Для використання шифрування потрібно надати дійсний ланцюжок сертифікатів SSL для вашого домену. Ви можете отримати безкоштовний сертифікат на <0>{{link}}</0> або придбати його в одному з надійних Центрів Сертифікації.",
@@ -378,20 +378,20 @@
     "encryption_key_input": "Скопіюйте/вставте сюди свій приватний ключ кодований PEM для вашого сертифіката.",
     "encryption_enable": "Увімкнути шифрування (HTTPS, DNS-over-HTTPS і DNS-over-TLS)",
     "encryption_enable_desc": "Якщо ввімкнено шифрування, інтерфейс адміністратора AdGuard Home буде працювати через HTTPS, а DNS-сервер буде прослуховувати запити через DNS-over-HTTPS і DNS-over-TLS.",
-    "encryption_chain_valid": "Ланцюжок сертифікатів дійсний",
-    "encryption_chain_invalid": "Ланцюжок сертифікатів не дійсний",
-    "encryption_key_valid": "Це дійсний приватний ключ {{type}}",
-    "encryption_key_invalid": "Це недійсний приватний ключ {{type}}",
+    "encryption_chain_valid": "Ланцюжок довіри сертифікатів дійсний.",
+    "encryption_chain_invalid": "Ланцюжок довіри сертифікатів не дійсний.",
+    "encryption_key_valid": "Дійсний {{type}} приватний ключ.",
+    "encryption_key_invalid": "Недійсний {{type}} приватний ключ.",
     "encryption_subject": "Обє'кт",
     "encryption_issuer": "Видавець",
     "encryption_hostnames": "Назви вузлів",
     "encryption_reset": "Ви впевнені, що хочете скинути налаштування шифрування?",
     "topline_expiring_certificate": "Ваш сертифікат SSL скоро закінчиться. Оновіть <0>Налаштування шифрування</0>.",
     "topline_expired_certificate": "Термін дії вашого сертифіката SSL закінчився. Оновіть <0>Налаштування шифрування</0>.",
-    "form_error_port_range": "Введіть значення порту в діапазоні 80−65535",
-    "form_error_port_unsafe": "Це небезпечний порт",
-    "form_error_equal": "Мають бути різні значення",
-    "form_error_password": "Пароль не співпадає",
+    "form_error_port_range": "Введіть значення порту в діапазоні 80−65535.",
+    "form_error_port_unsafe": "Це небезпечний порт.",
+    "form_error_equal": "Мають бути різні значення.",
+    "form_error_password": "Паролі не збігаються.",
     "reset_settings": "Скинути налаштування",
     "update_announcement": "AdGuard Home {{version}} тепер доступний! <0>Докладніше</0>.",
     "setup_guide": "Посібник з налаштування",

From ea6e033daecf5861c5bc40d2f8c8759edec2d163 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 1 Mar 2022 20:35:44 +0300
Subject: [PATCH 007/143] Pull request: all: upd chlog

Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 5933ed86b41646c61a595c94068890a1675a3ad1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Mar 1 20:31:47 2022 +0300

    all: upd chlog
---
 CHANGELOG.md | 52 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 35 insertions(+), 17 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a54f8c57..5b83fc01 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,7 +26,7 @@ and this project adheres to
 - Domain-specific private reverse DNS upstream servers are now validated to
   allow only `*.in-addr.arpa` and `*.ip6.arpa` domains pointing to
   locally-served networks ([#3381]).  **Note:**  If you already have invalid
-  entires in your configuration, consider removing them manually, since they
+  entries in your configuration, consider removing them manually, since they
   essentially had no effect.
 - Response filtering is now performed using the record types of the answer
   section of messages as opposed to the type of the question ([#4238]).
@@ -66,20 +66,13 @@ In this release, the schema version has changed from 12 to 13.
 
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
 
-### Fixed
-
-- Optimistic cache now responds with expired items even if those can't be
-  resolved again ([#4254]).
-- Unnecessarily complex hosts-related logic leading to infinite recursion in
-  some cases ([#4216]).
-
 ### Removed
 
 - Go 1.16 support.
 
 ### Security
 
-- Enforced password strength policy ([3503]).
+- Enforced password strength policy ([#3503]).
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
 
@@ -90,22 +83,46 @@ In this release, the schema version has changed from 12 to 13.
 [#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381
 [#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
 [#4213]: https://github.com/AdguardTeam/AdGuardHome/issues/4213
-[#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
-[#4254]: https://github.com/AdguardTeam/AdGuardHome/issues/4254
 
 [repr]: https://reproducible-builds.org/docs/source-date-epoch/
 
 
 
 <!--
-## [v0.107.4] - 2022-03-01 (APPROX.)
+## [v0.107.5] - 2022-04-04 (APPROX.)
+
+See also the [v0.107.5 GitHub milestone][ms-v0.107.5].
+
+[ms-v0.107.5]: https://github.com/AdguardTeam/AdGuardHome/milestone/42?closed=1
+-->
+
+
+
+## [v0.107.4] - 2022-03-01
 
 See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
 
-[ms-v0.107.4]: https://github.com/AdguardTeam/AdGuardHome/milestone/41?closed=1
--->
+### Fixed
+
+- Optimistic cache now responds with expired items even if those can't be
+  resolved again ([#4254]).
+- Unnecessarily complex hosts-related logic leading to infinite recursion in
+  some cases ([#4216]).
+
+### Security
+
+- Go version was updated to prevent the possibility of exploiting
+  [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773].
+
+[#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
+[#4254]: https://github.com/AdguardTeam/AdGuardHome/issues/4254
+
+[CVE-2022-23772]: https://www.cvedetails.com/cve/CVE-2022-23772
+[CVE-2022-23773]: https://www.cvedetails.com/cve/CVE-2022-23773
+[CVE-2022-23806]: https://www.cvedetails.com/cve/CVE-2022-23806
+[ms-v0.107.4]:    https://github.com/AdguardTeam/AdGuardHome/milestone/41?closed=1
 
 
 
@@ -824,11 +841,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
 
 
 <!--
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...HEAD
-[v0.107.4]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.3...v0.107.4
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...HEAD
+[v0.107.5]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5
 -->
 
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.3...HEAD
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...HEAD
+[v0.107.4]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.3...v0.107.4
 [v0.107.3]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.2...v0.107.3
 [v0.107.2]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.1...v0.107.2
 [v0.107.1]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.0...v0.107.1

From e0b557eda2680e63037ae0eee2106a1f9466d141 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 2 Mar 2022 14:21:33 +0300
Subject: [PATCH 008/143] Pull request: 4166 udp upstream

Merge in DNS/adguard-home from 4166-udp-upstream to master

Closes #4166.

Squashed commit of the following:

commit b8b6d1c7ac1e11e83c0c68e46e7f66fdc6043839
Merge: e5f01273 ea6e033d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 1 20:36:40 2022 +0300

    Merge branch 'master' into 4166-udp-upstream

commit e5f0127384d84c4395da5b79a1fd4a47acbe122c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 1 19:41:33 2022 +0300

    client: upd upstream examples

commit bd974f22231f11f4c57e19d6d13bc45dbfdf2fdf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 1 18:36:10 2022 +0300

    all: upd proxy

commit badf1325090ecd1dc86e42e7406dfb6653e07bf1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 4 14:36:50 2022 +0300

    WIP
---
 CHANGELOG.md                                         |  3 +++
 client/src/__locales/en.json                         |  2 ++
 .../src/components/Settings/Dns/Upstream/Examples.js | 12 +++++++++---
 go.mod                                               |  4 ++--
 go.sum                                               |  9 ++++-----
 internal/dnsforward/http.go                          |  2 +-
 internal/dnsforward/http_test.go                     | 10 +++++++++-
 7 files changed, 30 insertions(+), 12 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b83fc01..57adf5bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@ and this project adheres to
 
 ### Added
 
+- Support for hostnames for plain UDP upstream servers using the `udp://` scheme
+  ([#4166]).
 - Logs are now collected by default on FreeBSD and OpenBSD when AdGuard Home is
   installed as a service ([#4213]).
 - `windows/arm64` support ([#3057]).
@@ -82,6 +84,7 @@ In this release, the schema version has changed from 12 to 13.
 [#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
 [#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381
 [#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
+[#4166]: https://github.com/AdguardTeam/AdGuardHome/issues/4166
 [#4213]: https://github.com/AdguardTeam/AdGuardHome/issues/4213
 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 1559a24e..f1e1eac0 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Also a comment.",
     "example_regex_meaning": "block access to domains matching the specified regular expression.",
     "example_upstream_regular": "regular DNS (over UDP);",
+    "example_upstream_udp": "regular DNS (over UDP, hostname);",
     "example_upstream_dot": "encrypted <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "encrypted <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "encrypted <0>DNS-over-QUIC</0> (experimental);",
     "example_upstream_sdns": "<0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers;",
     "example_upstream_tcp": "regular DNS (over TCP);",
+    "example_upstream_tcp_hostname": "regular DNS (over TCP, hostname);",
     "all_lists_up_to_date_toast": "All lists are already up-to-date",
     "updated_upstream_dns_toast": "Upstream servers successfully saved",
     "dns_test_ok_toast": "Specified DNS servers are working correctly",
diff --git a/client/src/components/Settings/Dns/Upstream/Examples.js b/client/src/components/Settings/Dns/Upstream/Examples.js
index 2f145b3a..b4e0ce09 100644
--- a/client/src/components/Settings/Dns/Upstream/Examples.js
+++ b/client/src/components/Settings/Dns/Upstream/Examples.js
@@ -10,6 +10,15 @@ const Examples = (props) => (
             <li>
                 <code>94.140.14.140</code>: {props.t('example_upstream_regular')}
             </li>
+            <li>
+                <code>udp://dns-unfiltered.adguard.com</code>: <Trans>example_upstream_udp</Trans>
+            </li>
+            <li>
+                <code>tcp://94.140.14.140</code>: <Trans>example_upstream_tcp</Trans>
+            </li>
+            <li>
+                <code>tcp://dns-unfiltered.adguard.com</code>: <Trans>example_upstream_tcp_hostname</Trans>
+            </li>
             <li>
                 <code>tls://dns-unfiltered.adguard.com</code>:
                 <span>
@@ -67,9 +76,6 @@ const Examples = (props) => (
                     </Trans>
                 </span>
             </li>
-            <li>
-                <code>tcp://94.140.14.140</code>: <Trans>example_upstream_tcp</Trans>
-            </li>
             <li>
                 <code>sdns://...</code>:
                 <span>
diff --git a/go.mod b/go.mod
index 00182235..91fee199 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
 go 1.17
 
 require (
-	github.com/AdguardTeam/dnsproxy v0.41.1
-	github.com/AdguardTeam/golibs v0.10.5
+	github.com/AdguardTeam/dnsproxy v0.41.3
+	github.com/AdguardTeam/golibs v0.10.6
 	github.com/AdguardTeam/urlfilter v0.15.2
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/ameshkov/dnscrypt/v2 v2.2.3
diff --git a/go.sum b/go.sum
index 99f20fd1..4ae0c523 100644
--- a/go.sum
+++ b/go.sum
@@ -7,14 +7,13 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/AdguardTeam/dnsproxy v0.41.1 h1:sDWami83ZNp0XNdWsLECwIX/hPI5UnVrotRtPnrgDuo=
-github.com/AdguardTeam/dnsproxy v0.41.1/go.mod h1:PZ9l22h3Er+5mxFQB7oHZMTvx+aa9R6LbzA/ikXQlS0=
+github.com/AdguardTeam/dnsproxy v0.41.3 h1:FJnIf2pHaABUjAvB0P79nIXN5sBAvsUf2368NNw50+s=
+github.com/AdguardTeam/dnsproxy v0.41.3/go.mod h1:GCdEbTw683vBqksJIccPSYzBg2yIFbRiDnXltyIinug=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
-github.com/AdguardTeam/golibs v0.10.3/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
-github.com/AdguardTeam/golibs v0.10.5 h1:4/nl1yIBJOv5luVu9SURW8LfgOjI3zQ2moIUy/1k0y4=
-github.com/AdguardTeam/golibs v0.10.5/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
+github.com/AdguardTeam/golibs v0.10.6 h1:6UG6LxWFnG7TfjNzeApw+T68Kqqov0fcDYk9RjhTdhc=
+github.com/AdguardTeam/golibs v0.10.6/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
 github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
 github.com/AdguardTeam/urlfilter v0.15.2 h1:LZGgrm4l4Ys9eAqB+UUmZfiC6vHlDlYFhx0WXqo6LtQ=
 github.com/AdguardTeam/urlfilter v0.15.2/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go
index d653fa1e..b7fb66a6 100644
--- a/internal/dnsforward/http.go
+++ b/internal/dnsforward/http.go
@@ -459,7 +459,7 @@ func ValidateUpstreamsPrivate(upstreams []string, lnc LocalNetChecker) (err erro
 	return nil
 }
 
-var protocols = []string{"tls://", "https://", "tcp://", "sdns://", "quic://"}
+var protocols = []string{"udp://", "tcp://", "tls://", "https://", "sdns://", "quic://"}
 
 func validateUpstream(u string) (useDefault bool, err error) {
 	// Check if the user tries to specify upstream for domain.
diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go
index 66931f4d..6e28ab41 100644
--- a/internal/dnsforward/http_test.go
+++ b/internal/dnsforward/http_test.go
@@ -306,6 +306,14 @@ func TestValidateUpstream(t *testing.T) {
 		name:     "valid_default",
 		upstream: "sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
 		wantErr:  ``,
+	}, {
+		wantDef:  assert.True,
+		name:     "default_udp_host",
+		upstream: "udp://dns.google",
+	}, {
+		wantDef:  assert.True,
+		name:     "default_udp_ip",
+		upstream: "udp://8.8.8.8",
 	}, {
 		wantDef:  assert.False,
 		name:     "valid",
@@ -389,7 +397,7 @@ func TestValidateUpstreams(t *testing.T) {
 		},
 	}, {
 		name:    "invalid",
-		wantErr: `cannot prepare the upstream dhcp://fake.dns ([]): unsupported URL scheme: dhcp`,
+		wantErr: `cannot prepare the upstream dhcp://fake.dns ([]): unsupported url scheme: dhcp`,
 		set:     []string{"dhcp://fake.dns"},
 	}}
 

From 9a764b9b82ca418bd72bd897f7dd8e99312c7099 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Thu, 3 Mar 2022 17:52:11 +0300
Subject: [PATCH 009/143] Pull request: 3978 Query Log ECS

Merge in DNS/adguard-home from 3978-ecs-ip to master

Updates #3978.

Squashed commit of the following:

commit 915b94afa4b6d90169f73d4fa171bc81bcc267a7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 17:46:40 2022 +0300

    all: rm dot

commit 2dd2ed081b199de7e5d8269dae5d08d53b5eea6d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 17:42:45 2022 +0300

    client: imp txt

commit 8d5a23df739f0b650f9f3870141fd83e8fa0c1e0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 14:36:04 2022 +0300

    client: imp text

commit 69c856749a20144822ef3f1f67c5f3e3c24f5374
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 14:24:56 2022 +0300

    client: imp description

commit cd0150128ad29d1874492735a5d621c0803ad0bd
Merge: 28181fbc e0b557ed
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 2 21:02:16 2022 +0300

    Merge branch 'master' into 3978-ecs-ip

commit 28181fbc79eb22e7fd13cbd1d5a3c040af9fa2a4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Mar 2 20:45:50 2022 +0300

    client: show ecs

commit cdc5e7f8c4155b798426d815eed0da547ef6efb7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 17 20:15:56 2022 +0300

    openapi: fix milestone

commit 404d6d822fa1ba4ed4cd41d92d4c1b805342fe55
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 17 20:08:21 2022 +0300

    all: fix deps, docs

commit 8fb80526f1e251d3b7b193c53a4a6dee0e22c145
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 17 19:39:34 2022 +0300

    all: add querylog ecs backend
---
 CHANGELOG.md                                  |  4 +
 client/src/__locales/en.json                  |  3 +-
 .../src/components/Logs/Cells/DomainCell.js   |  9 ++
 client/src/components/Logs/Cells/index.js     |  1 +
 client/src/helpers/helpers.js                 |  2 +
 internal/dnsforward/stats.go                  | 84 +++++++++++--------
 internal/querylog/decode.go                   | 12 ++-
 internal/querylog/decode_test.go              |  2 +
 internal/querylog/json.go                     |  4 +
 internal/querylog/qlog.go                     |  6 ++
 internal/querylog/querylog.go                 |  4 +
 internal/querylog/searchcriterion.go          | 18 +---
 openapi/CHANGELOG.md                          |  9 +-
 openapi/openapi.yaml                          |  6 ++
 14 files changed, 108 insertions(+), 56 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57adf5bf..988d4066 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@ and this project adheres to
 
 ### Added
 
+- EDNS Client-Subnet information in the request details section of a query log
+  record ([#3978]).
 - Support for hostnames for plain UDP upstream servers using the `udp://` scheme
   ([#4166]).
 - Logs are now collected by default on FreeBSD and OpenBSD when AdGuard Home is
@@ -84,8 +86,10 @@ In this release, the schema version has changed from 12 to 13.
 [#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
 [#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381
 [#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
+[#3978]: https://github.com/AdguardTeam/AdGuardHome/issues/3978
 [#4166]: https://github.com/AdguardTeam/AdGuardHome/issues/4166
 [#4213]: https://github.com/AdguardTeam/AdGuardHome/issues/4213
+[#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
 
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index f1e1eac0..4d1dc857 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -287,7 +287,7 @@
     "form_enter_rate_limit": "Enter rate limit",
     "rate_limit": "Rate limit",
     "edns_enable": "Enable EDNS client subnet",
-    "edns_cs_desc": "Send clients' subnets to the DNS servers.",
+    "edns_cs_desc": "Add the EDNS Client Subnet option (ECS) to upstream requests and log the values sent by the clients in the query log.",
     "rate_limit_desc": "The number of requests per second allowed per client. Setting it to 0 means no limit.",
     "blocking_ipv4_desc": "IP address to be returned for a blocked A request",
     "blocking_ipv6_desc": "IP address to be returned for a blocked AAAA request",
@@ -502,6 +502,7 @@
     "interval_days": "{{count}} day",
     "interval_days_plural": "{{count}} days",
     "domain": "Domain",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Answer",
     "filter_added_successfully": "The list has been successfully added",
diff --git a/client/src/components/Logs/Cells/DomainCell.js b/client/src/components/Logs/Cells/DomainCell.js
index 5ab18280..620d6711 100644
--- a/client/src/components/Logs/Cells/DomainCell.js
+++ b/client/src/components/Logs/Cells/DomainCell.js
@@ -20,6 +20,7 @@ const DomainCell = ({
     time,
     tracker,
     type,
+    ecs,
 }) => {
     const { t } = useTranslation();
     const dnssec_enabled = useSelector((state) => state.dnsConfig.dnssec_enabled);
@@ -56,6 +57,13 @@ const DomainCell = ({
         };
     }
 
+    if (ecs) {
+        requestDetailsObj = {
+            ...requestDetailsObj,
+            ecs,
+        };
+    }
+
     requestDetailsObj = {
         ...requestDetailsObj,
         type_table_header: type,
@@ -168,6 +176,7 @@ DomainCell.propTypes = {
     time: propTypes.string.isRequired,
     type: propTypes.string.isRequired,
     tracker: propTypes.object,
+    ecs: propTypes.string,
 };
 
 export default DomainCell;
diff --git a/client/src/components/Logs/Cells/index.js b/client/src/components/Logs/Cells/index.js
index 2287f8d1..273d9495 100644
--- a/client/src/components/Logs/Cells/index.js
+++ b/client/src/components/Logs/Cells/index.js
@@ -238,6 +238,7 @@ Row.propTypes = {
         type: propTypes.string.isRequired,
         client_proto: propTypes.string.isRequired,
         client_id: propTypes.string,
+        ecs: propTypes.string,
         client_info: propTypes.shape({
             name: propTypes.string.isRequired,
             whois: propTypes.shape({
diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js
index 2546d5b9..5fb42c05 100644
--- a/client/src/helpers/helpers.js
+++ b/client/src/helpers/helpers.js
@@ -76,6 +76,7 @@ export const normalizeLogs = (logs) => logs.map((log) => {
         original_answer,
         upstream,
         cached,
+        ecs,
     } = log;
 
     const { name: domain, unicode_name: unicodeName, type } = question;
@@ -118,6 +119,7 @@ export const normalizeLogs = (logs) => logs.map((log) => {
         elapsedMs,
         upstream,
         cached,
+        ecs,
     };
 });
 
diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go
index d113a5fd..56cc19c5 100644
--- a/internal/dnsforward/stats.go
+++ b/internal/dnsforward/stats.go
@@ -41,55 +41,65 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
 	// uninitialized while in use.  This can happen after proxy server has been
 	// stopped, but its workers haven't yet exited.
 	if shouldLog && s.queryLog != nil {
-		p := &querylog.AddParams{
-			Question:          msg,
-			Answer:            pctx.Res,
-			OrigAnswer:        dctx.origResp,
-			Result:            dctx.result,
-			Elapsed:           elapsed,
-			ClientID:          dctx.clientID,
-			ClientIP:          ip,
-			AuthenticatedData: dctx.responseAD,
-		}
-
-		switch pctx.Proto {
-		case proxy.ProtoHTTPS:
-			p.ClientProto = querylog.ClientProtoDoH
-		case proxy.ProtoQUIC:
-			p.ClientProto = querylog.ClientProtoDoQ
-		case proxy.ProtoTLS:
-			p.ClientProto = querylog.ClientProtoDoT
-		case proxy.ProtoDNSCrypt:
-			p.ClientProto = querylog.ClientProtoDNSCrypt
-		default:
-			// Consider this a plain DNS-over-UDP or DNS-over-TCP request.
-		}
-
-		if pctx.Upstream != nil {
-			p.Upstream = pctx.Upstream.Address()
-		} else if cachedUps := pctx.CachedUpstreamAddr; cachedUps != "" {
-			p.Upstream = pctx.CachedUpstreamAddr
-			p.Cached = true
-		}
-
-		s.queryLog.Add(p)
+		s.logQuery(dctx, pctx, elapsed, ip)
 	}
 
-	s.updateStats(dctx, elapsed, *dctx.result, ip)
+	if s.stats != nil {
+		s.updateStats(dctx, elapsed, *dctx.result, ip)
+	}
 
 	return resultCodeSuccess
 }
 
+// logQuery pushes the request details into the query log.
+func (s *Server) logQuery(
+	dctx *dnsContext,
+	pctx *proxy.DNSContext,
+	elapsed time.Duration,
+	ip net.IP,
+) {
+	p := &querylog.AddParams{
+		Question:          pctx.Req,
+		ReqECS:            pctx.ReqECS,
+		Answer:            pctx.Res,
+		OrigAnswer:        dctx.origResp,
+		Result:            dctx.result,
+		Elapsed:           elapsed,
+		ClientID:          dctx.clientID,
+		ClientIP:          ip,
+		AuthenticatedData: dctx.responseAD,
+	}
+
+	switch pctx.Proto {
+	case proxy.ProtoHTTPS:
+		p.ClientProto = querylog.ClientProtoDoH
+	case proxy.ProtoQUIC:
+		p.ClientProto = querylog.ClientProtoDoQ
+	case proxy.ProtoTLS:
+		p.ClientProto = querylog.ClientProtoDoT
+	case proxy.ProtoDNSCrypt:
+		p.ClientProto = querylog.ClientProtoDNSCrypt
+	default:
+		// Consider this a plain DNS-over-UDP or DNS-over-TCP request.
+	}
+
+	if pctx.Upstream != nil {
+		p.Upstream = pctx.Upstream.Address()
+	} else if cachedUps := pctx.CachedUpstreamAddr; cachedUps != "" {
+		p.Upstream = pctx.CachedUpstreamAddr
+		p.Cached = true
+	}
+
+	s.queryLog.Add(p)
+}
+
+// updatesStats writes the request into statistics.
 func (s *Server) updateStats(
 	ctx *dnsContext,
 	elapsed time.Duration,
 	res filtering.Result,
 	clientIP net.IP,
 ) {
-	if s.stats == nil {
-		return
-	}
-
 	pctx := ctx.proxyCtx
 	e := stats.Entry{}
 	e.Domain = strings.ToLower(pctx.Req.Question[0].Name)
diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go
index a9810629..b4cfd7e5 100644
--- a/internal/querylog/decode.go
+++ b/internal/querylog/decode.go
@@ -14,7 +14,7 @@ import (
 	"github.com/miekg/dns"
 )
 
-type logEntryHandler (func(t json.Token, ent *logEntry) error)
+type logEntryHandler func(t json.Token, ent *logEntry) error
 
 var logEntryHandlers = map[string]logEntryHandler{
 	"CID": func(t json.Token, ent *logEntry) error {
@@ -109,6 +109,16 @@ var logEntryHandlers = map[string]logEntryHandler{
 
 		return err
 	},
+	"ECS": func(t json.Token, ent *logEntry) error {
+		v, ok := t.(string)
+		if !ok {
+			return nil
+		}
+
+		ent.ReqECS = v
+
+		return nil
+	},
 	"Cached": func(t json.Token, ent *logEntry) error {
 		v, ok := t.(bool)
 		if !ok {
diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go
index 3d651c81..c5d45280 100644
--- a/internal/querylog/decode_test.go
+++ b/internal/querylog/decode_test.go
@@ -32,6 +32,7 @@ func TestDecodeLogEntry(t *testing.T) {
 			`"QT":"A",` +
 			`"QC":"IN",` +
 			`"CP":"",` +
+			`"ECS":"1.2.3.0/24",` +
 			`"Answer":"` + ansStr + `",` +
 			`"Cached":true,` +
 			`"AD":true,` +
@@ -58,6 +59,7 @@ func TestDecodeLogEntry(t *testing.T) {
 			QClass:      "IN",
 			ClientID:    "cli42",
 			ClientProto: "",
+			ReqECS:      "1.2.3.0/24",
 			Answer:      ans,
 			Cached:      true,
 			Result: filtering.Result{
diff --git a/internal/querylog/json.go b/internal/querylog/json.go
index e4bb63aa..d6adebe4 100644
--- a/internal/querylog/json.go
+++ b/internal/querylog/json.go
@@ -78,6 +78,10 @@ func (l *queryLog) entryToJSON(entry *logEntry, anonFunc aghnet.IPMutFunc) (json
 		jsonEntry["client_id"] = entry.ClientID
 	}
 
+	if entry.ReqECS != "" {
+		jsonEntry["ecs"] = entry.ReqECS
+	}
+
 	if len(entry.Result.Rules) > 0 {
 		if r := entry.Result.Rules[0]; len(r.Text) > 0 {
 			jsonEntry["rule"] = r.Text
diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go
index 7dc38824..8856fd9c 100644
--- a/internal/querylog/qlog.go
+++ b/internal/querylog/qlog.go
@@ -81,6 +81,8 @@ type logEntry struct {
 	QType  string `json:"QT"`
 	QClass string `json:"QC"`
 
+	ReqECS string `json:"ECS,omitempty"`
+
 	ClientID    string      `json:"CID,omitempty"`
 	ClientProto ClientProto `json:"CP"`
 
@@ -189,6 +191,10 @@ func (l *queryLog) Add(params *AddParams) {
 		AuthenticatedData: params.AuthenticatedData,
 	}
 
+	if params.ReqECS != nil {
+		entry.ReqECS = params.ReqECS.String()
+	}
+
 	if params.Answer != nil {
 		var a []byte
 		a, err = params.Answer.Pack()
diff --git a/internal/querylog/querylog.go b/internal/querylog/querylog.go
index 18b52938..bd6e1569 100644
--- a/internal/querylog/querylog.go
+++ b/internal/querylog/querylog.go
@@ -77,6 +77,10 @@ type Config struct {
 type AddParams struct {
 	Question *dns.Msg
 
+	// ReqECS is the IP network extracted from EDNS Client-Subnet option of a
+	// request.
+	ReqECS *net.IPNet
+
 	// Answer is the response which is sent to the client, if any.
 	Answer *dns.Msg
 
diff --git a/internal/querylog/searchcriterion.go b/internal/querylog/searchcriterion.go
index 0595fd6e..a9bd4cff 100644
--- a/internal/querylog/searchcriterion.go
+++ b/internal/querylog/searchcriterion.go
@@ -99,24 +99,10 @@ func (c *searchCriterion) quickMatch(line string, findClient quickMatchClientFun
 		}
 
 		if c.strict {
-			return ctDomainOrClientCaseStrict(
-				c.value,
-				c.asciiVal,
-				clientID,
-				name,
-				host,
-				ip,
-			)
+			return ctDomainOrClientCaseStrict(c.value, c.asciiVal, clientID, name, host, ip)
 		}
 
-		return ctDomainOrClientCaseNonStrict(
-			c.value,
-			c.asciiVal,
-			clientID,
-			name,
-			host,
-			ip,
-		)
+		return ctDomainOrClientCaseNonStrict(c.value, c.asciiVal, clientID, name, host, ip)
 	case ctFilteringStatus:
 		// Go on, as we currently don't do quick matches against
 		// filtering statuses.
diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md
index 0762fdf2..1e2852be 100644
--- a/openapi/CHANGELOG.md
+++ b/openapi/CHANGELOG.md
@@ -2,7 +2,12 @@
 
 <!-- TODO(a.garipov): Reformat in accordance with the KeepAChangelog spec. -->
 
-## v0.107.3: API changes
+## v0.108.0: API changes
+
+### The new optional field `"ecs"` in `QueryLogItem`
+
+* The new optional field `"ecs"` in `GET /control/querylog` contains the IP
+  network from an EDNS Client-Subnet option from the request message if any.
 
 ### The new possible status code in `/install/configure` response.
 
@@ -10,6 +15,8 @@
   `POST /install/configure` which means that the specified password does not
   meet the strength requirements.
 
+## v0.107.3: API changes
+
 ### The new field `"version"` in `AddressesInfo`
 
 * The new field `"version"` in `GET /install/get_addresses` is the version of
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 85c372a3..8b21a01f 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -1905,6 +1905,12 @@
           - 'doq'
           - 'dnscrypt'
           - ''
+        'ecs':
+          'type': 'string'
+          'example': '192.168.0.0/16'
+          'description': >
+            The IP network defined by an EDNS Client-Subnet option in the
+            request message if any.
         'elapsedMs':
           'type': 'string'
           'example': '54.023928'

From f1d05a49f092fa80b01105fd1432dbc5b63e8232 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 3 Mar 2022 20:45:14 +0300
Subject: [PATCH 010/143] Pull request: client: fix en i18n

Merge in DNS/adguard-home from fix-en-i18n to master

Squashed commit of the following:

commit 406e6ece25c6581937a7c7bed34950d7bb2a856e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Mar 3 19:07:02 2022 +0300

    client: imp unsafe port msg

commit bd117695be387617facbe57479f0e3d6e81bf151
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Mar 3 18:51:48 2022 +0300

    client: fix more

commit cd9ed04d019b26960541569c38bf40ee252da94b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Mar 3 18:30:27 2022 +0300

    client: fix en i18n
---
 client/src/__locales/en.json | 86 ++++++++++++++++++------------------
 1 file changed, 43 insertions(+), 43 deletions(-)

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 4d1dc857..706ddf55 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP configuration successfully saved",
     "dhcp_ipv4_settings": "DHCP IPv4 Settings",
     "dhcp_ipv6_settings": "DHCP IPv6 Settings",
-    "form_error_required": "Required field.",
-    "form_error_ip4_format": "Invalid IPv4 address.",
-    "form_error_ip4_range_start_format": "Invalid IPv4 address of the range start.",
-    "form_error_ip4_range_end_format": "Invalid IPv4 address of the range end.",
-    "form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway.",
-    "form_error_ip6_format": "Invalid IPv6 address.",
-    "form_error_ip_format": "Invalid IP address.",
-    "form_error_mac_format": "Invalid MAC address.",
-    "form_error_client_id_format": "ClientID must contain only numbers, lowercase letters, and hyphens.",
-    "form_error_server_name": "Invalid server name.",
-    "form_error_subnet": "Subnet \"{{cidr}}\" does not contain the IP address \"{{ip}}\".",
-    "form_error_positive": "Must be greater than 0.",
-    "out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Must be lower than range start.",
-    "greater_range_start_error": "Must be greater than range start.",
-    "greater_range_end_error": "Must be greater than range end.",
-    "subnet_error": "Addresses must be in one subnet.",
-    "gateway_or_subnet_invalid": "Subnet mask invalid.",
+    "form_error_required": "Required field",
+    "form_error_ip4_format": "Invalid IPv4 address",
+    "form_error_ip4_range_start_format": "Invalid IPv4 address of the range start",
+    "form_error_ip4_range_end_format": "Invalid IPv4 address of the range end",
+    "form_error_ip4_gateway_format": "Invalid IPv4 address of the gateway",
+    "form_error_ip6_format": "Invalid IPv6 address",
+    "form_error_ip_format": "Invalid IP address",
+    "form_error_mac_format": "Invalid MAC address",
+    "form_error_client_id_format": "ClientID must contain only numbers, lowercase letters, and hyphens",
+    "form_error_server_name": "Invalid server name",
+    "form_error_subnet": "Subnet \"{{cidr}}\" does not contain the IP address \"{{ip}}\"",
+    "form_error_positive": "Must be greater than 0",
+    "out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Must be lower than range start",
+    "greater_range_start_error": "Must be greater than range start",
+    "greater_range_end_error": "Must be greater than range end",
+    "subnet_error": "Addresses must be in one subnet",
+    "gateway_or_subnet_invalid": "Subnet mask invalid",
     "dhcp_form_gateway_input": "Gateway IP",
     "dhcp_form_subnet_input": "Subnet mask",
     "dhcp_form_range_title": "Range of IP addresses",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Hostname",
     "dhcp_table_expires": "Expires",
     "dhcp_warning": "If you want to enable DHCP server anyway, make sure that there is no other active DHCP server in your network, as this may break the Internet connectivity for devices on the network!",
-    "dhcp_error": "AdGuard Home could not determine if there is another active DHCP server on the network.",
+    "dhcp_error": "AdGuard Home could not determine if there is another active DHCP server on the network",
     "dhcp_static_ip_error": "In order to use DHCP server a static IP address must be set. AdGuard Home failed to determine if this network interface is configured using a static IP address. Please set a static IP address manually.",
     "dhcp_dynamic_ip_found": "Your system uses dynamic IP address configuration for interface <0>{{interfaceName}}</0>. In order to use DHCP server, a static IP address must be set. Your current IP address is <0>{{ipAddress}}</0>. AdGuard Home will automatically set this IP address as static if you press the \"Enable DHCP server\" button.",
     "dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Choose allowlists",
     "enter_valid_blocklist": "Enter a valid URL to the blocklist.",
     "enter_valid_allowlist": "Enter a valid URL to the allowlist.",
-    "form_error_url_format": "Invalid URL format.",
-    "form_error_url_or_path_format": "Invalid URL or absolute path of the list.",
+    "form_error_url_format": "Invalid URL format",
+    "form_error_url_or_path_format": "Invalid URL or absolute path of the list",
     "custom_filter_rules": "Custom filtering rules",
     "custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.",
     "system_host_files": "System hosts files",
@@ -261,10 +261,10 @@
     "query_log_strict_search": "Use double quotes for strict search",
     "query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost",
     "anonymize_client_ip": "Anonymize client IP",
-    "anonymize_client_ip_desc": "Don't save the client's full IP address to logs or statistics.",
+    "anonymize_client_ip_desc": "Don't save the client's full IP address to logs or statistics",
     "dns_config": "DNS server configuration",
     "dns_cache_config": "DNS cache configuration",
-    "dns_cache_config_desc": "Here you can configure DNS cache.",
+    "dns_cache_config_desc": "Here you can configure DNS cache",
     "blocking_mode": "Blocking mode",
     "default": "Default",
     "nxdomain": "NXDOMAIN",
@@ -311,7 +311,7 @@
     "install_settings_listen": "Listen interface",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Your AdGuard Home admin web interface will be available on the following addresses:",
-    "form_error_port": "Enter valid port number.",
+    "form_error_port": "Enter valid port number",
     "install_settings_dns": "DNS server",
     "install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server on the following addresses:",
     "install_settings_all_interfaces": "All interfaces",
@@ -358,7 +358,7 @@
     "open_dashboard": "Open Dashboard",
     "install_saved": "Saved successfully",
     "encryption_title": "Encryption",
-    "encryption_desc": "Encryption (HTTPS/TLS) support for both DNS and admin web interface.",
+    "encryption_desc": "Encryption (HTTPS/QUIC/TLS) support for both DNS and admin web interface",
     "encryption_config_saved": "Encryption configuration saved",
     "encryption_server": "Server name",
     "encryption_server_enter": "Enter your domain name",
@@ -380,26 +380,26 @@
     "encryption_key_input": "Copy/paste your PEM-encoded private key for your certificate here.",
     "encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)",
     "encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
-    "encryption_chain_valid": "Certificate chain is valid.",
-    "encryption_chain_invalid": "Certificate chain is invalid.",
-    "encryption_key_valid": "This is a valid {{type}} private key.",
-    "encryption_key_invalid": "This is an invalid {{type}} private key.",
+    "encryption_chain_valid": "Certificate chain is valid",
+    "encryption_chain_invalid": "Certificate chain is invalid",
+    "encryption_key_valid": "This is a valid {{type}} private key",
+    "encryption_key_invalid": "This is an invalid {{type}} private key",
     "encryption_subject": "Subject",
     "encryption_issuer": "Issuer",
     "encryption_hostnames": "Hostnames",
     "encryption_reset": "Are you sure you want to reset encryption settings?",
     "topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings</0>.",
     "topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings</0>.",
-    "form_error_port_range": "Enter port number in the range of 80-65535.",
-    "form_error_port_unsafe": "This is an unsafe port.",
-    "form_error_equal": "Must not be equal.",
-    "form_error_password": "Password mismatched.",
+    "form_error_port_range": "Enter port number in the range of 80-65535",
+    "form_error_port_unsafe": "Unsafe port",
+    "form_error_equal": "Must not be equal",
+    "form_error_password": "Password mismatch",
     "reset_settings": "Reset settings",
     "update_announcement": "AdGuard Home {{version}} is now available! <0>Click here</0> for more info.",
     "setup_guide": "Setup Guide",
     "dns_addresses": "DNS addresses",
     "dns_start": "DNS server is starting up",
-    "dns_status_error": "Error checking the DNS server status.",
+    "dns_status_error": "Error checking the DNS server status",
     "down": "Down",
     "fix": "Fix",
     "dns_providers": "Here is a <0>list of known DNS providers</0> to choose from.",
@@ -408,7 +408,7 @@
     "manual_update": "Please <a>follow these steps</a> to update manually.",
     "processing_update": "Please wait, AdGuard Home is being updated",
     "clients_title": "Persistent clients",
-    "clients_desc": "Configure persistent client records for devices connected to AdGuard Home.",
+    "clients_desc": "Configure persistent client records for devices connected to AdGuard Home",
     "settings_global": "Global",
     "settings_custom": "Custom",
     "table_client": "Client",
@@ -435,9 +435,9 @@
     "client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
     "list_confirm_delete": "Are you sure you want to delete this list?",
     "auto_clients_title": "Runtime clients",
-    "auto_clients_desc": "Devices not on the list of Persistent clients that may still use AdGuard Home.",
+    "auto_clients_desc": "Devices not on the list of Persistent clients that may still use AdGuard Home",
     "access_title": "Access settings",
-    "access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
+    "access_desc": "Here you can configure access rules for the AdGuard Home DNS server",
     "access_allowed_title": "Allowed clients",
     "access_allowed_desc": "A list of CIDRs, IP addresses, or <a>ClientIDs</a>. If this list has entries, AdGuard Home will accept requests only from these clients.",
     "access_disallowed_title": "Disallowed clients",
@@ -477,8 +477,8 @@
     "dns_rewrites": "DNS rewrites",
     "form_domain": "Enter domain name or wildcard",
     "form_answer": "Enter IP address or domain name",
-    "form_error_domain_format": "Invalid domain format.",
-    "form_error_answer_format": "Invalid answer format.",
+    "form_error_domain_format": "Invalid domain format",
+    "form_error_answer_format": "Invalid answer format",
     "configure": "Configure",
     "main_settings": "Main settings",
     "block_services": "Block specific services",
@@ -510,7 +510,7 @@
     "filter_updated": "The list has been successfully updated",
     "statistics_configuration": "Statistics configuration",
     "statistics_retention": "Statistics retention",
-    "statistics_retention_desc": "If you decrease the interval value, some data will be lost.",
+    "statistics_retention_desc": "If you decrease the interval value, some data will be lost",
     "statistics_clear": "Clear statistics",
     "statistics_clear_confirm": "Are you sure you want to clear statistics?",
     "statistics_retention_confirm": "Are you sure you want to change statistics retention? If you decrease the interval value, some data will be lost",
@@ -520,7 +520,7 @@
     "interval_hours_plural": "{{count}} hours",
     "filters_configuration": "Filters configuration",
     "filters_enable": "Enable filters",
-    "filters_interval": "Filters update interval",
+    "filters_interval": "Filter update interval",
     "disabled": "Disabled",
     "username_label": "Username",
     "username_placeholder": "Enter username",
@@ -609,7 +609,7 @@
     "enter_cache_ttl_max_override": "Enter maximum TTL (seconds)",
     "cache_ttl_min_override_desc": "Extend short time-to-live values (seconds) received from the upstream server when caching DNS responses.",
     "cache_ttl_max_override_desc": "Set a maximum time-to-live value (seconds) for entries in the DNS cache.",
-    "ttl_cache_validation": "Minimum cache TTL override must be less than or equal to the maximum.",
+    "ttl_cache_validation": "Minimum cache TTL override must be less than or equal to the maximum",
     "cache_optimistic": "Optimistic caching",
     "cache_optimistic_desc": "Make AdGuard Home respond from the cache even when the entries are expired and also try to refresh them.",
     "filter_category_general": "General",
@@ -631,5 +631,5 @@
     "parental_control": "Parental Control",
     "safe_browsing": "Safe Browsing",
     "served_from_cache": "{{value}} <i>(served from cache)</i>",
-    "form_error_password_length": "Password must be at least {{value}} characters long."
+    "form_error_password_length": "Password must be at least {{value}} characters long"
 }

From 89d9b03dfec8a5f1406507d45e582e852e748859 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Fri, 4 Mar 2022 15:50:35 +0300
Subject: [PATCH 011/143] Pull request: all: upd go

Merge in DNS/adguard-home from upd-go to master

Squashed commit of the following:

commit 3b6c960afe073223dd73eaf650561509f0d13019
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Mar 4 15:45:15 2022 +0300

    all: upd go
---
 bamboo-specs/release.yaml | 6 +++---
 bamboo-specs/test.yaml    | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index 796d874d..50bf1362 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -7,7 +7,7 @@
 # Make sure to sync any changes with the branch overrides below.
 'variables':
   'channel': 'edge'
-  'dockerGo': 'adguard/golang-ubuntu:4.1'
+  'dockerGo': 'adguard/golang-ubuntu:4.2'
 
 'stages':
 - 'Make release':
@@ -266,7 +266,7 @@
     # need to build a few of these.
     'variables':
       'channel': 'beta'
-      'dockerGo': 'adguard/golang-ubuntu:4.1'
+      'dockerGo': 'adguard/golang-ubuntu:4.2'
 # release-vX.Y.Z branches are the branches from which the actual final release
 # is built.
 - '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -281,4 +281,4 @@
     # are the ones that actually get released.
     'variables':
       'channel': 'release'
-      'dockerGo': 'adguard/golang-ubuntu:4.1'
+      'dockerGo': 'adguard/golang-ubuntu:4.2'
diff --git a/bamboo-specs/test.yaml b/bamboo-specs/test.yaml
index e41f172f..fa410195 100644
--- a/bamboo-specs/test.yaml
+++ b/bamboo-specs/test.yaml
@@ -5,7 +5,7 @@
   'key': 'AHBRTSPECS'
   'name': 'AdGuard Home - Build and run tests'
 'variables':
-  'dockerGo': 'adguard/golang-ubuntu:4.1'
+  'dockerGo': 'adguard/golang-ubuntu:4.2'
 
 'stages':
 - 'Tests':

From e7b3c9969be7d2aeb9db772852f776776909712f Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 9 Mar 2022 16:59:31 +0300
Subject: [PATCH 012/143] Pull request: decr optimistic ttl

Merge in DNS/adguard-home from 2145-optimistic-ttl to master

Updates #2145.

Squashed commit of the following:

commit 81e5aba650980403d70d6756aebe73af228fe11a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 9 18:34:50 2022 +0500

    all: upd proxy
---
 CHANGELOG.md | 3 +++
 go.mod       | 2 +-
 go.sum       | 4 ++--
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 988d4066..a5cca836 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,8 @@ and this project adheres to
 
 ### Changed
 
+- The TTL of responses served from the optimistic cache is now lowered to 10
+  seconds.
 - Domain-specific private reverse DNS upstream servers are now validated to
   allow only `*.in-addr.arpa` and `*.ip6.arpa` domains pointing to
   locally-served networks ([#3381]).  **Note:**  If you already have invalid
@@ -76,6 +78,7 @@ In this release, the schema version has changed from 12 to 13.
 
 ### Security
 
+- `User-Agent` HTTP header removed from outcoming DNS-over-HTTPS requests.
 - Enforced password strength policy ([#3503]).
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
diff --git a/go.mod b/go.mod
index 91fee199..1aedfc38 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
 go 1.17
 
 require (
-	github.com/AdguardTeam/dnsproxy v0.41.3
+	github.com/AdguardTeam/dnsproxy v0.41.4
 	github.com/AdguardTeam/golibs v0.10.6
 	github.com/AdguardTeam/urlfilter v0.15.2
 	github.com/NYTimes/gziphandler v1.1.1
diff --git a/go.sum b/go.sum
index 4ae0c523..716b6955 100644
--- a/go.sum
+++ b/go.sum
@@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/AdguardTeam/dnsproxy v0.41.3 h1:FJnIf2pHaABUjAvB0P79nIXN5sBAvsUf2368NNw50+s=
-github.com/AdguardTeam/dnsproxy v0.41.3/go.mod h1:GCdEbTw683vBqksJIccPSYzBg2yIFbRiDnXltyIinug=
+github.com/AdguardTeam/dnsproxy v0.41.4 h1:zA8BJmWBkSL5kp4b8CblQRgIrLGzJ4IUGQ7tA1255Cw=
+github.com/AdguardTeam/dnsproxy v0.41.4/go.mod h1:GCdEbTw683vBqksJIccPSYzBg2yIFbRiDnXltyIinug=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=

From c346216424d6571f96d80f47a9224da4c47dbbf9 Mon Sep 17 00:00:00 2001
From: bakito <github@bakito.ch>
Date: Sat, 12 Mar 2022 12:46:15 +0100
Subject: [PATCH 013/143] correct openapi schema

---
 internal/home/controlfiltering.go |  2 +-
 openapi/openapi.yaml              | 16 +++++++---------
 2 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/internal/home/controlfiltering.go b/internal/home/controlfiltering.go
index 639403d8..51d86146 100644
--- a/internal/home/controlfiltering.go
+++ b/internal/home/controlfiltering.go
@@ -316,7 +316,7 @@ type filterJSON struct {
 	URL         string `json:"url"`
 	Name        string `json:"name"`
 	RulesCount  uint32 `json:"rules_count"`
-	LastUpdated string `json:"last_updated"`
+	LastUpdated string `json:"last_updated,omitempty"`
 }
 
 type filteringConfig struct {
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 8b21a01f..bb3bfe09 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -1396,7 +1396,6 @@
       'required':
       - 'enabled'
       - 'id'
-      - 'last_updated'
       - 'name'
       - 'rules_count'
       - 'url'
@@ -1434,6 +1433,10 @@
           'type': 'array'
           'items':
             '$ref': '#/components/schemas/Filter'
+        'whitelist_filters':
+          'type': 'array'
+          'items':
+            '$ref': '#/components/schemas/Filter'
         'user_rules':
           'type': 'array'
           'items':
@@ -1451,14 +1454,7 @@
       'description': 'Filtering URL settings'
       'properties':
         'data':
-          'properties':
-            'enabled':
-              'type': 'boolean'
-            'name':
-              'type': 'string'
-            'url':
-              'type': 'string'
-          'type': 'object'
+          '$ref': '#/components/schemas/Filter'
         'url':
           'type': 'string'
         'whitelist':
@@ -1860,6 +1856,8 @@
           'description': 'Previously added URL containing filtering rules'
           'type': 'string'
           'example': 'https://filters.adtidy.org/windows/filters/15.txt'
+        'whitelist':
+          'type': 'boolean'
     'QueryLogItem':
       'type': 'object'
       'description': 'Query log item'

From 573cbafe3f2f222d7c6413d8fe30c90ae295882d Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 15 Mar 2022 20:57:46 +0300
Subject: [PATCH 014/143] Pull request: 3597 arpdb

Merge in DNS/adguard-home from 3597-wrt-netlink to master

Updates #3597.

Squashed commit of the following:

commit 1709582cd204bb80c84775feabae8723ed3340f6
Merge: 0507b6ed e7b3c996
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 15 20:25:18 2022 +0300

    Merge branch 'master' into 3597-wrt-netlink

commit 0507b6ede1162554ca8ac7399d827264aca64f98
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 15 20:21:29 2022 +0300

    all: imp code

commit 71f9758d854b3e2cf90cbd3655ae4818cfbcf528
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 9 18:03:48 2022 +0500

    aghnet: imp naming

commit c949e765104f130aa3e5ba465bdebc3286bebe44
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 9 17:26:30 2022 +0500

    all: imp code, docs

commit cf605ddb401b6e7b0a7a4bb1b175a4dc588d253a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 8 15:33:52 2022 +0500

    all: imp code, docs

commit 2960c6549a7e4944cc0072ca47a0cd4e82ec850e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Sun Mar 6 21:34:58 2022 +0500

    all: imp code & docs, fix tests

commit 29c049f3aee91a826c3416961686396d562a7066
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 2 20:45:34 2022 +0300

    all: add arpdb
---
 CHANGELOG.md                           |   3 +
 internal/aghnet/arpdb.go               | 239 +++++++++++++++++++++++++
 internal/aghnet/arpdb_bsd.go           |  70 ++++++++
 internal/aghnet/arpdb_bsd_test.go      |  23 +++
 internal/aghnet/arpdb_linux.go         | 230 ++++++++++++++++++++++++
 internal/aghnet/arpdb_linux_test.go    |  84 +++++++++
 internal/aghnet/arpdb_openbsd.go       |  67 +++++++
 internal/aghnet/arpdb_openbsd_test.go  |  22 +++
 internal/aghnet/arpdb_test.go          | 168 +++++++++++++++++
 internal/aghnet/arpdb_unix.go          |  13 ++
 internal/aghnet/arpdb_windows.go       |  70 ++++++++
 internal/aghnet/arpdb_windows_test.go  |  23 +++
 internal/aghnet/hostscontainer_test.go |   4 -
 internal/aghnet/net_test.go            |   5 +
 internal/aghnet/testdata/proc_net_arp  |   4 +
 internal/aghos/os.go                   |   9 +-
 internal/home/clients.go               |  50 ++----
 internal/home/clients_test.go          |   8 +-
 internal/home/home.go                  |  10 +-
 19 files changed, 1058 insertions(+), 44 deletions(-)
 create mode 100644 internal/aghnet/arpdb.go
 create mode 100644 internal/aghnet/arpdb_bsd.go
 create mode 100644 internal/aghnet/arpdb_bsd_test.go
 create mode 100644 internal/aghnet/arpdb_linux.go
 create mode 100644 internal/aghnet/arpdb_linux_test.go
 create mode 100644 internal/aghnet/arpdb_openbsd.go
 create mode 100644 internal/aghnet/arpdb_openbsd_test.go
 create mode 100644 internal/aghnet/arpdb_test.go
 create mode 100644 internal/aghnet/arpdb_unix.go
 create mode 100644 internal/aghnet/arpdb_windows.go
 create mode 100644 internal/aghnet/arpdb_windows_test.go
 create mode 100644 internal/aghnet/testdata/proc_net_arp

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5cca836..48aa24d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,8 @@ and this project adheres to
 
 ### Changed
 
+- Improved detection of runtime clients through more resilient ARP processing
+  ([#3597]).
 - The TTL of responses served from the optimistic cache is now lowered to 10
   seconds.
 - Domain-specific private reverse DNS upstream servers are now validated to
@@ -89,6 +91,7 @@ In this release, the schema version has changed from 12 to 13.
 [#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
 [#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381
 [#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
+[#3597]: https://github.com/AdguardTeam/AdGuardHome/issues/3597
 [#3978]: https://github.com/AdguardTeam/AdGuardHome/issues/3978
 [#4166]: https://github.com/AdguardTeam/AdGuardHome/issues/4166
 [#4213]: https://github.com/AdguardTeam/AdGuardHome/issues/4213
diff --git a/internal/aghnet/arpdb.go b/internal/aghnet/arpdb.go
new file mode 100644
index 00000000..759a688a
--- /dev/null
+++ b/internal/aghnet/arpdb.go
@@ -0,0 +1,239 @@
+package aghnet
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"net"
+	"strings"
+	"sync"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
+	"github.com/AdguardTeam/golibs/errors"
+	"github.com/AdguardTeam/golibs/netutil"
+)
+
+// ARPDB: The Network Neighborhood Database
+
+// ARPDB stores and refreshes the network neighborhood reported by ARP (Address
+// Resolution Protocol).
+type ARPDB interface {
+	// Refresh updates the stored data.  It must be safe for concurrent use.
+	Refresh() (err error)
+
+	// Neighbors returnes the last set of data reported by ARP.  Both the method
+	// and it's result must be safe for concurrent use.
+	Neighbors() (ns []Neighbor)
+}
+
+// NewARPDB returns the ARPDB properly initialized for the OS.
+func NewARPDB() (arp ARPDB, err error) {
+	arp = newARPDB()
+
+	err = arp.Refresh()
+	if err != nil {
+		return nil, fmt.Errorf("arpdb initial refresh: %w", err)
+	}
+
+	return arp, nil
+}
+
+// Empty ARPDB implementation
+
+// EmptyARPDB is the ARPDB implementation that does nothing.
+type EmptyARPDB struct{}
+
+// type check
+var _ ARPDB = EmptyARPDB{}
+
+// Refresh implements the ARPDB interface for EmptyARPContainer.  It does
+// nothing and always returns nil error.
+func (EmptyARPDB) Refresh() (err error) { return nil }
+
+// Neighbors implements the ARPDB interface for EmptyARPContainer.  It always
+// returns nil.
+func (EmptyARPDB) Neighbors() (ns []Neighbor) { return nil }
+
+// ARPDB Helper Types
+
+// Neighbor is the pair of IP address and MAC address reported by ARP.
+type Neighbor struct {
+	// Name is the hostname of the neighbor.  Empty name is valid since not each
+	// implementation of ARP is able to retrieve that.
+	Name string
+
+	// IP contains either IPv4 or IPv6.
+	IP net.IP
+
+	// MAC contains the hardware address.
+	MAC net.HardwareAddr
+}
+
+// Clone returns the deep copy of n.
+func (n Neighbor) Clone() (clone Neighbor) {
+	return Neighbor{
+		Name: n.Name,
+		IP:   netutil.CloneIP(n.IP),
+		MAC:  netutil.CloneMAC(n.MAC),
+	}
+}
+
+// neighs is the helper type that stores neighbors to avoid copying its methods
+// among all the ARPDB implementations.
+type neighs struct {
+	mu *sync.RWMutex
+	ns []Neighbor
+}
+
+// len returns the length of the neighbors slice.  It's safe for concurrent use.
+func (ns *neighs) len() (l int) {
+	ns.mu.RLock()
+	defer ns.mu.RUnlock()
+
+	return len(ns.ns)
+}
+
+// clone returns a deep copy of the underlying neighbors slice.  It's safe for
+// concurrent use.
+func (ns *neighs) clone() (cloned []Neighbor) {
+	ns.mu.RLock()
+	defer ns.mu.RUnlock()
+
+	cloned = make([]Neighbor, len(ns.ns))
+	for i, n := range ns.ns {
+		cloned[i] = n.Clone()
+	}
+
+	return cloned
+}
+
+// reset replaces the underlying slice with the new one.  It's safe for
+// concurrent use.
+func (ns *neighs) reset(with []Neighbor) {
+	ns.mu.Lock()
+	defer ns.mu.Unlock()
+
+	ns.ns = with
+}
+
+// Command ARPDB
+
+// parseNeighsFunc parses the text from sc as if it'd be an output of some
+// ARP-related command.  lenHint is a hint for the size of the allocated slice
+// of Neighbors.
+type parseNeighsFunc func(sc *bufio.Scanner, lenHint int) (ns []Neighbor)
+
+// runCmdFunc is the function that runs some command and returns its output
+// wrapped to be a io.Reader.
+type runCmdFunc func() (r io.Reader, err error)
+
+// cmdARPDB is the implementation of the ARPDB that uses command line to
+// retrieve data.
+type cmdARPDB struct {
+	parse  parseNeighsFunc
+	runcmd runCmdFunc
+	ns     *neighs
+}
+
+// type check
+var _ ARPDB = (*cmdARPDB)(nil)
+
+// runCmd runs the cmd with it's args and returns the result wrapped to be an
+// io.Reader.  The error is returned either if the exit code retured by command
+// not equals 0 or the execution itself failed.
+func runCmd(cmd string, args ...string) (r io.Reader, err error) {
+	var code int
+	var out string
+	code, out, err = aghos.RunCommand(cmd, args...)
+	if err != nil {
+		return nil, err
+	} else if code != 0 {
+		return nil, fmt.Errorf("unexpected exit code %d", code)
+	}
+
+	return strings.NewReader(out), nil
+}
+
+// Refresh implements the ARPDB interface for *cmdARPDB.
+func (arp *cmdARPDB) Refresh() (err error) {
+	defer func() { err = errors.Annotate(err, "cmd arpdb: %w") }()
+
+	var r io.Reader
+	r, err = arp.runcmd()
+	if err != nil {
+		return fmt.Errorf("running command: %w", err)
+	}
+
+	sc := bufio.NewScanner(r)
+	ns := arp.parse(sc, arp.ns.len())
+	if err = sc.Err(); err != nil {
+		return fmt.Errorf("scanning the output: %w", err)
+	}
+
+	arp.ns.reset(ns)
+
+	return nil
+}
+
+// Neighbors implements the ARPDB interface for *cmdARPDB.
+func (arp *cmdARPDB) Neighbors() (ns []Neighbor) {
+	return arp.ns.clone()
+}
+
+// Composite ARPDB
+
+// arpdbs is the ARPDB that combines several ARPDB implementations and
+// consequently switches between those.
+type arpdbs struct {
+	// arps is the set of ARPDB implementations to range through.
+	arps []ARPDB
+	// last is the last succeeded ARPDB index.
+	last int
+}
+
+// newARPDBs returns a properly initialized *arpdbs.  It begins refreshing from
+// the first of arps.
+func newARPDBs(arps ...ARPDB) (arp *arpdbs) {
+	return &arpdbs{
+		arps: arps,
+		last: 0,
+	}
+}
+
+// type check
+var _ ARPDB = (*arpdbs)(nil)
+
+// Refresh implements the ARPDB interface for *arpdbs.
+func (arp *arpdbs) Refresh() (err error) {
+	var errs []error
+	l := len(arp.arps)
+	// Start from the last succeeded implementation.
+	for i := 0; i < l; i++ {
+		cur := (arp.last + i) % l
+		err = arp.arps[cur].Refresh()
+		if err == nil {
+			// The succeeded implementation found so update the last succeeded
+			// index.
+			arp.last = cur
+
+			return nil
+		}
+
+		errs = append(errs, err)
+	}
+
+	if len(errs) > 0 {
+		err = errors.List("each arpdb failed", errs...)
+	}
+
+	return err
+}
+
+// Neighbors implements the ARPDB interface for *arpdbs.
+func (arp *arpdbs) Neighbors() (ns []Neighbor) {
+	if l := len(arp.arps); l > 0 && arp.last < l {
+		return arp.arps[arp.last].Neighbors()
+	}
+
+	return nil
+}
diff --git a/internal/aghnet/arpdb_bsd.go b/internal/aghnet/arpdb_bsd.go
new file mode 100644
index 00000000..fe00418a
--- /dev/null
+++ b/internal/aghnet/arpdb_bsd.go
@@ -0,0 +1,70 @@
+//go:build darwin || freebsd
+// +build darwin freebsd
+
+package aghnet
+
+import (
+	"bufio"
+	"net"
+	"strings"
+	"sync"
+
+	"github.com/AdguardTeam/golibs/log"
+	"github.com/AdguardTeam/golibs/netutil"
+)
+
+func newARPDB() *cmdARPDB {
+	return &cmdARPDB{
+		parse:  parseArpA,
+		runcmd: rcArpA,
+		ns: &neighs{
+			mu: &sync.RWMutex{},
+			ns: make([]Neighbor, 0),
+		},
+	}
+}
+
+// parseArpA parses the output of the "arp -a" command on macOS and FreeBSD.
+// The expected input format:
+//
+//   host.name (192.168.0.1) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet]
+//
+func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
+	ns = make([]Neighbor, 0, lenHint)
+	for sc.Scan() {
+		ln := sc.Text()
+
+		fields := strings.Fields(ln)
+		if len(fields) < 4 {
+			continue
+		}
+
+		n := Neighbor{}
+
+		if ipStr := fields[1]; len(ipStr) < 2 {
+			continue
+		} else if ip := net.ParseIP(ipStr[1 : len(ipStr)-1]); ip == nil {
+			continue
+		} else {
+			n.IP = ip
+		}
+
+		hwStr := fields[3]
+		if mac, err := net.ParseMAC(hwStr); err != nil {
+			continue
+		} else {
+			n.MAC = mac
+		}
+
+		host := fields[0]
+		if err := netutil.ValidateDomainName(host); err != nil {
+			log.Debug("parsing arp output: %s", err)
+		} else {
+			n.Name = host
+		}
+
+		ns = append(ns, n)
+	}
+
+	return ns
+}
diff --git a/internal/aghnet/arpdb_bsd_test.go b/internal/aghnet/arpdb_bsd_test.go
new file mode 100644
index 00000000..bbadc600
--- /dev/null
+++ b/internal/aghnet/arpdb_bsd_test.go
@@ -0,0 +1,23 @@
+//go:build darwin || freebsd
+// +build darwin freebsd
+
+package aghnet
+
+import (
+	"net"
+)
+
+const arpAOutput = `
+hostname.one (192.168.1.2) at ab:cd:ef:ab:cd:ef on en0 ifscope [ethernet]
+hostname.two (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 1198 seconds [ethernet]
+`
+
+var wantNeighs = []Neighbor{{
+	Name: "hostname.one",
+	IP:   net.IPv4(192, 168, 1, 2),
+	MAC:  net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
+}, {
+	Name: "hostname.two",
+	IP:   net.ParseIP("::ffff:ffff"),
+	MAC:  net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
+}}
diff --git a/internal/aghnet/arpdb_linux.go b/internal/aghnet/arpdb_linux.go
new file mode 100644
index 00000000..976d8b7a
--- /dev/null
+++ b/internal/aghnet/arpdb_linux.go
@@ -0,0 +1,230 @@
+//go:build linux
+// +build linux
+
+package aghnet
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"io/fs"
+	"net"
+	"strings"
+	"sync"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
+	"github.com/AdguardTeam/golibs/log"
+	"github.com/AdguardTeam/golibs/netutil"
+	"github.com/AdguardTeam/golibs/stringutil"
+)
+
+func newARPDB() (arp *arpdbs) {
+	// Use the common storage among the implementations.
+	ns := &neighs{
+		mu: &sync.RWMutex{},
+		ns: make([]Neighbor, 0),
+	}
+
+	var parseF parseNeighsFunc
+	if aghos.IsOpenWrt() {
+		parseF = parseArpAWrt
+	} else {
+		parseF = parseArpA
+	}
+
+	return newARPDBs(
+		// Try /proc/net/arp first.
+		&fsysARPDB{ns: ns, fsys: aghos.RootDirFS(), filename: "proc/net/arp"},
+		// Try "arp -a" then.
+		&cmdARPDB{parse: parseF, runcmd: rcArpA, ns: ns},
+		// Try "ip neigh" finally.
+		&cmdARPDB{parse: parseIPNeigh, runcmd: rcIPNeigh, ns: ns},
+	)
+}
+
+// fsysARPDB accesses the ARP cache file to update the database.
+type fsysARPDB struct {
+	ns       *neighs
+	fsys     fs.FS
+	filename string
+}
+
+// type check
+var _ ARPDB = (*fsysARPDB)(nil)
+
+// Refresh implements the ARPDB interface for *fsysARPDB.
+func (arp *fsysARPDB) Refresh() (err error) {
+	var f fs.File
+	f, err = arp.fsys.Open(arp.filename)
+	if err != nil {
+		return fmt.Errorf("opening %q: %w", arp.filename, err)
+	}
+
+	sc := bufio.NewScanner(f)
+	// Skip the header.
+	if !sc.Scan() {
+		return nil
+	} else if err = sc.Err(); err != nil {
+		return err
+	}
+
+	ns := make([]Neighbor, 0, arp.ns.len())
+	for sc.Scan() {
+		ln := sc.Text()
+		fields := stringutil.SplitTrimmed(ln, " ")
+		if len(fields) != 6 {
+			continue
+		}
+
+		n := Neighbor{}
+		if n.IP = net.ParseIP(fields[0]); n.IP == nil || n.IP.IsUnspecified() {
+			continue
+		} else if n.MAC, err = net.ParseMAC(fields[3]); err != nil {
+			continue
+		}
+
+		ns = append(ns, n)
+	}
+
+	arp.ns.reset(ns)
+
+	return nil
+}
+
+// Neighbors implements the ARPDB interface for *fsysARPDB.
+func (arp *fsysARPDB) Neighbors() (ns []Neighbor) {
+	return arp.ns.clone()
+}
+
+// parseArpAWrt parses the output of the "arp -a" command on OpenWrt.  The
+// expected input format:
+//
+//   IP address       HW type     Flags       HW address            Mask     Device
+//   192.168.11.98    0x1         0x2         5a:92:df:a9:7e:28     *        wan
+//
+func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
+	if !sc.Scan() {
+		// Skip the header.
+		return
+	}
+
+	ns = make([]Neighbor, 0, lenHint)
+	for sc.Scan() {
+		ln := sc.Text()
+
+		fields := strings.Fields(ln)
+		if len(fields) < 4 {
+			continue
+		}
+
+		n := Neighbor{}
+
+		if ip := net.ParseIP(fields[0]); ip == nil || n.IP.IsUnspecified() {
+			continue
+		} else {
+			n.IP = ip
+		}
+
+		hwStr := fields[3]
+		if mac, err := net.ParseMAC(hwStr); err != nil {
+			log.Debug("parsing arp output: %s", err)
+
+			continue
+		} else {
+			n.MAC = mac
+		}
+
+		ns = append(ns, n)
+	}
+
+	return ns
+}
+
+// parseArpA parses the output of the "arp -a" command on Linux.  The expected
+// input format:
+//
+//   hostname (192.168.1.1) at ab:cd:ef:ab:cd:ef [ether] on enp0s3
+//
+func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
+	ns = make([]Neighbor, 0, lenHint)
+	for sc.Scan() {
+		ln := sc.Text()
+
+		fields := strings.Fields(ln)
+		if len(fields) < 4 {
+			continue
+		}
+
+		n := Neighbor{}
+
+		if ipStr := fields[1]; len(ipStr) < 2 {
+			continue
+		} else if ip := net.ParseIP(ipStr[1 : len(ipStr)-1]); ip == nil {
+			continue
+		} else {
+			n.IP = ip
+		}
+
+		hwStr := fields[3]
+		if mac, err := net.ParseMAC(hwStr); err != nil {
+			log.Debug("parsing arp output: %s", err)
+
+			continue
+		} else {
+			n.MAC = mac
+		}
+
+		host := fields[0]
+		if verr := netutil.ValidateDomainName(host); verr != nil {
+			log.Debug("parsing arp output: %s", verr)
+		} else {
+			n.Name = host
+		}
+
+		ns = append(ns, n)
+	}
+
+	return ns
+}
+
+// rcIPNeigh runs "ip neigh".
+func rcIPNeigh() (r io.Reader, err error) {
+	return runCmd("ip", "neigh")
+}
+
+// parseIPNeigh parses the output of the "ip neigh" command on Linux.  The
+// expected input format:
+//
+//   192.168.1.1 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef REACHABLE
+//
+func parseIPNeigh(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
+	ns = make([]Neighbor, 0, lenHint)
+	for sc.Scan() {
+		ln := sc.Text()
+
+		fields := strings.Fields(ln)
+		if len(fields) < 5 {
+			continue
+		}
+
+		n := Neighbor{}
+
+		if ip := net.ParseIP(fields[0]); ip == nil {
+			continue
+		} else {
+			n.IP = ip
+		}
+
+		if mac, err := net.ParseMAC(fields[4]); err != nil {
+			log.Debug("parsing arp output: %s", err)
+
+			continue
+		} else {
+			n.MAC = mac
+		}
+
+		ns = append(ns, n)
+	}
+
+	return ns
+}
diff --git a/internal/aghnet/arpdb_linux_test.go b/internal/aghnet/arpdb_linux_test.go
new file mode 100644
index 00000000..0439c9b5
--- /dev/null
+++ b/internal/aghnet/arpdb_linux_test.go
@@ -0,0 +1,84 @@
+//go:build linux
+// +build linux
+
+package aghnet
+
+import (
+	"io"
+	"net"
+	"strings"
+	"sync"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+const arpAOutputWrt = `
+IP address    HW type     Flags       HW address            Mask     Device
+192.168.1.2   0x1         0x2         ab:cd:ef:ab:cd:ef     *        wan
+::ffff:ffff   0x1         0x2         ef:cd:ab:ef:cd:ab     *        wan`
+
+const arpAOutput = `
+? (192.168.1.2) at ab:cd:ef:ab:cd:ef on en0 ifscope [ethernet]
+? (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 100 seconds [ethernet]`
+
+const ipNeighOutput = `
+192.168.1.2 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef DELAY
+::ffff:ffff dev enp0s3 lladdr ef:cd:ab:ef:cd:ab router STALE`
+
+var wantNeighs = []Neighbor{{
+	IP:  net.IPv4(192, 168, 1, 2),
+	MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
+}, {
+	IP:  net.ParseIP("::ffff:ffff"),
+	MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
+}}
+
+func TestFSysARPDB(t *testing.T) {
+	a := &fsysARPDB{
+		ns: &neighs{
+			mu: &sync.RWMutex{},
+			ns: make([]Neighbor, 0),
+		},
+		fsys:     testdata,
+		filename: "proc_net_arp",
+	}
+
+	err := a.Refresh()
+	require.NoError(t, err)
+
+	ns := a.Neighbors()
+	assert.Equal(t, wantNeighs, ns)
+}
+
+func TestCmdARPDB_arpawrt(t *testing.T) {
+	a := &cmdARPDB{
+		parse:  parseArpAWrt,
+		runcmd: func() (r io.Reader, err error) { return strings.NewReader(arpAOutputWrt), nil },
+		ns: &neighs{
+			mu: &sync.RWMutex{},
+			ns: make([]Neighbor, 0),
+		},
+	}
+
+	err := a.Refresh()
+	require.NoError(t, err)
+
+	assert.Equal(t, wantNeighs, a.Neighbors())
+}
+
+func TestCmdARPDB_ipneigh(t *testing.T) {
+	a := &cmdARPDB{
+		parse:  parseIPNeigh,
+		runcmd: func() (r io.Reader, err error) { return strings.NewReader(ipNeighOutput), nil },
+		ns: &neighs{
+			mu: &sync.RWMutex{},
+			ns: make([]Neighbor, 0),
+		},
+	}
+	err := a.Refresh()
+	require.NoError(t, err)
+
+	assert.Equal(t, wantNeighs, a.Neighbors())
+}
diff --git a/internal/aghnet/arpdb_openbsd.go b/internal/aghnet/arpdb_openbsd.go
new file mode 100644
index 00000000..805cb459
--- /dev/null
+++ b/internal/aghnet/arpdb_openbsd.go
@@ -0,0 +1,67 @@
+//go:build openbsd
+// +build openbsd
+
+package aghnet
+
+import (
+	"bufio"
+	"net"
+	"strings"
+	"sync"
+
+	"github.com/AdguardTeam/golibs/log"
+)
+
+func newARPDB() *cmdARPDB {
+	return &cmdARPDB{
+		runcmd: rcArpA,
+		parse:  parseArpA,
+		ns: &neighs{
+			mu: &sync.RWMutex{},
+			ns: make([]Neighbor, 0),
+		},
+	}
+}
+
+// parseArpA parses the output of the "arp -a" command on OpenBSD.  The expected
+// input format:
+//
+//   Host        Ethernet Address  Netif Expire    Flags
+//   192.168.1.1 ab:cd:ef:ab:cd:ef   em0 19m59s
+//
+func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
+	// Skip the header.
+	if !sc.Scan() {
+		return nil
+	}
+
+	ns = make([]Neighbor, 0, lenHint)
+	for sc.Scan() {
+		ln := sc.Text()
+
+		fields := strings.Fields(ln)
+		if len(fields) < 2 {
+			continue
+		}
+
+		n := Neighbor{}
+
+		if ip := net.ParseIP(fields[0]); ip == nil {
+			continue
+		} else {
+			n.IP = ip
+		}
+
+		if mac, err := net.ParseMAC(fields[1]); err != nil {
+			log.Debug("parsing arp output: %s", err)
+
+			continue
+		} else {
+			n.MAC = mac
+		}
+
+		ns = append(ns, n)
+	}
+
+	return ns
+}
diff --git a/internal/aghnet/arpdb_openbsd_test.go b/internal/aghnet/arpdb_openbsd_test.go
new file mode 100644
index 00000000..b1021513
--- /dev/null
+++ b/internal/aghnet/arpdb_openbsd_test.go
@@ -0,0 +1,22 @@
+//go:build openbsd
+// +build openbsd
+
+package aghnet
+
+import (
+	"net"
+)
+
+const arpAOutput = `
+Host        Ethernet Address  Netif Expire    Flags
+192.168.1.2 ab:cd:ef:ab:cd:ef   em0 19m56s
+::ffff:ffff ef:cd:ab:ef:cd:ab   em0 permanent l
+`
+
+var wantNeighs = []Neighbor{{
+	IP:  net.IPv4(192, 168, 1, 2),
+	MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
+}, {
+	IP:  net.ParseIP("::ffff:ffff"),
+	MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
+}}
diff --git a/internal/aghnet/arpdb_test.go b/internal/aghnet/arpdb_test.go
new file mode 100644
index 00000000..ce3da4fb
--- /dev/null
+++ b/internal/aghnet/arpdb_test.go
@@ -0,0 +1,168 @@
+package aghnet
+
+import (
+	"io"
+	"net"
+	"strings"
+	"sync"
+	"testing"
+
+	"github.com/AdguardTeam/golibs/errors"
+	"github.com/AdguardTeam/golibs/testutil"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TestARPDB is the mock implementation of ARPDB to use in tests.
+type TestARPDB struct {
+	OnRefresh   func() (err error)
+	OnNeighbors func() (ns []Neighbor)
+}
+
+// Refresh implements the ARPDB interface for *TestARPDB.
+func (arp *TestARPDB) Refresh() (err error) {
+	return arp.OnRefresh()
+}
+
+// Neighbors implements the ARPDB interface for *TestARPDB.
+func (arp *TestARPDB) Neighbors() (ns []Neighbor) {
+	return arp.OnNeighbors()
+}
+
+func TestARPDBS(t *testing.T) {
+	knownIP := net.IP{1, 2, 3, 4}
+	knownMAC := net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF}
+
+	succRefrCount, failRefrCount := 0, 0
+	clnp := func() {
+		succRefrCount, failRefrCount = 0, 0
+	}
+
+	succDB := &TestARPDB{
+		OnRefresh: func() (err error) { succRefrCount++; return nil },
+		OnNeighbors: func() (ns []Neighbor) {
+			return []Neighbor{{Name: "abc", IP: knownIP, MAC: knownMAC}}
+		},
+	}
+	failDB := &TestARPDB{
+		OnRefresh:   func() (err error) { failRefrCount++; return errors.Error("refresh failed") },
+		OnNeighbors: func() (ns []Neighbor) { return nil },
+	}
+
+	t.Run("begin_with_success", func(t *testing.T) {
+		t.Cleanup(clnp)
+
+		a := newARPDBs(succDB, failDB)
+		err := a.Refresh()
+		require.NoError(t, err)
+
+		assert.Equal(t, 1, succRefrCount)
+		assert.Zero(t, failRefrCount)
+		assert.NotEmpty(t, a.Neighbors())
+	})
+
+	t.Run("begin_with_fail", func(t *testing.T) {
+		t.Cleanup(clnp)
+
+		a := newARPDBs(failDB, succDB)
+		err := a.Refresh()
+		require.NoError(t, err)
+
+		assert.Equal(t, 1, succRefrCount)
+		assert.Equal(t, 1, failRefrCount)
+		assert.NotEmpty(t, a.Neighbors())
+	})
+
+	t.Run("fail_only", func(t *testing.T) {
+		t.Cleanup(clnp)
+
+		wantMsg := `each arpdb failed: 2 errors: "refresh failed", "refresh failed"`
+
+		a := newARPDBs(failDB, failDB)
+		err := a.Refresh()
+		require.Error(t, err)
+
+		testutil.AssertErrorMsg(t, wantMsg, err)
+
+		assert.Equal(t, 2, failRefrCount)
+		assert.Empty(t, a.Neighbors())
+	})
+
+	t.Run("fail_after_success", func(t *testing.T) {
+		t.Cleanup(clnp)
+
+		shouldFail := false
+		unstableDB := &TestARPDB{
+			OnRefresh: func() (err error) {
+				if shouldFail {
+					err = errors.Error("unstable failed")
+				}
+				shouldFail = !shouldFail
+
+				return err
+			},
+			OnNeighbors: func() (ns []Neighbor) {
+				if !shouldFail {
+					return failDB.OnNeighbors()
+				}
+
+				return succDB.OnNeighbors()
+			},
+		}
+		a := newARPDBs(unstableDB, succDB)
+
+		// Unstable ARPDB should refresh successfully.
+		err := a.Refresh()
+		require.NoError(t, err)
+
+		assert.Zero(t, succRefrCount)
+		assert.NotEmpty(t, a.Neighbors())
+
+		// Unstable ARPDB should fail and the succDB should be used.
+		err = a.Refresh()
+		require.NoError(t, err)
+
+		assert.Equal(t, 1, succRefrCount)
+		assert.NotEmpty(t, a.Neighbors())
+
+		// Only the last succeeded ARPDB should be used.
+		err = a.Refresh()
+		require.NoError(t, err)
+
+		assert.Equal(t, 2, succRefrCount)
+		assert.NotEmpty(t, a.Neighbors())
+	})
+
+	t.Run("empty", func(t *testing.T) {
+		a := newARPDBs()
+		require.NoError(t, a.Refresh())
+
+		assert.Empty(t, a.Neighbors())
+	})
+}
+
+func TestCmdARPDB_arpa(t *testing.T) {
+	a := &cmdARPDB{
+		parse: parseArpA,
+		ns: &neighs{
+			mu: &sync.RWMutex{},
+			ns: make([]Neighbor, 0),
+		},
+	}
+
+	t.Run("arp_a", func(t *testing.T) {
+		a.runcmd = func() (r io.Reader, err error) { return strings.NewReader(arpAOutput), nil }
+
+		err := a.Refresh()
+		require.NoError(t, err)
+
+		assert.Equal(t, wantNeighs, a.Neighbors())
+	})
+
+	t.Run("runcmd_error", func(t *testing.T) {
+		a.runcmd = func() (r io.Reader, err error) { return nil, errors.Error("can't run") }
+
+		err := a.Refresh()
+		testutil.AssertErrorMsg(t, "cmd arpdb: running command: can't run", err)
+	})
+}
diff --git a/internal/aghnet/arpdb_unix.go b/internal/aghnet/arpdb_unix.go
new file mode 100644
index 00000000..50346f92
--- /dev/null
+++ b/internal/aghnet/arpdb_unix.go
@@ -0,0 +1,13 @@
+//go:build !windows
+// +build !windows
+
+package aghnet
+
+import (
+	"io"
+)
+
+// rcArpA runs "arp -a".
+func rcArpA() (r io.Reader, err error) {
+	return runCmd("arp", "-a")
+}
diff --git a/internal/aghnet/arpdb_windows.go b/internal/aghnet/arpdb_windows.go
new file mode 100644
index 00000000..5156330b
--- /dev/null
+++ b/internal/aghnet/arpdb_windows.go
@@ -0,0 +1,70 @@
+//go:build windows
+// +build windows
+
+package aghnet
+
+import (
+	"bufio"
+	"io"
+	"net"
+	"strings"
+	"sync"
+)
+
+func newARPDB() *cmdARPDB {
+	return &cmdARPDB{
+		runcmd: rcArpA,
+		ns: &neighs{
+			mu: &sync.RWMutex{},
+			ns: make([]Neighbor, 0),
+		},
+		parse: parseArpA,
+	}
+}
+
+// rcArpA runs "arp /a".
+func rcArpA() (r io.Reader, err error) {
+	return runCmd("arp", "/a")
+}
+
+// parseArpA parses the output of the "arp /a" command on Windows.  The expected
+// input format (the first line is empty):
+//
+//
+//   Interface: 192.168.56.16 --- 0x7
+//     Internet Address      Physical Address      Type
+//     192.168.56.1          0a-00-27-00-00-00     dynamic
+//     192.168.56.255        ff-ff-ff-ff-ff-ff     static
+//
+func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
+	ns = make([]Neighbor, 0, lenHint)
+	for sc.Scan() {
+		ln := sc.Text()
+		if ln == "" {
+			continue
+		}
+
+		fields := strings.Fields(ln)
+		if len(fields) != 3 {
+			continue
+		}
+
+		n := Neighbor{}
+
+		if ip := net.ParseIP(fields[0]); ip == nil {
+			continue
+		} else {
+			n.IP = ip
+		}
+
+		if mac, err := net.ParseMAC(fields[1]); err != nil {
+			continue
+		} else {
+			n.MAC = mac
+		}
+
+		ns = append(ns, n)
+	}
+
+	return ns
+}
diff --git a/internal/aghnet/arpdb_windows_test.go b/internal/aghnet/arpdb_windows_test.go
new file mode 100644
index 00000000..ad88ff8e
--- /dev/null
+++ b/internal/aghnet/arpdb_windows_test.go
@@ -0,0 +1,23 @@
+//go:build windows
+// +build windows
+
+package aghnet
+
+import (
+	"net"
+)
+
+const arpAOutput = `
+
+Interface: 192.168.1.1 --- 0x7
+  Internet Address      Physical Address      Type
+  192.168.1.2           ab-cd-ef-ab-cd-ef     dynamic
+  ::ffff:ffff           ef-cd-ab-ef-cd-ab     static`
+
+var wantNeighs = []Neighbor{{
+	IP:  net.IPv4(192, 168, 1, 2),
+	MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
+}, {
+	IP:  net.ParseIP("::ffff:ffff"),
+	MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
+}}
diff --git a/internal/aghnet/hostscontainer_test.go b/internal/aghnet/hostscontainer_test.go
index a141ce00..40bfb34c 100644
--- a/internal/aghnet/hostscontainer_test.go
+++ b/internal/aghnet/hostscontainer_test.go
@@ -3,7 +3,6 @@ package aghnet
 import (
 	"io/fs"
 	"net"
-	"os"
 	"path"
 	"strings"
 	"sync/atomic"
@@ -281,7 +280,6 @@ func TestHostsContainer_PathsToPatterns(t *testing.T) {
 }
 
 func TestHostsContainer_Translate(t *testing.T) {
-	testdata := os.DirFS("./testdata")
 	stubWatcher := aghtest.FSWatcher{
 		OnEvents: func() (e <-chan struct{}) { return nil },
 		OnAdd:    func(name string) (err error) { return nil },
@@ -360,8 +358,6 @@ func TestHostsContainer_Translate(t *testing.T) {
 func TestHostsContainer(t *testing.T) {
 	const listID = 1234
 
-	testdata := os.DirFS("./testdata")
-
 	testCases := []struct {
 		want []*rules.DNSRewrite
 		name string
diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go
index b5bf2297..34e99faa 100644
--- a/internal/aghnet/net_test.go
+++ b/internal/aghnet/net_test.go
@@ -1,7 +1,9 @@
 package aghnet
 
 import (
+	"io/fs"
 	"net"
+	"os"
 	"testing"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
@@ -11,6 +13,9 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+// testdata is the filesystem containing data for testing the package.
+var testdata fs.FS = os.DirFS("./testdata")
+
 func TestMain(m *testing.M) {
 	aghtest.DiscardLogOutput(m)
 }
diff --git a/internal/aghnet/testdata/proc_net_arp b/internal/aghnet/testdata/proc_net_arp
new file mode 100644
index 00000000..07d214e1
--- /dev/null
+++ b/internal/aghnet/testdata/proc_net_arp
@@ -0,0 +1,4 @@
+IP address     HW type     Flags       HW address            Mask     Device
+192.168.1.2    0x1         0x2         ab:cd:ef:ab:cd:ef     *        wan
+::ffff:ffff    0x1         0x0         ef:cd:ab:ef:cd:ab     *        br-lan
+0.0.0.0        0x0         0x0         00:00:00:00:00:00     *        unspec
\ No newline at end of file
diff --git a/internal/aghos/os.go b/internal/aghos/os.go
index 29eb1afc..8ac189a1 100644
--- a/internal/aghos/os.go
+++ b/internal/aghos/os.go
@@ -52,11 +52,12 @@ func HaveAdminRights() (bool, error) {
 	return haveAdminRights()
 }
 
-// MaxCmdOutputSize is the maximum length of performed shell command output.
-const MaxCmdOutputSize = 2 * 1024
+// MaxCmdOutputSize is the maximum length of performed shell command output in
+// bytes.
+const MaxCmdOutputSize = 64 * 1024
 
 // RunCommand runs shell command.
-func RunCommand(command string, arguments ...string) (int, string, error) {
+func RunCommand(command string, arguments ...string) (code int, output string, err error) {
 	cmd := exec.Command(command, arguments...)
 	out, err := cmd.Output()
 	if len(out) > MaxCmdOutputSize {
@@ -66,7 +67,7 @@ func RunCommand(command string, arguments ...string) (int, string, error) {
 	if errors.As(err, new(*exec.ExitError)) {
 		return cmd.ProcessState.ExitCode(), string(out), nil
 	} else if err != nil {
-		return 1, "", fmt.Errorf("exec.Command(%s) failed: %w: %s", command, err, string(out))
+		return 1, "", fmt.Errorf("command %q failed: %w: %s", command, err, out)
 	}
 
 	return cmd.ProcessState.ExitCode(), string(out), nil
diff --git a/internal/home/clients.go b/internal/home/clients.go
index f539adbe..d9aad40e 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -4,10 +4,7 @@ import (
 	"bytes"
 	"fmt"
 	"net"
-	"os/exec"
-	"runtime"
 	"sort"
-	"strings"
 	"sync"
 	"time"
 
@@ -99,6 +96,9 @@ type clientsContainer struct {
 	// hosts database.
 	etcHosts *aghnet.HostsContainer
 
+	// arpdb stores the neighbors retrieved from ARP.
+	arpdb aghnet.ARPDB
+
 	testing bool // if TRUE, this object is used for internal tests
 }
 
@@ -109,6 +109,7 @@ func (clients *clientsContainer) Init(
 	objects []*clientObject,
 	dhcpServer *dhcpd.Server,
 	etcHosts *aghnet.HostsContainer,
+	arpdb aghnet.ARPDB,
 ) {
 	if clients.list != nil {
 		log.Fatal("clients.list != nil")
@@ -121,6 +122,7 @@ func (clients *clientsContainer) Init(
 
 	clients.dhcpServer = dhcpServer
 	clients.etcHosts = etcHosts
+	clients.arpdb = arpdb
 	clients.addFromConfig(objects)
 
 	if clients.testing {
@@ -807,16 +809,18 @@ func (clients *clientsContainer) addFromHostsFile(hosts *netutil.IPMap) {
 // addFromSystemARP adds the IP-hostname pairings from the output of the arp -a
 // command.
 func (clients *clientsContainer) addFromSystemARP() {
-	if runtime.GOOS == "windows" {
+	if err := clients.arpdb.Refresh(); err != nil {
+		log.Error("refreshing arp container: %s", err)
+
+		clients.arpdb = aghnet.EmptyARPDB{}
+
 		return
 	}
 
-	cmd := exec.Command("arp", "-a")
-	log.Tracef("executing %q %q", cmd.Path, cmd.Args)
-	data, err := cmd.Output()
-	if err != nil || cmd.ProcessState.ExitCode() != 0 {
-		log.Debug("command %q has failed: %q code:%d",
-			cmd.Path, err, cmd.ProcessState.ExitCode())
+	ns := clients.arpdb.Neighbors()
+	if len(ns) == 0 {
+		log.Debug("refreshing arp container: the update is empty")
+
 		return
 	}
 
@@ -825,30 +829,14 @@ func (clients *clientsContainer) addFromSystemARP() {
 
 	clients.rmHostsBySrc(ClientSourceARP)
 
-	n := 0
-	// TODO(a.garipov): Rewrite to use bufio.Scanner.
-	lines := strings.Split(string(data), "\n")
-	for _, ln := range lines {
-		lparen := strings.Index(ln, " (")
-		rparen := strings.Index(ln, ") ")
-		if lparen == -1 || rparen == -1 || lparen >= rparen {
-			continue
-		}
-
-		host := ln[:lparen]
-		ipStr := ln[lparen+2 : rparen]
-		ip := net.ParseIP(ipStr)
-		if netutil.ValidateDomainName(host) != nil || ip == nil {
-			continue
-		}
-
-		ok := clients.addHostLocked(ip, host, ClientSourceARP)
-		if ok {
-			n++
+	added := 0
+	for _, n := range ns {
+		if clients.addHostLocked(n.IP, "", ClientSourceARP) {
+			added++
 		}
 	}
 
-	log.Debug("clients: added %d client aliases from 'arp -a' command output", n)
+	log.Debug("clients: added %d client aliases from arp neighborhood", added)
 }
 
 // updateFromDHCP adds the clients that have a non-empty hostname from the DHCP
diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go
index 6cc5d46f..8afd5621 100644
--- a/internal/home/clients_test.go
+++ b/internal/home/clients_test.go
@@ -18,7 +18,7 @@ func TestClients(t *testing.T) {
 	clients := clientsContainer{}
 	clients.testing = true
 
-	clients.Init(nil, nil, nil)
+	clients.Init(nil, nil, nil, nil)
 
 	t.Run("add_success", func(t *testing.T) {
 		c := &Client{
@@ -194,7 +194,7 @@ func TestClientsWHOIS(t *testing.T) {
 	clients := clientsContainer{
 		testing: true,
 	}
-	clients.Init(nil, nil, nil)
+	clients.Init(nil, nil, nil, nil)
 	whois := &RuntimeClientWHOISInfo{
 		Country: "AU",
 		Orgname: "Example Org",
@@ -253,7 +253,7 @@ func TestClientsAddExisting(t *testing.T) {
 	clients := clientsContainer{
 		testing: true,
 	}
-	clients.Init(nil, nil, nil)
+	clients.Init(nil, nil, nil, nil)
 
 	t.Run("simple", func(t *testing.T) {
 		ip := net.IP{1, 1, 1, 1}
@@ -332,7 +332,7 @@ func TestClientsCustomUpstream(t *testing.T) {
 	clients := clientsContainer{
 		testing: true,
 	}
-	clients.Init(nil, nil, nil)
+	clients.Init(nil, nil, nil, nil)
 
 	// Add client with upstreams.
 	ok, err := clients.Add(&Client{
diff --git a/internal/home/home.go b/internal/home/home.go
index 021751e8..114ba4b6 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -293,7 +293,15 @@ func setupConfig(args options) (err error) {
 		}
 	}
 
-	Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts)
+	var arpdb aghnet.ARPDB
+	arpdb, err = aghnet.NewARPDB()
+	if err != nil {
+		log.Info("warning: creating arpdb: %s; using stub", err)
+
+		arpdb = aghnet.EmptyARPDB{}
+	}
+
+	Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts, arpdb)
 
 	if args.bindPort != 0 {
 		uc := aghalg.UniqChecker{}

From 778585865ebd2234596b8acbe5510bcb96de45fc Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Fri, 18 Mar 2022 13:37:27 +0300
Subject: [PATCH 015/143] Pull request: 3142 custom private subnets

Merge in DNS/adguard-home from 3142-custom-subnets to master

Updates #3142.

Squashed commit of the following:

commit 11469ade75b9dc32ee6d93e3aa35cf79dbaa28b2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 17 19:56:02 2022 +0300

    all: upd golibs, use subnet set
---
 CHANGELOG.md                           |   3 +
 go.mod                                 |   2 +-
 go.sum                                 |   3 +-
 internal/aghnet/subnetdetector.go      | 158 ----------------
 internal/aghnet/subnetdetector_test.go | 252 -------------------------
 internal/dnsforward/dns.go             |   6 +-
 internal/dnsforward/dns_test.go        |  22 ++-
 internal/dnsforward/dnsforward.go      |  20 +-
 internal/dnsforward/dnsforward_test.go |  66 +++----
 internal/dnsforward/filter_test.go     |  11 +-
 internal/dnsforward/http.go            |  24 +--
 internal/dnsforward/http_test.go       |   6 +-
 internal/home/config.go                |   4 +
 internal/home/dns.go                   |  43 ++++-
 internal/home/home.go                  |   5 -
 15 files changed, 105 insertions(+), 520 deletions(-)
 delete mode 100644 internal/aghnet/subnetdetector.go
 delete mode 100644 internal/aghnet/subnetdetector_test.go

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48aa24d7..d663d78e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@ and this project adheres to
 
 ### Added
 
+- The ability to customize the set of networks considered private through the
+  new `private_networks` setting ([#3142]).
 - EDNS Client-Subnet information in the request details section of a query log
   record ([#3978]).
 - Support for hostnames for plain UDP upstream servers using the `udp://` scheme
@@ -88,6 +90,7 @@ In this release, the schema version has changed from 12 to 13.
 [#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
+[#3142]: https://github.com/AdguardTeam/AdGuardHome/issues/3142
 [#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
 [#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381
 [#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
diff --git a/go.mod b/go.mod
index 1aedfc38..8cfd5475 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.17
 
 require (
 	github.com/AdguardTeam/dnsproxy v0.41.4
-	github.com/AdguardTeam/golibs v0.10.6
+	github.com/AdguardTeam/golibs v0.10.8
 	github.com/AdguardTeam/urlfilter v0.15.2
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/ameshkov/dnscrypt/v2 v2.2.3
diff --git a/go.sum b/go.sum
index 716b6955..5e5ef775 100644
--- a/go.sum
+++ b/go.sum
@@ -12,8 +12,9 @@ github.com/AdguardTeam/dnsproxy v0.41.4/go.mod h1:GCdEbTw683vBqksJIccPSYzBg2yIFb
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
-github.com/AdguardTeam/golibs v0.10.6 h1:6UG6LxWFnG7TfjNzeApw+T68Kqqov0fcDYk9RjhTdhc=
 github.com/AdguardTeam/golibs v0.10.6/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
+github.com/AdguardTeam/golibs v0.10.8 h1:diU9gP9qG1qeLbAkzIwfUerpHSqzR6zaBgzvRMR/m6Q=
+github.com/AdguardTeam/golibs v0.10.8/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
 github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
 github.com/AdguardTeam/urlfilter v0.15.2 h1:LZGgrm4l4Ys9eAqB+UUmZfiC6vHlDlYFhx0WXqo6LtQ=
 github.com/AdguardTeam/urlfilter v0.15.2/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
diff --git a/internal/aghnet/subnetdetector.go b/internal/aghnet/subnetdetector.go
deleted file mode 100644
index fd338aaa..00000000
--- a/internal/aghnet/subnetdetector.go
+++ /dev/null
@@ -1,158 +0,0 @@
-package aghnet
-
-import (
-	"net"
-)
-
-// SubnetDetector describes IP address properties.
-type SubnetDetector struct {
-	// spNets is the collection of special-purpose address registries as defined
-	// by RFC 6890.
-	spNets []*net.IPNet
-
-	// locServedNets is the collection of locally-served networks as defined by
-	// RFC 6303.
-	locServedNets []*net.IPNet
-}
-
-// NewSubnetDetector returns a new IP detector.
-//
-// TODO(a.garipov):  Decide whether an error is actually needed.
-func NewSubnetDetector() (snd *SubnetDetector, err error) {
-	spNets := []string{
-		// "This" network.
-		"0.0.0.0/8",
-		// Private-Use Networks.
-		"10.0.0.0/8",
-		// Shared Address Space.
-		"100.64.0.0/10",
-		// Loopback.
-		"127.0.0.0/8",
-		// Link Local.
-		"169.254.0.0/16",
-		// Private-Use Networks.
-		"172.16.0.0/12",
-		// IETF Protocol Assignments.
-		"192.0.0.0/24",
-		// DS-Lite.
-		"192.0.0.0/29",
-		// TEST-NET-1
-		"192.0.2.0/24",
-		// 6to4 Relay Anycast.
-		"192.88.99.0/24",
-		// Private-Use Networks.
-		"192.168.0.0/16",
-		// Network Interconnect Device Benchmark Testing.
-		"198.18.0.0/15",
-		// TEST-NET-2.
-		"198.51.100.0/24",
-		// TEST-NET-3.
-		"203.0.113.0/24",
-		// Reserved for Future Use.
-		"240.0.0.0/4",
-		// Limited Broadcast.
-		"255.255.255.255/32",
-
-		// Loopback.
-		"::1/128",
-		// Unspecified.
-		"::/128",
-		// IPv4-IPv6 Translation Address.
-		"64:ff9b::/96",
-
-		// IPv4-Mapped Address.  Since this network is used for mapping
-		// IPv4 addresses, we don't include it.
-		// "::ffff:0:0/96",
-
-		// Discard-Only Prefix.
-		"100::/64",
-		// IETF Protocol Assignments.
-		"2001::/23",
-		// TEREDO.
-		"2001::/32",
-		// Benchmarking.
-		"2001:2::/48",
-		// Documentation.
-		"2001:db8::/32",
-		// ORCHID.
-		"2001:10::/28",
-		// 6to4.
-		"2002::/16",
-		// Unique-Local.
-		"fc00::/7",
-		// Linked-Scoped Unicast.
-		"fe80::/10",
-	}
-
-	// TODO(e.burkov): It's a subslice of the slice above.  Should be done
-	// smarter.
-	locServedNets := []string{
-		// IPv4.
-		"10.0.0.0/8",
-		"172.16.0.0/12",
-		"192.168.0.0/16",
-		"127.0.0.0/8",
-		"169.254.0.0/16",
-		"192.0.2.0/24",
-		"198.51.100.0/24",
-		"203.0.113.0/24",
-		"255.255.255.255/32",
-		// IPv6.
-		"::/128",
-		"::1/128",
-		"fe80::/10",
-		"2001:db8::/32",
-		"fd00::/8",
-	}
-
-	snd = &SubnetDetector{
-		spNets:        make([]*net.IPNet, len(spNets)),
-		locServedNets: make([]*net.IPNet, len(locServedNets)),
-	}
-	for i, ipnetStr := range spNets {
-		var ipnet *net.IPNet
-		_, ipnet, err = net.ParseCIDR(ipnetStr)
-		if err != nil {
-			return nil, err
-		}
-
-		snd.spNets[i] = ipnet
-	}
-	for i, ipnetStr := range locServedNets {
-		var ipnet *net.IPNet
-		_, ipnet, err = net.ParseCIDR(ipnetStr)
-		if err != nil {
-			return nil, err
-		}
-
-		snd.locServedNets[i] = ipnet
-	}
-
-	return snd, nil
-}
-
-// anyNetContains ranges through the given ipnets slice searching for the one
-// which contains the ip.  For internal use only.
-//
-// TODO(e.burkov): Think about memoization.
-func anyNetContains(ipnets *[]*net.IPNet, ip net.IP) (is bool) {
-	for _, ipnet := range *ipnets {
-		if ipnet.Contains(ip) {
-			return true
-		}
-	}
-
-	return false
-}
-
-// IsSpecialNetwork returns true if IP address is contained by any of
-// special-purpose IP address registries.  It's safe for concurrent use.
-func (snd *SubnetDetector) IsSpecialNetwork(ip net.IP) (is bool) {
-	return anyNetContains(&snd.spNets, ip)
-}
-
-// IsLocallyServedNetwork returns true if IP address is contained by any of
-// locally-served IP address registries.  It's safe for concurrent use.
-func (snd *SubnetDetector) IsLocallyServedNetwork(ip net.IP) (is bool) {
-	return anyNetContains(&snd.locServedNets, ip)
-}
diff --git a/internal/aghnet/subnetdetector_test.go b/internal/aghnet/subnetdetector_test.go
deleted file mode 100644
index f4b7678c..00000000
--- a/internal/aghnet/subnetdetector_test.go
+++ /dev/null
@@ -1,252 +0,0 @@
-package aghnet
-
-import (
-	"net"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-func TestSubnetDetector_DetectSpecialNetwork(t *testing.T) {
-	snd, err := NewSubnetDetector()
-	require.NoError(t, err)
-
-	testCases := []struct {
-		name string
-		ip   net.IP
-		want bool
-	}{{
-		name: "not_specific",
-		ip:   net.ParseIP("8.8.8.8"),
-		want: false,
-	}, {
-		name: "this_host_on_this_network",
-		ip:   net.ParseIP("0.0.0.0"),
-		want: true,
-	}, {
-		name: "private-Use",
-		ip:   net.ParseIP("10.0.0.0"),
-		want: true,
-	}, {
-		name: "shared_address_space",
-		ip:   net.ParseIP("100.64.0.0"),
-		want: true,
-	}, {
-		name: "loopback",
-		ip:   net.ParseIP("127.0.0.0"),
-		want: true,
-	}, {
-		name: "link_local",
-		ip:   net.ParseIP("169.254.0.0"),
-		want: true,
-	}, {
-		name: "private-use",
-		ip:   net.ParseIP("172.16.0.0"),
-		want: true,
-	}, {
-		name: "ietf_protocol_assignments",
-		ip:   net.ParseIP("192.0.0.0"),
-		want: true,
-	}, {
-		name: "ds-lite",
-		ip:   net.ParseIP("192.0.0.0"),
-		want: true,
-	}, {
-		name: "documentation_(test-net-1)",
-		ip:   net.ParseIP("192.0.2.0"),
-		want: true,
-	}, {
-		name: "6to4_relay_anycast",
-		ip:   net.ParseIP("192.88.99.0"),
-		want: true,
-	}, {
-		name: "private-use",
-		ip:   net.ParseIP("192.168.0.0"),
-		want: true,
-	}, {
-		name: "benchmarking",
-		ip:   net.ParseIP("198.18.0.0"),
-		want: true,
-	}, {
-		name: "documentation_(test-net-2)",
-		ip:   net.ParseIP("198.51.100.0"),
-		want: true,
-	}, {
-		name: "documentation_(test-net-3)",
-		ip:   net.ParseIP("203.0.113.0"),
-		want: true,
-	}, {
-		name: "reserved",
-		ip:   net.ParseIP("240.0.0.0"),
-		want: true,
-	}, {
-		name: "limited_broadcast",
-		ip:   net.ParseIP("255.255.255.255"),
-		want: true,
-	}, {
-		name: "loopback_address",
-		ip:   net.ParseIP("::1"),
-		want: true,
-	}, {
-		name: "unspecified_address",
-		ip:   net.ParseIP("::"),
-		want: true,
-	}, {
-		name: "ipv4-ipv6_translation",
-		ip:   net.ParseIP("64:ff9b::"),
-		want: true,
-	}, {
-		name: "discard-only_address_block",
-		ip:   net.ParseIP("100::"),
-		want: true,
-	}, {
-		name: "ietf_protocol_assignments",
-		ip:   net.ParseIP("2001::"),
-		want: true,
-	}, {
-		name: "teredo",
-		ip:   net.ParseIP("2001::"),
-		want: true,
-	}, {
-		name: "benchmarking",
-		ip:   net.ParseIP("2001:2::"),
-		want: true,
-	}, {
-		name: "documentation",
-		ip:   net.ParseIP("2001:db8::"),
-		want: true,
-	}, {
-		name: "orchid",
-		ip:   net.ParseIP("2001:10::"),
-		want: true,
-	}, {
-		name: "6to4",
-		ip:   net.ParseIP("2002::"),
-		want: true,
-	}, {
-		name: "unique-local",
-		ip:   net.ParseIP("fc00::"),
-		want: true,
-	}, {
-		name: "linked-scoped_unicast",
-		ip:   net.ParseIP("fe80::"),
-		want: true,
-	}}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			assert.Equal(t, tc.want, snd.IsSpecialNetwork(tc.ip))
-		})
-	}
-}
-
-func TestSubnetDetector_DetectLocallyServedNetwork(t *testing.T) {
-	snd, err := NewSubnetDetector()
-	require.NoError(t, err)
-
-	testCases := []struct {
-		name string
-		ip   net.IP
-		want bool
-	}{{
-		name: "not_specific",
-		ip:   net.ParseIP("8.8.8.8"),
-		want: false,
-	}, {
-		name: "private-Use",
-		ip:   net.ParseIP("10.0.0.0"),
-		want: true,
-	}, {
-		name: "loopback",
-		ip:   net.ParseIP("127.0.0.0"),
-		want: true,
-	}, {
-		name: "link_local",
-		ip:   net.ParseIP("169.254.0.0"),
-		want: true,
-	}, {
-		name: "private-use",
-		ip:   net.ParseIP("172.16.0.0"),
-		want: true,
-	}, {
-		name: "documentation_(test-net-1)",
-		ip:   net.ParseIP("192.0.2.0"),
-		want: true,
-	}, {
-		name: "private-use",
-		ip:   net.ParseIP("192.168.0.0"),
-		want: true,
-	}, {
-		name: "documentation_(test-net-2)",
-		ip:   net.ParseIP("198.51.100.0"),
-		want: true,
-	}, {
-		name: "documentation_(test-net-3)",
-		ip:   net.ParseIP("203.0.113.0"),
-		want: true,
-	}, {
-		name: "limited_broadcast",
-		ip:   net.ParseIP("255.255.255.255"),
-		want: true,
-	}, {
-		name: "loopback_address",
-		ip:   net.ParseIP("::1"),
-		want: true,
-	}, {
-		name: "unspecified_address",
-		ip:   net.ParseIP("::"),
-		want: true,
-	}, {
-		name: "documentation",
-		ip:   net.ParseIP("2001:db8::"),
-		want: true,
-	}, {
-		name: "linked-scoped_unicast",
-		ip:   net.ParseIP("fe80::"),
-		want: true,
-	}, {
-		name: "locally_assigned",
-		ip:   net.ParseIP("fd00::1"),
-		want: true,
-	}, {
-		name: "not_locally_assigned",
-		ip:   net.ParseIP("fc00::1"),
-		want: false,
-	}}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			assert.Equal(t, tc.want, snd.IsLocallyServedNetwork(tc.ip))
-		})
-	}
-}
-
-func TestSubnetDetector_Detect_parallel(t *testing.T) {
-	t.Parallel()
-
-	snd, err := NewSubnetDetector()
-	require.NoError(t, err)
-
-	testFunc := func() {
-		for _, ip := range []net.IP{
-			net.IPv4allrouter,
-			net.IPv4allsys,
-			net.IPv4bcast,
-			net.IPv4zero,
-			net.IPv6interfacelocalallnodes,
-			net.IPv6linklocalallnodes,
-			net.IPv6linklocalallrouters,
-			net.IPv6loopback,
-			net.IPv6unspecified,
-		} {
-			_ = snd.IsSpecialNetwork(ip)
-			_ = snd.IsLocallyServedNetwork(ip)
-		}
-	}
-
-	const goroutinesNum = 50
-	for i := 0; i < goroutinesNum; i++ {
-		go testFunc()
-	}
-}
diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go
index dd5a4dd5..63f694bd 100644
--- a/internal/dnsforward/dns.go
+++ b/internal/dnsforward/dns.go
@@ -252,7 +252,7 @@ func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
 		return rc
 	}
 
-	dctx.isLocalClient = s.subnetDetector.IsLocallyServedNetwork(ip)
+	dctx.isLocalClient = s.privateNets.Contains(ip)
 
 	return rc
 }
@@ -374,7 +374,7 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
 	// Restrict an access to local addresses for external clients.  We also
 	// assume that all the DHCP leases we give are locally-served or at least
 	// don't need to be inaccessible externally.
-	if !s.subnetDetector.IsLocallyServedNetwork(ip) {
+	if !s.privateNets.Contains(ip) {
 		log.Debug("dns: addr %s is not from locally-served network", ip)
 
 		return resultCodeSuccess
@@ -481,7 +481,7 @@ func (s *Server) processLocalPTR(ctx *dnsContext) (rc resultCode) {
 	s.serverLock.RLock()
 	defer s.serverLock.RUnlock()
 
-	if !s.subnetDetector.IsLocallyServedNetwork(ip) {
+	if !s.privateNets.Contains(ip) {
 		return resultCodeSuccess
 	}
 
diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go
index 4fc87ccf..54104268 100644
--- a/internal/dnsforward/dns_test.go
+++ b/internal/dnsforward/dns_test.go
@@ -4,35 +4,41 @@ import (
 	"net"
 	"testing"
 
-	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
 	"github.com/AdguardTeam/AdGuardHome/internal/filtering"
 	"github.com/AdguardTeam/dnsproxy/proxy"
 	"github.com/AdguardTeam/dnsproxy/upstream"
+	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/miekg/dns"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
 func TestServer_ProcessDetermineLocal(t *testing.T) {
-	snd, err := aghnet.NewSubnetDetector()
-	require.NoError(t, err)
 	s := &Server{
-		subnetDetector: snd,
+		privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
 	}
 
 	testCases := []struct {
+		want  assert.BoolAssertionFunc
 		name  string
 		cliIP net.IP
-		want  bool
 	}{{
+		want:  assert.True,
 		name:  "local",
 		cliIP: net.IP{192, 168, 0, 1},
-		want:  true,
 	}, {
+		want:  assert.False,
 		name:  "external",
 		cliIP: net.IP{250, 249, 0, 1},
-		want:  false,
+	}, {
+		want:  assert.False,
+		name:  "invalid",
+		cliIP: net.IP{1, 2, 3, 4, 5},
+	}, {
+		want:  assert.False,
+		name:  "nil",
+		cliIP: nil,
 	}}
 
 	for _, tc := range testCases {
@@ -47,7 +53,7 @@ func TestServer_ProcessDetermineLocal(t *testing.T) {
 			}
 			s.processDetermineLocal(dctx)
 
-			assert.Equal(t, tc.want, dctx.isLocalClient)
+			tc.want(t, dctx.isLocalClient)
 		})
 	}
 }
diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go
index 2d32cfd2..48e344b3 100644
--- a/internal/dnsforward/dnsforward.go
+++ b/internal/dnsforward/dnsforward.go
@@ -74,7 +74,7 @@ type Server struct {
 	localDomainSuffix string
 
 	ipset          ipsetCtx
-	subnetDetector *aghnet.SubnetDetector
+	privateNets    netutil.SubnetSet
 	localResolvers *proxy.Proxy
 	sysResolvers   aghnet.SystemResolvers
 	recDetector    *recursionDetector
@@ -111,13 +111,13 @@ const defaultLocalDomainSuffix = ".lan."
 
 // DNSCreateParams are parameters to create a new server.
 type DNSCreateParams struct {
-	DNSFilter      *filtering.DNSFilter
-	Stats          stats.Stats
-	QueryLog       querylog.QueryLog
-	DHCPServer     dhcpd.ServerInterface
-	SubnetDetector *aghnet.SubnetDetector
-	Anonymizer     *aghnet.IPMut
-	LocalDomain    string
+	DNSFilter   *filtering.DNSFilter
+	Stats       stats.Stats
+	QueryLog    querylog.QueryLog
+	DHCPServer  dhcpd.ServerInterface
+	PrivateNets netutil.SubnetSet
+	Anonymizer  *aghnet.IPMut
+	LocalDomain string
 }
 
 // domainNameToSuffix converts a domain name into a local domain suffix.
@@ -161,7 +161,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
 		dnsFilter:         p.DNSFilter,
 		stats:             p.Stats,
 		queryLog:          p.QueryLog,
-		subnetDetector:    p.SubnetDetector,
+		privateNets:       p.PrivateNets,
 		localDomainSuffix: localDomainSuffix,
 		recDetector:       newRecursionDetector(recursionTTL, cachedRecurrentReqNum),
 		clientIDCache: cache.New(cache.Config{
@@ -315,7 +315,7 @@ func (s *Server) Exchange(ip net.IP) (host string, err error) {
 	}
 
 	resolver := s.internalProxy
-	if s.subnetDetector.IsLocallyServedNetwork(ip) {
+	if s.privateNets.Contains(ip) {
 		if !s.conf.UsePrivateRDNS {
 			return "", nil
 		}
diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go
index bc90d760..36761f41 100644
--- a/internal/dnsforward/dnsforward_test.go
+++ b/internal/dnsforward/dnsforward_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/AdguardTeam/dnsproxy/proxy"
 	"github.com/AdguardTeam/dnsproxy/upstream"
 	"github.com/AdguardTeam/golibs/errors"
+	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/AdguardTeam/golibs/timeutil"
 	"github.com/miekg/dns"
@@ -69,14 +70,11 @@ func createTestServer(
 	f := filtering.New(filterConf, filters)
 	f.SetEnabled(true)
 
-	snd, err := aghnet.NewSubnetDetector()
-	require.NoError(t, err)
-	require.NotNil(t, snd)
-
+	var err error
 	s, err = NewServer(DNSCreateParams{
-		DHCPServer:     &testDHCP{},
-		DNSFilter:      f,
-		SubnetDetector: snd,
+		DHCPServer:  &testDHCP{},
+		DNSFilter:   f,
+		PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
 	})
 	require.NoError(t, err)
 
@@ -770,16 +768,11 @@ func TestBlockedCustomIP(t *testing.T) {
 		Data: []byte(rules),
 	}}
 
-	snd, err := aghnet.NewSubnetDetector()
-	require.NoError(t, err)
-	require.NotNil(t, snd)
-
 	f := filtering.New(&filtering.Config{}, filters)
-	var s *Server
-	s, err = NewServer(DNSCreateParams{
-		DHCPServer:     &testDHCP{},
-		DNSFilter:      f,
-		SubnetDetector: snd,
+	s, err := NewServer(DNSCreateParams{
+		DHCPServer:  &testDHCP{},
+		DNSFilter:   f,
+		PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
 	})
 	require.NoError(t, err)
 
@@ -913,15 +906,10 @@ func TestRewrite(t *testing.T) {
 	f := filtering.New(c, nil)
 	f.SetEnabled(true)
 
-	snd, err := aghnet.NewSubnetDetector()
-	require.NoError(t, err)
-	require.NotNil(t, snd)
-
-	var s *Server
-	s, err = NewServer(DNSCreateParams{
-		DHCPServer:     &testDHCP{},
-		DNSFilter:      f,
-		SubnetDetector: snd,
+	s, err := NewServer(DNSCreateParams{
+		DHCPServer:  &testDHCP{},
+		DNSFilter:   f,
+		PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
 	})
 	require.NoError(t, err)
 
@@ -1028,15 +1016,10 @@ func (d *testDHCP) Leases(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) {
 func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {}
 
 func TestPTRResponseFromDHCPLeases(t *testing.T) {
-	snd, err := aghnet.NewSubnetDetector()
-	require.NoError(t, err)
-	require.NotNil(t, snd)
-
-	var s *Server
-	s, err = NewServer(DNSCreateParams{
-		DNSFilter:      filtering.New(&filtering.Config{}, nil),
-		DHCPServer:     &testDHCP{},
-		SubnetDetector: snd,
+	s, err := NewServer(DNSCreateParams{
+		DNSFilter:   filtering.New(&filtering.Config{}, nil),
+		DHCPServer:  &testDHCP{},
+		PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
 	})
 	require.NoError(t, err)
 
@@ -1105,16 +1088,11 @@ func TestPTRResponseFromHosts(t *testing.T) {
 	}, nil)
 	flt.SetEnabled(true)
 
-	var snd *aghnet.SubnetDetector
-	snd, err = aghnet.NewSubnetDetector()
-	require.NoError(t, err)
-	require.NotNil(t, snd)
-
 	var s *Server
 	s, err = NewServer(DNSCreateParams{
-		DHCPServer:     &testDHCP{},
-		DNSFilter:      flt,
-		SubnetDetector: snd,
+		DHCPServer:  &testDHCP{},
+		DNSFilter:   flt,
+		PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
 	})
 	require.NoError(t, err)
 
@@ -1227,9 +1205,7 @@ func TestServer_Exchange(t *testing.T) {
 	srv.conf.ResolveClients = true
 	srv.conf.UsePrivateRDNS = true
 
-	var err error
-	srv.subnetDetector, err = aghnet.NewSubnetDetector()
-	require.NoError(t, err)
+	srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
 
 	localIP := net.IP{192, 168, 1, 1}
 	testCases := []struct {
diff --git a/internal/dnsforward/filter_test.go b/internal/dnsforward/filter_test.go
index 84570bce..69dcf8f9 100644
--- a/internal/dnsforward/filter_test.go
+++ b/internal/dnsforward/filter_test.go
@@ -4,7 +4,6 @@ import (
 	"net"
 	"testing"
 
-	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
 	"github.com/AdguardTeam/AdGuardHome/internal/filtering"
 	"github.com/AdguardTeam/dnsproxy/proxy"
@@ -39,14 +38,10 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
 	f := filtering.New(&filtering.Config{}, filters)
 	f.SetEnabled(true)
 
-	snd, err := aghnet.NewSubnetDetector()
-	require.NoError(t, err)
-	require.NotNil(t, snd)
-
 	s, err := NewServer(DNSCreateParams{
-		DHCPServer:     &testDHCP{},
-		DNSFilter:      f,
-		SubnetDetector: snd,
+		DHCPServer:  &testDHCP{},
+		DNSFilter:   f,
+		PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
 	})
 	require.NoError(t, err)
 
diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go
index b7fb66a6..2b7cfd13 100644
--- a/internal/dnsforward/http.go
+++ b/internal/dnsforward/http.go
@@ -10,7 +10,6 @@ import (
 	"time"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
-	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/dnsproxy/proxy"
 	"github.com/AdguardTeam/dnsproxy/upstream"
 	"github.com/AdguardTeam/golibs/errors"
@@ -167,7 +166,7 @@ func (req *dnsConfig) checkBootstrap() (err error) {
 }
 
 // validate returns an error if any field of req is invalid.
-func (req *dnsConfig) validate(snd *aghnet.SubnetDetector) (err error) {
+func (req *dnsConfig) validate(privateNets netutil.SubnetSet) (err error) {
 	if req.Upstreams != nil {
 		err = ValidateUpstreams(*req.Upstreams)
 		if err != nil {
@@ -176,7 +175,7 @@ func (req *dnsConfig) validate(snd *aghnet.SubnetDetector) (err error) {
 	}
 
 	if req.LocalPTRUpstreams != nil {
-		err = ValidateUpstreamsPrivate(*req.LocalPTRUpstreams, snd)
+		err = ValidateUpstreamsPrivate(*req.LocalPTRUpstreams, privateNets)
 		if err != nil {
 			return fmt.Errorf("validating private upstream servers: %w", err)
 		}
@@ -224,7 +223,7 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	err = req.validate(s.subnetDetector)
+	err = req.validate(s.privateNets)
 	if err != nil {
 		aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
 
@@ -350,17 +349,6 @@ func IsCommentOrEmpty(s string) (ok bool) {
 	return len(s) == 0 || s[0] == '#'
 }
 
-// LocalNetChecker is used to check if the IP address belongs to a local
-// network.
-type LocalNetChecker interface {
-	// IsLocallyServedNetwork returns true if ip is contained in any of address
-	// registries defined by RFC 6303.
-	IsLocallyServedNetwork(ip net.IP) (ok bool)
-}
-
-// type check
-var _ LocalNetChecker = (*aghnet.SubnetDetector)(nil)
-
 // newUpstreamConfig validates upstreams and returns an appropriate upstream
 // configuration or nil if it can't be built.
 //
@@ -422,8 +410,8 @@ func stringKeysSorted(m map[string][]upstream.Upstream) (sorted []string) {
 // 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.  lnc must not be nil.
-func ValidateUpstreamsPrivate(upstreams []string, lnc LocalNetChecker) (err error) {
+// 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 err
@@ -444,7 +432,7 @@ func ValidateUpstreamsPrivate(upstreams []string, lnc LocalNetChecker) (err erro
 			continue
 		}
 
-		if !lnc.IsLocallyServedNetwork(subnet.IP) {
+		if !privateNets.Contains(subnet.IP) {
 			errs = append(
 				errs,
 				fmt.Errorf("arpa domain %q should point to a locally-served network", domain),
diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go
index 6e28ab41..f468f7ae 100644
--- a/internal/dnsforward/http_test.go
+++ b/internal/dnsforward/http_test.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/AdGuardHome/internal/filtering"
+	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -410,8 +411,7 @@ func TestValidateUpstreams(t *testing.T) {
 }
 
 func TestValidateUpstreamsPrivate(t *testing.T) {
-	snd, err := aghnet.NewSubnetDetector()
-	require.NoError(t, err)
+	ss := netutil.SubnetSetFunc(netutil.IsLocallyServed)
 
 	testCases := []struct {
 		name    string
@@ -452,7 +452,7 @@ func TestValidateUpstreamsPrivate(t *testing.T) {
 		set := []string{"192.168.0.1", tc.u}
 
 		t.Run(tc.name, func(t *testing.T) {
-			err = ValidateUpstreamsPrivate(set, snd)
+			err := ValidateUpstreamsPrivate(set, ss)
 			testutil.AssertErrorMsg(t, tc.wantErr, err)
 		})
 	}
diff --git a/internal/home/config.go b/internal/home/config.go
index 6ecb4ccb..c018f16e 100644
--- a/internal/home/config.go
+++ b/internal/home/config.go
@@ -126,6 +126,10 @@ type dnsConfig struct {
 	// ResolveClients enables and disables resolving clients with RDNS.
 	ResolveClients bool `yaml:"resolve_clients"`
 
+	// PrivateNets is the set of IP networks for which the private reverse DNS
+	// resolver should be used.
+	PrivateNets []string `yaml:"private_networks"`
+
 	// UsePrivateRDNS defines if the PTR requests for unknown addresses from
 	// locally-served networks should be resolved via private PTR resolvers.
 	UsePrivateRDNS bool `yaml:"use_private_ptr_resolvers"`
diff --git a/internal/home/dns.go b/internal/home/dns.go
index 4c27abd9..d676f6af 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -77,13 +77,36 @@ func initDNSServer() (err error) {
 	filterConf.HTTPRegister = httpRegister
 	Context.dnsFilter = filtering.New(&filterConf, nil)
 
+	var privateNets netutil.SubnetSet
+	switch len(config.DNS.PrivateNets) {
+	case 0:
+		// Use an optimized locally-served matcher.
+		privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
+	case 1:
+		var n *net.IPNet
+		n, err = netutil.ParseSubnet(config.DNS.PrivateNets[0])
+		if err != nil {
+			return fmt.Errorf("preparing the set of private subnets: %w", err)
+		}
+
+		privateNets = n
+	default:
+		var nets []*net.IPNet
+		nets, err = netutil.ParseSubnets(config.DNS.PrivateNets...)
+		if err != nil {
+			return fmt.Errorf("preparing the set of private subnets: %w", err)
+		}
+
+		privateNets = netutil.SliceSubnetSet(nets)
+	}
+
 	p := dnsforward.DNSCreateParams{
-		DNSFilter:      Context.dnsFilter,
-		Stats:          Context.stats,
-		QueryLog:       Context.queryLog,
-		SubnetDetector: Context.subnetDetector,
-		Anonymizer:     anonymizer,
-		LocalDomain:    config.DHCP.LocalDomainName,
+		DNSFilter:   Context.dnsFilter,
+		Stats:       Context.stats,
+		QueryLog:    Context.queryLog,
+		PrivateNets: privateNets,
+		Anonymizer:  anonymizer,
+		LocalDomain: config.DHCP.LocalDomainName,
 	}
 	if Context.dhcpServer != nil {
 		p.DHCPServer = Context.dhcpServer
@@ -133,7 +156,7 @@ func onDNSRequest(pctx *proxy.DNSContext) {
 	if config.DNS.ResolveClients && !ip.IsLoopback() {
 		Context.rdns.Begin(ip)
 	}
-	if !Context.subnetDetector.IsSpecialNetwork(ip) {
+	if !netutil.IsSpecialPurpose(ip) {
 		Context.whois.Begin(ip)
 	}
 }
@@ -360,10 +383,14 @@ func startDNSServer() error {
 
 	const topClientsNumber = 100 // the number of clients to get
 	for _, ip := range Context.stats.GetTopClientsIP(topClientsNumber) {
+		if ip == nil {
+			continue
+		}
+
 		if config.DNS.ResolveClients && !ip.IsLoopback() {
 			Context.rdns.Begin(ip)
 		}
-		if !Context.subnetDetector.IsSpecialNetwork(ip) {
+		if !netutil.IsSpecialPurpose(ip) {
 			Context.whois.Begin(ip)
 		}
 	}
diff --git a/internal/home/home.go b/internal/home/home.go
index 114ba4b6..7096be78 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -66,8 +66,6 @@ type homeContext struct {
 
 	updater *updater.Updater
 
-	subnetDetector *aghnet.SubnetDetector
-
 	// mux is our custom http.ServeMux.
 	mux *http.ServeMux
 
@@ -477,9 +475,6 @@ func run(args options, clientBuildFS fs.FS) {
 	Context.web, err = initWeb(args, clientBuildFS)
 	fatalOnError(err)
 
-	Context.subnetDetector, err = aghnet.NewSubnetDetector()
-	fatalOnError(err)
-
 	if !Context.firstRun {
 		err = initDNSServer()
 		fatalOnError(err)

From b9790f663a6a0951b684c80989ef35e4b55c5662 Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Mon, 21 Mar 2022 18:57:31 +0300
Subject: [PATCH 016/143] Pull request: 4409 fix icons height

Updates #4409

Squashed commit of the following:

commit 132073ccf00ba6eb6ddacfc82c8d2e01f3d4b011
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Mar 21 15:22:33 2022 +0300

    client: remove height

commit 29970f33e7af26e406c442510d626fc0cfdae0ce
Merge: 96b3abcf 77858586
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Mar 21 15:10:49 2022 +0300

    Merge branch 'master' into 4409-icon

commit 96b3abcfa4561da466cc53331b8f751d55f59351
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Mar 21 10:22:55 2022 +0300

    client: fix icons height
---
 client/src/components/ui/Icons.css | 1 -
 1 file changed, 1 deletion(-)

diff --git a/client/src/components/ui/Icons.css b/client/src/components/ui/Icons.css
index 73f4c864..929f4725 100644
--- a/client/src/components/ui/Icons.css
+++ b/client/src/components/ui/Icons.css
@@ -1,7 +1,6 @@
 .icons {
     display: inline-block;
     vertical-align: middle;
-    height: 100%;
 }
 
 .icon--24 {

From f8e45c13f3c0968b261235c34dc13d309c57de17 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 22 Mar 2022 15:21:03 +0300
Subject: [PATCH 017/143] Pull request: 3142 swap arp and rdns priority

Merge in DNS/adguard-home from 3142-fix-clients to master

Updates #3142.
Updates #3597.

Squashed commit of the following:

commit 4dcabedbfb1a4e4a0aaba588f708e4625442fce8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 22 15:13:15 2022 +0300

    all: imp log of changes

commit 481088d05eecac1109daf378e0b4d5f6b2cf099b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 22 14:36:44 2022 +0300

    all: swap arp and rdns priority
---
 CHANGELOG.md                      | 2 ++
 internal/dnsforward/dnsforward.go | 4 +++-
 internal/home/clients.go          | 6 ++----
 internal/home/rdns.go             | 8 +++-----
 4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d663d78e..bc986774 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,8 @@ and this project adheres to
 
 ### Changed
 
+- Reverse DNS now has a greater priority as the source of runtime clients'
+  informmation than ARP neighborhood.
 - Improved detection of runtime clients through more resilient ARP processing
   ([#3597]).
 - The TTL of responses served from the optimistic cache is now lowered to 10
diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go
index 48e344b3..a5b0098a 100644
--- a/internal/dnsforward/dnsforward.go
+++ b/internal/dnsforward/dnsforward.go
@@ -314,7 +314,7 @@ func (s *Server) Exchange(ip net.IP) (host string, err error) {
 		StartTime: time.Now(),
 	}
 
-	resolver := s.internalProxy
+	var resolver *proxy.Proxy
 	if s.privateNets.Contains(ip) {
 		if !s.conf.UsePrivateRDNS {
 			return "", nil
@@ -322,6 +322,8 @@ func (s *Server) Exchange(ip net.IP) (host string, err error) {
 
 		resolver = s.localResolvers
 		s.recDetector.add(*req)
+	} else {
+		resolver = s.internalProxy
 	}
 
 	if err = resolver.Resolve(ctx); err != nil {
diff --git a/internal/home/clients.go b/internal/home/clients.go
index d9aad40e..08d70bb9 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -53,8 +53,8 @@ type clientSource uint
 // Client sources.  The order determines the priority.
 const (
 	ClientSourceWHOIS clientSource = iota
-	ClientSourceRDNS
 	ClientSourceARP
+	ClientSourceRDNS
 	ClientSourceDHCP
 	ClientSourceHostsFile
 )
@@ -723,9 +723,7 @@ func (clients *clientsContainer) AddHost(ip net.IP, host string, src clientSourc
 	clients.lock.Lock()
 	defer clients.lock.Unlock()
 
-	ok = clients.addHostLocked(ip, host, src)
-
-	return ok, nil
+	return clients.addHostLocked(ip, host, src), nil
 }
 
 // addHostLocked adds a new IP-hostname pairing.  For internal use only.
diff --git a/internal/home/rdns.go b/internal/home/rdns.go
index cba748af..7a2d1bcd 100644
--- a/internal/home/rdns.go
+++ b/internal/home/rdns.go
@@ -125,14 +125,12 @@ func (r *RDNS) workerLoop() {
 			log.Debug("rdns: resolving %q: %s", ip, err)
 
 			continue
-		}
-
-		if host == "" {
+		} else if host == "" {
 			continue
 		}
 
-		// Don't handle any errors since AddHost doesn't return non-nil
-		// errors for now.
+		// Don't handle any errors since AddHost doesn't return non-nil errors
+		// for now.
 		_, _ = r.clients.AddHost(ip, host, ClientSourceRDNS)
 	}
 }

From b16b1d1d248db9cd8806f367fb41cdd45a120ccb Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 22 Mar 2022 18:28:43 +0300
Subject: [PATCH 018/143] Pull request: home: fix adding client

Merge in DNS/adguard-home from fix-arp-names to master

Updates #3597.

Squashed commit of the following:

commit b4737a342ab4c3685512bd1271a2dc9fa25256d0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 22 16:55:52 2022 +0300

    home: fix adding client
---
 internal/home/clients.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/internal/home/clients.go b/internal/home/clients.go
index 08d70bb9..9230e565 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -735,6 +735,7 @@ func (clients *clientsContainer) addHostLocked(ip net.IP, host string, src clien
 			return false
 		}
 
+		rc.Host = host
 		rc.Source = src
 	} else {
 		rc = &RuntimeClient{
@@ -829,7 +830,7 @@ func (clients *clientsContainer) addFromSystemARP() {
 
 	added := 0
 	for _, n := range ns {
-		if clients.addHostLocked(n.IP, "", ClientSourceARP) {
+		if clients.addHostLocked(n.IP, n.Name, ClientSourceARP) {
 			added++
 		}
 	}

From beb674ecbc0cf47510e62064fb03497650f58e42 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 23 Mar 2022 14:19:45 +0300
Subject: [PATCH 019/143] Pull request: filtering: fix qq regex legacy

Merge in DNS/adguard-home from qq-rule to master

Updates #3717.

Squashed commit of the following:

commit 1e2d50077067e5f95da645091686349ce9c8a6bc
Merge: 7290a1c4 b16b1d1d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 14:14:10 2022 +0300

    Merge branch 'master' into qq-rule

commit 7290a1c456a7f47e91cc9485f5e112b92cb595ba
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 18 20:36:17 2022 +0300

    filtering: fix qq regex legacy
---
 internal/filtering/blocked.go | 213 +++++++++++++++++++++-------------
 1 file changed, 132 insertions(+), 81 deletions(-)

diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index 3ecc2619..1d165cf4 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -19,9 +19,12 @@ type svc struct {
 // Keep in sync with:
 // client/src/helpers/constants.js
 // client/src/components/ui/Icons.js
-var serviceRulesArray = []svc{
-	{"whatsapp", []string{"||whatsapp.net^", "||whatsapp.com^"}},
-	{"facebook", []string{
+var serviceRulesArray = []svc{{
+	name:  "whatsapp",
+	rules: []string{"||whatsapp.net^", "||whatsapp.com^"},
+}, {
+	name: "facebook",
+	rules: []string{
 		"||facebook.com^",
 		"||facebook.net^",
 		"||fbcdn.net^",
@@ -33,9 +36,13 @@ var serviceRulesArray = []svc{
 		"||facebookcorewwwi.onion^",
 		"||fbcdn.com^",
 		"||fb.watch^",
-	}},
-	{"twitter", []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"}},
-	{"youtube", []string{
+	},
+}, {
+	name:  "twitter",
+	rules: []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"},
+}, {
+	name: "youtube",
+	rules: []string{
 		"||youtube.com^",
 		"||ytimg.com^",
 		"||youtu.be^",
@@ -43,35 +50,75 @@ var serviceRulesArray = []svc{
 		"||youtubei.googleapis.com^",
 		"||youtube-nocookie.com^",
 		"||youtube",
-	}},
-	{"twitch", []string{"||twitch.tv^", "||ttvnw.net^", "||jtvnw.net^", "||twitchcdn.net^"}},
-	{"netflix", []string{"||nflxext.com^", "||netflix.com^", "||nflximg.net^", "||nflxvideo.net^", "||nflxso.net^"}},
-	{"instagram", []string{"||instagram.com^", "||cdninstagram.com^"}},
-	{"snapchat", []string{
+	},
+}, {
+	name:  "twitch",
+	rules: []string{"||twitch.tv^", "||ttvnw.net^", "||jtvnw.net^", "||twitchcdn.net^"},
+}, {
+	name: "netflix",
+	rules: []string{
+		"||nflxext.com^",
+		"||netflix.com^",
+		"||nflximg.net^",
+		"||nflxvideo.net^",
+		"||nflxso.net^",
+	},
+}, {
+	name:  "instagram",
+	rules: []string{"||instagram.com^", "||cdninstagram.com^"},
+}, {
+	name: "snapchat",
+	rules: []string{
 		"||snapchat.com^",
 		"||sc-cdn.net^",
 		"||snap-dev.net^",
 		"||snapkit.co",
 		"||snapads.com^",
 		"||impala-media-production.s3.amazonaws.com^",
-	}},
-	{"discord", []string{"||discord.gg^", "||discordapp.net^", "||discordapp.com^", "||discord.com^", "||discord.media^"}},
-	{"ok", []string{"||ok.ru^"}},
-	{"skype", []string{"||skype.com^", "||skypeassets.com^"}},
-	{"vk", []string{"||vk.com^", "||userapi.com^", "||vk-cdn.net^", "||vkuservideo.net^"}},
-	{"origin", []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"}},
-	{"steam", []string{
+	},
+}, {
+	name: "discord",
+	rules: []string{
+		"||discord.gg^",
+		"||discordapp.net^",
+		"||discordapp.com^",
+		"||discord.com^",
+		"||discord.media^",
+	},
+}, {
+	name:  "ok",
+	rules: []string{"||ok.ru^"},
+}, {
+	name:  "skype",
+	rules: []string{"||skype.com^", "||skypeassets.com^"},
+}, {
+	name:  "vk",
+	rules: []string{"||vk.com^", "||userapi.com^", "||vk-cdn.net^", "||vkuservideo.net^"},
+}, {
+	name:  "origin",
+	rules: []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"},
+}, {
+	name: "steam",
+	rules: []string{
 		"||steam.com^",
 		"||steampowered.com^",
 		"||steamcommunity.com^",
 		"||steamstatic.com^",
 		"||steamstore-a.akamaihd.net^",
 		"||steamcdn-a.akamaihd.net^",
-	}},
-	{"epic_games", []string{"||epicgames.com^", "||easyanticheat.net^", "||easy.ac^", "||eac-cdn.com^"}},
-	{"reddit", []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"}},
-	{"mail_ru", []string{"||mail.ru^"}},
-	{"cloudflare", []string{
+	},
+}, {
+	name:  "epic_games",
+	rules: []string{"||epicgames.com^", "||easyanticheat.net^", "||easy.ac^", "||eac-cdn.com^"},
+}, {
+	name:  "reddit",
+	rules: []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"},
+}, {
+	name:  "mail_ru",
+	rules: []string{"||mail.ru^"},
+}, {
+	name: "cloudflare",
+	rules: []string{
 		"||cloudflare.com^",
 		"||cloudflare-dns.com^",
 		"||cloudflare.net^",
@@ -86,8 +133,10 @@ var serviceRulesArray = []svc{
 		"||warp.plus^",
 		"||1.1.1.1^",
 		"||dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion^",
-	}},
-	{"amazon", []string{
+	},
+}, {
+	name: "amazon",
+	rules: []string{
 		"||amazon.com^",
 		"||media-amazon.com^",
 		"||primevideo.com^",
@@ -114,8 +163,10 @@ var serviceRulesArray = []svc{
 		"||amazon.co.uk^",
 		"||createspace.com^",
 		"||aws",
-	}},
-	{"ebay", []string{
+	},
+}, {
+	name: "ebay",
+	rules: []string{
 		"||ebay.com^",
 		"||ebayimg.com^",
 		"||ebaystatic.com^",
@@ -141,8 +192,10 @@ var serviceRulesArray = []svc{
 		"||ebay.com.my^",
 		"||ebay.com.sg^",
 		"||ebay.co.uk^",
-	}},
-	{"tiktok", []string{
+	},
+}, {
+	name: "tiktok",
+	rules: []string{
 		"||tiktok.com^",
 		"||tiktokcdn.com^",
 		"||musical.ly^",
@@ -162,59 +215,55 @@ var serviceRulesArray = []svc{
 		"||bytedance.map.fastly.net^",
 		"||douyin.com^",
 		"||tiktokv.com^",
-	}},
-	{"vimeo", []string{
-		"||vimeo.com^",
-		"||vimeocdn.com^",
-		"*vod-adaptive.akamaized.net^",
-	}},
-	{"pinterest", []string{
-		"||pinterest.*^",
-		"||pinimg.com^",
-	}},
-	{"imgur", []string{
-		"||imgur.com^",
-	}},
-	{"dailymotion", []string{
-		"||dailymotion.com^",
-		"||dm-event.net^",
-		"||dmcdn.net^",
-	}},
-	{"qq", []string{
-		// block qq.com and subdomains excluding WeChat domains
-		"||qq.com^$denyallow=wx*.qq.com|weixin.qq.com",
+	},
+}, {
+	name:  "vimeo",
+	rules: []string{"||vimeo.com^", "||vimeocdn.com^", "*vod-adaptive.akamaized.net^"},
+}, {
+	name:  "pinterest",
+	rules: []string{"||pinterest.*^", "||pinimg.com^"},
+}, {
+	name:  "imgur",
+	rules: []string{"||imgur.com^"},
+}, {
+	name:  "dailymotion",
+	rules: []string{"||dailymotion.com^", "||dm-event.net^", "||dmcdn.net^"},
+}, {
+	name: "qq",
+	rules: []string{
+		// Block qq.com and subdomains excluding WeChat's domains.
+		"||qq.com^$denyallow=wx.qq.com|weixin.qq.com",
 		"||qqzaixian.com^",
-	}},
-	{"wechat", []string{
-		"||wechat.com^",
-		"||weixin.qq.com^",
-		"||wx.qq.com^",
-	}},
-	{"viber", []string{
-		"||viber.com^",
-	}},
-	{"weibo", []string{
-		"||weibo.com^",
-	}},
-	{"9gag", []string{
-		"||9cache.com^",
-		"||9gag.com^",
-	}},
-	{"telegram", []string{
-		"||t.me^",
-		"||telegram.me^",
-		"||telegram.org^",
-	}},
-	{"disneyplus", []string{
+	},
+}, {
+	name:  "wechat",
+	rules: []string{"||wechat.com^", "||weixin.qq.com^", "||wx.qq.com^"},
+}, {
+	name:  "viber",
+	rules: []string{"||viber.com^"},
+}, {
+	name:  "weibo",
+	rules: []string{"||weibo.com^"},
+}, {
+	name:  "9gag",
+	rules: []string{"||9cache.com^", "||9gag.com^"},
+}, {
+	name:  "telegram",
+	rules: []string{"||t.me^", "||telegram.me^", "||telegram.org^"},
+}, {
+	name: "disneyplus",
+	rules: []string{
 		"||disney-plus.net^",
 		"||disneyplus.com^",
 		"||disney.playback.edge.bamgrid.com^",
 		"||media.dssott.com^",
-	}},
-	{"hulu", []string{
-		"||hulu.com^",
-	}},
-	{"spotify", []string{
+	},
+}, {
+	name:  "hulu",
+	rules: []string{"||hulu.com^"},
+}, {
+	name: "spotify",
+	rules: []string{
 		"/_spotify-connect._tcp.local/",
 		"||spotify.com^",
 		"||scdn.co^",
@@ -226,13 +275,15 @@ var serviceRulesArray = []svc{
 		"||audio4-ak-spotify-com.akamaized.net^",
 		"||heads-ak-spotify-com.akamaized.net^",
 		"||heads4-ak-spotify-com.akamaized.net^",
-	}},
-	{"tinder", []string{
+	},
+}, {
+	name: "tinder",
+	rules: []string{
 		"||gotinder.com^",
 		"||tinder.com^",
 		"||tindersparks.com^",
-	}},
-}
+	},
+}}
 
 // convert array to map
 func initBlockedServices() {

From 2c33ab6a92c7066a26e68b6fff0fa04d8218f054 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 23 Mar 2022 14:36:17 +0300
Subject: [PATCH 020/143] Pull request: client: upd i18n

Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 0de5987fbc8da3d609a2d0f5ab34c07959ceb818
Merge: cea47b73 beb674ec
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Mar 23 14:19:57 2022 +0300

    Merge branch 'master' into upd-i18n

commit cea47b733dc32a3c63c2598dc8f20367b5a9753f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Mar 23 14:17:48 2022 +0300

    client: upd i18n
---
 client/src/__locales/be.json    | 113 ++++++++++----------
 client/src/__locales/cs.json    |  89 ++++++++--------
 client/src/__locales/da.json    |  53 +++++-----
 client/src/__locales/de.json    |  95 ++++++++---------
 client/src/__locales/es.json    |  89 ++++++++--------
 client/src/__locales/fi.json    |  79 +++++++-------
 client/src/__locales/fr.json    |  83 ++++++++-------
 client/src/__locales/hr.json    |  97 ++++++++---------
 client/src/__locales/hu.json    |  89 ++++++++--------
 client/src/__locales/id.json    |  91 ++++++++--------
 client/src/__locales/it.json    |  95 ++++++++---------
 client/src/__locales/ja.json    |  35 ++++---
 client/src/__locales/ko.json    |  87 ++++++++--------
 client/src/__locales/nl.json    |  91 ++++++++--------
 client/src/__locales/pl.json    |  91 ++++++++--------
 client/src/__locales/pt-br.json |  87 ++++++++--------
 client/src/__locales/pt-pt.json |  87 ++++++++--------
 client/src/__locales/ro.json    |  91 ++++++++--------
 client/src/__locales/ru.json    |  93 +++++++++--------
 client/src/__locales/si-lk.json | 162 +++++++++++++++++------------
 client/src/__locales/sk.json    |  81 ++++++++-------
 client/src/__locales/sl.json    |  83 ++++++++-------
 client/src/__locales/sr-cs.json | 178 +++++++++++++++++++++++---------
 client/src/__locales/sv.json    |  44 ++++----
 client/src/__locales/tr.json    | 125 +++++++++++-----------
 client/src/__locales/uk.json    | 104 ++++++++++---------
 client/src/__locales/vi.json    | 108 +++++++++++--------
 client/src/__locales/zh-cn.json |  87 ++++++++--------
 client/src/__locales/zh-hk.json |   6 ++
 client/src/__locales/zh-tw.json |  89 ++++++++--------
 30 files changed, 1462 insertions(+), 1240 deletions(-)

diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json
index db3d9e85..c01cbaa4 100644
--- a/client/src/__locales/be.json
+++ b/client/src/__locales/be.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Налады кліентаў",
-    "example_upstream_reserved": "Вы можаце паказаць DNS-сервер <0>для канкрэтнага дамена(аў)</0>",
-    "example_upstream_comment": "Вы можаце паказаць каментар",
+    "example_upstream_reserved": "upstream <0>для канкрэтных даменаў</0>;",
+    "example_upstream_comment": "каментар.",
     "upstream_parallel": "Ужыць адначасныя запыты да ўсіх сервераў для паскарэння апрацоўкі запыту",
     "parallel_requests": "Паралельныя запыты",
     "load_balancing": "Размеркаванне нагрузкі",
@@ -36,14 +36,14 @@
     "dhcp_ipv4_settings": "Налады DHCP IPv4",
     "dhcp_ipv6_settings": "Налады DHCP IPv6",
     "form_error_required": "Абавязковае поле",
-    "form_error_ip4_format": "Няслушны фармат IPv4",
+    "form_error_ip4_format": "Няслушны IPv4-адрас",
     "form_error_ip4_range_start_format": "Няслушны IPv4-адрас пачатку дыяпазону",
     "form_error_ip4_range_end_format": "Няслушны IPv4-адрас канца дыяпазону",
     "form_error_ip4_gateway_format": "Няслушны IPv4-адрас шлюза",
-    "form_error_ip6_format": "Няслушны фармат IPv6",
-    "form_error_ip_format": "Няслушны фармат IP-адраса",
-    "form_error_mac_format": "Некарэктны фармат MAC",
-    "form_error_client_id_format": "Няслушны фармат ID кліента",
+    "form_error_ip6_format": "Няслушны IPv6-адрас",
+    "form_error_ip_format": "Няслушны IP-адрас",
+    "form_error_mac_format": "Некарэктны MAC-адрас",
+    "form_error_client_id_format": "ClientID павінен утрымліваць толькі лічбы, малыя літары і злучкі",
     "form_error_server_name": "Няслушнае імя сервера",
     "form_error_subnet": "Падсетка «{{cidr}}» не ўтрымвае IP-адраса «{{ip}}»",
     "form_error_positive": "Павінна быць больш 0",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Імя хаста",
     "dhcp_table_expires": "Мінае",
     "dhcp_warning": "Калі вы ўсё адно хочаце ўключыць DHCP-сервер, пераканайцеся, што ў сеціве больш няма актыўных DHCP-сервераў. Інакш гэта можа зламаць доступ у сеціва для падлучаных прылад!",
-    "dhcp_error": "Мы не змаглі вызначыць наяўнасць іншых DHCP-сервераў у сеціве.",
+    "dhcp_error": "AdGuard Home не можа вызначыць, ці ёсць у сетцы іншы актыўны DHCP-сервер",
     "dhcp_static_ip_error": "Для таго, каб выкарыстоўваць DHCP-сервер, павінен быць усталяваны статычны IP-адрас. Мы не змаглі вызначыць, ці выкарыстоўвае гэты інтэрфейс сеціва статычны IP-адрас. Калі ласка, усталюйце яго ручна.",
     "dhcp_dynamic_ip_found": "Ваша сістэма выкарыстоўвае дынамічны IP-адрас для інтэрфейсу <0>{{interfaceName}}</0>. Каб выкарыстоўваць DHCP-сервер трэба ўсталяваць статычны IP-адрас. Ваш бягучы IP-адрас – <0>{{ipAddress}}</0>. Мы аўтаматычна ўсталюем яго як статычны, калі вы націснеце кнопку Ўключыць DHCP.",
     "dhcp_lease_added": "Статычная арэнда \"{{key}}\" паспяхова дададзена",
@@ -165,10 +165,10 @@
     "enabled_filtering_toast": "Фільтрацыя ўкл.",
     "disabled_safe_browsing_toast": "Бяспечная навігацыя выключана",
     "enabled_safe_browsing_toast": "Бяспечная навігацыя ўключана",
-    "disabled_parental_toast": "Бацькоўскі кантроль выкл.",
-    "enabled_parental_toast": "Бацькоўскі кантроль укл.",
-    "disabled_safe_search_toast": "Бяспечны пошук выкл.",
-    "enabled_save_search_toast": "Бяспечны пошук укл.",
+    "disabled_parental_toast": "Адключаны бацькоўскі кантроль",
+    "enabled_parental_toast": "Уключаны бацькоўскі кантроль",
+    "disabled_safe_search_toast": "Адключаны бяспечны пошук",
+    "enabled_save_search_toast": "Уключаны бяспечны пошук",
     "enabled_table_header": "УКЛ.",
     "name_table_header": "Імя",
     "list_url_table_header": "URL-адрас спіса",
@@ -202,19 +202,21 @@
     "custom_filter_rules_hint": "Уводзьце па адным правіле на радок. Вы можаце выкарыстоўваць правілы блакавання ці сінтаксіс файлаў hosts.",
     "system_host_files": "Сістэмныя hosts-файлы",
     "examples_title": "Прыклады",
-    "example_meaning_filter_block": "заблакаваць доступ да дамена example.org і ўсім яго паддаменам",
-    "example_meaning_filter_whitelist": "адблакаваць доступ да дамена example.org і ўсім яго паддаменам",
-    "example_meaning_host_block": "Зараз AdGuard Home верне 127.0.0.1 для дамена example.org (але не для яго паддаменаў).",
-    "example_comment": "! Так можна дадаваць апісанне",
-    "example_comment_meaning": "каментар",
-    "example_comment_hash": "# І вось так таксама",
-    "example_regex_meaning": "блакуе доступ да даменаў, што адпавядаюць <0>зададзенаму рэгулярнаму выразу</0>",
-    "example_upstream_regular": "звычайны DNS (наўзверх UDP)",
-    "example_upstream_dot": "зашыфраваны <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-па-над-TLS</a>",
-    "example_upstream_doh": "зашыфраваны <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a>",
-    "example_upstream_doq": "зашыфраваны <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "вы можаце выкарыстоўваць <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Stamps</a> для <a href='https://dnscrypt.info/' target='_blank'>DNSCrypt</a> ці <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a> рэзалвераў",
-    "example_upstream_tcp": "звычайны DNS (наўзверх TCP)",
+    "example_meaning_filter_block": "заблакаваць доступ да example.org і ўсім яго паддаменам;",
+    "example_meaning_filter_whitelist": "адблакаваць доступ да example.org і ўсім яго паддаменам;",
+    "example_meaning_host_block": "адказаць 127.0.0.1 для example.org (але не для яго паддаменаў);",
+    "example_comment": "! Так можна дадаваць апісанне.",
+    "example_comment_meaning": "каментар;",
+    "example_comment_hash": "# І вось так таксама.",
+    "example_regex_meaning": "блакаваць доступ да даменаў, якія адпавядаюць зададзенаму рэгулярнаму выразу.",
+    "example_upstream_regular": "звычайны DNS (наўзверх UDP);",
+    "example_upstream_udp": "звычайны DNS (праз UDP, імя хаста);",
+    "example_upstream_dot": "зашыфраваны <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "зашыфраваны <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "зашыфраваны <0>DNS-over-QUIC (эксперыментальны)</0>;",
+    "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt</1> ці <2>DNS-over-HTTPS</2> рэзалвераў;",
+    "example_upstream_tcp": "звычайны DNS (наўзверх TCP);",
+    "example_upstream_tcp_hostname": "звычайны DNS (праз TCP, імя хаста);",
     "all_lists_up_to_date_toast": "Усе спісы ўжо абноўлены",
     "updated_upstream_dns_toast": "Upstream DNS-серверы абноўлены",
     "dns_test_ok_toast": "Паказаныя серверы DNS працуюць карэктна",
@@ -259,7 +261,7 @@
     "query_log_strict_search": "Ужывайце падвойныя двукоссі для строгага пошуку",
     "query_log_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання запытаў? Пры скарачэнні інтэрвалу дадзеныя могуць быць згублены",
     "anonymize_client_ip": "Ананімізацыя IP-адрасы кліента",
-    "anonymize_client_ip_desc": "Не захоўвайце поўны IP-адрас кліента ў часопісах і статыстыцы",
+    "anonymize_client_ip_desc": "Не захоўвайце поўныя IP-адрасы гэтых удзельнікаў у часопісах або статыстыцы",
     "dns_config": "Налады DNS-сервера",
     "dns_cache_config": "Налада кэша DNS",
     "dns_cache_config_desc": "Тут можна наладзіць кэш DNS",
@@ -275,9 +277,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "Ідэнтыфікатар кліента",
-    "client_id_placeholder": "Увядзіце ідэнтыфікатар кліента",
-    "client_id_desc": "Розныя кліенты могуць ідэнтыфікавацца па адмысловым ідэнтыфікатары кліента. <a>Тут</a> вы можаце даведацца больш пра ідэнтыфікацыю кліентаў.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Увядзіце ClientID",
+    "client_id_desc": "Кліенты могуць ідэнтыфікавацца па ClientID. <a>Тут</a> вы можаце даведацца больш пра ідэнтыфікацыю кліентаў.",
     "download_mobileconfig_doh": "Спампаваць .mobileconfig для DNS-over-HTTPS",
     "download_mobileconfig_dot": "Спампаваць .mobileconfig для DNS-over-TLS",
     "download_mobileconfig": "Загрузіць файл канфігурацыі",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Увядзіце rate limit",
     "rate_limit": "Ограничение скорости",
     "edns_enable": "Уключыць адпраўленне EDNS Client Subnet",
-    "edns_cs_desc": "Калі ўключыць гэту опцыю, AdGuard Home будзе адпраўляць падсеціва кліентаў на DNS-сервера.",
+    "edns_cs_desc": "Дадайце опцыю кліенцкай падсеткі EDNS (ECS) да запытаў вышэй па плыні і запісвайце значэнні, адпраўленыя кліентамі, у журнал запытаў.",
     "rate_limit_desc": "Абмежаванне на колькасць запытаў у секунду для кожнага кліента (0 — неабмежавана)",
     "blocking_ipv4_desc": "IP-адрас, што вяртаецца пры блакаванню A-запыту",
     "blocking_ipv6_desc": "IP-адрас, што вяртаецца пры блакаванню AAAA-запыту",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Інтэрфейс сеціва",
     "install_settings_port": "Порт",
     "install_settings_interface_link": "Ваш ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны па наступных адрасах:",
-    "form_error_port": "Увядзіце карэктны порт",
+    "form_error_port": "Увядзіце карэктны нумар порта",
     "install_settings_dns": "DNS-сервер",
     "install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне DNS-сервера на адным з наступных адрасоў:",
     "install_settings_all_interfaces": "Усе інтэрфейсы",
@@ -334,12 +336,12 @@
     "install_devices_router_list_4": "Вы не можаце ўсталяваць уласны DNS-сервер на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе DNS-сервераў для вашай пэўнай мадэлі маршрутызатара.",
     "install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню \"Пуск\" ці праз пошук Windows.",
     "install_devices_windows_list_2": "Перайдзіце ў \"Сеціва і інтэрнэт\", а потым у \"Цэнтр кіравання сеціва і агульным доступам\"",
-    "install_devices_windows_list_3": "У левым боку экрана знайдзіце \"Змена параметраў адаптара\" і клікніце па ім.",
-    "install_devices_windows_list_4": "Вылучыце ваша актыўнае падлучэнне, потым клікніце па ім правай клавішай мышы і выберыце \"Уласцівасці\".",
+    "install_devices_windows_list_3": "У левым боку экрана клікніце «Змена параметраў адаптара».",
+    "install_devices_windows_list_4": "Пстрыкніце правай кнопкай мышы ваша актыўнае злучэнне і абярыце Уласцівасці.",
     "install_devices_windows_list_5": "Знайдзіце ў спісе пункт \"IP версіі 4 (TCP/IPv4)\", вылучыце яго і потым ізноў націсніце \"Уласцівасці\".",
     "install_devices_windows_list_6": "Абярыце \"Выкарыстаць наступныя адрасы DNS-сервераў\" і ўвядзіце адрас AdGuard Home.",
-    "install_devices_macos_list_1": "Клікніце па абразку Apple і перайдзіце ў «Сістэмныя налады».",
-    "install_devices_macos_list_2": "Клікніце па іконцы «Сеціва».",
+    "install_devices_macos_list_1": "Клікніце па абразку Apple і перайдзіце ў Сістэмныя налады.",
+    "install_devices_macos_list_2": "Клікніце па іконцы Сеціва.",
     "install_devices_macos_list_3": "Абярыце першае падлучэнне ў спісе і націсніце кнопку «Дадаткова».",
     "install_devices_macos_list_4": "Абярыце ўкладку «DNS» і дадайце адрасы AdGuard Home.",
     "install_devices_android_list_1": "У меню кіравання націсніце абразок «Налады».",
@@ -356,7 +358,7 @@
     "open_dashboard": "Адкрыць Панэль кіравання",
     "install_saved": "Паспяхова захавана",
     "encryption_title": "Шыфраванне",
-    "encryption_desc": "Падтрымка шыфравання (HTTPS/TLS) для DNS і ўэб-інтэрфейсу адміністравання",
+    "encryption_desc": "Падтрымка шыфравання (HTTPS/QUIC/TLS) для DNS і ўэб-інтэрфейсу адміністравання",
     "encryption_config_saved": "Налады шыфравання захаваны",
     "encryption_server": "Імя сервера",
     "encryption_server_enter": "Увядзіце ваша даменавае імя",
@@ -367,7 +369,7 @@
     "encryption_https_desc": "Калі порт HTTPS наладжаны, ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны праз HTTPS, а таксама DNS-over-HTTPS сервер будзе даступны па шляху '/dns-query'.",
     "encryption_dot": "Порт DNS-over-TLS",
     "encryption_dot_desc": "Калі гэты порт наладжаны, AdGuard Home запусціць DNS-over-TLS-сервер на гэтаму порту.",
-    "encryption_doq": "Порт DNS-over-QUIC",
+    "encryption_doq": "Порт DNS-over-QUIC (эксперыментальны)",
     "encryption_doq_desc": "Калі гэты порт наладжаны, AdGuard Home запусціць сервер DNS-over-QUIC на гэтым порце. Гэта эксперыментальна і можа быць ненадзейна. Апроч таго, не так шмат кліентаў падтрымвае гэты спосаб цяпер.",
     "encryption_certificates": "Сертыфікаты",
     "encryption_certificates_desc": "Для выкарыстання шыфравання вам трэба падаць валідны ланцужок SSL-сертыфікатаў для вашага дамена. Вы можаце атрымаць дармовы сертыфікат на <0>{{link}}</0> ці вы можаце купіць яго ў аднаго з давераных Цэнтраў Сертыфікацыі.",
@@ -388,8 +390,8 @@
     "encryption_reset": "Вы ўпэўнены, што хочаце скінуць налады шыфравання?",
     "topline_expiring_certificate": "Ваш SSL-сертыфікат хутка мінае. Абновіце <0>Налады шыфравання</0>.",
     "topline_expired_certificate": "Ваш SSL-сертыфікат мінуў. Абновіце <0>Налады шыфравання</0>.",
-    "form_error_port_range": "Увядзіце значэнне порта з інтэрвалу 80-65535",
-    "form_error_port_unsafe": "Гэта небяспечны порт",
+    "form_error_port_range": "Увядзіце нумар порта з інтэрвалу 80-65535",
+    "form_error_port_unsafe": "Небяспечны порт",
     "form_error_equal": "Не павінны быць роўныя",
     "form_error_password": "Паролі не супадаюць",
     "reset_settings": "Скінуць налады",
@@ -397,15 +399,16 @@
     "setup_guide": "Інструкцыя па наладзе",
     "dns_addresses": "Адрасы DNS",
     "dns_start": "DNS-сервер запускаецца",
-    "dns_status_error": "Памылка пры атрыманні стану DNS-сервера",
+    "dns_status_error": "Памылка праверкі стану DNS-сервера",
     "down": "Уніз",
     "fix": "Выправіць",
     "dns_providers": "<0>Спіс вядомых DNS-правайдараў</0> на выбар.",
     "update_now": "Абнавіць цяпер",
     "update_failed": "Памылка аўто-абнаўлення. Калі ласка, <a>кіруйцеся інструкцыі</a> для абнаўлення ручна.",
+    "manual_update": "Калі ласка, <a>кіруйцеся інструкцыі</a> для абнаўлення ручна.",
     "processing_update": "Калі ласка, пачакайце, AdGuard Home абнаўляецца",
-    "clients_title": "Кліенты",
-    "clients_desc": "Наладзьце прылады, што выкарыстоўваюць AdGuard Home",
+    "clients_title": "Пастаянныя кліенты",
+    "clients_desc": "Наладзьце пастаянныя запісы кліентаў для прылад, падлучаных да AdGuard Home",
     "settings_global": "Глабальныя",
     "settings_custom": "Свае",
     "table_client": "Кліент",
@@ -416,7 +419,7 @@
     "client_edit": "Рэдагаваць кліента",
     "client_identifier": "Ідэнтыфікатар",
     "ip_address": "IP-адрас",
-    "client_identifier_desc": "Кліенты могуць быць ідэнтыфікаваны па IP-адрасе, CIDR ці MAC-адрасу. Звярніце ўвагу, што выкарыстанне MAC як ідэнтыфікатара магчыма, толькі калі AdGuard Home таксама з'яўляецца і <0>DHCP-серверам</0>",
+    "client_identifier_desc": "Кліентаў можна ідэнтыфікаваць па іх IP-адрасе, CIDR, MAC-адрасе або ClientID (можна выкарыстоўваць для DoT/DoH/DoQ). Даведайцеся больш пра тое, як ідэнтыфікаваць кліентаў <0>тут</0>.",
     "form_enter_ip": "Увядзіце IP",
     "form_enter_subnet_ip": "Увядзіце IP-адрас у падсеткі «{{cidr}}»",
     "form_enter_mac": "Увядзіце MAC",
@@ -432,13 +435,13 @@
     "client_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць кліента \"{{key}}\"?",
     "list_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць гэты спіс?",
     "auto_clients_title": "Кліенты (runtime)",
-    "auto_clients_desc": "Дадзеныя пра кліентаў, якія скарыстаюць AdGuard Home, але не захоўваюцца ў наладах",
+    "auto_clients_desc": "Прылады, якіх няма ў спісе пастаянных кліентаў, якія ўсё яшчэ могуць выкарыстоўваць AdGuard Home",
     "access_title": "Налады доступу",
-    "access_desc": "Тут вы можаце наладзіць правілы доступу да DNS-серверу AdGuard Home.",
+    "access_desc": "Тут вы можаце наладзіць правілы доступу да DNS-серверу AdGuard Home",
     "access_allowed_title": "Дазволеныя кліенты",
-    "access_allowed_desc": "Спіс CIDR- ці IP-адрасоў. Калі ён наладжаны, AdGuard Home будзе прымаць запыты толькі з гэтых IP-адрасоў.",
+    "access_allowed_desc": "Спіс CIDR, IP-адрасоў або <a>ClientID</a>. Калі ў гэтым спісе ёсць запісы, AdGuard Home будзе прымаць запыты толькі ад гэтых кліентаў.",
     "access_disallowed_title": "Забароненыя кліенты",
-    "access_disallowed_desc": "Спіс CIDR- ці IP-адрасоў. Калі ён наладжаны, AdGuard Home будзе ігнараваць запыты з гэтых IP-адрасоў.",
+    "access_disallowed_desc": "Спіс CIDR, IP-адрасоў або <a>ClientID</a>. Калі ў гэтым спісе ёсць запісы, AdGuard Home выдаліць запыты ад гэтых кліентаў. Гэта поле ігнаруецца, калі ёсць запісы ў Дазволеныя кліенты.",
     "access_blocked_title": "Заблакаваныя дамены",
     "access_blocked_desc": "Не блытайце гэта з фільтрамі. AdGuard Home будзе ігнараваць DNS-запыты з гэтымі даменамі.",
     "access_settings_saved": "Налады доступу паспяхова захаваны",
@@ -499,6 +502,7 @@
     "interval_days": "{{count}} дзень",
     "interval_days_plural": "{{count}} дзён",
     "domain": "Дамен",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Адказ",
     "filter_added_successfully": "Спіс паспяхова дададзены",
@@ -531,7 +535,7 @@
     "netname": "Назва сеціва",
     "network": "Сеціва",
     "descr": "Апісанне",
-    "whois": "Хто",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Даведайцеся больш</0> пра стварэнне ўласных спісаў блакавання хастоў.",
     "blocked_by_response": "Заблакавана па CNAME ці IP у адказе",
     "blocked_by_cname_or_ip": "Заблакавана з дапамогай CNAME ці IP",
@@ -551,7 +555,7 @@
     "autofix_warning_list": "Будуць выконвацца наступныя заданні: <0>Дэактываваць сістэмны DNSStubListener</0> <0>Усталяваць адрас сервера DNS на 127.0.0.1</0> <0>Стварыць сімвалічную спасылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Спыніць DNSStubListener (перазагрузіць сістэмную службу)</0>.",
     "autofix_warning_result": "У выніку ўсе DNS-запыты ад вашай сістэмы будуць па змаўчанні апрацоўвацца AdGuard Home.\n",
     "tags_title": "Тэгі",
-    "tags_desc": "Вы можаце выбраць тэгі, якія адпавядаюць кліенту. Тэгі могуць быць улучаны ў правілы фільтрацыі і дазволяць вам ужываць іх больш дакладна. <0>Даведацца больш</0>.",
+    "tags_desc": "Вы можаце выбраць тэгі, якія адпавядаюць кліенту. Уключыце тэгі ў правілы фільтрацыі, каб прымяняць іх больш дакладна. <0>Даведацца больш</0>.",
     "form_select_tags": "Выбраць тэгі кліента",
     "check_title": "Праверыць фільтрацыю",
     "check_desc": "Праверыць фільтрацыю імя хаста",
@@ -593,18 +597,18 @@
     "allowed": "Дазволены",
     "filtered": "Адфільтраваныя",
     "rewritten": "Перапісаныя",
-    "safe_search": "Уключыць Бяспечны пошук",
+    "safe_search": "Бяспечны пошук",
     "blocklist": "Чорны спіс",
     "milliseconds_abbreviation": "мс",
     "cache_size": "Памер кэша",
-    "cache_size_desc": "Памер кэша DNS (у байтах)",
+    "cache_size_desc": "Памер кэша DNS (у байтах).",
     "cache_ttl_min_override": "Перавызначыць мінімальны TTL",
     "cache_ttl_max_override": "Перавызначыць максімальны TTL",
     "enter_cache_size": "Увядзіце памер кэша (байты)",
     "enter_cache_ttl_min_override": "Увядзіце мінімальны TTL (секунды)",
     "enter_cache_ttl_max_override": "Увядзіце максімальны TTL (секунды)",
-    "cache_ttl_min_override_desc": "Перавызначыць TTL-значэнне (мінімальнае), атрыманае з upstream-сервера",
-    "cache_ttl_max_override_desc": "Усталюйце максімальнае TTL-значэнне (секунды) для запісаў у кэшы DNS",
+    "cache_ttl_min_override_desc": "Пашырыць кароткія значэнні часу жыцця (секунды), атрыманыя ад сервера вышэй па плыні пры кэшаванні адказаў DNS.",
+    "cache_ttl_max_override_desc": "Усталюйце максімальнае TTL-значэнне (секунды) для запісаў у кэшы DNS.",
     "ttl_cache_validation": "Мінімальнае значэнне TTL кэша павінна быць менш ці роўна максімальнаму значэнню",
     "cache_optimistic": "Аптымістычнае кэшаванне",
     "cache_optimistic_desc": "Прымусьце AdGuard Home адказваць з кэша, нават калі тэрмін дзеяння запісаў скончыўся, а таксама паспрабуйце абнавіць іх.",
@@ -626,5 +630,6 @@
     "use_saved_key": "Скарыстаць захаваны раней ключ",
     "parental_control": "Бацькоўскі кантроль",
     "safe_browsing": "Бяспечны інтэрнэт",
-    "served_from_cache": "{{value}} <i>(атрымана з кэша)</i>"
+    "served_from_cache": "{{value}} <i>(атрымана з кэша)</i>",
+    "form_error_password_length": "Пароль павінен быць не менш за {{value}} сімвалаў"
 }
diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json
index 8d89f204..26759394 100644
--- a/client/src/__locales/cs.json
+++ b/client/src/__locales/cs.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Konfigurace DHCP byla úspěšně uložena",
     "dhcp_ipv4_settings": "Nastavení DHCP IPv4",
     "dhcp_ipv6_settings": "Nastavení DHCP IPv6",
-    "form_error_required": "Povinné pole.",
-    "form_error_ip4_format": "Neplatná adresa IPv4.",
-    "form_error_ip4_range_start_format": "Neplatná adresa IPv4 na začátku rozsahu.",
-    "form_error_ip4_range_end_format": "Neplatná adresa IPv4 na konci rozsahu.",
-    "form_error_ip4_gateway_format": "Neplatná adresa IPv4 brány.",
-    "form_error_ip6_format": "Neplatná adresa IPv6.",
-    "form_error_ip_format": "Neplatná IP adresa.",
-    "form_error_mac_format": "Neplatná adresa MAC.",
-    "form_error_client_id_format": "ID klienta musí obsahovat pouze čísla, malá písmena a spojovníky.",
-    "form_error_server_name": "Neplatný název serveru.",
-    "form_error_subnet": "Podsíť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\".",
-    "form_error_positive": "Musí být větší než 0.",
-    "out_of_range_error": "Musí být mimo rozsah \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Musí být menší než začátek rozsahu.",
-    "greater_range_start_error": "Musí být větší než začátek rozsahu.",
-    "greater_range_end_error": "Musí být větší než konec rozsahu.",
-    "subnet_error": "Adresy musí být v jedné podsíti.",
-    "gateway_or_subnet_invalid": "Neplatná maska podsítě.",
+    "form_error_required": "Povinné pole",
+    "form_error_ip4_format": "Neplatná adresa IPv4",
+    "form_error_ip4_range_start_format": "Neplatná adresa IPv4 na začátku rozsahu",
+    "form_error_ip4_range_end_format": "Neplatná adresa IPv4 na konci rozsahu",
+    "form_error_ip4_gateway_format": "Neplatná adresa IPv4 brány",
+    "form_error_ip6_format": "Neplatná adresa IPv6",
+    "form_error_ip_format": "Neplatná IP adresa",
+    "form_error_mac_format": "Neplatná adresa MAC",
+    "form_error_client_id_format": "ID klienta musí obsahovat pouze čísla, malá písmena a spojovníky",
+    "form_error_server_name": "Neplatný název serveru",
+    "form_error_subnet": "Podsíť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
+    "form_error_positive": "Musí být větší než 0",
+    "out_of_range_error": "Musí být mimo rozsah \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Musí být menší než začátek rozsahu",
+    "greater_range_start_error": "Musí být větší než začátek rozsahu",
+    "greater_range_end_error": "Musí být větší než konec rozsahu",
+    "subnet_error": "Adresy musí být v jedné podsíti",
+    "gateway_or_subnet_invalid": "Neplatná maska podsítě",
     "dhcp_form_gateway_input": "IP brána",
     "dhcp_form_subnet_input": "Maska podsítě",
     "dhcp_form_range_title": "Rozsah IP adres",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Název hostitele",
     "dhcp_table_expires": "Vyprší",
     "dhcp_warning": "Pokud přesto chcete server DHCP povolit, ujistěte se, že ve Vaší síti není žádný jiný aktivní server DHCP, protože by to mohlo narušit připojení k Internetu pro zařízení v síti!",
-    "dhcp_error": "AdGuard Home nemohl určit, zda je v síti jiný aktivní server DHCP.",
+    "dhcp_error": "AdGuard Home nemohl určit, zda je v síti jiný aktivní server DHCP",
     "dhcp_static_ip_error": "Pro použití serveru DHCP musí být nastavena statická IP adresa. AdGuard Home nemohl zjistit, zda je toto síťové rozhraní nakonfigurováno pomocí statické adresy IP. Nastavte prosím statickou IP adresu ručně.",
     "dhcp_dynamic_ip_found": "Váš systém používá konfiguraci dynamické IP adresy pro rozhraní <0>{{interfaceName}}</0>. Pro použití serveru DHCP musí být nastavena statická IP adresa. Vaše aktuální IP adresa je <0>{{ipAddress}}</0>. AdGuard Home automaticky nastaví tuto IP adresu jako statickou, pokud stisknete tlačítko \"Zapnout DHCP server\".",
     "dhcp_lease_added": "Statický pronájem \"{{key}}\" byl úspěšně přidán",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Vyberte seznamy povolených",
     "enter_valid_blocklist": "Zadejte platnou adresu URL na seznam blokovaných.",
     "enter_valid_allowlist": "Zadejte platnou adresu URL na seznam povolených.",
-    "form_error_url_format": "Neplatný formát URL.",
-    "form_error_url_or_path_format": "Neplatná URL nebo úplná cesta k seznamu.",
+    "form_error_url_format": "Neplatný formát URL",
+    "form_error_url_or_path_format": "Neplatná URL nebo úplná cesta k seznamu",
     "custom_filter_rules": "Vlastní pravidla filtrování",
     "custom_filter_rules_hint": "Na každý řádek vložte jedno pravidlo. Můžete použít buď pravidla blokování reklam nebo syntaxe hostitelských souborů.",
     "system_host_files": "Systémové soubory hostitelů",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Také komentář.",
     "example_regex_meaning": "blokuje přístup doménám, které vyhovují regulárnímu výrazu.",
     "example_upstream_regular": "obvyklý DNS (přes UDP);",
+    "example_upstream_udp": "obvyklý DNS (skrze UDP, název hostitele);",
     "example_upstream_dot": "šifrovaný <0>DNS skrze TLS</0>;",
     "example_upstream_doh": "šifrovaný <0>DNS skrze HTTPS</0>;",
     "example_upstream_doq": "šifrovaný <0>DNS skrze QUIC</0> (experimentální);",
     "example_upstream_sdns": "<0>DNS razítka</0> pro <1>DNSCrypt</1> nebo <2>DNS skrze HTTPS</2> řešitele;",
     "example_upstream_tcp": "obvyklý DNS (přes TCP);",
+    "example_upstream_tcp_hostname": "obvyklý DNS (skrze TCP, název hostitele);",
     "all_lists_up_to_date_toast": "Všechny seznamy jsou již aktuální",
     "updated_upstream_dns_toast": "Odchozí servery byly úspěšně uloženy",
     "dns_test_ok_toast": "Specifikované DNS servery pracují správně",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Pro striktní vyhledávání použijte dvojité uvozovky",
     "query_log_retention_confirm": "Opravdu chcete změnit uchovávání protokolu dotazů? Pokud snížíte hodnotu intervalu, některá data budou ztracena",
     "anonymize_client_ip": "Anonymizovat IP klienta",
-    "anonymize_client_ip_desc": "Neukládat úplnou IP adresu klienta do protokolů a statistik.",
+    "anonymize_client_ip_desc": "Neukládat úplnou IP adresu klienta do protokolů a statistik",
     "dns_config": "Konfigurace DNS serveru",
     "dns_cache_config": "Konfigurace mezipaměti DNS",
-    "dns_cache_config_desc": "Zde můžete konfigurovat mezipaměť DNS.",
+    "dns_cache_config_desc": "Zde můžete konfigurovat mezipaměť DNS",
     "blocking_mode": "Režim blokování",
     "default": "Výchozí",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Zadejte rychlostní limit",
     "rate_limit": "Rychlostní limit",
     "edns_enable": "Povolit klientskou podsíť EDNS",
-    "edns_cs_desc": "Odeslat podsítě klientů na servery DNS.",
+    "edns_cs_desc": "Přidá možnost podsítě klienta EDNS (ECS) do odchozích požadavků a zaznamá hodnoty odeslané klienty do protokolu dotazů.",
     "rate_limit_desc": "Počet požadavků za sekundu, které smí jeden klient provádět (0: neomezeno)",
     "blocking_ipv4_desc": "IP adresa, která se má vrátit v případě blokovaného požadavku typu A",
     "blocking_ipv6_desc": "IP adresa, která se má vrátit v případě blokovaného požadavku typu AAAA",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Síťové rozhraní",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Vaše administrátorské webové rozhraní AdGuard Home bude k dispozici na těchto adresách:",
-    "form_error_port": "Zadejte platné číslo portu.",
+    "form_error_port": "Zadejte platné číslo portu",
     "install_settings_dns": "DNS server",
     "install_settings_dns_desc": "Budete muset nakonfigurovat Vaše zařízení nebo router, aby používali DNS server na následujících adresách:",
     "install_settings_all_interfaces": "Všechna rozhraní",
@@ -356,7 +358,7 @@
     "open_dashboard": "Otevřít hlavní panel",
     "install_saved": "Úspěšně uloženo",
     "encryption_title": "Šifrování",
-    "encryption_desc": "Podpora šifrování (HTTPS/TLS) pro webové rozhraní DNS i administrátora.",
+    "encryption_desc": "Podpora šifrování (HTTPS/TLS) pro webové rozhraní DNS i administrátora",
     "encryption_config_saved": "Konfigurace šifrování byla uložena",
     "encryption_server": "Název serveru",
     "encryption_server_enter": "Zadejte název domény",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Zde můžete nakopírovat/vložit soukromý klíč k certifikátu PEM.",
     "encryption_enable": "Povolit šifrování (HTTPS, DNS skrze HTTPS a DNS skrze TLS)",
     "encryption_enable_desc": "Pokud je šifrování zapnuto, administrátorské rozhraní AdGuard Home bude pracovat skrze HTTPS a DNS server bude naslouchat požadavky přes DNS skrze HTTPS a DNS skrze TLS.",
-    "encryption_chain_valid": "Certifikační řetězec je platný.",
-    "encryption_chain_invalid": "Certifikační řetězec je neplatný.",
-    "encryption_key_valid": "Toto je platný {{type}} osobní klíč.",
-    "encryption_key_invalid": "Toto je neplatný {{type}} osobní klíč.",
+    "encryption_chain_valid": "Certifikační řetězec je platný",
+    "encryption_chain_invalid": "Certifikační řetězec je neplatný",
+    "encryption_key_valid": "Toto je platný {{type}} osobní klíč",
+    "encryption_key_invalid": "Toto je neplatný {{type}} osobní klíč",
     "encryption_subject": "Subjekt",
     "encryption_issuer": "Vydavatel",
     "encryption_hostnames": "Názvy hostitelů",
     "encryption_reset": "Opravdu chcete obnovit nastavení šifrování?",
     "topline_expiring_certificate": "Váš SSL certifikát brzy vyprší. Aktualizujte <0>Nastavení šifrování</0>.",
     "topline_expired_certificate": "Váš SSL certifikát vypršel. Aktualizujte <0>Nastavení šifrování</0>.",
-    "form_error_port_range": "Zadejte číslo portu v rozmezí 80-65535.",
-    "form_error_port_unsafe": "Toto není bezpečný port.",
-    "form_error_equal": "Nesmí se shodovat.",
-    "form_error_password": "Heslo se neshoduje.",
+    "form_error_port_range": "Zadejte číslo portu v rozmezí 80-65535",
+    "form_error_port_unsafe": "Nezabezpečený port",
+    "form_error_equal": "Nesmí se shodovat",
+    "form_error_password": "Heslo se neshoduje",
     "reset_settings": "Resetovat nastavení",
     "update_announcement": "AdGuard Home {{version}} je nyní k dispozici! <0>Klikněte zde<0> pro více informací.",
     "setup_guide": "Průvodce nastavením",
     "dns_addresses": "Adresy DNS",
     "dns_start": "Spouští se DNS server",
-    "dns_status_error": "Chyba při kontrole stavu DNS serveru.",
+    "dns_status_error": "Chyba při kontrole stavu DNS serveru",
     "down": "Dolů",
     "fix": "Opravit",
     "dns_providers": "Zde je <0>seznam známých poskytovatelů DNS</0>, z nichž si můžete vybrat.",
@@ -406,7 +408,7 @@
     "manual_update": "Prosím <a>následujte tyto kroky</a> a aktualizujte ručně.",
     "processing_update": "Čekejte prosím, AdGuard Home se aktualizuje",
     "clients_title": "Stálí klienti",
-    "clients_desc": "Konfigurace stálých klientských záznamů pro zařízení připojená k AdGuard Home.",
+    "clients_desc": "Konfigurace stálých klientských záznamů pro zařízení připojená k AdGuard Home",
     "settings_global": "Globální",
     "settings_custom": "Vlastní",
     "table_client": "Klient",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Opravdu chcete odstranit klienta \"{{key}}\"?",
     "list_confirm_delete": "Opravdu chcete smazat tento seznam?",
     "auto_clients_title": "Spuštění klienti",
-    "auto_clients_desc": "Zařízení, která nejsou na seznamu stálých klientů, a mohou nadále používat AdGuard Home.",
+    "auto_clients_desc": "Zařízení, která nejsou na seznamu stálých klientů, a mohou nadále používat AdGuard Home",
     "access_title": "Nastavení přístupu",
-    "access_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home.",
+    "access_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home",
     "access_allowed_title": "Povolení klienti",
     "access_allowed_desc": "Seznam CIDR, IP adres nebo <a>ID klientů</a>. Pokud tento seznam obsahuje položky, AdGuard Home bude přijímat požadavky pouze od těchto klientů.",
     "access_disallowed_title": "Blokovaní klienti",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Přesměrování DNS",
     "form_domain": "Zadejte doménu",
     "form_answer": "Zadejte IP adresu nebo název domény",
-    "form_error_domain_format": "Neplatný formát domény.",
-    "form_error_answer_format": "Neplatný formát odpovědi.",
+    "form_error_domain_format": "Neplatný formát domény",
+    "form_error_answer_format": "Neplatný formát odpovědi",
     "configure": "Konfigurovat",
     "main_settings": "Hlavní nastavení",
     "block_services": "Blokovat specifické služby",
@@ -500,6 +502,7 @@
     "interval_days": "Dny: {{count}}",
     "interval_days_plural": "Dny: {{count}}",
     "domain": "Doména",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Odpověď",
     "filter_added_successfully": "Seznam byl úspěšně přidán",
@@ -507,7 +510,7 @@
     "filter_updated": "Seznam byl úspěšně aktualizován",
     "statistics_configuration": "Konfigurace statistik",
     "statistics_retention": "Uchovávání statistik",
-    "statistics_retention_desc": "Pokud hodnotu intervalu snížíte, některá data budou ztracena.",
+    "statistics_retention_desc": "Pokud hodnotu intervalu snížíte, některá data budou ztracena",
     "statistics_clear": " Vyčistit statistiky",
     "statistics_clear_confirm": "Opravdu chcete vyčistit statistiky?",
     "statistics_retention_confirm": "Opravdu chcete změnit uchovávání statistik? Pokud snížíte hodnotu intervalu, některá data budou ztracena",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Zadejte maximální hodnotu TTL (v sekundách)",
     "cache_ttl_min_override_desc": "Prodlužte nejkratší hodnotu TTL (v sekundách) obdrženou z odchozího serveru při ukládání DNS odpovědí do mezipaměti.",
     "cache_ttl_max_override_desc": "Nastavte maximální hodnotu TTL (v sekundách) pro položky v mezipaměti DNS.",
-    "ttl_cache_validation": "Minimální přepis TTL mezipaměti musí být menší nebo roven maximální hodnotě.",
+    "ttl_cache_validation": "Minimální přepis TTL mezipaměti musí být menší nebo roven maximální hodnotě",
     "cache_optimistic": "Optimistické ukládání do mezipaměti",
     "cache_optimistic_desc": "Nechte AdGuard Home odpovědět z mezipaměti, i když už platnost položek skončila. Také se je pokuste obnovit.",
     "filter_category_general": "Obecné",
@@ -628,5 +631,5 @@
     "parental_control": "Rodičovská ochrana",
     "safe_browsing": "Bezpečné prohlížení",
     "served_from_cache": "{{value}} <i>(převzato z mezipaměti)</i>",
-    "form_error_password_length": "Heslo musí být alespoň {{value}} znaků dlouhé."
+    "form_error_password_length": "Heslo musí být alespoň {{value}} znaků dlouhé"
 }
diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json
index fb67607d..1b12ad06 100644
--- a/client/src/__locales/da.json
+++ b/client/src/__locales/da.json
@@ -37,21 +37,21 @@
     "dhcp_ipv6_settings": "DHCP IPv6-indstillinger",
     "form_error_required": "Obligatorisk felt",
     "form_error_ip4_format": "Ugyldig IPv4-adresse",
-    "form_error_ip4_range_start_format": "Ugyldig IPv4-startadresse for området.",
-    "form_error_ip4_range_end_format": "Ugyldig IPv4-slutadresse for området.",
-    "form_error_ip4_gateway_format": "Ugyldig IPv4 gateway-adresse.",
-    "form_error_ip6_format": "Ugyldig IPv6-adresse.",
-    "form_error_ip_format": "Ugyldig IP-adresse.",
-    "form_error_mac_format": "Ugyldig MAC-adresse.",
-    "form_error_client_id_format": "KlientID må kun indeholde cifre, minuskler og bindestreger.",
-    "form_error_server_name": "Ugyldigt servernavn.",
-    "form_error_subnet": "Undernet \"{{cidr}}\" indeholder ikke IP-adressen \"{{ip}}\".",
-    "form_error_positive": "Skal være større end 0.",
-    "out_of_range_error": "Skal være uden for området \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Skal være mindre end starten på området.",
-    "greater_range_start_error": "Skal være større end starten på ​​området.",
-    "greater_range_end_error": "Skal være større end områdeslutning.",
-    "subnet_error": "Adresser ska være i ét undernet.",
+    "form_error_ip4_range_start_format": "Ugyldig IPv4-startadresse for området",
+    "form_error_ip4_range_end_format": "Ugyldig IPv4-slutadresse for området",
+    "form_error_ip4_gateway_format": "Ugyldig IPv4 gateway-adresse",
+    "form_error_ip6_format": "Ugyldig IPv6-adresse",
+    "form_error_ip_format": "Ugyldig IP-adresse",
+    "form_error_mac_format": "Ugyldig MAC-adresse",
+    "form_error_client_id_format": "KlientID må kun indeholde cifre, minuskler og bindestreger",
+    "form_error_server_name": "Ugyldigt servernavn",
+    "form_error_subnet": "Undernet \"{{cidr}}\" indeholder ikke IP-adressen \"{{ip}}\"",
+    "form_error_positive": "Skal være større end 0",
+    "out_of_range_error": "Skal være uden for området \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Skal være mindre end starten på området",
+    "greater_range_start_error": "Skal være større end starten på ​​området",
+    "greater_range_end_error": "Skal være større end områdeslutning",
+    "subnet_error": "Adresser ska være i ét undernet",
     "gateway_or_subnet_invalid": "Undernetmaske ugyldig",
     "dhcp_form_gateway_input": "Gateway IP",
     "dhcp_form_subnet_input": "Undernetmaske",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Værtsnavn",
     "dhcp_table_expires": "Udløber",
     "dhcp_warning": "Vil du alligevel aktivere DHCP-serveren, så sørg for at der ikke er nogen anden aktiv DHCP-server på dit netværk, da dette kan ødelægge Internetkonnektiviteten for netværksenhederne!",
-    "dhcp_error": "AdGuard Home kunne ikke afgøres, om der findes en anden DHCP-server på netværket.",
+    "dhcp_error": "AdGuard Home kunne ikke afgøres, om der findes en anden DHCP-server på netværket",
     "dhcp_static_ip_error": "For at kunne bruge DHCP-serveren skal der opsættes en statisk IP-adresse. Da det ikke kunne afgøres, om denne netværksinterface er opsat vha. en statisk IP-adresse, bedes du opsætte en manuelt.",
     "dhcp_dynamic_ip_found": "Dit system bruger en dynamisk IP-adresseopsætning til interface <0>{{interfaceName}}</0>. For at kunne bruge DHCP-serveren skal en statisk IP-adresse indstilles. Din aktuelle IP-adresse er <0>{{ipAddress}}</0>. AdGuard Home vil automatisk indstille denne IP-adresse som din statiske hvis du trykker på knappen \"Aktivér DHCP-server\".",
     "dhcp_lease_added": "Statisk lease \"{{key}}\" tilføjet",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Også en kommentar.",
     "example_regex_meaning": "blokér adgang til domæner matchernde det angivne regulære udtryk",
     "example_upstream_regular": "almindelig DNS (over UDP)",
+    "example_upstream_udp": "almindelig DNS (over UDP, værtsnavn);",
     "example_upstream_dot": "krypteret <0>DNS-over-TLS</0>",
     "example_upstream_doh": "krypteret <0>DNS-over-HTTPS</0>",
     "example_upstream_doq": "krypteret <0>DNS-over-QUIC</0>(eksperimentel);",
     "example_upstream_sdns": "<0>DNS Stamps</0> til <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-opløsere;",
     "example_upstream_tcp": "almindelig DNS (over TCP)",
+    "example_upstream_tcp_hostname": "almindelig DNS (over TCP, værtsnavn);",
     "all_lists_up_to_date_toast": "Alle lister er allerede opdaterede",
     "updated_upstream_dns_toast": "Upstream-servere er gemt",
     "dns_test_ok_toast": "Angivne DNS-servere fungerer korrekt",
@@ -259,7 +261,7 @@
     "query_log_strict_search": "Brug dobbelt anførselstegn til stringent søgning",
     "query_log_retention_confirm": "Sikker på, at du vil ændre forespørgselsloggens opbevaringperiode? Mindskes intervalværdien, mistes data",
     "anonymize_client_ip": "Anonymisér klient-IP",
-    "anonymize_client_ip_desc": "Gem ikke klientens fulde IP-adresse i logfiler og statistikker",
+    "anonymize_client_ip_desc": "Gem ikke fuld klient IP-adresse i logfiler eller statistikker",
     "dns_config": "DNS-serveropsætning",
     "dns_cache_config": "DNS-cacheopsætning",
     "dns_cache_config_desc": "Hér kan DNS-cache opsættes.",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Angiv hyppighedsgrænse",
     "rate_limit": "Hyppighedsgrænse",
     "edns_enable": "Aktivér EDNS-klientundernet",
-    "edns_cs_desc": "Send klienters undernet til DNS-serverne.",
+    "edns_cs_desc": "Tilføj indstillingen EDNS Client Subnet (ECS) til upstream-forespørgsler og log de af klienterne sendte værdier i forespørgselsloggen.",
     "rate_limit_desc": "Antallet af forespørgsler pr. sekund tilladt pr. klient (værdien 0 = ubegrænset)",
     "blocking_ipv4_desc": "Returneret IP-adresse for en blokeret A-forespørgsel",
     "blocking_ipv6_desc": "Returneret IP-adresse for en blokeret AAAA-forespørgsel",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Overvågningsgrænseflade",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Din AdGuard Home admin webgrænseflade vil være tilgængelig på flg. adresser:",
-    "form_error_port": "Angiv gyldig portnummer",
+    "form_error_port": "Angiv gyldigt portnummer",
     "install_settings_dns": "DNS-server",
     "install_settings_dns_desc": "Du skal opsætte dine enheder eller router til at bruge DNS-serveren på flg. adresser:",
     "install_settings_all_interfaces": "Alle grænseflader",
@@ -356,7 +358,7 @@
     "open_dashboard": "Åbn Dashboard",
     "install_saved": "Gemt",
     "encryption_title": "Kryptering",
-    "encryption_desc": "Krypteringsunderstøttelse (HTTPS/TLS) til både DNS og admin-webgrænseflade.",
+    "encryption_desc": "Krypteringsunderstøttelse (HTTPS/TLS) til både DNS og admin-webgrænseflade",
     "encryption_config_saved": "Krypteringsopsætning gemt",
     "encryption_server": "Servernavn",
     "encryption_server_enter": "Angiv dit domænenavn",
@@ -389,7 +391,7 @@
     "topline_expiring_certificate": "Dit SSL-certifikat er ved at udløbe. Opdatér <0>Krypteringsindstillinger</0>.",
     "topline_expired_certificate": "Dit SSL-certifikat er udløbet. Opdatér <0>Krypteringsindstillinger</0>.",
     "form_error_port_range": "Angiv portnummer i intervallet 80-65535",
-    "form_error_port_unsafe": "Dette er en usikker port",
+    "form_error_port_unsafe": "Ikke-sikker port",
     "form_error_equal": "Må ikke svare til.",
     "form_error_password": "Adgangskoder matcher ikke.",
     "reset_settings": "Nulstil indstillinger",
@@ -406,7 +408,7 @@
     "manual_update": "<a>Følg disse trin</a> for at opdatere manuelt.",
     "processing_update": "Vent venligst, AdGuard Home bliver opdateret",
     "clients_title": "Blivende klienter",
-    "clients_desc": "Opsæt blivende klientposter for enheder tilsluttet AdGuard Home.",
+    "clients_desc": "Opsæt permanente klientposter for enheder tilsluttet AdGuard Home",
     "settings_global": "Global",
     "settings_custom": "Tilpasset",
     "table_client": "Klient",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Sikker på, at du vil slette klient \"{{key}}\"?",
     "list_confirm_delete": "Sikker på, at du vil slette denne liste?",
     "auto_clients_title": "Klienter (runtime)",
-    "auto_clients_desc": "Enheder, som ikke er på listen over Blivende klienter, som stadig kan bruge AdGuard Home.",
+    "auto_clients_desc": "Enheder, som ikke er på listen over Permanente klienter, kan stadig bruge AdGuard Home",
     "access_title": "Adgangsindstillinger",
-    "access_desc": "Her kan du opsætte adgangsregler for AdGuard Home DNS-serveren.",
+    "access_desc": "Her kan adgangsregler for AdGuard Home DNS-serveren opsættes",
     "access_allowed_title": "Tilladte klienter",
     "access_allowed_desc": "En liste over CIDR'er, IP-adresser eller <a>KlientID'er</a>. Har listen poster, accepterer AdGuard Home kun forespørgsler fra disse klienter.",
     "access_disallowed_title": "Ikke tilladte klienter",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} dag",
     "interval_days_plural": "{{count}} dage",
     "domain": "Domæne",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Svar",
     "filter_added_successfully": "Listen er tilføjet",
@@ -517,7 +520,7 @@
     "interval_hours_plural": "{{count}} timer",
     "filters_configuration": "Filteropsætninger",
     "filters_enable": "Aktivér filtre",
-    "filters_interval": "Filtrenes opdateringsinterval",
+    "filters_interval": "Filteropdateringsinterval",
     "disabled": "Deaktiveret",
     "username_label": "Brugernavn",
     "username_placeholder": "Angiv brugernavn",
diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json
index fc9f5878..6f8ff803 100644
--- a/client/src/__locales/de.json
+++ b/client/src/__locales/de.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP-Konfiguration erfolgreich gespeichert",
     "dhcp_ipv4_settings": "DHCP-IPv4-Einstellungen",
     "dhcp_ipv6_settings": "DHCP-IPv6-Einstellungen",
-    "form_error_required": "Pflichtfeld.",
-    "form_error_ip4_format": "Ungültige IPv4-Adresse.",
-    "form_error_ip4_range_start_format": "Ungültige IPv4-Adresse des Bereichsbeginns.",
-    "form_error_ip4_range_end_format": "Ungültige IPv4-Adresse des Bereichsendes.",
-    "form_error_ip4_gateway_format": "Ungültige IPv4-Adresse des Gateways.",
-    "form_error_ip6_format": "Ungültige IPv6-Adresse.",
-    "form_error_ip_format": "Ungültige IP-Adresse.",
-    "form_error_mac_format": "Ungültige MAC-Adresse.",
-    "form_error_client_id_format": "Client-ID muss nur Zahlen, Kleinbuchstaben und Bindestriche enthalten.",
-    "form_error_server_name": "Ungültiger Servername.",
-    "form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“.",
-    "form_error_positive": "Muss größer als 0 sein.",
-    "out_of_range_error": "Muss außerhalb des Bereichs „{{start}}“-„{{end}}“ liegen.",
-    "lower_range_start_error": "Muss niedriger als der Bereichsbeginn sein.",
-    "greater_range_start_error": "Muss größer als der Bereichsbeginn sein.",
-    "greater_range_end_error": "Muss größer als das Bereichsende sein.",
-    "subnet_error": "Die Adressen müssen innerhalb eines Subnetzes liegen.",
-    "gateway_or_subnet_invalid": "Ungültige Subnetzmaske.",
+    "form_error_required": "Pflichtfeld",
+    "form_error_ip4_format": "Ungültige IPv4-Adresse",
+    "form_error_ip4_range_start_format": "Ungültiger Bereichsbeginn der IPv4-Adresse",
+    "form_error_ip4_range_end_format": "Ungültiges Bereichsende der IPv4-Adresse",
+    "form_error_ip4_gateway_format": "Ungültige IPv4-Adresse des Gateways",
+    "form_error_ip6_format": "Ungültige IPv6-Adresse",
+    "form_error_ip_format": "Ungültige IP-Adresse",
+    "form_error_mac_format": "Ungültige MAC-Adresse",
+    "form_error_client_id_format": "Client-ID muss nur Zahlen, Kleinbuchstaben und Bindestriche enthalten",
+    "form_error_server_name": "Ungültiger Servername",
+    "form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“",
+    "form_error_positive": "Muss größer als 0 sein",
+    "out_of_range_error": "Muss außerhalb des Bereichs „{{start}}“-„{{end}}“ liegen",
+    "lower_range_start_error": "Muss niedriger als der Bereichsbeginn sein",
+    "greater_range_start_error": "Muss größer als der Bereichsbeginn sein",
+    "greater_range_end_error": "Muss größer als das Bereichsende sein",
+    "subnet_error": "Die Adressen müssen innerhalb eines Subnetzes liegen",
+    "gateway_or_subnet_invalid": "Ungültige Subnetzmaske",
     "dhcp_form_gateway_input": "Gateway-IP",
     "dhcp_form_subnet_input": "Subnetz-Maske",
     "dhcp_form_range_title": "Bereich von IP-Adressen",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Hostname",
     "dhcp_table_expires": "Gültig bis",
     "dhcp_warning": "Wenn Sie den DHCP-Server trotzdem aktivieren möchten, stellen Sie sicher, dass sich in Ihrem Netzwerk kein anderer aktiver DHCP-Server befindet. Andernfalls kann es bei angeschlossenen Geräten zu einem Ausfall des Internets kommen!",
-    "dhcp_error": "AdGuard Home konnte nicht ermitteln, ob es einen anderen aktiven DHCP-Server im Netzwerk gibt.",
+    "dhcp_error": "AdGuard Home konnte keinen anderen aktiven DHCP-Server im Netzwerk feststellen",
     "dhcp_static_ip_error": "Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Es konnte nicht ermittelt werden, ob diese Netzwerkschnittstelle mit statischer IP-Adresse konfiguriert ist. Bitte legen Sie eine statische IP-Adresse manuell fest.",
     "dhcp_dynamic_ip_found": "Ihr System verwendet die dynamische Konfiguration der IP-Adresse für die Schnittstelle <0>{{interfaceName}}</0>. Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Ihre aktuelle IP-Adresse ist <0>{{ipAddress}}</0>. Diese IP-Adresse wird automatisch als statisch festgelegt, sobald Sie auf die Schaltfläche „DHCP-Server aktivieren“ klicken.",
     "dhcp_lease_added": "Statische Zuweisung „{{key}}“ erfolgreich hinzugefügt",
@@ -196,7 +196,7 @@
     "choose_allowlist": "Freigabeliste wählen",
     "enter_valid_blocklist": "Gültige Webadresse zur Sperrliste eingeben.",
     "enter_valid_allowlist": "Gültige Webadresse zur Freigabeliste eingeben.",
-    "form_error_url_format": "Ungültiges URL-Format.",
+    "form_error_url_format": "Ungültiges URL-Format",
     "form_error_url_or_path_format": "Ungültige URL oder absoluter Pfad der Liste",
     "custom_filter_rules": "Benutzerdefinierte Filterregeln",
     "custom_filter_rules_hint": "Geben Sie pro Zeile eine Regel ein. Sie können entweder Werbefilterregeln oder Host-Datei-Syntax verwenden.",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Auch ein Kommentar.",
     "example_regex_meaning": "Zugriff auf die Domains sperren, die dem angegebenen regulären Ausdruck entsprechen.",
     "example_upstream_regular": "reguläres DNS (over UDP);",
+    "example_upstream_udp": "normales DNS (über UDP, Hostname);",
     "example_upstream_dot": "verschlüsseltes <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "verschlüsseltes <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "verschlüsseltes <0>DNS-over-QUIC</0> (experimentell);",
     "example_upstream_sdns": "<0>DNS-Stempel</0> für <1>DNSCrypt</1> oder <2>DNS-over-HTTPS</2> Resolver;",
     "example_upstream_tcp": "reguläres DNS (over TCP);",
+    "example_upstream_tcp_hostname": "normales DNS (über TCP, Hostname);",
     "all_lists_up_to_date_toast": "Alle Listen sind bereits auf dem neuesten Stand",
     "updated_upstream_dns_toast": "Upstream-Server erfolgreich gespeichert",
     "dns_test_ok_toast": "Angegebene DNS-Server arbeiten ordnungsgemäß",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Doppelte Anführungszeichen für die strikte Suche verwenden",
     "query_log_retention_confirm": "Möchten Sie die Aufbewahrung des Abfrageprotokolls wirklich ändern? Wenn Sie den Zeitabstand verringern, gehen einige Daten verloren.",
     "anonymize_client_ip": "Client-IP anonymisieren",
-    "anonymize_client_ip_desc": "Vollständige IP-Adresse des Clients nicht in Protokollen und Statistiken speichern.",
+    "anonymize_client_ip_desc": "Vollständige IP-Adresse des Clients nicht in Protokollen und Statistiken speichern",
     "dns_config": "DNS-Serverkonfiguration",
     "dns_cache_config": "Konfiguration des DNS-Cache",
-    "dns_cache_config_desc": "Hier können Sie den DNS-Cache konfigurieren.",
+    "dns_cache_config_desc": "Hier können Sie den DNS-Cache konfigurieren",
     "blocking_mode": "Sperrmodus",
     "default": "Standard",
     "nxdomain": "NXDomain",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Begrenzungswert eingeben",
     "rate_limit": "Begrenzungswert",
     "edns_enable": "EDNS Client Subnetz aktivieren",
-    "edns_cs_desc": "Senden Sie die Subnetze der Clients an die DNS-Server.",
+    "edns_cs_desc": "Die Option EDNS Client Subnetz (ECS) zu Upstream-Anfragen hinzufügen und die von Clients gesendeten Werte protokollieren.",
     "rate_limit_desc": "Die Anzahl der Anfragen pro Sekunde, die ein einzelner Client stellen darf. Das Setzen auf 0 bedeutet keine Begrenzung.",
     "blocking_ipv4_desc": "IP-Adresse, die für eine gesperrte A-Anfrage zurückgegeben werden soll",
     "blocking_ipv6_desc": "IP-Adresse, die für eine gesperrte AAAA-Anfrage zurückgegeben werden soll",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Netzwerk-Schnittstelle\n",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Ihre AdGuard Home Admin-Weboberfläche ist unter den folgenden Adressen verfügbar:",
-    "form_error_port": "Geben Sie eine gültige Portnummer ein.",
+    "form_error_port": "Geben Sie eine gültige Portnummer ein",
     "install_settings_dns": "DNS-Server",
     "install_settings_dns_desc": "Sie müssen Ihre Geräte oder Ihren Router so konfigurieren, dass er den DNS-Server unter den folgenden Adressen verwendet:",
     "install_settings_all_interfaces": "Alle Schnittstellen",
@@ -356,7 +358,7 @@
     "open_dashboard": "Übersicht öffnen",
     "install_saved": "Erfolgreich gespeichert",
     "encryption_title": "Verschlüsselung",
-    "encryption_desc": "Verschlüsselungsunterstützung (HTTPS/TLS) für DNS- und Admin-Weboberfläche.",
+    "encryption_desc": "Unterstützung von Verschlüsselung (HTTPS/QUIC/TLS) für DNS- und Admin-Weboberfläche",
     "encryption_config_saved": "Verschlüsselungskonfiguration gespeichert",
     "encryption_server": "Servername",
     "encryption_server_enter": "Domain-Namen eingeben",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Kopieren Sie Ihren PEM-codierten privaten Schlüssel für Ihr Zertifikat und fügen Sie ihn hier ein.",
     "encryption_enable": "Verschlüsselung aktivieren (HTTPS, DNS-over-HTTPS und DNS-over-TLS)",
     "encryption_enable_desc": "Wenn die Verschlüsselung aktiviert ist, funktioniert die AdGuard Home Admin-Oberfläche über HTTPS, und der DNS-Server wartet auf Anfragen über DNS-over-HTTPS und DNS-over-TLS.",
-    "encryption_chain_valid": "Zertifikatskette ist gültig.",
-    "encryption_chain_invalid": "Zertifikatskette ist ungültig.",
-    "encryption_key_valid": "Dies ist ein gültiger {{type}} privater Schlüssel.",
-    "encryption_key_invalid": "Dies ist ein ungültiger {{type}} privater Schlüssel.",
+    "encryption_chain_valid": "Zertifikatskette ist gültig",
+    "encryption_chain_invalid": "Zertifikatskette ist ungültig",
+    "encryption_key_valid": "Das ist ein gültiger {{type}} privater Schlüssel",
+    "encryption_key_invalid": "Das ist ein ungültiger {{type}} privater Schlüssel",
     "encryption_subject": "Ausgestellt für",
     "encryption_issuer": "Ausgestellt von",
     "encryption_hostnames": "Hostnamen",
     "encryption_reset": "Möchten Sie die Verschlüsselungseinstellungen wirklich zurücksetzen?",
     "topline_expiring_certificate": "Ihr SSL-Zertifikat läuft demnächst ab. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.",
     "topline_expired_certificate": "Ihr SSL-Zertifikat ist abgelaufen. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.",
-    "form_error_port_range": "Geben Sie die Portnummer zwischen 80 und 65535 ein.",
-    "form_error_port_unsafe": "Dies ist ein unsicherer Port.",
-    "form_error_equal": "Sollten nicht übereinstimmen.",
-    "form_error_password": "Passwörter stimmen nicht überein.",
+    "form_error_port_range": "Geben Sie die Portnummer zwischen 80 und 65535 ein",
+    "form_error_port_unsafe": "Unsicherer Port",
+    "form_error_equal": "Sollten nicht übereinstimmen",
+    "form_error_password": "Passwörter stimmen nicht überein",
     "reset_settings": "Einstellungen zurücksetzen",
     "update_announcement": "AdGuard Home {{version}} ist jetzt verfügbar! <0>Klicken Sie hier</0> für weitere Informationen.",
     "setup_guide": "Einrichtungsassistent",
     "dns_addresses": "DNS-Adressen",
     "dns_start": "DNS-Server wird gestartet",
-    "dns_status_error": "Fehler bei Statusabfrage des DNS-Server.",
+    "dns_status_error": "Fehler bei Statusabfrage des DNS-Server",
     "down": "Nicht erreichbar",
     "fix": "Beheben",
     "dns_providers": "Hier finden Sie eine <0>Liste der bekannten DNS-Anbieter</0> zur Auswahl.",
@@ -406,7 +408,7 @@
     "manual_update": "Bitte <a>befolgen Sie diese Schritte</a>, um manuell zu aktualisieren.",
     "processing_update": "Bitte warten Sie, AdGuard Home wird aktualisiert …",
     "clients_title": "Persistente Clients",
-    "clients_desc": "Datensätze persistenter Clients für Geräte konfigurieren, die mit AdGuard Home verbunden sind.",
+    "clients_desc": "Datensätze persistenter Clients für Geräte konfigurieren, die mit AdGuard Home verbunden sind",
     "settings_global": "Allgemein",
     "settings_custom": "Benutzerdefiniert",
     "table_client": "Client",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Möchten Sie den Client „{{key}}“ wirklich löschen?",
     "list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?",
     "auto_clients_title": "Laufzeit-Clients",
-    "auto_clients_desc": "Geräte, die nicht auf der Liste der persistenten Clients stehen und trotzdem AdGuard Home verwenden dürfen.",
+    "auto_clients_desc": "Geräte, die nicht auf der Liste der persistenten Clients stehen und trotzdem AdGuard Home verwenden dürfen",
     "access_title": "Zugriffsrechte",
-    "access_desc": "Hier können Sie die Zugriffsregeln für den AdGuard Home DNS-Server konfigurieren.",
+    "access_desc": "Hier können Sie die Zugriffsregeln für den DNS-Server von AdGuard Home konfigurieren",
     "access_allowed_title": "Zugelassene Clients",
     "access_allowed_desc": "Eine Liste von CIDRs, IP-Adressen oder <a>Client-IDs</a>. Wenn diese Liste gefüllt ist, akzeptiert AdGuard Home nur Anfragen von diesen Clients.",
     "access_disallowed_title": "Nicht zugelassene Clients",
@@ -475,8 +477,8 @@
     "dns_rewrites": "DNS-Umschreibungen",
     "form_domain": "Domain eingeben",
     "form_answer": "IP-Adresse oder Domainname eingeben",
-    "form_error_domain_format": "Ungültiges Domainformat.",
-    "form_error_answer_format": "Ungültiges Antwortformat.",
+    "form_error_domain_format": "Ungültiges Domainformat",
+    "form_error_answer_format": "Ungültiges Antwortformat",
     "configure": "Konfigurieren",
     "main_settings": "Grundeinstellungen",
     "block_services": "Bestimmte Dienste sperren",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} Tag",
     "interval_days_plural": "{{count}} Tage",
     "domain": "Domain",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Antwort",
     "filter_added_successfully": "Der Filter wurde erfolgreich hinzugefügt",
@@ -507,7 +510,7 @@
     "filter_updated": "Der Filter wurde erfolgreich aktualisiert",
     "statistics_configuration": "Statistikkonfiguration",
     "statistics_retention": "Statistiken speichern",
-    "statistics_retention_desc": "Wenn Sie den Intervallwert verringern, gehen einige Daten verloren.",
+    "statistics_retention_desc": "Wenn Sie den Intervallwert verringern, gehen einige Daten verloren",
     "statistics_clear": "Statistiken leeren",
     "statistics_clear_confirm": "Möchten Sie die Statistiken wirklich löschen?",
     "statistics_retention_confirm": "Möchten Sie wirklich die Aufbewahrung der Statistiken ändern? Wenn Sie den Zeitabstand verringern, gehen einige Daten verloren.",
@@ -599,14 +602,14 @@
     "milliseconds_abbreviation": "ms",
     "cache_size": "Größe des Cache",
     "cache_size_desc": "Größe des DNS-Zwischenspeichers (in Bytes).",
-    "cache_ttl_min_override": "TTL-Minimalwert überschreiben (in Sekunden)",
-    "cache_ttl_max_override": "TTL-Höchstwert überschreiben (in Sekunden)",
+    "cache_ttl_min_override": "TTL-Minimalwert überschreiben",
+    "cache_ttl_max_override": "TTL-Höchstwert überschreiben",
     "enter_cache_size": "Größe des Cache (Bytes) eingeben",
-    "enter_cache_ttl_min_override": "TTL-Minimalwert eingeben",
-    "enter_cache_ttl_max_override": "TTL-Höchstwert eingeben",
+    "enter_cache_ttl_min_override": "TTL-Minimalwert eingeben (in Sekunden)",
+    "enter_cache_ttl_max_override": "TTL-Höchstwert eingeben (in Sekunden)",
     "cache_ttl_min_override_desc": "Kurze Time-to-Live-Werte (Sekunden) verlängern, die vom Upstream-Server beim Caching von DNS-Antworten empfangen werden.",
     "cache_ttl_max_override_desc": "Maximalen Time-to-Live-Wert (Sekunden) für Einträge im DNS-Cache festlegen.",
-    "ttl_cache_validation": "Der minimale Cache-TTL-Override muss kleiner oder gleich dem maximalen Wert sein.",
+    "ttl_cache_validation": "Der Überschreibungswert für die minimale TTL muss kleiner oder gleich dem für die maximale TTL sein",
     "cache_optimistic": "Optimistisches Caching",
     "cache_optimistic_desc": "Sorgt dafür, dass AdGuard Home auch dann aus dem Cache antwortet, wenn die Einträge abgelaufen sind, und versucht zudem, diese zu aktualisieren.",
     "filter_category_general": "Allgemein",
@@ -628,5 +631,5 @@
     "parental_control": "Kindersicherung",
     "safe_browsing": "Internetsicherheit",
     "served_from_cache": "{{value}} <i>(aus dem Cache abgerufen)</i>",
-    "form_error_password_length": "Das Passwort muss mindestens {{value}} Zeichen enthalten."
+    "form_error_password_length": "Das Passwort muss mindestens {{value}} Zeichen enthalten"
 }
diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json
index 76eedb0f..a83527ed 100644
--- a/client/src/__locales/es.json
+++ b/client/src/__locales/es.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Configuración DHCP guardado correctamente",
     "dhcp_ipv4_settings": "Configuración DHCP IPv4",
     "dhcp_ipv6_settings": "Configuración DHCP IPv6",
-    "form_error_required": "Campo obligatorio.",
-    "form_error_ip4_format": "Dirección IPv4 no válida.",
-    "form_error_ip4_range_start_format": "Dirección IPv4 no válida del inicio de rango.",
-    "form_error_ip4_range_end_format": "Dirección IPv4 no válida del final de rango.",
-    "form_error_ip4_gateway_format": "Dirección IPv4 no válida de la puerta de enlace.",
-    "form_error_ip6_format": "Dirección IPv6 no válida.",
-    "form_error_ip_format": "Dirección IP no válida.",
-    "form_error_mac_format": "Dirección MAC no válida.",
-    "form_error_client_id_format": "El ID de cliente debe contener solo números, letras minúsculas y guiones.",
-    "form_error_server_name": "Nombre de servidor no válido.",
-    "form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\".",
-    "form_error_positive": "Debe ser mayor que 0.",
-    "out_of_range_error": "Debe estar fuera del rango \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Debe ser inferior que el inicio de rango.",
-    "greater_range_start_error": "Debe ser mayor que el inicio de rango.",
-    "greater_range_end_error": "Debe ser mayor que el final de rango.",
-    "subnet_error": "Las direcciones deben estar en una subred.",
-    "gateway_or_subnet_invalid": "Máscara de subred no válida.",
+    "form_error_required": "Campo obligatorio",
+    "form_error_ip4_format": "Dirección IPv4 no válida",
+    "form_error_ip4_range_start_format": "Dirección IPv4 no válida del inicio de rango",
+    "form_error_ip4_range_end_format": "Dirección IPv4 no válida del final de rango",
+    "form_error_ip4_gateway_format": "Dirección IPv4 no válida de la puerta de enlace",
+    "form_error_ip6_format": "Dirección IPv6 no válida",
+    "form_error_ip_format": "Dirección IP no válida",
+    "form_error_mac_format": "Dirección MAC no válida",
+    "form_error_client_id_format": "El ID de cliente debe contener solo números, letras minúsculas y guiones",
+    "form_error_server_name": "Nombre de servidor no válido",
+    "form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\"",
+    "form_error_positive": "Debe ser mayor que 0",
+    "out_of_range_error": "Debe estar fuera del rango \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Debe ser inferior que el inicio de rango",
+    "greater_range_start_error": "Debe ser mayor que el inicio de rango",
+    "greater_range_end_error": "Debe ser mayor que el final de rango",
+    "subnet_error": "Las direcciones deben estar en una subred",
+    "gateway_or_subnet_invalid": "Máscara de subred no válida",
     "dhcp_form_gateway_input": "IP de puerta de enlace",
     "dhcp_form_subnet_input": "Máscara de subred",
     "dhcp_form_range_title": "Rango de direcciones IP",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Nombre del host",
     "dhcp_table_expires": "Expira",
     "dhcp_warning": "Si de todos modos deseas habilitar el servidor DHCP, asegúrate de que no hay otro servidor DHCP activo en tu red. ¡De lo contrario, puedes dejar sin conexión a Internet a los dispositivos conectados!",
-    "dhcp_error": "AdGuard Home no pudo determinar si hay otro servidor DHCP activo en la red.",
+    "dhcp_error": "AdGuard Home no pudo determinar si hay otro servidor DHCP activo en la red",
     "dhcp_static_ip_error": "Para poder utilizar el servidor DHCP se debe establecer una dirección IP estática. AdGuard Home no pudo determinar si esta interfaz de red está configurada utilizando una dirección IP estática. Por favor establece una dirección IP estática manualmente.",
     "dhcp_dynamic_ip_found": "Tu sistema utiliza la configuración de dirección IP dinámica para la interfaz <0>{{interfaceName}}</0>. Para poder utilizar el servidor DHCP se debe establecer una dirección IP estática. Tu dirección IP actual es <0>{{ipAddress}}</0>. AdGuard Home establecerá automáticamente esta dirección IP como estática si presionas el botón \"Habilitar servidor DHCP\".",
     "dhcp_lease_added": "Asignación estática \"{{key}}\" añadido correctamente",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Elegir listas de permitido",
     "enter_valid_blocklist": "Ingresa una URL válida para la lista de bloqueo.",
     "enter_valid_allowlist": "Ingresa una URL válida para la lista de permitido.",
-    "form_error_url_format": "Formato de URL no válido.",
-    "form_error_url_or_path_format": "URL o ruta absoluta no válida para la lista.",
+    "form_error_url_format": "Formato de URL no válido",
+    "form_error_url_or_path_format": "URL o ruta absoluta no válida para la lista",
     "custom_filter_rules": "Reglas de filtrado personalizado",
     "custom_filter_rules_hint": "Ingresa una regla por línea. Puedes utilizar reglas de bloqueo o la sintaxis de los archivos hosts.",
     "system_host_files": "Archivos hosts del sistema",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# También un comentario.",
     "example_regex_meaning": "bloquea el acceso a los dominios que coincidan con la expresión regular especificada.",
     "example_upstream_regular": "DNS regular (mediante UDP).",
+    "example_upstream_udp": "DNS regular (mediante UDP, nombre del host).",
     "example_upstream_dot": "cifrado <0>DNS mediante TLS</0>.",
     "example_upstream_doh": "cifrado <0>DNS mediante HTTPS</0>.",
     "example_upstream_doq": "cifrado <0>DNS mediante QUIC</0> (experimental).",
     "example_upstream_sdns": "<0>DNS Stamps</0> para <1>DNSCrypt</1> o resolutores <2>DNS mediante HTTPS</2>.",
     "example_upstream_tcp": "DNS regular (mediante TCP).",
+    "example_upstream_tcp_hostname": "DNS regular (mediante TCP, nombre del host).",
     "all_lists_up_to_date_toast": "Todas las listas ya están actualizadas",
     "updated_upstream_dns_toast": "Servidores DNS de subida guardados correctamente",
     "dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Usar comillas dobles para una búsqueda estricta",
     "query_log_retention_confirm": "¿Estás seguro de que deseas cambiar la retención del registro de consultas? Si disminuye el valor del intervalo, se perderán algunos datos",
     "anonymize_client_ip": "Anonimizar IP del cliente",
-    "anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros o estadísticas.",
+    "anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros o estadísticas",
     "dns_config": "Configuración del servidor DNS",
     "dns_cache_config": "Configuración de la caché DNS",
-    "dns_cache_config_desc": "Aquí puedes configurar la caché DNS.",
+    "dns_cache_config_desc": "Aquí puedes configurar la caché DNS",
     "blocking_mode": "Modo de bloqueo",
     "default": "Predeterminado",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Ingresa el límite de cantidad",
     "rate_limit": "Límite de cantidad",
     "edns_enable": "Habilitar subred de cliente EDNS",
-    "edns_cs_desc": "Envía las subredes de los clientes a los servidores DNS.",
+    "edns_cs_desc": "Añade la opción subred de cliente EDNS (ECS) a las peticiones del DNS de subida y registra los valores enviados por los clientes en el registro de consultas.",
     "rate_limit_desc": "Número de peticiones por segundo permitidas por cliente. Establecerlo en 0 significa que no hay límite.",
     "blocking_ipv4_desc": "Dirección IP devolverá una petición A bloqueada",
     "blocking_ipv6_desc": "Dirección IP devolverá una petición AAAA bloqueada",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Interfaz de escucha",
     "install_settings_port": "Puerto",
     "install_settings_interface_link": "La interfaz web de administración de AdGuard Home estará disponible en las siguientes direcciones:",
-    "form_error_port": "Ingresa un número de puerto válido.",
+    "form_error_port": "Ingresa un número de puerto válido",
     "install_settings_dns": "Servidor DNS",
     "install_settings_dns_desc": "Deberás configurar tus dispositivos o router para usar el servidor DNS en las siguientes direcciones:",
     "install_settings_all_interfaces": "Todas las interfaces",
@@ -356,7 +358,7 @@
     "open_dashboard": "Abrir panel de control",
     "install_saved": "Guardado correctamente",
     "encryption_title": "Cifrado",
-    "encryption_desc": "Soporte de cifrado (HTTPS/TLS) tanto para DNS como para la interfaz web de administración.",
+    "encryption_desc": "Soporte de cifrado (HTTPS/QUIC/TLS) tanto para DNS como para la interfaz web de administración",
     "encryption_config_saved": "Configuración de cifrado guardado",
     "encryption_server": "Nombre del servidor",
     "encryption_server_enter": "Ingresa el nombre del dominio",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Copia/pega aquí tu clave privada codificada PEM para tu certificado.",
     "encryption_enable": "Habilitar cifrado (HTTPS, DNS mediante HTTPS y DNS mediante TLS)",
     "encryption_enable_desc": "Si el cifrado está habilitado, la interfaz de administración de AdGuard Home funcionará a través de HTTPS, y el servidor DNS escuchará las peticiones DNS mediante HTTPS y DNS mediante TLS.",
-    "encryption_chain_valid": "La cadena de certificado es válida.",
-    "encryption_chain_invalid": "La cadena de certificado no es válida.",
-    "encryption_key_valid": "Esta es una clave privada {{type}} válida.",
-    "encryption_key_invalid": "Esta es una clave privada {{type}} no válida.",
+    "encryption_chain_valid": "La cadena de certificado es válida",
+    "encryption_chain_invalid": "La cadena de certificado no es válida",
+    "encryption_key_valid": "Esta es una clave privada {{type}} válida",
+    "encryption_key_invalid": "Esta es una clave privada {{type}} no válida",
     "encryption_subject": "Asunto",
     "encryption_issuer": "Emisor",
     "encryption_hostnames": "Nombres de hosts",
     "encryption_reset": "¿Estás seguro de que deseas restablecer la configuración de cifrado?",
     "topline_expiring_certificate": "Tu certificado SSL está a punto de expirar. Actualiza la <0>configuración de cifrado</0>.",
     "topline_expired_certificate": "Tu certificado SSL ha expirado. Actualiza la <0>configuración de cifrado</0>.",
-    "form_error_port_range": "Ingresa el número del puerto en el rango de 80 a 65535.",
-    "form_error_port_unsafe": "Este es un puerto inseguro.",
-    "form_error_equal": "No debe ser igual.",
-    "form_error_password": "La contraseña no coincide.",
+    "form_error_port_range": "Ingresa el número del puerto en el rango de 80 a 65535",
+    "form_error_port_unsafe": "Puerto inseguro",
+    "form_error_equal": "No debe ser igual",
+    "form_error_password": "La contraseña no coincide",
     "reset_settings": "Restablecer configuración",
     "update_announcement": "¡AdGuard Home {{version}} ya está disponible! <0>Haz clic aquí</0> para más información.",
     "setup_guide": "Guía de configuración",
     "dns_addresses": "Direcciones DNS",
     "dns_start": "El servidor DNS está iniciando",
-    "dns_status_error": "Error al obtener el estado del servidor DNS.",
+    "dns_status_error": "Error al obtener el estado del servidor DNS",
     "down": "Abajo",
     "fix": "Corregir",
     "dns_providers": "Aquí hay una <0>lista de proveedores DNS</0> conocidos para elegir.",
@@ -406,7 +408,7 @@
     "manual_update": "Por favor <a>sigue estos pasos</a> para actualizar manualmente.",
     "processing_update": "Por favor espera, AdGuard Home se está actualizando",
     "clients_title": "Clientes persistentes",
-    "clients_desc": "Configurar registros de clientes persistentes para dispositivos conectados a AdGuard Home.",
+    "clients_desc": "Configurar registros de clientes persistentes para dispositivos conectados a AdGuard Home",
     "settings_global": "Global",
     "settings_custom": "Personalizado",
     "table_client": "Cliente",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "¿Estás seguro de que deseas eliminar el cliente \"{{key}}\"?",
     "list_confirm_delete": "¿Estás seguro de que deseas eliminar esta lista?",
     "auto_clients_title": "Clientes activos",
-    "auto_clients_desc": "Dispositivos que no están en la lista de clientes persistentes que aún pueden utilizar AdGuard Home.",
+    "auto_clients_desc": "Dispositivos que no están en la lista de clientes persistentes que aún pueden utilizar AdGuard Home",
     "access_title": "Configuración de acceso",
-    "access_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home.",
+    "access_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home",
     "access_allowed_title": "Clientes permitidos",
     "access_allowed_desc": "Lista de CIDR, direcciones IP o <a>ID de clientes</a>. Si esta lista tiene entradas, AdGuard Home aceptará peticiones solo de estos clientes.",
     "access_disallowed_title": "Clientes no permitidos",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Reescrituras DNS",
     "form_domain": "Ingresa el nombre del dominio o comodín",
     "form_answer": "Ingresa la dirección IP o el nombre del dominio",
-    "form_error_domain_format": "Formato de dominio no válido.",
-    "form_error_answer_format": "Formato de respuesta no válido.",
+    "form_error_domain_format": "Formato de dominio no válido",
+    "form_error_answer_format": "Formato de respuesta no válido",
     "configure": "Configurar",
     "main_settings": "Configuración principal",
     "block_services": "Bloquear servicios específicos",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} día",
     "interval_days_plural": "{{count}} días",
     "domain": "Dominio",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Respuesta",
     "filter_added_successfully": "La lista ha sido añadida correctamente",
@@ -507,7 +510,7 @@
     "filter_updated": "La lista ha sido actualizada correctamente",
     "statistics_configuration": "Configuración de estadísticas",
     "statistics_retention": "Retención de estadísticas",
-    "statistics_retention_desc": "Si disminuye el valor del intervalo, se perderán algunos datos.",
+    "statistics_retention_desc": "Si disminuye el valor del intervalo, se perderán algunos datos",
     "statistics_clear": "Borrar estadísticas",
     "statistics_clear_confirm": "¿Estás seguro de que deseas borrar las estadísticas?",
     "statistics_retention_confirm": "¿Estás seguro de que deseas cambiar la retención de estadísticas? Si disminuye el valor del intervalo, se perderán algunos datos",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Ingresa el TTL máximo (en segundos)",
     "cache_ttl_min_override_desc": "Amplía el corto tiempo de vida (segundos) de los valores recibidos del servidor DNS de subida al almacenar en caché las respuestas DNS.",
     "cache_ttl_max_override_desc": "Establece un valor de tiempo de vida (segundos) máximo para las entradas en la caché DNS.",
-    "ttl_cache_validation": "La anulación TTL mínimo de la caché debe ser menor o igual al máximo.",
+    "ttl_cache_validation": "La anulación TTL mínimo de la caché debe ser menor o igual al máximo",
     "cache_optimistic": "Caché optimista",
     "cache_optimistic_desc": "Haz que AdGuard Home responda desde la caché incluso cuando las entradas estén expiradas y también intente actualizarlas.",
     "filter_category_general": "General",
@@ -628,5 +631,5 @@
     "parental_control": "Control parental",
     "safe_browsing": "Navegación segura",
     "served_from_cache": "{{value}} <i>(servido desde la caché)</i>",
-    "form_error_password_length": "La contraseña debe tener al menos {{value}} caracteres."
+    "form_error_password_length": "La contraseña debe tener al menos {{value}} caracteres"
 }
diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json
index 899748a7..5e6c8089 100644
--- a/client/src/__locales/fi.json
+++ b/client/src/__locales/fi.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP-asetukset tallennettiin",
     "dhcp_ipv4_settings": "DHCP:n IPv4-asetukset",
     "dhcp_ipv6_settings": "DHCP:n IPv6-asetukset",
-    "form_error_required": "Pakollinen kenttä.",
-    "form_error_ip4_format": "Virheellinen IPv4-osoite.",
-    "form_error_ip4_range_start_format": "Virheellinen IPv4-osoitealueen aloitusosoite.",
-    "form_error_ip4_range_end_format": "Virheellinen IPv4-osoitealueen päätösosoite.",
-    "form_error_ip4_gateway_format": "Virheellinen yhdyskäytävän IPv4-osoite.",
-    "form_error_ip6_format": "Virheellinen IPv6-osoite.",
-    "form_error_ip_format": "Virheellinen IP-osoite.",
-    "form_error_mac_format": "Virheellinen MAC-osoite.",
-    "form_error_client_id_format": "Päätelaitteen ID voi sisältää ainoastaan numeroita, pieniä kirjaimia sekä yhdysviivoja.",
-    "form_error_server_name": "Virheellinen palvelimen nimi.",
-    "form_error_subnet": "Aliverkko \"{{cidr}}\" ei sisällä IP-osoitetta \"{{ip}}\".",
-    "form_error_positive": "Oltava suurempi kuin 0.",
-    "out_of_range_error": "Oltava alueen \"{{start}}\" - \"{{end}}\" ulkopuolella.",
-    "lower_range_start_error": "Oltava alueen aloitusarvoa pienempi.",
-    "greater_range_start_error": "Oltava alueen aloitusarvoa suurempi.",
-    "greater_range_end_error": "Oltava alueen päätösarvoa pienempi.",
-    "subnet_error": "Osoitteiden tulee olla yhdessä aliverkossa.",
-    "gateway_or_subnet_invalid": "Virheellinen aliverkon peite.",
+    "form_error_required": "Pakollinen kenttä",
+    "form_error_ip4_format": "Virheellinen IPv4-osoite",
+    "form_error_ip4_range_start_format": "Virheellinen IPv4-osoitealueen aloitusosoite",
+    "form_error_ip4_range_end_format": "Virheellinen IPv4-osoitealueen päätösosoite",
+    "form_error_ip4_gateway_format": "Virheellinen yhdyskäytävän IPv4-osoite",
+    "form_error_ip6_format": "Virheellinen IPv6-osoite",
+    "form_error_ip_format": "Virheellinen IP-osoite",
+    "form_error_mac_format": "Virheellinen MAC-osoite",
+    "form_error_client_id_format": "Päätelaitteen ID voi sisältää ainoastaan numeroita, pieniä kirjaimia sekä yhdysviivoja",
+    "form_error_server_name": "Virheellinen palvelimen nimi",
+    "form_error_subnet": "Aliverkko \"{{cidr}}\" ei sisällä IP-osoitetta \"{{ip}}\"",
+    "form_error_positive": "Oltava suurempi kuin 0",
+    "out_of_range_error": "Oltava alueen \"{{start}}\" - \"{{end}}\" ulkopuolella",
+    "lower_range_start_error": "Oltava alueen aloitusarvoa pienempi",
+    "greater_range_start_error": "Oltava alueen aloitusarvoa suurempi",
+    "greater_range_end_error": "Oltava alueen päätösarvoa pienempi",
+    "subnet_error": "Osoitteiden tulee olla yhdessä aliverkossa",
+    "gateway_or_subnet_invalid": "Virheellinen aliverkon peite",
     "dhcp_form_gateway_input": "Yhdyskäytävän IP-osoite",
     "dhcp_form_subnet_input": "Aliverkon peite",
     "dhcp_form_range_title": "IP-osoitealue",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Isäntänimi",
     "dhcp_table_expires": "Erääntyy",
     "dhcp_warning": "Jos tahdot kuitenkin ottaa DHCP-palvelimen käyttöön, varmista, ettei verkossasi ole muita aktiivisia DHCP-palvelimia, koska tämä voi rikkoa Internet-yhteyden muilta verkon laitteilta!",
-    "dhcp_error": "AdGuard Home ei voinut tunnistaa, onko verkossa toista aktiivista DHCP-palvelinta.",
+    "dhcp_error": "AdGuard Home ei voinut tunnistaa, onko verkossa toista aktiivista DHCP-palvelinta",
     "dhcp_static_ip_error": "Jotta DHCP-palvelinta voidaan käyttää, on määritettävä kiinteä IP-osoite. AdGuard Home ei voinut tunnistaa, onko tälle verkkosovittimelle määritetty IP-osoite kiinteä. Määritä kiinteä IP-osoite itse.",
     "dhcp_dynamic_ip_found": "Järjestelmäsi käyttää verkkosovittimelle <0>{{interfaceName}}</0> dynaamista IP-osoitetta. Jotta voit käyttää DHCP-palvelinta, on sovittimelle määritettävä kiinteä IP-osoite. Nykyinen IP-osoitteesi on <0>{{ipAddress}}</0>. Tämä osoite määritetään automaattisesti kiinteäksi, jos painat \"Ota DHCP-palvelin käyttöön\" -painiketta.",
     "dhcp_lease_added": "Kiinteä laina \"{{key}}\" on lisätty",
@@ -117,7 +117,7 @@
     "stats_adult": "Estetyt aikuisille tarkoitetut sivustot",
     "stats_query_domain": "Kysytyimmät verkkotunnukset",
     "for_last_24_hours": "viimeisten 24 tunnin ajalta",
-    "for_last_days": "viimeisen {{count}} päivän ajalta",
+    "for_last_days": "viimeisten {{count}} päivän ajalta",
     "for_last_days_plural": "viimeisten {{count}} päivän ajalta",
     "stats_disabled": "Tilastointi ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksista</0>.",
     "stats_disabled_short": "Tilastointi ei ole käytössä",
@@ -127,7 +127,7 @@
     "top_clients": "Käytetyimmät päätelaitteet",
     "no_clients_found": "Päätelaitteita ei löytynyt",
     "general_statistics": "Yleiset tilastot",
-    "number_of_dns_query_days": "Käsiteltyjen DNS-pyyntöjen määrä viimeisen {{count}} päivän ajalta",
+    "number_of_dns_query_days": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten {{count}} päivän ajalta",
     "number_of_dns_query_days_plural": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten {{count}} päivän ajalta",
     "number_of_dns_query_24_hours": "Käsiteltyjen DNS-pyyntöjen määrä viimeisten 24 tunnin ajalta",
     "number_of_dns_query_blocked_24_hours": "Mainoseston suodattimien ja hosts-estolistojen estämien DNS-pyyntöjen määrä",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Valitse sallittujen listat",
     "enter_valid_blocklist": "Syötä estolistan URL-osoite.",
     "enter_valid_allowlist": "Syötä sallittujen listan URL-osoite.",
-    "form_error_url_format": "Virheellinen URL-osoitteen muoto.",
-    "form_error_url_or_path_format": "Syötä listan URL-osoite tai tarkka tiedostosijainti.",
+    "form_error_url_format": "Virheellinen URL-osoitteen muoto",
+    "form_error_url_or_path_format": "Syötä listan URL-osoite tai tarkka tiedostosijainti",
     "custom_filter_rules": "Omat suodatussäännöt",
     "custom_filter_rules_hint": "Syötä yksi sääntö per rivi. Voit käyttää mainoseston sääntöjen tai hosts-tiedostojen syntakseja.",
     "system_host_files": "Järjestelmän hosts-tiedostot",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Tämäkin on kommentti.",
     "example_regex_meaning": "estä pääsy määritettyä säännöllistä lauseketta vastaaviin verkkotunnuksiin.",
     "example_upstream_regular": "tavallinen DNS (UDP:n välityksellä);",
+    "example_upstream_udp": "tavallinen DNS (UDP, isäntänimi);",
     "example_upstream_dot": "salattu <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "salattu <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "salattu <0>DNS-over-QUIC</0> (kokeellinen);",
     "example_upstream_sdns": "<0>DNS Stamp</0> -merkinnät <1>DNSCrypt</1> tai <2>DNS-over-HTTPS</2> -resolvereille;",
     "example_upstream_tcp": "tavallinen DNS (TCP:n välityksellä);",
+    "example_upstream_tcp_hostname": "tavallinen DNS (TCP, isäntänimi);",
     "all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla",
     "updated_upstream_dns_toast": "Ylävirtojen palvelimet tallennettiin",
     "dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Käytettävä verkkosovitin",
     "install_settings_port": "Portti",
     "install_settings_interface_link": "AdGuard Home -asennuksesi hallintapaneeli on käytettävissä seuraavilla osoitteilla:",
-    "form_error_port": "Syötä oikea portin numero.",
+    "form_error_port": "Syötä kelvollinen portti",
     "install_settings_dns": "DNS-palvelin",
     "install_settings_dns_desc": "Sinun on määritettävä laitteesi tai reitittimesi käyttämään DNS-palvelinta seuraavissa osoitteissa:",
     "install_settings_all_interfaces": "Kaikki verkkosovittimet",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Kopioi/liitä tähän varmenteesi PEM-koodattu yksityinen avain.",
     "encryption_enable": "Käytä salausta (HTTPS, DNS-over-HTTPS ja DNS-over-TLS)",
     "encryption_enable_desc": "Jos salaus on käytössä, AdGuard Homen hallinta on käytettävissä HTTPS-yhteydellä ja DNS-palvelin kuuntelee pyyntöjä DNS-over-HTTPS ja DNS-over-TLS -yhteyksillä.",
-    "encryption_chain_valid": "Varmenneketju on kelvollinen.",
-    "encryption_chain_invalid": "Varmenneketju ei kelpaa.",
-    "encryption_key_valid": "Tämä yksityinen {{type}}-avain on kelvollinen.",
-    "encryption_key_invalid": "Tämä yksityinen {{type}}-avain ei kelpaa.",
+    "encryption_chain_valid": "Varmenneketju on kelvollinen",
+    "encryption_chain_invalid": "Varmenneketju ei kelpaa",
+    "encryption_key_valid": "Tämä yksityinen {{type}}-avain on kelvollinen",
+    "encryption_key_invalid": "Tämä yksityinen {{type}}-avain ei kelpaa",
     "encryption_subject": "Aihe",
     "encryption_issuer": "Toimittaja",
     "encryption_hostnames": "Isäntänimet",
     "encryption_reset": "Haluatko varmasti palauttaa salausasetukset?",
     "topline_expiring_certificate": "SSL-varmenteesi on erääntymässä. Päivitä <0>Salausasetukset</0>.",
     "topline_expired_certificate": "SSL-varmenteesi on erääntynyt. Päivitä <0>Salausasetukset</0>.",
-    "form_error_port_range": "Syötä portti väliltä 80-65535.",
-    "form_error_port_unsafe": "Tämä portti ei ole turvallinen.",
-    "form_error_equal": "Ei voi olla sama.",
-    "form_error_password": "Salasanat eivät täsmää.",
+    "form_error_port_range": "Syötä portti väliltä 80-65535",
+    "form_error_port_unsafe": "Portti ei ole turvallinen",
+    "form_error_equal": "Ei voi olla sama",
+    "form_error_password": "Salasanat eivät täsmää",
     "reset_settings": "Tyhjennä asetukset",
     "update_announcement": "AdGuard Home {{version}} on nyt saatavilla! <0>Paina tästä</0> saadaksesi lisätietoja.",
     "setup_guide": "Asennusopas",
     "dns_addresses": "DNS-osoitteet",
     "dns_start": "DNS-palvelin käynnistyy",
-    "dns_status_error": "Virhe tarkistettaessa DNS-palvelimen tilaa.",
+    "dns_status_error": "Virhe tarkistettaessa DNS-palvelimen tilaa",
     "down": "yhteydetön",
     "fix": "Korjaa",
     "dns_providers": "Katso <0>luettelo tunnetuista DNS-palveluista</0>, joista valita.",
@@ -406,7 +408,7 @@
     "manual_update": "Seuraa <a>näitä ohjeita</a> päivittääksesi manuaalisesti.",
     "processing_update": "Odota kun AdGuard Home päivittyy",
     "clients_title": "Pysyvät päätelaitteet",
-    "clients_desc": "Määritä pysyvät AdGuard Homeen yhdistetyt päätelaittetiedot.",
+    "clients_desc": "Määritä pysyvät AdGuard Homeen yhdistetyt päätelaitetiedot.",
     "settings_global": "Yleinen",
     "settings_custom": "Oma",
     "table_client": "Päätelaite",
@@ -475,8 +477,8 @@
     "dns_rewrites": "DNS-uudelleenohjaukset",
     "form_domain": "Syötä verkkotunnus tai jokerimerkki",
     "form_answer": "Syötä IP-osoite tai verkkotunnus",
-    "form_error_domain_format": "Virheellinen verkkotunnuksen muoto.",
-    "form_error_answer_format": "Virheellinen vastauksen muoto.",
+    "form_error_domain_format": "Virheellinen verkkotunnuksen muoto",
+    "form_error_answer_format": "Virheellinen vastauksen muoto",
     "configure": "Määritä",
     "main_settings": "Pääasetukset",
     "block_services": "Estä tietyt palvelut",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} päivä",
     "interval_days_plural": "{{count}} päivää",
     "domain": "Verkkotunnus",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Vastaus",
     "filter_added_successfully": "Lista lisättiin",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Syötä enimmäis-TTL (sekunteina)",
     "cache_ttl_min_override_desc": "Pidennä ylävirran palvelimelta vastaanotettuja, lyhyitä elinaika-arvoja (sekunteina) tallennettaessa DNS-vastauksia välimuistiin.",
     "cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden enimmäiselinaika (sekunteina).",
-    "ttl_cache_validation": "Välimuistin vähimmäiselinajan tulee olla pienempi tai sama kuin enimmäiselinajan.",
+    "ttl_cache_validation": "Välimuistin vähimmäiselinajan on oltava pienempi tai sama kuin enimmäiselinajan",
     "cache_optimistic": "Optimistinen välimuisti",
     "cache_optimistic_desc": "Pakota AdGuard Home vastaamaan välimuistista vaikka sen tiedot olisivat vanhentuneet. Pyri samalla myös päivittämään tiedot.",
     "filter_category_general": "Yleiset",
@@ -628,5 +631,5 @@
     "parental_control": "Lapsilukko",
     "safe_browsing": "Turvallinen selaus",
     "served_from_cache": "{{value}} <i>(jaettu välimuistista)</i>",
-    "form_error_password_length": "Salasanan on oltava ainakin {{value}} merkkiä."
+    "form_error_password_length": "Salasanan on oltava ainakin {{value}} merkkiä"
 }
diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json
index b10433ae..afbe91cd 100644
--- a/client/src/__locales/fr.json
+++ b/client/src/__locales/fr.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Configuration du serveur DHCP sauvegardée",
     "dhcp_ipv4_settings": "Paramètres IPv4 du DHCP",
     "dhcp_ipv6_settings": "Paramètres IPv6 du DHCP",
-    "form_error_required": "Champ requis.",
-    "form_error_ip4_format": "Adresse IPv4 invalide.",
-    "form_error_ip4_range_start_format": "Adresse de début de plage IPv4 incorrecte.",
-    "form_error_ip4_range_end_format": "Adresse de fin de plage IPv4 incorrecte.",
-    "form_error_ip4_gateway_format": "Adresse de passerelle IPv4 invalide.",
-    "form_error_ip6_format": "Adresse IPv6 invalide.",
-    "form_error_ip_format": "Adresse IP invalide.",
-    "form_error_mac_format": "Format MAC invalide.",
-    "form_error_client_id_format": "ClientID ne doit contenir que des chiffres, des lettres minuscules et des traits d'union.",
-    "form_error_server_name": "Nom de serveur invalide.",
-    "form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} ».",
-    "form_error_positive": "Doit être supérieur à 0.",
-    "out_of_range_error": "Doit être hors plage « {{start}} » - « {{end}} ».",
-    "lower_range_start_error": "Doit être inférieur au début de plage.",
-    "greater_range_start_error": "Doit être supérieur au début de plage.",
-    "greater_range_end_error": "Doit être supérieur à la fin de plage.",
-    "subnet_error": "Les adresses doivent être dans le même sous-réseau.",
-    "gateway_or_subnet_invalid": "Masque de sous-réseau invalide.",
+    "form_error_required": "Champ requis",
+    "form_error_ip4_format": "Adresse IPv4 invalide",
+    "form_error_ip4_range_start_format": "Adresse de début de plage IPv4 incorrecte",
+    "form_error_ip4_range_end_format": "Adresse de fin de plage IPv4 incorrecte",
+    "form_error_ip4_gateway_format": "Adresse de passerelle IPv4 invalide",
+    "form_error_ip6_format": "Adresse IPv6 invalide",
+    "form_error_ip_format": "Adresse IP invalide",
+    "form_error_mac_format": "Adresse MAC invalide",
+    "form_error_client_id_format": "L'ID du client ne doit contenir que des chiffres, des lettres minuscules et des traits d'union",
+    "form_error_server_name": "Nom de serveur invalide",
+    "form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} »",
+    "form_error_positive": "Doit être supérieur à 0",
+    "out_of_range_error": "Doit être hors plage « {{start}} » - « {{end}} »",
+    "lower_range_start_error": "Doit être inférieur au début de plage",
+    "greater_range_start_error": "Doit être supérieur au début de plage",
+    "greater_range_end_error": "Doit être supérieur à la fin de plage",
+    "subnet_error": "Les adresses doivent être dans le même sous-réseau",
+    "gateway_or_subnet_invalid": "Masque de sous-réseau invalide",
     "dhcp_form_gateway_input": "IP de la passerelle",
     "dhcp_form_subnet_input": "Masque de sous-réseau",
     "dhcp_form_range_title": "Rangée des adresses IP",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Choisir des listes d’autorisation",
     "enter_valid_blocklist": "Saisissez une URL valide vers la liste de blocage.",
     "enter_valid_allowlist": "Saisissez une URL valide vers la liste d’autorisation.",
-    "form_error_url_format": "Format d’URL incorrect.",
-    "form_error_url_or_path_format": "Entrez une URL ou le chemin absolu de la liste.",
+    "form_error_url_format": "Format d’URL incorrect",
+    "form_error_url_or_path_format": "Lien URL soit chemin absolu de la liste invalide",
     "custom_filter_rules": "Règles de filtrage d'utilisateur",
     "custom_filter_rules_hint": "Saisissez la règle en une ligne. C'est possible d'utiliser les règles de blocage ou la syntaxe des fichiers hosts.",
     "system_host_files": "Fichier d'hôtes système",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Aussi un commentaire.",
     "example_regex_meaning": "bloque l’accès aux domaines correspondants à l'expression régulière spécifiée .",
     "example_upstream_regular": "DNS classique (au-dessus de UDP) ;",
+    "example_upstream_udp": "DNS normal (sur UDP, nom d’hôte) ;",
     "example_upstream_dot": "<0>DNS-over-TLS</0> chiffré ;",
     "example_upstream_doh": "<0>DNS-over-HTTPS</0> chiffré ;",
     "example_upstream_doq": "<0>DNS-over-QUIC</0> chiffré (expérimental) ;",
     "example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps</0> pour <1>DNSCrypt</1> ou les résolveurs <2>DNS_over_HTTPS</2> ;",
     "example_upstream_tcp": "DNS classique (au-dessus de TCP) ;",
+    "example_upstream_tcp_hostname": "DNS normal (sur TCP, nom d’hôte) ;",
     "all_lists_up_to_date_toast": "Toutes les listes sont déjà à jour",
     "updated_upstream_dns_toast": "Serveurs en amont enregistrés",
     "dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Utilisez les doubles guillemets pour une recherche stricte",
     "query_log_retention_confirm": "Êtes-vous sûr de vouloir modifier la rétention des journaux de requêtes ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues",
     "anonymize_client_ip": "Anonymiser l’IP du client",
-    "anonymize_client_ip_desc": "Ne pas enregistrer l’adresse IP complète du client dans les journaux et statistiques .",
+    "anonymize_client_ip_desc": "Ne pas enregistrer l’adresse IP complète du client dans les journaux et statistiques",
     "dns_config": "Configuration du serveur DNS",
     "dns_cache_config": "Configuration du cache DNS",
-    "dns_cache_config_desc": "Ici, vous pouvez configurer le cache DNS .",
+    "dns_cache_config_desc": "Ici, vous pouvez configurer le cache DNS",
     "blocking_mode": "Mode du blocage",
     "default": "Par défaut",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Entrez la limite de taux",
     "rate_limit": "Limite de taux",
     "edns_enable": "Activer le sous-réseau du client EDNS",
-    "edns_cs_desc": "Envoyer les sous-réseaux des clients aux serveurs DNS.",
+    "edns_cs_desc": "Ajouter l'option du sous-réseau Client EDNS (ECS) au requêtes en amont et enregistrer les valeurs envoyées par les clients dans le journal des requêtes.",
     "rate_limit_desc": "Le nombre de requêtes par seconde qu’un seul client est autorisé à faire. Le réglage 0 fait illimité.",
     "blocking_ipv4_desc": "Adresse IP à renvoyer pour une demande A bloquée",
     "blocking_ipv6_desc": "Adresse IP à renvoyer pour une demande AAAA bloquée",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Interface d'écoute",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Votre interface web administrateur AdGuard Home sera disponible sur les adresses suivantes :",
-    "form_error_port": "Entrez un port valide.",
+    "form_error_port": "Saisissez un numéro de port valide",
     "install_settings_dns": "Serveur DNS",
     "install_settings_dns_desc": "Vous devrez configurer vos appareils et votre routeur pour utiliser le serveur DNS sur les adresses suivantes :",
     "install_settings_all_interfaces": "Toutes les interfaces",
@@ -356,7 +358,7 @@
     "open_dashboard": "Ouvrir le Tableau de bord",
     "install_saved": "Enregistré avec succès",
     "encryption_title": "Chiffrement",
-    "encryption_desc": "Le support du chiffrement (HTTPS/TLS) pour les DNS et l'interface web administrateur",
+    "encryption_desc": "Le support du chiffrement (HTTPS/QUIC/TLS) pour les DNS et l'interface web administrateur",
     "encryption_config_saved": "Configuration de chiffrement enregistrée",
     "encryption_server": "Nom du serveur",
     "encryption_server_enter": "Entrez votre nom de domaine",
@@ -379,25 +381,25 @@
     "encryption_enable": "Activer le chiffrement (HTTPS, DNS-over-HTTPS et DNS-over-TLS)",
     "encryption_enable_desc": "Si le chiffrement est activé, l'interface administrateur AdGuard Home fonctionnera via HTTPS et le serveur DNS écoutera les requêtes via DNS-over-HTTPS et DNS-over-TLS.",
     "encryption_chain_valid": "Chaîne de certificat valide.",
-    "encryption_chain_invalid": "Chaîne de certificat invalide.",
-    "encryption_key_valid": "Ceci est une clé privée {{type}} valide.",
-    "encryption_key_invalid": "Ceci est une clé privée {{type}} invalide.",
+    "encryption_chain_invalid": "Chaîne de certificat invalide",
+    "encryption_key_valid": "Ceci est une clé privée {{type}} valide",
+    "encryption_key_invalid": "Ceci est une clé privée {{type}} invalide",
     "encryption_subject": "Objet",
     "encryption_issuer": "Émetteur",
     "encryption_hostnames": "Noms d'hôte",
     "encryption_reset": "Voulez-vous vraiment réinitialiser les paramètres de chiffrement ?",
     "topline_expiring_certificate": "Votre certificat SSL est sur le point d'expirer. Mettez à jour vos <0>Paramètres de chiffrement</0>.",
     "topline_expired_certificate": "Votre certificat SSL a expiré. Mettez à jour vos <0>Paramètres de chiffrement</0>.",
-    "form_error_port_range": "Saisissez une valeur de port entre 80 et 65535.",
-    "form_error_port_unsafe": "C'est un port non fiable.",
-    "form_error_equal": "Ne doit pas être égal .",
-    "form_error_password": "Mots de passe différents.",
+    "form_error_port_range": "Saisissez une valeur de port entre 80 et 65535",
+    "form_error_port_unsafe": "Port non fiable",
+    "form_error_equal": "Ne doit pas être égal",
+    "form_error_password": "Mots de passe différents",
     "reset_settings": "Réinitialiser les paramètres",
     "update_announcement": "AdGuard Home {{version}} est disponible ! <0>Cliquez ici</0> pour plus d'informations.",
     "setup_guide": "Guide d'installation",
     "dns_addresses": "Adresses DNS",
     "dns_start": "Démarrage du serveur DNS",
-    "dns_status_error": "Erreur lors de la récupération du statut du serveur DNS .",
+    "dns_status_error": "Erreur lors de la vérification du statut du serveur DNS",
     "down": "Descendant",
     "fix": "Corriger",
     "dns_providers": "Voici une <0>liste de fournisseurs DNS connus</0>.",
@@ -406,7 +408,7 @@
     "manual_update": "Veuillez <a>suivre ces étapes</a> pour mettre à jour manuellement.",
     "processing_update": "Veuillez patienter, AdGuard Home est en cours de mise à jour",
     "clients_title": "Clients persistants",
-    "clients_desc": "Configurez des enregistrements clients persistants pour les appareils connectés à AdGuard Home.",
+    "clients_desc": "Configurer des dossiers de clients persistants pour les appareils connectés à AdGuard Home",
     "settings_global": "Général",
     "settings_custom": "Personnalisé",
     "table_client": "Client",
@@ -435,7 +437,7 @@
     "auto_clients_title": "Clients d'exécution",
     "auto_clients_desc": "Appareils ne figurant pas sur la liste des clients persistants qui peuvent encore utiliser AdGuard Home.",
     "access_title": "Paramètres d'accès",
-    "access_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home.",
+    "access_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home",
     "access_allowed_title": "Clients autorisés",
     "access_allowed_desc": "Une liste de CIDRs, d'adresses IP, ou de <a>ClientIDs</a>. Si cette liste comporte des entrées, AdGuard Home n'acceptera que les demandes provenant de ces clients.",
     "access_disallowed_title": "Clients non autorisés",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Réécritures DNS",
     "form_domain": "Saisissez un domaine ou caracrtère générique",
     "form_answer": "Saisissez une adresse IP ou un nom de domaine",
-    "form_error_domain_format": "Nom de domaine invalide.",
-    "form_error_answer_format": "Format de réponse invalide.",
+    "form_error_domain_format": "Format de domaine invalide",
+    "form_error_answer_format": "Format de réponse invalide",
     "configure": "Configurer",
     "main_settings": "Paramètres principaux",
     "block_services": "Bloquer des services spécifiques",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} jour",
     "interval_days_plural": "{{count}} jours",
     "domain": "Domaine",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Réponse",
     "filter_added_successfully": "Le filtre a été ajouté avec succès",
@@ -507,7 +510,7 @@
     "filter_updated": "Le filtre a été mis à jour avec succès",
     "statistics_configuration": "Configuration des statistiques",
     "statistics_retention": "Maintien des statistiques",
-    "statistics_retention_desc": "Si vous baissez la valeur de l'intervalle, des données seront perdues .",
+    "statistics_retention_desc": "Si vous baissez la valeur de l'intervalle, des données seront perdues",
     "statistics_clear": " Effacer les statistiques",
     "statistics_clear_confirm": "Voulez-vous vraiment effacer les statistiques ?",
     "statistics_retention_confirm": "Êtes-vous sûr de vouloir modifier le maintien des statistiques ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Entrez le TTL maximum (secondes)",
     "cache_ttl_min_override_desc": "Prolonger les valeurs courtes de durée de vie (en secondes) reçues du serveur en amont lors de la mise en cache des réponses DNS .",
     "cache_ttl_max_override_desc": "Établir la valeur de durée de vie TTL maximale (en secondes) pour les saisies dans le cache du DNS .",
-    "ttl_cache_validation": "La valeur TTL minimale du cache doit être inférieure ou égale à la valeur maximale .",
+    "ttl_cache_validation": "La valeur TTL minimale du cache doit être inférieure ou égale à la valeur maximale",
     "cache_optimistic": "Caching optimiste",
     "cache_optimistic_desc": "Faites en sorte qu'AdGuard Home réponde à partir du cache même lorsque les entrées ont expiré et essayez également de les actualiser.",
     "filter_category_general": "Général",
@@ -628,5 +631,5 @@
     "parental_control": "Contrôle parental",
     "safe_browsing": "Navigation sécurisée",
     "served_from_cache": "{{value}} <i>(depuis le cache)</i>",
-    "form_error_password_length": "Le mot de passe doit comporter au moins {{value}} caractères."
+    "form_error_password_length": "Le mot de passe doit comporter au moins {{value}} caractères"
 }
diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json
index 5b0dc1dc..47f2296c 100644
--- a/client/src/__locales/hr.json
+++ b/client/src/__locales/hr.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Postavke klijenta",
-    "example_upstream_reserved": "VI možete odrediti DNS upstream-ove <0>za određene domene</0>",
-    "example_upstream_comment": "Možete odrediti komentar",
+    "example_upstream_reserved": "upstream <0>za određene domene</0>;",
+    "example_upstream_comment": "komentar.",
     "upstream_parallel": "Koristi paralelne upite kako bi ubrzali rješavanje istovremenim ispitavanjem svih upstream poslužitelja.",
     "parallel_requests": "Paralelni zahtjevi",
     "load_balancing": "Load-balancing",
@@ -36,13 +36,13 @@
     "dhcp_ipv4_settings": "DHCP IPv4 postavke",
     "dhcp_ipv6_settings": "DHCP IPv6 postavke",
     "form_error_required": "Obavezno polje",
-    "form_error_ip4_format": "Nevažeći IPv4 format",
+    "form_error_ip4_format": "Nevažeća IPv4 adresa",
     "form_error_ip4_range_start_format": "Nepravilan početak ranga IPv4 adresa",
     "form_error_ip4_range_end_format": "Nepravilan kraj ranga IPv4 adresa",
     "form_error_ip4_gateway_format": "Nepravilna IPV4 adresa čvora",
-    "form_error_ip6_format": "Nevažeći IPv6 format",
-    "form_error_ip_format": "Nevažeći format IP adrese",
-    "form_error_mac_format": "Nevažeći MAC format",
+    "form_error_ip6_format": "Nevažeći IPv6 adresa",
+    "form_error_ip_format": "Nepravilna IP adresa",
+    "form_error_mac_format": "Nevažeći MAC adresa",
     "form_error_client_id_format": "ID klijenta može sadržavati samo brojeve, mala slova i crtice",
     "form_error_server_name": "Nevažeće ime poslužitelja",
     "form_error_subnet": "Podmrežu \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Naziv računala",
     "dhcp_table_expires": "Istječe",
     "dhcp_warning": "Ako svejedno želite omogućiti DHCP poslužitelj, provjerite da nema drugog aktivnog DHCP poslužitelja na vašoj mreži. Inače može pokvariti Internet za ostale povezane uređaje!",
-    "dhcp_error": "AdGuard Home nije mogao utvrditi postoji li drugi aktivni DHCP poslužitelj na mreži.",
+    "dhcp_error": "AdGuard Home nije mogao utvrditi postoji li drugi aktivni DHCP poslužitelj na mreži",
     "dhcp_static_ip_error": "Da bi se koristio DHCP poslužitelj mora se postaviti statička IP adresa. AdGuard Home nije uspio utvrditi je li ovo mrežno sučelje konfigurirano pomoću statičke IP adrese. Ručno postavite statičku IP adresu.",
     "dhcp_dynamic_ip_found": "Vaš sustav koristi postavke dinamičke IP adrese za sučelje <0>{{interfaceName}}</0>. Za korištenje DHCP poslužitelja mora se postaviti statička IP adresa. Vaša trenutna IP adresa je <0>{{ipAddress}}</0>. AdGuard Home automatski će postaviti ovu IP adresu kao statičnu ako pritisnete gumb \"Omogući DHCP\".",
     "dhcp_lease_added": "Statični lease \"{{key}}\" je uspješno dodan",
@@ -202,19 +202,21 @@
     "custom_filter_rules_hint": "Unesite jedno pravilo po liniji. Možete koristiti sintaksu za pravila blokiranja oglasa ili za hosts datoteke.",
     "system_host_files": "Datoteke host sustava",
     "examples_title": "Primjeri",
-    "example_meaning_filter_block": "blokira pristup domeni example.org kao i svim njenim poddomenama",
-    "example_meaning_filter_whitelist": "odblokira pristup domeni example.org kao i svim njenim poddomenama",
-    "example_meaning_host_block": "AdGuard Home će sada vratiti 127.0.0.1 adresu na example.org domenu (ali ne i poddomene).",
-    "example_comment": "! Ovdje ide komentar",
-    "example_comment_meaning": "samo komentar",
-    "example_comment_hash": "# Također komentar",
-    "example_regex_meaning": "blokira pristup domenama koje se podudaraju s regularnim izrazom",
-    "example_upstream_regular": "zadani DNS (putem UDP)",
-    "example_upstream_dot": "šifrirano <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "šifrirano <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "šifrirano <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "možete koristiti <0>DNS Stamps</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2> rezolvere",
-    "example_upstream_tcp": "zadani DNS (putem TCP)",
+    "example_meaning_filter_block": "blokira pristup example.org i svim njenim poddomenama;",
+    "example_meaning_filter_whitelist": "odblokira pristup example.org i svim njenim poddomenama;",
+    "example_meaning_host_block": "vratiti 127.0.0.1 adresu na example.org domenu (ali ne i poddomene);",
+    "example_comment": "! Ovdje ide komentar.",
+    "example_comment_meaning": "samo komentar;",
+    "example_comment_hash": "# Također komentar.",
+    "example_regex_meaning": "blokira pristup domenama koje se podudaraju s regularnim izrazom.",
+    "example_upstream_regular": "zadani DNS (putem UDP);",
+    "example_upstream_udp": "obični DNS (preko UDP-a, ime hosta);",
+    "example_upstream_dot": "šifrirano <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "šifrirano <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "šifrirano <0>DNS-over-QUIC</0> (eksperimentalno);",
+    "example_upstream_sdns": "<0>DNS Stamps</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2> rezolvere;",
+    "example_upstream_tcp": "zadani DNS (putem TCP);",
+    "example_upstream_tcp_hostname": "obični DNS (preko TCP-a, ime hosta);",
     "all_lists_up_to_date_toast": "Svi popisi su ažurirani",
     "updated_upstream_dns_toast": "Uzvodni poslužitelji uspješno su spremljeni",
     "dns_test_ok_toast": "Odabrani DNS poslužitelji su trenutno aktivni",
@@ -275,9 +277,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "ID klijenta",
-    "client_id_placeholder": "Unesite ID klijenta",
-    "client_id_desc": "Različiti klijenti mogu se prepoznati pomoću posebnog ID-a klijenta. <a>Ovdje</a> možete saznati više o tome kako prepoznati klijente.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Unesite ClientID",
+    "client_id_desc": "Različiti klijenti mogu se prepoznati pomoću posebnog ClientID. <a>Ovdje</a> možete saznati više o tome kako prepoznati klijente.",
     "download_mobileconfig_doh": "Preuzmi .mobileconfig za DNS-over-HTTPS",
     "download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS",
     "download_mobileconfig": "Preuzmite konfiguracijsku datoteku",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Unesite ograničenje",
     "rate_limit": "Ograničenje",
     "edns_enable": "Omogući podmrežu klijenta EDNS-a",
-    "edns_cs_desc": "Pošaljite podmreže klijenata DNS poslužiteljima.",
+    "edns_cs_desc": "Dodajte opciju EDNS klijentske podmreže (ECS) uzvodnim zahtjevima i zabilježite vrijednosti koje su klijenti poslali u dnevnik upita.",
     "rate_limit_desc": "Broj zahtjeva u sekundi koji su dopušteni po jednom klijentu. Postavljanje na 0 znači neograničeno.",
     "blocking_ipv4_desc": "Povratna IP adresa za blokirane A zahtjeve",
     "blocking_ipv6_desc": "Povratna IP adresa za blokirane AAAA zahtjeve",
@@ -334,8 +336,8 @@
     "install_devices_router_list_4": "Na nekim se vrstama usmjerivača ne može postaviti prilagođeni DNS poslužitelj. U ovom slučaju, može vam pomoći ako postavite AdGuard Home kao <0>DHCP poslužitelj</0>. U suprotnom, trebali biste potražiti priručnik o tome kako prilagoditi DNS poslužitelje za vaš određeni model routera.",
     "install_devices_windows_list_1": "Otvorite Upravljačku ploču putem Start izbornika ili Windows pretrage.",
     "install_devices_windows_list_2": "Idite na kategoriju Mreža i Internet i odaberite Centar za mreže i zajedničko korištenje.",
-    "install_devices_windows_list_3": "Na lijevoj strani zaslona pronađite \"Promjena postavki prilagodnika\" i kliknite na nju.",
-    "install_devices_windows_list_4": "Odaberite aktivnu vezu, pritisnite desni klik na nju i odaberite Svojstva.",
+    "install_devices_windows_list_3": "Na lijevoj strani zaslona kliknite \"Promjena postavki prilagodnika\".",
+    "install_devices_windows_list_4": "Desnom tipkom miša kliknite svoju aktivnu vezu i odaberite Svojstva.",
     "install_devices_windows_list_5": "Na popisu pronađite \"Internet Protocol Version 4 (TCP/IPv4)\" (ili, za IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), odaberite ga i zatim ponovno kliknite svojstva.",
     "install_devices_windows_list_6": "Odaberite \"Koristi sljedeće adrese DNS poslužitelja\" i unesite adrese AdGuard Home poslužitelja.",
     "install_devices_macos_list_1": "Pritisnite na Apple ikonu i idite u Postavke sustava.",
@@ -356,7 +358,7 @@
     "open_dashboard": "Otvori upravljačku ploču",
     "install_saved": "Uspješno spremljeno",
     "encryption_title": "Šifriranje",
-    "encryption_desc": "Podrška šifriranja (HTTPS/TLS) za DNS i administratorsko web sučelje",
+    "encryption_desc": "Podrška šifriranja (HTTPS/QUIC/TLS) za DNS i administratorsko web sučelje",
     "encryption_config_saved": "Konfiguracija šifriranja spremljena",
     "encryption_server": "Naziv poslužitelja",
     "encryption_server_enter": "Unesite naziv domene",
@@ -367,7 +369,7 @@
     "encryption_https_desc": "Ako je HTTPS port postavljen, AdGuard Home administracijsko sučelje biti će dostupno putem HTTPS-a, a također će pružiti DNS-over-HTTPS na '/dns-query' lokaciji.",
     "encryption_dot": "DNS-over-TLS port",
     "encryption_dot_desc": "Ako je ovaj port postavljen, AdGuard Home će pokrenuti DNS-over-TLS poslužitelj na ovom portu.",
-    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq": "DNS-over-QUIC port (eksperimentalno)",
     "encryption_doq_desc": "Ako je ovaj port postavljen, AdGuard Home će na ovom portu pokrenuti DNS-over-QUIC poslužitelj. Eksperimentalno je i možda nije pouzdano. Također, trenutno nema previše klijenata koji to podržavaju.",
     "encryption_certificates": "Certifikati",
     "encryption_certificates_desc": "Da biste koristili šifriranje, za svoju domenu morate osigurati važeći lanac SSL certifikata. Besplatan certifikat možete dobiti na <0>{{link}}</0> ili ga možete kupiti od jednog od pouzdanih izdavatelja certifikata.",
@@ -389,7 +391,7 @@
     "topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja</0>.",
     "topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.",
     "form_error_port_range": "Unesite broj porta od 80 do 65536",
-    "form_error_port_unsafe": "Ovo je nesigurna port",
+    "form_error_port_unsafe": "Nesigurna port",
     "form_error_equal": "Ne bi trebalo biti jednako",
     "form_error_password": "Lozinka se ne podudara",
     "reset_settings": "Poništi postavke",
@@ -397,15 +399,16 @@
     "setup_guide": "Vodič za postavljanje",
     "dns_addresses": "DNS adrese",
     "dns_start": "Pokreće se DNS poslužitelj",
-    "dns_status_error": "Pogreška pri dohvatu statusa DNS poslužitelja",
+    "dns_status_error": "Pogreška pri provjeravanju statusa DNS poslužitelja",
     "down": "Ne radi",
     "fix": "Popravi",
     "dns_providers": "Ovo je <0>popis poznatih DNS poslužitelja</0> za izbor.",
     "update_now": "Ažuriraj sada",
     "update_failed": "Neuspješno automatsko ažuriranje. Molimo <a>pratite ove korake</a> za ručno ažuriranje.",
+    "manual_update": "Molimo <a>pratite ove korake</a> za ručno ažuriranje.",
     "processing_update": "Molimo pričekajte, AdGuard Home se ažurira",
-    "clients_title": "Klijenti",
-    "clients_desc": "Postavite uređaje povezane na AdGuard Home",
+    "clients_title": "Uporni klijenti",
+    "clients_desc": "Konfigurirajte trajne zapise klijenata za uređaje povezane s AdGuard Home",
     "settings_global": "Globalno",
     "settings_custom": "Prilagođeno",
     "table_client": "Klijent",
@@ -416,7 +419,7 @@
     "client_edit": "Uredi klijenta",
     "client_identifier": "Identifikator",
     "ip_address": "IP adresa",
-    "client_identifier_desc": "Klijenti se mogu identificirati putem IP adrese, CIDR-a, MAC adrese ili posebnog ID-a klijenta (može se koristiti za DoT/DoH/DoQ). <0>Ovdje</0> možete saznati više o tome kako prepoznati klijente.",
+    "client_identifier_desc": "Klijenti se mogu identificirati putem IP adrese, CIDR-a, MAC adrese ili posebnog ClientID (može se koristiti za DoT/DoH/DoQ). <0>Ovdje</0> možete saznati više o tome kako prepoznati klijente.",
     "form_enter_ip": "Unesite IP adresu",
     "form_enter_subnet_ip": "Unesite IP adresu u podmrežu \"{{cidr}}\"",
     "form_enter_mac": "Unesite MAC adresu",
@@ -431,14 +434,14 @@
     "clients_not_found": "Nema pronađenih klijenata",
     "client_confirm_delete": "Jeste li sigurni da želite ukloniti \"{{key}}\" klijenta?",
     "list_confirm_delete": "Jeste li sigurni da želite ukloniti ovaj popis?",
-    "auto_clients_title": "Klijenti (runtime)",
-    "auto_clients_desc": "Podaci na klijentu koji koriste AdGuard Home, ali se ne spremaju u postavke",
+    "auto_clients_title": "Runtime klijenti",
+    "auto_clients_desc": "Podaci na klijentu koji koriste AdGuard Home, ali se ne mijenjaju u postavkama",
     "access_title": "Postavke pristupa",
-    "access_desc": "Postavite pravila pristupa za AdGuard Home DNS poslužitelj.",
+    "access_desc": "Ovdje možete konfigurirati pravila pristupa za AdGuard Home DNS poslužitelj",
     "access_allowed_title": "Dopušteni klijenti",
-    "access_allowed_desc": "Popis CIDR-ova, IP adresa ili ID-ova klijenata. Ako je konfiguriran, AdGuard Home prihvatit će zahtjeve samo tih klijenata.",
+    "access_allowed_desc": "Popis CIDR-ova, IP adresa ili <a>ClientIDs</a>. Ako ovaj popis ima unose, AdGuard Home prihvatit će zahtjeve samo tih klijenata.",
     "access_disallowed_title": "Nedopušteni klijenti",
-    "access_disallowed_desc": "Popis CIDR-ova, IP adresa ili ID-ova klijenata. Ako je konfiguriran, AdGuard Home će odbaciti zahtjeve tih klijenata. Ako su konfigurirani dopušteni klijenti, ovo se polje zanemaruje.",
+    "access_disallowed_desc": "Popis CIDR-ova, IP adresa ili <a>ClientIDs</a>. Ako ovaj popis ima unose, AdGuard Home će odbaciti zahtjeve tih klijenata. Ovo polje se zanemaruje ako postoje unosi u Dopušteni klijenti.",
     "access_blocked_title": "Nedopuštene domene",
     "access_blocked_desc": "Ne smije se miješati s filterima. AdGuard Home ispušta DNS upite koji odgovaraju tim domenama, a ti se upiti čak i ne pojavljuju u zapisniku upita. Možete navesti točne nazive domena, zamjenske znakove ili pravila filtriranja URL-a, npr || example.org example.org. example.org^\" u skladu s tim.",
     "access_settings_saved": "Postavke pristupa su uspješno spremljene",
@@ -499,6 +502,7 @@
     "interval_days": "{{count}} dan",
     "interval_days_plural": "{{count}} dana",
     "domain": "Domena",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Odgovor",
     "filter_added_successfully": "Popis je uspješno dodan",
@@ -531,7 +535,7 @@
     "netname": "Naziv mreže",
     "network": "Mreža",
     "descr": "Opis",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Saznajte više</0> o stvaranju vlastitog popisa poslužitelja.",
     "blocked_by_response": "Blokirano od strane CNAME-a ili IP-a u odgovoru",
     "blocked_by_cname_or_ip": "Blokirao CNAME ili IP",
@@ -551,10 +555,10 @@
     "autofix_warning_list": "Izvodi sljedeće radnje: <0>Deaktiviraj DNSStubListener sustav</0> <0>Postavi adresu DNS poslužitelja na 127.0.0.1</0> <0>Zamijeni simbolički cilj veze iz /etc/resolv.conf u /run/systemd/resolve/resolv.conf</0> <0>Zaustavi DNSStubListener (ponovno pokreni systemd-resolved uslugu)</0>",
     "autofix_warning_result": "Kao rezultat toga, sve DNS zahtjeve iz vašeg sustava će AdGuard Home obraditi prema zadanim postavkama.",
     "tags_title": "Oznake",
-    "tags_desc": "Možete odabrati oznake koje odgovaraju klijentu. Oznake se mogu uključiti u pravila filtriranja i omogućuju vam da ih točnije primijenite. <0>Saznajte više</0>",
+    "tags_desc": "Možete odabrati oznake koje odgovaraju klijentu. Uključite oznake u pravila filtriranja kako biste ih preciznije primijenili. <0>Saznajte više</0>.",
     "form_select_tags": "Odaberite oznake klijenta",
     "check_title": "Provjerite filtriranje",
-    "check_desc": "Provjerite je li naziv računala filtriran",
+    "check_desc": "Provjerite je li naziv hosta filtriran.",
     "check": "Provjeri",
     "form_enter_host": "Unesite naziv računala",
     "filtered_custom_rules": "Filtrirano prilagođenim pravilima filtriranja",
@@ -597,15 +601,15 @@
     "blocklist": "Popis nedopuštenih",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Veličina predmemorije",
-    "cache_size_desc": "Veličina DNS predmemorije (u bajtovima)",
+    "cache_size_desc": "Veličina DNS predmemorije (u bajtovima).",
     "cache_ttl_min_override": "Nadjačaj minimum TTL-a",
     "cache_ttl_max_override": "Nadjačaj maksimum TTL-a",
     "enter_cache_size": "Unesite veličinu predmemorije (u bajtovima)",
     "enter_cache_ttl_min_override": "Unesite minimalni TTL (u sekundama)",
     "enter_cache_ttl_max_override": "Unesite maksimalni TTL (u sekundama)",
-    "cache_ttl_min_override_desc": "Povećajte kratke vrijednosti TTL-a (u sekundama) primljene od upstream poslužitelja prilikom predmemoriranja DNS odgovora",
-    "cache_ttl_max_override_desc": "Postavite maksimalnu vrijednost TTL-a (u sekundama) za zapise u DNS predmemoriju",
-    "ttl_cache_validation": "Minimalna vrijednost TTL predmemorije mora biti manja ili jednaka maksimalnoj vrijednosti",
+    "cache_ttl_min_override_desc": "Povećajte kratke vrijednosti TTL-a (u sekundama) primljene od upstream poslužitelja prilikom predmemoriranja DNS odgovora.",
+    "cache_ttl_max_override_desc": "Postavite maksimalnu vrijednost TTL-a (u sekundama) za zapise u DNS predmemoriju.",
+    "ttl_cache_validation": "Minimalno nadjačavanje TTL-a predmemorije mora biti manje ili jednako maksimalnom",
     "cache_optimistic": "Optimistično predmemoriranje",
     "cache_optimistic_desc": "Učinite da AdGuard Home reagira iz predmemorije čak i kada su unosi istekli i pokušajte ih osvježiti.",
     "filter_category_general": "Općenito",
@@ -626,5 +630,6 @@
     "use_saved_key": "Korištenje prethodno spremljenog ključa",
     "parental_control": "Roditeljska zaštita",
     "safe_browsing": "Sigurno surfanje",
-    "served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>"
+    "served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>",
+    "form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova"
 }
diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json
index 5ffa9d15..a47d069c 100644
--- a/client/src/__locales/hu.json
+++ b/client/src/__locales/hu.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP beállítások sikeresen el lettek mentve",
     "dhcp_ipv4_settings": "DHCP IPv4 Beállítások",
     "dhcp_ipv6_settings": "DHCP IPv6 Beállítások",
-    "form_error_required": "Kötelező mező.",
-    "form_error_ip4_format": "Érvénytelen IPv4 cím.",
-    "form_error_ip4_range_start_format": "Érvénytelen IPv4-cím a tartomány kezdetéhez.",
-    "form_error_ip4_range_end_format": "Érvénytelen IPv4-cím a tartomány végén.",
-    "form_error_ip4_gateway_format": "Az átjáróhoz (gateway) érvénytelen IPv4 cím lett megadva.",
-    "form_error_ip6_format": "Érvénytelen IPv6 cím.",
-    "form_error_ip_format": "Érvénytelen IP-cím.",
-    "form_error_mac_format": "Érvénytelen MAC cím.",
-    "form_error_client_id_format": "A ClientID (kliens azonosító) csak számokat, kisbetűket és kötőjeleket tartalmazhat.",
-    "form_error_server_name": "Érvénytelen szervernév.",
-    "form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet.",
-    "form_error_positive": "0-nál nagyobbnak kell lennie.",
-    "out_of_range_error": "A következő tartományon kívül legyen: \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Kisebb legyen, mint a tartomány kezdete.",
-    "greater_range_start_error": "Nagyobbnak kell lennie, mint a tartomány kezdete.",
-    "greater_range_end_error": "Nagyobb legyen, mint a tartomány vége.",
-    "subnet_error": "A címeknek egy alhálózatban kell lenniük.",
-    "gateway_or_subnet_invalid": "Az alhálózati maszk érvénytelen.",
+    "form_error_required": "Kötelező mező",
+    "form_error_ip4_format": "Érvénytelen IPv4 cím",
+    "form_error_ip4_range_start_format": "Érvénytelen IPv4-cím a tartomány kezdetéhez",
+    "form_error_ip4_range_end_format": "Érvénytelen IPv4-cím a tartomány végén",
+    "form_error_ip4_gateway_format": "Az átjáróhoz (gateway) érvénytelen IPv4 cím lett megadva",
+    "form_error_ip6_format": "Érvénytelen IPv6 cím",
+    "form_error_ip_format": "Érvénytelen IP-cím",
+    "form_error_mac_format": "Érvénytelen MAC cím",
+    "form_error_client_id_format": "A ClientID (kliens azonosító) csak számokat, kisbetűket és kötőjeleket tartalmazhat",
+    "form_error_server_name": "Érvénytelen szervernév",
+    "form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet",
+    "form_error_positive": "0-nál nagyobbnak kell lennie",
+    "out_of_range_error": "A következő tartományon kívül legyen: \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Kisebb legyen, mint a tartomány kezdete",
+    "greater_range_start_error": "Nagyobbnak kell lennie, mint a tartomány kezdete",
+    "greater_range_end_error": "Nagyobb legyen, mint a tartomány vége",
+    "subnet_error": "A címeknek egy alhálózatban kell lenniük",
+    "gateway_or_subnet_invalid": "Az alhálózati maszk érvénytelen",
     "dhcp_form_gateway_input": "Átjáró IP",
     "dhcp_form_subnet_input": "Alhálózati maszk",
     "dhcp_form_range_title": "IP-címek tartománya",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Hosztnév",
     "dhcp_table_expires": "Lejár",
     "dhcp_warning": "Ha engedélyezni szeretné a DHCP-kiszolgálót, ellenőrizze, hogy nincs-e más aktív DHCP-kiszolgáló a hálózaton, mert ez megszakíthatja a hálózati eszközök internetkapcsolatát.",
-    "dhcp_error": "Az AdGuard Home nem tudta megállapítani, hogy van-e másik aktív DHCP-szerver a hálózaton.",
+    "dhcp_error": "Az AdGuard Home nem tudta megállapítani, hogy van-e másik aktív DHCP-szerver a hálózaton",
     "dhcp_static_ip_error": "A DHCP szerver használatához statikus IP-címet kell beállítani. Nem sikerült meghatározni, hogy ez a hálózati interfész statikus IP-cím használatával van-e beállítva. Állítson be kézzel egy statikus IP-címet.",
     "dhcp_dynamic_ip_found": "A rendszer dinamikus IP-cím konfigurációt használ az <0>{{interfaceName}}</0> interfészhez. A DHCP szerver használatához statikus IP-címet kell beállítani. Jelenlegi IP-címe: <0>{{ipAddress}}</0>. Automatikusan beállítjuk ezt az IP címet statikusnak, ha rányom a DHCP engedélyezése gombra.",
     "dhcp_lease_added": "Statikus bérlet \"{{key}}\" sikeresen hozzáadva",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Engedélyezési lista választás",
     "enter_valid_blocklist": "Adjon meg egy érvényes URL-t a blokkolási listához.",
     "enter_valid_allowlist": "Adjon meg egy érvényes URL-t az engedélyezési listához.",
-    "form_error_url_format": "Érvénytelen URL formátum.",
-    "form_error_url_or_path_format": "Helytelen URL vagy abszolút elérési útvonal a listához.",
+    "form_error_url_format": "Érvénytelen URL formátum",
+    "form_error_url_or_path_format": "Helytelen URL vagy abszolút elérési útvonal a listához",
     "custom_filter_rules": "Egyéni szűrési szabályok",
     "custom_filter_rules_hint": "Adjon meg egy szabályt egy sorban. Használhat egyszerű hirdetésblokkolási szabályokat vagy hosztfájl szintaxist.",
     "system_host_files": "Rendszer hosztfájlok",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Ez is egy megjegyzés.",
     "example_regex_meaning": "blokkolja a hozzáférést azokhoz a domainekhez, amik illeszkednek a megadott reguláris kifejezésre.",
     "example_upstream_regular": "hagyományos DNS (UDP felett);",
+    "example_upstream_udp": "normál DNS (UDP felett, hostnév);",
     "example_upstream_dot": "titkosított <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "titkosított <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "titkosított <0>DNS-over-QUIC</0> (kísérleti);",
     "example_upstream_sdns": "<0>DNS Stamps</0> a <1>DNSCrypt</1> vagy <2>DNS-over-HTTPS</2> feloldókhoz;",
     "example_upstream_tcp": "hagyományos DNS (TCP felett);",
+    "example_upstream_tcp_hostname": "normál DNS (TCP felett, hostnév);",
     "all_lists_up_to_date_toast": "Már minden lista naprakész",
     "updated_upstream_dns_toast": "Upstream szerverek sikeresen mentve",
     "dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Használjon \"dupla idézőjelet\" a pontos kereséshez",
     "query_log_retention_confirm": "Biztos benne, hogy megváltoztatja a kérések naplójának megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
     "anonymize_client_ip": "Kliens IP-címének anonimizálása",
-    "anonymize_client_ip_desc": "Ne mentse el a kliens teljes IP-címét a naplókban és a statisztikákban.",
+    "anonymize_client_ip_desc": "Ne mentse el a kliens teljes IP-címét a naplókban és a statisztikákban",
     "dns_config": "DNS szerver beállításai",
     "dns_cache_config": "DNS gyorsítótár beállításai",
-    "dns_cache_config_desc": "Itt tudja konfigurálni a DNS gyorsítótárat.",
+    "dns_cache_config_desc": "Itt tudja konfigurálni a DNS gyorsítótárat",
     "blocking_mode": "Blokkolás módja",
     "default": "Alapértelmezett",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Adja meg a kérések maximális számát",
     "rate_limit": "Kérések korlátozása",
     "edns_enable": "EDNS kliens alhálózat engedélyezése",
-    "edns_cs_desc": "Ha engedélyezve van, az AdGuard Home a kliensek alhálózatait küldi el a DNS-kiszolgálóknak.",
+    "edns_cs_desc": "Adja hozzá az EDNS Client Subnet beállítást (ECS) a felfelé irányuló kérésekhez, és naplózza a kliensek által küldött értékeket a lekérdezési naplóban.",
     "rate_limit_desc": "Maximálisan hány kérést küldhet egy kliens másodpercenkén. Ha 0-ra állítja, akkor nincs korlátozás.",
     "blocking_ipv4_desc": "A blokkolt A kéréshez visszaadandó IP-cím",
     "blocking_ipv6_desc": "A blokkolt AAAA kéréshez visszaadandó IP-cím",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Figyelő felület",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Az AdGuard Home webes admin felülete elérhető a következő címe(ke)n:",
-    "form_error_port": "Adjon meg egy érvényes portot.",
+    "form_error_port": "Adjon meg egy érvényes portot",
     "install_settings_dns": "DNS szerver",
     "install_settings_dns_desc": "Be kell állítania az eszközeit vagy a routerét, hogy használni tudja a DNS szervert a következő címeken:",
     "install_settings_all_interfaces": "Minden felület",
@@ -356,7 +358,7 @@
     "open_dashboard": "Irányítópult megnyitása",
     "install_saved": "Sikeres mentés",
     "encryption_title": "Titkosítás",
-    "encryption_desc": "Titkosítás (HTTPS/TLS) támogatása mind a DNS, mind pedig a webes admin felület számára.",
+    "encryption_desc": "Titkosítás (HTTPS/QUIC/TLS) támogatása mind a DNS, mind pedig a webes admin felület számára",
     "encryption_config_saved": "Titkosítási beállítások mentve",
     "encryption_server": "Szerver neve",
     "encryption_server_enter": "Adja meg az Ön domain címét",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Másolja ki és illessze be ide a tanúsítványa PEM-kódolt privát kulcsát.",
     "encryption_enable": "Titkosítás engedélyezése (HTTPS, DNS-over-HTTPS, és DNS-over-TLS)",
     "encryption_enable_desc": "Ha a titkosítás engedélyezve van, az AdGuard Home admin felülete működik HTTPS-en keresztül, és a DNS szerver is várja a kéréseket DNS-over-HTTPS-en, valamint DNS-over-TLS-en keresztül.",
-    "encryption_chain_valid": "A tanúsítványlánc érvényes.",
-    "encryption_chain_invalid": "A tanúsítványlánc érvénytelen.",
-    "encryption_key_valid": "Ez egy érvényes {{type}} privát kulcs.",
-    "encryption_key_invalid": "Ez egy érvénytelen {{type}} privát kulcs.",
+    "encryption_chain_valid": "A tanúsítványlánc érvényes",
+    "encryption_chain_invalid": "A tanúsítványlánc érvénytelen",
+    "encryption_key_valid": "Ez egy érvényes {{type}} privát kulcs",
+    "encryption_key_invalid": "Ez egy érvénytelen {{type}} privát kulcs",
     "encryption_subject": "Tárgy",
     "encryption_issuer": "Kibocsátó",
     "encryption_hostnames": "Hosztnevek",
     "encryption_reset": "Biztosan visszaállítja a titkosítási beállításokat?",
     "topline_expiring_certificate": "Az SSL-tanúsítványa hamarosan lejár. Frissítse a <0>Titkosítási beállításokat</0>.",
     "topline_expired_certificate": "Az SSL-tanúsítványa lejárt. Frissítse a <0>Titkosítási beállításokat</0>.",
-    "form_error_port_range": "Adjon meg egy portszámot a 80-65535 tartományon belül.",
-    "form_error_port_unsafe": "Ez a port nem biztonságos.",
-    "form_error_equal": "Nem egyezhetnek.",
-    "form_error_password": "A jelszavak nem egyeznek.",
+    "form_error_port_range": "Adjon meg egy portszámot a 80-65535 tartományon belül",
+    "form_error_port_unsafe": "Ez a port nem biztonságos",
+    "form_error_equal": "Nem egyezhetnek",
+    "form_error_password": "A jelszavak nem egyeznek",
     "reset_settings": "Beállítások visszaállítása",
     "update_announcement": "Az AdGuard Home {{version}} verziója elérhető! <0>Kattintson ide</0> további információkért.",
     "setup_guide": "Beállítási útmutató",
     "dns_addresses": "DNS címek",
     "dns_start": "A DNS szerver indul",
-    "dns_status_error": "Hiba történt a DNS szerver állapotának ellenőrzésekor.",
+    "dns_status_error": "Hiba történt a DNS szerver állapotának ellenőrzésekor",
     "down": "Nem elérhető",
     "fix": "Állandó",
     "dns_providers": "Itt van az <0>ismert DNS szolgáltatók listája</0>, amelyekből választhat.",
@@ -406,7 +408,7 @@
     "manual_update": "Kérjük, hogy <a>kövesse ezeket a lépéseket</a> a manuális frissítéshez.",
     "processing_update": "Kérjük várjon, az AdGuard Home frissítése folyamatban van",
     "clients_title": "Fenntartott kliensek",
-    "clients_desc": "Állítsa be az AdGuard Home-ban fenntartott kliens rekordokat az egyes eszközeihez.",
+    "clients_desc": "Állítsa be az AdGuard Home-ban fenntartott kliens rekordokat az egyes eszközeihez",
     "settings_global": "Globális",
     "settings_custom": "Egyéni",
     "table_client": "Kliens",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Biztosan törölni szeretné a(z) \"{{key}}\" klienst?",
     "list_confirm_delete": "Biztosan törölni kívánja ezt a listát?",
     "auto_clients_title": "Futási idejű kliensek",
-    "auto_clients_desc": "Ezek az eszközök nem szerepelnek a fenntartott kliensek listáján, de használják az AdGuard Home-ot.",
+    "auto_clients_desc": "Ezek az eszközök nem szerepelnek a fenntartott kliensek listáján, de használják az AdGuard Home-ot",
     "access_title": "Hozzáférési beállítások",
-    "access_desc": "Itt konfigurálhatja az AdGuard Home DNS-kiszolgáló hozzáférési szabályait.",
+    "access_desc": "Itt konfigurálhatja az AdGuard Home DNS-kiszolgáló hozzáférési szabályait",
     "access_allowed_title": "Engedélyezett kliensek",
     "access_allowed_desc": "CIDR-ek, IP-címek vagy <a>ClientID-k</a> listája. Ha be van állítva, akkor az AdGuard Home csak azokat a lekérdezéseket engedélyezi, amelyek ezektől a kliensektől érkeznek.",
     "access_disallowed_title": "Nem engedélyezett kliensek",
@@ -475,8 +477,8 @@
     "dns_rewrites": "DNS átírások",
     "form_domain": "Adja meg a domain nevet vagy a helyettesítő karaktert",
     "form_answer": "Adjon meg egy IP-címet vagy egy domain nevet",
-    "form_error_domain_format": "Érvénytelen domain formátum.",
-    "form_error_answer_format": "Érvénytelen válasz formátum.",
+    "form_error_domain_format": "Érvénytelen domain formátum",
+    "form_error_answer_format": "Érvénytelen válasz formátum",
     "configure": "Beállítás",
     "main_settings": "Fő beállítások",
     "block_services": "Speciális szolgáltatások blokkolása",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} nap",
     "interval_days_plural": "{{count}} nap",
     "domain": "Domain",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Válasz",
     "filter_added_successfully": "A lista sikeresen hozzá lett adva",
@@ -507,7 +510,7 @@
     "filter_updated": "A lista sikeresen frissítve lett",
     "statistics_configuration": "Statisztikai beállítások",
     "statistics_retention": "Statisztika megőrzése",
-    "statistics_retention_desc": "Ha csökkenti az intervallum értékét, az előtte levő adatok elvesznek.",
+    "statistics_retention_desc": "Ha csökkenti az intervallum értékét, az előtte levő adatok elvesznek",
     "statistics_clear": "Statisztikák visszaállítása",
     "statistics_clear_confirm": "Biztosan vissza akarja állítani a statisztikákat?",
     "statistics_retention_confirm": "Biztos benne, hogy megváltoztatja a statisztika megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Adja meg a maximális TTL-t (másodpercben)",
     "cache_ttl_min_override_desc": "Megnöveli a DNS kiszolgálótól kapott rövid TTL értékeket (másodpercben), ha gyorsítótárazza a DNS kéréseket.",
     "cache_ttl_max_override_desc": "Állítson be egy maximális TTL értéket (másodpercben) a DNS gyorsítótár bejegyzéseihez.",
-    "ttl_cache_validation": "A minimális gyorsítótár TTL értéknek kisebbnek vagy egyenlőnek kell lennie a maximum értékkel.",
+    "ttl_cache_validation": "A minimális gyorsítótár TTL értéknek kisebbnek vagy egyenlőnek kell lennie a maximum értékkel",
     "cache_optimistic": "Optimista gyorsítótár",
     "cache_optimistic_desc": "Lehetővé teszi, hogy az AdGuard Home a gyorsítótárból válaszoljon, még abban az esetben is, ha az ott lévő bejegyzések lejértak, és próbálja meg frissíteni őket.",
     "filter_category_general": "Általános",
@@ -628,5 +631,5 @@
     "parental_control": "Szülői felügyelet",
     "safe_browsing": "Biztonságos böngészés",
     "served_from_cache": "{{value}} <i>(gyorsítótárból kiszolgálva)</i>",
-    "form_error_password_length": "A jelszó legalább {{value}} karakter hosszú kell, hogy legyen."
+    "form_error_password_length": "A jelszó legalább {{value}} karakter hosszú kell, hogy legyen"
 }
diff --git a/client/src/__locales/id.json b/client/src/__locales/id.json
index 58148123..75e87b0d 100644
--- a/client/src/__locales/id.json
+++ b/client/src/__locales/id.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Pengaturan klien",
-    "example_upstream_reserved": "Anda dapat menetapkan DNS upstream <0>untuk domain spesifik</0>",
-    "example_upstream_comment": "Anda dapat menentukan komentar",
+    "example_upstream_reserved": "upstream <0>untuk domain spesifik</0>;",
+    "example_upstream_comment": "komentar.",
     "upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan",
     "parallel_requests": "Permintaan paralel",
     "load_balancing": "Penyeimbang beban",
@@ -43,7 +43,7 @@
     "form_error_ip6_format": "Alamat IPv6 tidak valid",
     "form_error_ip_format": "Alamat IP tidak valid",
     "form_error_mac_format": "Alamat MAC tidak valid",
-    "form_error_client_id_format": "ID Klien hanya boleh berisi angka, huruf kecil, dan tanda hubung",
+    "form_error_client_id_format": "ClientID hanya boleh berisi angka, huruf kecil, dan tanda hubung",
     "form_error_server_name": "Nama server tidak valid",
     "form_error_subnet": "Subnet \"{{cidr}}\" tidak berisi alamat IP \"{{ip}}\"",
     "form_error_positive": "Harus lebih dari 0",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Nama host",
     "dhcp_table_expires": "Kadaluwarsa",
     "dhcp_warning": "Jika anda ingin mengaktifkan server DHCP bawaan, pastikan tidak ada server DHCP lain yang aktif. Jika tidak, akan memutus koneksi internet pada perangkat yang telah terhubung!",
-    "dhcp_error": "AdGuard Home tidak dapat menentukan apakah ada server DHCP aktif lain pada jaringan.",
+    "dhcp_error": "AdGuard Home tidak dapat menentukan apakah ada server DHCP aktif lain pada jaringan",
     "dhcp_static_ip_error": "Jika ingin menggunakan server DHCP, alamat IP statis harus diatur. AdGuard Home gagal menentukan jika antarmuka jaringan ini dikonfigurasi menggunakan alamat IP statis. Silakan atur alamat IP statis secara manual.",
     "dhcp_dynamic_ip_found": "Sistem Anda menggunakan konfigurasi alamat IP dinamis untuk antarmuka <0>{{interfaceName}}</0>. Untuk menggunakan server DHCP, alamat IP statis harus ditetapkan. Alamat IP Anda saat ini adalah <0>{{ipAddress}}</0>. AdGuard Home akan secara otomatis menetapkan alamat IP ini sebagai statis jika Anda menekan tombol Aktifkan DHCP.",
     "dhcp_lease_added": "Static lease \"{{key}}\" berhasil ditambahkan",
@@ -202,19 +202,21 @@
     "custom_filter_rules_hint": "Masukkan satu aturan dalam sebuah baris. Anda dapat menggunakan baik aturan adblock maupun sintaks file hosts.",
     "system_host_files": "File host sistem",
     "examples_title": "Contoh",
-    "example_meaning_filter_block": "Blokir akses ke example.org dan seluruh subdomainnya",
-    "example_meaning_filter_whitelist": "Buka blokir akses ke domain example.orf dan seluruh subdomainnya",
-    "example_meaning_host_block": "AdGuard Home sekarang akan mengembalikan alamat 127.0.0.1  untuk domain example.org (namun tidak subdomainnya)",
-    "example_comment": "! Komentar di sini",
-    "example_comment_meaning": "hanya sebuah komentar",
-    "example_comment_hash": "Juga sebuah komentar",
-    "example_regex_meaning": "blokir akses ke domain yang cocok dengan <0>ekspresi reguler yang ditentukan</0>",
-    "example_upstream_regular": "DNS reguler (melalui UDP)",
-    "example_upstream_dot": "terenkripsi <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "terenkripsi <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "terenkripsi <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "anda bisa menggunakan <0>Stempel DNS</0> untuk <1>DNSCrypt</1> atau pengarah <2>DNS-over-HTTPS</2>",
-    "example_upstream_tcp": "DNS reguler (melalui TCP)",
+    "example_meaning_filter_block": "blokir akses ke example.org dan seluruh subdomainnya;",
+    "example_meaning_filter_whitelist": "buka blokir akses ke domain example.orf dan seluruh subdomainnya;",
+    "example_meaning_host_block": "merespons dengan 127.0.0.1 untuk example.org (tetapi tidak untuk subdomainnya);",
+    "example_comment": "! Komentar di sini.",
+    "example_comment_meaning": "hanya sebuah komentar;",
+    "example_comment_hash": "# Juga sebuah komentar.",
+    "example_regex_meaning": "blokir akses ke domain yang cocok dengan ekspresi reguler yang ditentukan.",
+    "example_upstream_regular": "DNS reguler (melalui UDP);",
+    "example_upstream_udp": "DNS biasa (lebih dari UDP, nama host);",
+    "example_upstream_dot": "terenkripsi <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "terenkripsi <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "terenkripsi <0>DNS-over-QUIC</0> (eksperimental);",
+    "example_upstream_sdns": "<0>Stempel DNS</0> untuk <1>DNSCrypt</1> atau pengarah <2>DNS-over-HTTPS</2>;",
+    "example_upstream_tcp": "DNS reguler (melalui TCP);",
+    "example_upstream_tcp_hostname": "DNS biasa (lebih dari TCP, nama host);",
     "all_lists_up_to_date_toast": "Semua daftar sudah diperbarui",
     "updated_upstream_dns_toast": "Server upstream berhasil disimpan",
     "dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar",
@@ -275,9 +277,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "ID Klien",
-    "client_id_placeholder": "Masukkan ID klien",
-    "client_id_desc": "Klien yang berbeda dapat diidentifikasi dengan ID klien khusus. <a>Di sini</a> Anda dapat mempelajari lebih lanjut tentang cara mengidentifikasi klien.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Masukkan ClientID",
+    "client_id_desc": "Klien dapat diidentifikasi oleh ClientID. Pelajari lebih lanjut tentang cara mengidentifikasi klien <a>di sini</a>.",
     "download_mobileconfig_doh": "Unduh .mobileconfig untuk DNS-over-HTTPS",
     "download_mobileconfig_dot": "Unduh .mobileconfig untuk DNS-over-TLS",
     "download_mobileconfig": "Unduh berkas konfigurasi",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Masukkan batas nilai",
     "rate_limit": "Batas nilai",
     "edns_enable": "Aktifkan EDNS Klien Subnet",
-    "edns_cs_desc": "Kirim subnet klien ke server DNS.",
+    "edns_cs_desc": "Tambahkan opsi EDNS Client Subnet (ECS) ke permintaan upstream dan catat nilai yang dikirim oleh klien di log kueri.",
     "rate_limit_desc": "Jumlah permintaan per detik yang diperbolehkan untuk satu klien. Atur ke 0 untuk tidak terbatas.",
     "blocking_ipv4_desc": "Alamat IP akan dikembalikan untuk permintaan A yang diblokir",
     "blocking_ipv6_desc": "Alamat IP akan dipulihkan untuk permintaan AAAA yang diblokir",
@@ -334,12 +336,12 @@
     "install_devices_router_list_4": "Anda tidak dapat menyetel server DNS kustom pada beberapa tipe router. Dalam hal ini mungkin membantu jika Anda mengatur AdGuard Home sebagai <0>server DHCP</0>. Jika tidak, Anda harus mencari petunjuk tentang cara mengkustomisasi server DNS untuk model router khusus Anda.",
     "install_devices_windows_list_1": "Buka Panel Kontrol melalui menu Start atau pencarian Windows.",
     "install_devices_windows_list_2": "Masuk ke kategori Jaringan dan Internet (Network and Internet) dan kemudian ke Pusat Jaringan dan Berbagi (Network and Sharing Center).",
-    "install_devices_windows_list_3": "Di sisi kiri layar temukan \"Ubah pengaturan adaptor\" dan klik di atasnya.",
-    "install_devices_windows_list_4": "Pilih koneksi aktif Anda, klik kanan padanya dan pilih Properties.",
+    "install_devices_windows_list_3": "Di panel kiri, klik \"Ubah pengaturan adaptor\".",
+    "install_devices_windows_list_4": "Klik kanan koneksi aktif Anda dan pilih Properties.",
     "install_devices_windows_list_5": "Temukan \"Internet Protocol Version 4 (TCP/IPv4)\" (atau, untuk IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\") dalam daftar, pilih dan kemudian klik Properties lagi.",
     "install_devices_windows_list_6": "Pilih \"Gunakan alamat server DNS berikut\" dan masukkan alamat server Beranda AdGuard Anda.",
-    "install_devices_macos_list_1": "Klik pada ikon Apple dan pergi ke System Preferences.",
-    "install_devices_macos_list_2": "Klik pada Jaringan (Network)",
+    "install_devices_macos_list_1": "Klik ikon Apple dan pergi ke System Preferences.",
+    "install_devices_macos_list_2": "Klik Network.",
     "install_devices_macos_list_3": "Pilih koneksi pertama dalam daftar dan klik Advanced.",
     "install_devices_macos_list_4": "Pilih tab DNS dan masukkan alamat server AdGuard Anda.",
     "install_devices_android_list_1": "Dari layar beranda Menu Android, ketuk Pengaturan.",
@@ -356,7 +358,7 @@
     "open_dashboard": "Buka Beranda",
     "install_saved": "Berhasil disimpan",
     "encryption_title": "Enkripsi",
-    "encryption_desc": "Enkripsi (HTTPS / TLS) untuk DNS dan antarmuka admin",
+    "encryption_desc": "Enkripsi (HTTPS/QUIC/TLS) untuk DNS dan antarmuka admin",
     "encryption_config_saved": "Pengaturan enkripsi telah tersimpan",
     "encryption_server": "Nama server",
     "encryption_server_enter": "Masukkan nama domain anda",
@@ -367,7 +369,7 @@
     "encryption_https_desc": "Jika port HTTPS dikonfigurasi, antarmuka admin Home AdGuard akan dapat diakses melalui HTTPS, dan itu juga akan memberikan DNS-over-HTTPS di lokasi '/ dns-query'.",
     "encryption_dot": "Port DNS-over-TLS",
     "encryption_dot_desc": "Jika port ini terkonfigurasi, AdGuard Home akan menjalankan server DNS-over-TLS dalam port ini",
-    "encryption_doq": "Port DNS-lewat-QUIC",
+    "encryption_doq": "Port DNS-over-QUIC (eksperimental)",
     "encryption_doq_desc": "Jika port ini diatur secara sepesifik, AdGuard Home akan menjalankan server DNS-lewat-QUIC pada port ini. Ini adalah eksperimental dan mungkin tidak dapat diandalkan. Juga, tidak banyak klien yang mendukungnya saat ini.",
     "encryption_certificates": "Sertifikat",
     "encryption_certificates_desc": "Untuk menggunakan enkripsi, Anda perlu memberikan rantai sertifikat SSL yang valid untuk domain Anda. Anda bisa mendapatkan sertifikat gratis di <0>{{link}}</0> atau Anda dapat membelinya dari salah satu Otoritas Sertifikat tepercaya.",
@@ -403,9 +405,10 @@
     "dns_providers": "Berikut adalah <0>daftar penyedia DNS yang dikenal</0> untuk dipilih.",
     "update_now": "Perbarui sekarang",
     "update_failed": "Pembaruan otomatis gagal. Silahkan <a>ikuti petunjuk ini</a> untuk perbarui secara manual.",
+    "manual_update": "Silakan <a>mengikuti langkah berikut</a> untuk memperbarui secara manual.",
     "processing_update": "Silahkan tunggu, AdGuard Home sedang diperbarui",
-    "clients_title": "Klien",
-    "clients_desc": "Atur perangkat yang terhubung ke AdGuard Home",
+    "clients_title": "Klien yang gigih",
+    "clients_desc": "Konfigurasikan catatan klien persisten untuk perangkat yang terhubung ke AdGuard Home",
     "settings_global": "Global",
     "settings_custom": "Kustom",
     "table_client": "Klien",
@@ -416,7 +419,7 @@
     "client_edit": "Ubah Klien",
     "client_identifier": "Identifikasi",
     "ip_address": "Alamat IP",
-    "client_identifier_desc": "Klien dapat diidentifikasi oleh alamat IP, CIDR, alamat MAC atau ID klien khusus (dapat digunakan untuk DoT/DoH/DoQ). <0>Di sini</0> Anda dapat mempelajari lebih lanjut tentang cara mengidentifikasi klien.",
+    "client_identifier_desc": "Klien dapat diidentifikasi oleh alamat IP, CIDR, alamat MAC atau ClientID (dapat digunakan untuk DoT/DoH/DoQ). <0>Di sini</0> Anda dapat mempelajari lebih lanjut tentang cara mengidentifikasi klien.",
     "form_enter_ip": "Masukkan IP",
     "form_enter_subnet_ip": "Masukkan alamat IP di subnet \"{{cidr}}\"",
     "form_enter_mac": "Masukkan MAC",
@@ -432,13 +435,13 @@
     "client_confirm_delete": "Apakah anda yakin ingin menghapus klien \"{{key}}\"?",
     "list_confirm_delete": "Anda yakin ingin menghapus daftar ini?",
     "auto_clients_title": "Klien (waktu berjalan)",
-    "auto_clients_desc": "Data pada klien yang menggunakan AdGuard Home, tetapi tidak disimpan dalam konfigurasi",
+    "auto_clients_desc": "Perangkat yang tidak ada dalam daftar klien Persisten yang mungkin masih menggunakan AdGuard Home",
     "access_title": "Pengaturan akses",
-    "access_desc": "Disini anda dapat mengatur aturan akses untuk server AdGuard Home DNS.",
+    "access_desc": "Disini anda dapat mengatur aturan akses untuk server AdGuard Home DNS",
     "access_allowed_title": "Klien yang diizinkan",
-    "access_allowed_desc": "Daftar CIDR, alamat IP, atau ID klien. Jika dikonfigurasi, AdGuard Home hanya akan menerima permintaan dari klien ini.",
+    "access_allowed_desc": "Daftar CIDR, alamat IP, atau <a>ClientID</a>. Jika daftar ini memiliki entri, AdGuard Home hanya akan menerima permintaan dari klien ini.",
     "access_disallowed_title": "Klien yang tidak diizinkan",
-    "access_disallowed_desc": "Daftar CIDR, alamat IP, atau ID klien. Jika dikonfigurasi, AdGuard Home akan menghapus permintaan dari klien ini. Jika klien yang diizinkan dikonfigurasi, bidang ini diabaikan.",
+    "access_disallowed_desc": "Daftar CIDR, alamat IP, atau <a>ClientID</a>. Jika daftar ini memiliki entri, AdGuard Home akan membatalkan permintaan dari klien ini. Bidang ini diabaikan jika ada entri di klien yang diizinkan.",
     "access_blocked_title": "Domain yang diblokir",
     "access_blocked_desc": "Jangan bingung dengan filter. AdGuard Home menghapus kueri DNS yang cocok dengan domain ini, dan kueri ini bahkan tidak muncul di log kueri. Anda dapat menentukan nama domain, karakter pengganti, atau aturan filter URL yang tepat, mis. \"example.org\", \"*.example.org\", atau \"||example.org^\" yang sesuai.",
     "access_settings_saved": "Pengaturan akses berhasil disimpan",
@@ -474,7 +477,7 @@
     "dns_rewrites": "DNS rewrite",
     "form_domain": "Masukkan nama domain",
     "form_answer": "Masaukan alamat IP atau nama domain",
-    "form_error_domain_format": "Nama domain tidak valid",
+    "form_error_domain_format": "Format domain tidak valid",
     "form_error_answer_format": "Format jawaban tidak valid",
     "configure": "Konfigurasi",
     "main_settings": "Pengaturan utama",
@@ -499,6 +502,7 @@
     "interval_days": "{{count}} hari",
     "interval_days_plural": "{{count}} hari",
     "domain": "Domain",
+    "ecs": "ECS",
     "punycode": "Kode kecil",
     "answer": "Jawab",
     "filter_added_successfully": "Filter telah berhasil ditambahkan",
@@ -531,7 +535,7 @@
     "netname": "Nama jaringan",
     "network": "Jaringan",
     "descr": "Deskripsi",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Pelajari lebih lanjut</0> tentang membuat daftar hitam host Anda sendiri.",
     "blocked_by_response": "Diblokir oleh CNAME atau IP sebagai respon",
     "blocked_by_cname_or_ip": "Diblokir oleh CNAME atau IP",
@@ -551,10 +555,10 @@
     "autofix_warning_list": "Ini akan melakukan tugas berikut: <0>Nonaktifkan sistem DNSStubListener</0> <0> Atur alamat server DNS ke 127.0.0.1</0> <0>Ganti target tautan simbolis /etc/resolv.conf pakai /run/systemd/resolve/resolv.conf</0> <0>Hentikan DNSStubListener (muat ulang layanan sistemd-resolve service)</0>",
     "autofix_warning_result": "Hasilnya, semua permintaan DNS dari sistem anda akan diproses oleh AdGuardHome secara standar.",
     "tags_title": "Tag",
-    "tags_desc": "Anda dapat memilih tag sesuai dengan klien. Tag dapat dimasukkan dalam aturan pemfilteran dan memungkinkan Anda untuk menerapkannya lebih akurat. <0>Pelajari lebih</0>",
+    "tags_desc": "Anda dapat memilih tag sesuai dengan klien. Tag dapat dimasukkan dalam aturan pemfilteran dan memungkinkan Anda untuk menerapkannya lebih akurat. <0>Pelajari lebih</0>.",
     "form_select_tags": "Pilih tag klien",
     "check_title": "Periksa penyaringan",
-    "check_desc": "Periksa apakah nama host telah tersaring",
+    "check_desc": "Periksa apakah nama host telah tersaring.",
     "check": "Periksa",
     "form_enter_host": "Masukkan nama host",
     "filtered_custom_rules": "Tersaring oleh aturan penyaring Buatan",
@@ -593,18 +597,18 @@
     "allowed": "Dibolehkan",
     "filtered": "Tersaring",
     "rewritten": "Tulis ulang",
-    "safe_search": "Aktifkan Pencarian Aman",
+    "safe_search": "Pencarian aman",
     "blocklist": "Daftar blokir",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Ukuran cache",
-    "cache_size_desc": "Ukuran DNS cache (dalam bit)",
+    "cache_size_desc": "Ukuran DNS cache (dalam bit).",
     "cache_ttl_min_override": "Tumpuk TTL minimum",
     "cache_ttl_max_override": "Tumpuk TTL maksimum",
     "enter_cache_size": "Masukkan ukuran cache (bytes)",
     "enter_cache_ttl_min_override": "Masukkan TTL minimum (detik)",
     "enter_cache_ttl_max_override": "Masukkan TTL maksimum (detik)",
-    "cache_ttl_min_override_desc": "Perpanjang nilai waktu-online singkat (detik) yang diterima dari server upstream saat menyimpan respons DNS",
-    "cache_ttl_max_override_desc": "Tetapkan nilai waktu-online maksimum (detik) untuk entri di cache DNS",
+    "cache_ttl_min_override_desc": "Perpanjang nilai waktu-online singkat (detik) yang diterima dari server upstream saat menyimpan respons DNS.",
+    "cache_ttl_max_override_desc": "Tetapkan nilai waktu-online maksimum (detik) untuk entri di cache DNS.",
     "ttl_cache_validation": "Nilai TTL cache minimum harus kurang dari atau sama dengan nilai maksimum",
     "cache_optimistic": "Caching yang optimis",
     "cache_optimistic_desc": "Buat AdGuard Home merespons dari cache bahkan ketika entri telah kedaluwarsa dan juga mencoba untuk menyegarkannya.",
@@ -626,5 +630,6 @@
     "use_saved_key": "Gunakan kunci yang disimpan sebelumnya",
     "parental_control": "Kontrol Orang Tua",
     "safe_browsing": "Penjelajahan Aman",
-    "served_from_cache": "{{value}} <i>(disajikan dari cache)</i>"
+    "served_from_cache": "{{value}} <i>(disajikan dari cache)</i>",
+    "form_error_password_length": "Kata sandi harus minimal {{value}} karakter"
 }
diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json
index fd560562..cd499740 100644
--- a/client/src/__locales/it.json
+++ b/client/src/__locales/it.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Salvataggio configurazione server DHCP riuscito",
     "dhcp_ipv4_settings": "Impostazioni DHCP IPv4",
     "dhcp_ipv6_settings": "Impostazioni DHCP IPv6",
-    "form_error_required": "Campo richiesto.",
-    "form_error_ip4_format": "Indirizzo IPv4 non valido.",
-    "form_error_ip4_range_start_format": "Indirizzo IPV4 non valido dell'intervallo iniziale.",
-    "form_error_ip4_range_end_format": "Indirizzo IPV4 non valido dell'intervallo finale.",
-    "form_error_ip4_gateway_format": "Indirizzo gateway IPv4 non valido.",
-    "form_error_ip6_format": "Indirizzo IPv6 non valido.",
-    "form_error_ip_format": "Indirizzo IP non valido.",
-    "form_error_mac_format": "Indirizzo MAC non valido.",
-    "form_error_client_id_format": "Il ClientID deve contenere solo numeri, lettere minuscole, e trattini.",
-    "form_error_server_name": "Nome server non valido.",
-    "form_error_subnet": "Il subnet \"{{cidr}}\" non contiene l'indirizzo IP \"{{ip}}\".",
-    "form_error_positive": "Deve essere maggiore di 0.",
-    "out_of_range_error": "Deve essere fuori intervallo \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Deve essere inferiore dell'intervallo di inizio.",
-    "greater_range_start_error": "Deve essere maggiore dell'intervallo di inizio.",
-    "greater_range_end_error": "Deve essere maggiore dell'intervallo di fine.",
-    "subnet_error": "Gli indirizzi devono trovarsi in una sottorete.",
-    "gateway_or_subnet_invalid": "Maschera di sottorete non valida.",
+    "form_error_required": "Campo richiesto",
+    "form_error_ip4_format": "Indirizzo IPv4 non valido",
+    "form_error_ip4_range_start_format": "Indirizzo IPV4 non valido dell'intervallo iniziale",
+    "form_error_ip4_range_end_format": "Indirizzo IPV4 non valido dell'intervallo finale",
+    "form_error_ip4_gateway_format": "Indirizzo gateway IPv4 non valido",
+    "form_error_ip6_format": "Indirizzo IPv6 non valido",
+    "form_error_ip_format": "Indirizzo IP non valido",
+    "form_error_mac_format": "Indirizzo MAC non valido",
+    "form_error_client_id_format": "Il ClientID deve contenere solo numeri, lettere minuscole, e trattini",
+    "form_error_server_name": "Nome server non valido",
+    "form_error_subnet": "Il subnet \"{{cidr}}\" non contiene l'indirizzo IP \"{{ip}}\"",
+    "form_error_positive": "Deve essere maggiore di 0",
+    "out_of_range_error": "Deve essere fuori intervallo \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Deve essere inferiore dell'intervallo di inizio",
+    "greater_range_start_error": "Deve essere maggiore dell'intervallo di inizio",
+    "greater_range_end_error": "Deve essere maggiore dell'intervallo di fine",
+    "subnet_error": "Gli indirizzi devono trovarsi in una sottorete",
+    "gateway_or_subnet_invalid": "Maschera di sottorete non valida",
     "dhcp_form_gateway_input": "IP Gateway",
     "dhcp_form_subnet_input": "Maschera di sottorete",
     "dhcp_form_range_title": "Intervallo di indirizzi IP",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Nome host",
     "dhcp_table_expires": "Scaduto",
     "dhcp_warning": "Se desideri attivare il server DHCP integrato, assicurati che non vi siano altri server DHCP attivi, ciò potrebbe causare problemi di connessione alla rete per i dispositivi collegati!",
-    "dhcp_error": "AdGuard Home non può determinare se è presente un altro server DHCP attivo nella rete.",
+    "dhcp_error": "AdGuard Home non può determinare se è presente un altro server DHCP attivo nella rete",
     "dhcp_static_ip_error": "Per utilizzare il server DHCP è necessario impostare un indirizzo IP statico. AdGuard Home non è riuscito a determinare se questa interfaccia di rete è configurata utilizzando un indirizzo IP statico. Ti preghiamo di impostare manualmente un indirizzo IP statico.",
     "dhcp_dynamic_ip_found": "Il tuo sistema utilizza una configurazione di indirizzi IP dinamici per l'interfaccia <0>{{interfaceName}}</0>. Per poter utilizzare un server DHCP, è necessario impostare un indirizzo IP statico. Il tuo indirizzo IP attuale è <0>{{ipAddress}}</0>. AdGuard Home imposterà automaticamente questo indirizzo come statico quando cliccherai il pulsante \"Attiva server DHCP\".",
     "dhcp_lease_added": "Lease statici \"{{key}}\" aggiunti correttamente",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Scegli liste bianche",
     "enter_valid_blocklist": "Inserisci un URL valido alla lista nera.",
     "enter_valid_allowlist": "Inserisci un URL valido alla lista bianca.",
-    "form_error_url_format": "Formato URL non valido.",
-    "form_error_url_or_path_format": "URL o percorso assoluto dell'elenco non validi.",
+    "form_error_url_format": "Formato URL non valido",
+    "form_error_url_or_path_format": "URL o percorso assoluto dell'elenco non validi",
     "custom_filter_rules": "Regole filtri personalizzate",
     "custom_filter_rules_hint": "Inserisci una regola per riga. Puoi utilizzare la sintassi delle regole blocca-annunci o quelle dei file hosts.",
     "system_host_files": "File host di sistema",
@@ -209,12 +209,14 @@
     "example_comment_meaning": "solo un commento;",
     "example_comment_hash": "# Anche un commento.",
     "example_regex_meaning": "blocca l'accesso ai domini corrispondenti alla specifica espressione regolare.",
-    "example_upstream_regular": "DNS regolari (tramite UDP);",
+    "example_upstream_regular": "DNS regolare (over UDP);",
+    "example_upstream_udp": "DNS regolare (over UDP, nome host);",
     "example_upstream_dot": "<0>DNS su TLS</0> crittografato;",
     "example_upstream_doh": "<0>DNS su HTTPS</0> crittografato;",
     "example_upstream_doq": "<0>DNS su QUIC</0> crittografato (sperimentale);",
     "example_upstream_sdns": "<0>DNS Stamps</0> per <1>DNSCrypt</1> oppure i risolutori <2>DNS su HTTPS</2>;",
-    "example_upstream_tcp": "DNS regolari (tramite TCP);",
+    "example_upstream_tcp": "DNS regolare (over TCP);",
+    "example_upstream_tcp_hostname": "DNS regolare (over TCP, nome host);",
     "all_lists_up_to_date_toast": "Tutti gli elenchi sono aggiornati",
     "updated_upstream_dns_toast": "I server upstream sono stati salvati correttamente",
     "dns_test_ok_toast": "I server DNS specificati funzionano correttamente",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Utilizzare le doppie virgolette per una ricerca precisa",
     "query_log_retention_confirm": "Sei sicuro di voler modificare il registro delle richieste? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi",
     "anonymize_client_ip": "Anonimizza client IP",
-    "anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro o nelle statistiche.",
+    "anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro o nelle statistiche",
     "dns_config": "Configurazione server DNS",
     "dns_cache_config": "Configurazione cache DNS",
-    "dns_cache_config_desc": "Qui puoi configurare la cache DNS.",
+    "dns_cache_config_desc": "Qui puoi configurare la cache DNS",
     "blocking_mode": "Modalità di blocco",
     "default": "Predefinito",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Imposta limite delle richieste",
     "rate_limit": "Limite delle richieste",
     "edns_enable": "Attiva client di sottorete EDNS",
-    "edns_cs_desc": "Invia le sottoreti dei client ai server DNS.",
+    "edns_cs_desc": "Aggiunge l'opzione EDNS Client Subnet (ECS) alle richieste upstream e registra i valori inviati dai client nel registro delle richieste.",
     "rate_limit_desc": "Il numero di richieste al secondo consentite da un singolo client. Impostare questo valore a 0 rimuove le limitazioni.",
     "blocking_ipv4_desc": "Indirizzo IP per una richiesta DNS IPv4 bloccata",
     "blocking_ipv6_desc": "Indirizzo IP restituito per una richiesta DNS IPv6 bloccata",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Interfaccia d'ascolto",
     "install_settings_port": "Porta",
     "install_settings_interface_link": "La tua interfaccia web di amministrazione di AdGuard Home sarà disponibile ai seguenti indirizzi:",
-    "form_error_port": "Immettere un valore di porta valido.",
+    "form_error_port": "Immettere un valore di porta valido",
     "install_settings_dns": "Server DNS",
     "install_settings_dns_desc": "Sarà necessario configurare i dispositivi o il router per utilizzare il server DNS nei seguenti indirizzi:",
     "install_settings_all_interfaces": "Tutte le interfacce",
@@ -356,7 +358,7 @@
     "open_dashboard": "Apri pannello di controllo",
     "install_saved": "Salvataggio riuscito",
     "encryption_title": "crittografia",
-    "encryption_desc": "Supporto alla crittografia (HTTPS / TLS) per DNS ed interfaccia web amministrazione.",
+    "encryption_desc": "Supporto alla crittografia (HTTPS/QUIC/TLS) per DNS ed interfaccia web di amministrazione",
     "encryption_config_saved": "Configurazione crittografia salvata",
     "encryption_server": "Nome server",
     "encryption_server_enter": "Inserisci il tuo nome di dominio",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Copia/Incolla qui la tua chiave privata codificata PEM per il tuo certificato.",
     "encryption_enable": "Attiva crittografia (HTTPS, DNS su HTTPS e DNS su TLS)",
     "encryption_enable_desc": "Se la crittografia è attiva, l'interfaccia di amministrazione di AdGuard Home funzionerà su HTTPS e il server DNS ascolterà le richieste su DNS su HTTPS e DNS su TLS.",
-    "encryption_chain_valid": "La catena di certificati è valida.",
-    "encryption_chain_invalid": "La catena di certificati non è valida.",
-    "encryption_key_valid": "Questa è una chiave privata {{type}} valida.",
-    "encryption_key_invalid": "Questa è una chiave privata {{type}} non valida.",
+    "encryption_chain_valid": "La catena di certificati è valida",
+    "encryption_chain_invalid": "La catena di certificati non è valida",
+    "encryption_key_valid": "Questa è una chiave privata {{type}} valida",
+    "encryption_key_invalid": "Questa è una chiave privata {{type}} non valida",
     "encryption_subject": "Soggetto",
     "encryption_issuer": "Emittente",
     "encryption_hostnames": "Nomi host",
     "encryption_reset": "Sei sicuro di voler ripristinare le impostazioni di crittografia?",
     "topline_expiring_certificate": "Il tuo certificato SSL sta per scadere. Aggiorna le<0> Impostazioni di crittografia </ 0>.",
     "topline_expired_certificate": "Il tuo certificato SSL è scaduto. Aggiorna le <0> Impostazioni di crittografia </ 0>.",
-    "form_error_port_range": "Immettere il valore della porta nell'intervallo 80-65535.",
-    "form_error_port_unsafe": "Questa è una porta non sicura.",
-    "form_error_equal": "Non deve essere uguale.",
-    "form_error_password": "Password non corrispondente.",
+    "form_error_port_range": "Immettere il valore della porta nell'intervallo 80-65535",
+    "form_error_port_unsafe": "Questa porta non è sicura",
+    "form_error_equal": "Non deve essere uguale",
+    "form_error_password": "Password non corrispondente",
     "reset_settings": "Reimposta impostazioni",
     "update_announcement": "AdGuard Home {{version}} è ora disponibile! <0>Clicca qui</0> per più informazioni.",
     "setup_guide": "Configurazione guidata",
     "dns_addresses": "Indirizzo DNS",
     "dns_start": "Il server DNS si sta avviando",
-    "dns_status_error": "Errore nel recupero dello stato del server DNS.",
+    "dns_status_error": "Errore nel recupero dello stato del server DNS",
     "down": "Spenta",
     "fix": "Risolvi",
     "dns_providers": "Qui c'è un <0>elenco di fornitori DNS noti</0> da cui scegliere.",
@@ -406,7 +408,7 @@
     "manual_update": "Ti invitiamo a <a>seguire questi passaggi</a> per aggiornare manualmente.",
     "processing_update": "Perfavore aspetta, AdGuard Home si sta aggiornando",
     "clients_title": "Client persistenti",
-    "clients_desc": "Configura le registrazioni dei client persistenti per i dispositivi connessi ad AdGuard Home.",
+    "clients_desc": "Configura le registrazioni dei client persistenti per i dispositivi connessi ad AdGuard Home",
     "settings_global": "Globale",
     "settings_custom": "Personalizzato",
     "table_client": "Client",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Sei sicuro di voler eliminare il client \"{{key}}\"?",
     "list_confirm_delete": "Sei sicuro di voler eliminare questo elenco?",
     "auto_clients_title": "Client in tempo reale",
-    "auto_clients_desc": "Dispositivi non presenti nell'elenco dei client Persistenti che possono ancora utilizzare AdGuard Home.",
+    "auto_clients_desc": "Dispositivi non presenti nell'elenco dei client Persistenti che possono ancora utilizzare AdGuard Home",
     "access_title": "Impostazioni di accesso",
-    "access_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home.",
+    "access_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home",
     "access_allowed_title": "Client permessi",
     "access_allowed_desc": "Un elenco di CIDR, indirizzi IP, o <a>ClientID</a>. Se l'elenco conterrà elementi, AdGuard Home accetterà richieste solo da questi client.",
     "access_disallowed_title": "Client non permessi",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Riscrittura DNS",
     "form_domain": "Inserisci il dominio",
     "form_answer": "Inserisci l'indirizzo IP o il nome del dominio",
-    "form_error_domain_format": "Formato del dominio non valido.",
-    "form_error_answer_format": "Formato di risposta non valido.",
+    "form_error_domain_format": "Formato del dominio non valido",
+    "form_error_answer_format": "Formato di risposta non valido",
     "configure": "Configura",
     "main_settings": "Impostazioni principali",
     "block_services": "Blocca servizi specifici",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} giorni",
     "interval_days_plural": "{{count}} giorni",
     "domain": "Dominio",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Risposta",
     "filter_added_successfully": "L'elenco è stato aggiunto correttamente",
@@ -507,7 +510,7 @@
     "filter_updated": "L'elenco è stato aggiornato correttamente",
     "statistics_configuration": "Configurazione delle statistiche",
     "statistics_retention": "Conservazione delle statistiche",
-    "statistics_retention_desc": "Se dovessi diminuire il valore di intervallo, alcuni dati andranno persi.",
+    "statistics_retention_desc": "Se dovessi diminuire il valore di intervallo, alcuni dati andranno persi",
     "statistics_clear": "Azzera statistiche",
     "statistics_clear_confirm": "Sei sicuro di voler azzerare le statistiche?",
     "statistics_retention_confirm": "Sei sicuro di voler modificare la conservazione delle statistiche? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi",
@@ -517,7 +520,7 @@
     "interval_hours_plural": "{{count}} ore",
     "filters_configuration": "Configurazione filtri",
     "filters_enable": "Attiva i filtri",
-    "filters_interval": "Intervallo aggiornamento filtri",
+    "filters_interval": "Intervallo aggiornamento filtro",
     "disabled": "Disattivato",
     "username_label": "Nome utente",
     "username_placeholder": "Inserisci nome utente",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Immetti TTL massimo (in secondi)",
     "cache_ttl_min_override_desc": "Estende i valori di breve durata (in secondi) ricevuti dal server upstream durante la memorizzazione nella cache delle risposte DNS.",
     "cache_ttl_max_override_desc": "Imposta un valore di durata massima (secondi) per le voci nella cache DNS.",
-    "ttl_cache_validation": "La sovrascrittura del valore TTL minimo della cache deve essere inferiore o uguale a quello massimo.",
+    "ttl_cache_validation": "La sovrascrittura del valore TTL minimo della cache deve essere inferiore o uguale a quello massimo",
     "cache_optimistic": "Optimistic caching",
     "cache_optimistic_desc": "Fai in modo che AdGuard Home risponda dalla cache anche quando le voci risultano scadute e prova anche ad aggiornarle.",
     "filter_category_general": "Generali",
@@ -628,5 +631,5 @@
     "parental_control": "Controllo Parentale",
     "safe_browsing": "Navigazione Sicura",
     "served_from_cache": "{{value}} <i>(fornito dalla cache)</i>",
-    "form_error_password_length": "La password deve essere lunga almeno {{value}} caratteri."
+    "form_error_password_length": "La password deve contenere almeno {{value}} caratteri"
 }
diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json
index 7c2f8ba1..0cd45548 100644
--- a/client/src/__locales/ja.json
+++ b/client/src/__locales/ja.json
@@ -45,13 +45,13 @@
     "form_error_mac_format": "MACアドレスが無効です",
     "form_error_client_id_format": "ClientIDには、数字、小文字、ハイフン以外は使用できません",
     "form_error_server_name": "サーバー名が無効です",
-    "form_error_subnet": "IPアドレス「{{ip}}」はサブネット「{{cidr}}」に含まれていません",
+    "form_error_subnet": "IPアドレス「{{ip}}」がサブネット「{{cidr}}」に含まれていません",
     "form_error_positive": "0より大きい値でなければなりません",
-    "out_of_range_error": "\"{{start}}\"-\"{{end}}\" の範囲外である必要があります",
+    "out_of_range_error": "\"{{start}}\"〜\"{{end}}\" の範囲外である必要があります",
     "lower_range_start_error": "範囲開始よりも低い値である必要があります",
     "greater_range_start_error": "範囲開始値より大きい値でなければなりません",
     "greater_range_end_error": "範囲終了値より大きい値でなければなりません",
-    "subnet_error": "アドレスは1つのサブネット内にある必要があります",
+    "subnet_error": "両アドレスが同じサブネット内にある必要があります",
     "gateway_or_subnet_invalid": "サブネットマスクが無効です",
     "dhcp_form_gateway_input": "ゲートウェイIP",
     "dhcp_form_subnet_input": "サブネットマスク",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "ホスト名",
     "dhcp_table_expires": "有効期限",
     "dhcp_warning": "ともかくDHCPサーバを有効にしたい場合は、ネットワーク内で他に稼働中のDHCPサーバがないことを確認してください。そうでなければ、ネットワーク上デバイスでインターネット接続を壊してしまう可能性があります!",
-    "dhcp_error": "ネットワーク上に別の稼働中DHCPサーバがあるかどうか、AdGuard Homeは判断できませんでした。",
+    "dhcp_error": "ネットワーク上に別の稼働中DHCPサーバがあるかどうか、AdGuard Homeは判断できませんでした",
     "dhcp_static_ip_error": "DHCPサーバーを使用するには、静的IPアドレスを設定する必要があります。このネットワークインターフェースが静的IPアドレスを使用するように設定されているかどうかを、AdGuard Homeは判断できませんでした。手動で静的IPアドレスを設定してください。",
     "dhcp_dynamic_ip_found": "お使いのシステムは、インターフェース<0>{{interfaceName}}</0>用に動的IPアドレス構成を使用しています。DHCPサーバを使用するには、静的IPアドレスで設定する必要があります。あなたの現在のIPアドレスは<0>{{ipAddress}}</0>です。「DHCPサーバを有効にする」ボタンを押すと、AdGuard Homeは自動的にこのIPアドレスを静的IPアドレスとして設定します。",
     "dhcp_lease_added": "静的割り当て \"{{key}}\" の追加に成功しました",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# これもコメントです",
     "example_regex_meaning": "指定の正規表現に一致するドメインへのアクセスをブロックします。",
     "example_upstream_regular": "通常のDNS(over UDP)。",
+    "example_upstream_udp": "通常のDNS(over UDP, ホスト名)。",
     "example_upstream_dot": "暗号化されている <0>DNS-over-TLS</0>。",
     "example_upstream_doh": "暗号化されている <0>DNS-over-HTTPS</0>。",
     "example_upstream_doq": "暗号化 <0>DNS-over-QUIC</0>(実験的)。",
     "example_upstream_sdns": "<1>DNSCrypt</1> または <2>DNS-over-HTTPS</2> リゾルバのための <0>DNS Stamps</0>。",
     "example_upstream_tcp": "通常のDNS(over TCP)。",
+    "example_upstream_tcp_hostname": "通常のDNS(over TCP, ホスト名)。",
     "all_lists_up_to_date_toast": "すべてのリストは既に最新です",
     "updated_upstream_dns_toast": "上流DNSサーバを保存しました。",
     "dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "完全一致検索には二重引用符を使用します",
     "query_log_retention_confirm": "クエリ・ログの保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます",
     "anonymize_client_ip": "クライアントIPを匿名化する",
-    "anonymize_client_ip_desc": "ログと統計にクライアントのフルPアドレスを保存しません。",
+    "anonymize_client_ip_desc": "ログと統計にクライアントのフルIPアドレスを保存しないようにします。",
     "dns_config": "DNSサーバ設定",
     "dns_cache_config": "DNSキャッシュ設定",
-    "dns_cache_config_desc": "ここでDNSキャッシュを設定できます。",
+    "dns_cache_config_desc": "こちらではDNSキャッシュを設定できます。",
     "blocking_mode": "ブロックモード",
     "default": "デフォルト",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "頻度制限を入力してください",
     "rate_limit": "頻度制限",
     "edns_enable": "EDNSクライアントサブネットを有効にする",
-    "edns_cs_desc": "有効にすると、AdGuard HomeはクライアントのサブネットをDNSサーバへ送信します。",
+    "edns_cs_desc": "アップストリームリクエストにEDNSクライアントサブネットオプション(ECS)を追加し、クライアントから送信された値をクエリログに記録します。",
     "rate_limit_desc": "一つのクライアントに対して許可される1秒あたりのリクエスト数(「0」に設定すると、制限なしになります)",
     "blocking_ipv4_desc": "ブロックされたAリクエストに対して応答されるIPアドレス",
     "blocking_ipv6_desc": "ブロックされたAAAAリクエストに対して応答されるIPアドレス",
@@ -356,7 +358,7 @@
     "open_dashboard": "ダッシュボードを開きます",
     "install_saved": "保存に成功しました",
     "encryption_title": "暗号化",
-    "encryption_desc": "DNSと管理者ウェブインターフェースの両方に対する暗号化(HTTPS/TLS)サポート。",
+    "encryption_desc": "DNSと管理者ウェブインターフェースの両方に対する暗号化(HTTPS/QUIC/TLS)サポート。",
     "encryption_config_saved": "暗号化構成が保存されました。",
     "encryption_server": "サーバ名",
     "encryption_server_enter": "ドメイン名を入力してください",
@@ -379,7 +381,7 @@
     "encryption_enable": "暗号化を有効にする(HTTPS、DNS-over-HTTPS、DNS-over-TLS)",
     "encryption_enable_desc": "暗号化が有効になっていると、AdGuard Home 管理インターフェースはHTTPS経由で動作し、DNSサーバはDNS-over-HTTPSおよびDNS-over-TLS経由で要求を待ち受けます。",
     "encryption_chain_valid": "証明書チェーンは有効です。",
-    "encryption_chain_invalid": "証明書チェーンは無効です",
+    "encryption_chain_invalid": "証明書チェーンが無効です",
     "encryption_key_valid": "これは有効な{{type}}プライベートキーです。",
     "encryption_key_invalid": "これは無効な{{type}}プライベートキーです",
     "encryption_subject": "件名",
@@ -388,7 +390,7 @@
     "encryption_reset": "暗号化設定をリセットして良いですか?",
     "topline_expiring_certificate": "SSL証明書は期限切れになります。<0>暗号化設定</0>を更新します。",
     "topline_expired_certificate": "SSL証明書は期限切れです。<0>暗号化設定</0>を更新します。",
-    "form_error_port_range": "80〜65535 の範囲でポート番号を入力してください",
+    "form_error_port_range": "80〜65535 の範囲内でポート番号を入力してください",
     "form_error_port_unsafe": "これは不安全なポートです",
     "form_error_equal": "同じ値であってはなりません",
     "form_error_password": "パスワードが一致しません",
@@ -397,7 +399,7 @@
     "setup_guide": "セットアップガイド",
     "dns_addresses": "DNSアドレス",
     "dns_start": "DNSサーバが起動処理中です",
-    "dns_status_error": "DNSサーバ・ステータスの確認エラー",
+    "dns_status_error": "DNSサーバーステータスの確認エラー",
     "down": "ダウン",
     "fix": "改善",
     "dns_providers": "こちらは、選択可能な<0>既知のDNSプロバイダの一覧</0>です。",
@@ -435,7 +437,7 @@
     "auto_clients_title": "ランタイムクライアント",
     "auto_clients_desc": "永続的クライアントのリストに未登録で、AdGuard Homeを使用する場合があるデバイスのリスト。",
     "access_title": "アクセス設定",
-    "access_desc": "ここで、AdGuard Home DNSサーバのアクセスルールを設定できます。",
+    "access_desc": "こちらでは、AdGuard Home DNSサーバーのアクセスルールを設定できます。",
     "access_allowed_title": "許可されたクライアント",
     "access_allowed_desc": "CIDR、IPアドレス、または<a>ClientID</a>のリスト。このリストに入力がある場合、AdGuard Homeはリストに入っているクライアントからのみリクエストを受け入れます。",
     "access_disallowed_title": "拒否するクライアント",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}}日",
     "interval_days_plural": "{{count}}日",
     "domain": "ドメイン",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "応答",
     "filter_added_successfully": "フィルタの追加に成功しました",
@@ -507,7 +510,7 @@
     "filter_updated": "フィルタの更新に成功しました",
     "statistics_configuration": "統計設定",
     "statistics_retention": "統計保持",
-    "statistics_retention_desc": "※保持期間を短くすると、一部のデータが失われます。",
+    "statistics_retention_desc": "※保持期間を短くすると、一部のデータは失われます。",
     "statistics_clear": "統計を消去する",
     "statistics_clear_confirm": "統計を消去してもよろしいですか?",
     "statistics_retention_confirm": "統計の保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます",
@@ -517,7 +520,7 @@
     "interval_hours_plural": "{{count}}時間",
     "filters_configuration": "フィルタ設定",
     "filters_enable": "フィルタを有効にする",
-    "filters_interval": "フィルタの更新間隔",
+    "filters_interval": "フィルタの更新頻度",
     "disabled": "無効",
     "username_label": "ユーザ名",
     "username_placeholder": "ユーザ名を入力してください",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "最大TTL(秒単位)を入力してください",
     "cache_ttl_min_override_desc": "DNS応答をキャッシュするとき、上流サーバから受信した短いTTL(秒単位)を延長します。",
     "cache_ttl_max_override_desc": "DNSキャッシュ内のエントリの最大TTL(秒単位)を設定します。",
-    "ttl_cache_validation": "最小キャッシュTTL上書きは最大値以下にする必要があります",
+    "ttl_cache_validation": "最小キャッシュTTL上書き値は最大値以下にする必要があります",
     "cache_optimistic": "Optimistic cashing (オプティミスティック・キャッシュ)",
     "cache_optimistic_desc": "エントリの有効期限が切れた場合でも、AdGuard Homeがキャッシュから応答するようにし、エントリの更新も試みます。",
     "filter_category_general": "一般",
@@ -628,5 +631,5 @@
     "parental_control": "ペアレンタルコントロール",
     "safe_browsing": "セーフブラウジング",
     "served_from_cache": "{{value}} <i>(キャッシュから応答)</i>",
-    "form_error_password_length": "パスワードは{{value}}文字以上にしてください。"
+    "form_error_password_length": "パスワードは{{value}}文字以上にしてください"
 }
diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json
index ff52d178..19440d13 100644
--- a/client/src/__locales/ko.json
+++ b/client/src/__locales/ko.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP 구성이 성공적으로 저장되었습니다",
     "dhcp_ipv4_settings": "DHCP IPv4 설정",
     "dhcp_ipv6_settings": "DHCP IPv6 설정",
-    "form_error_required": "필수 필드.",
-    "form_error_ip4_format": "잘못된 IPv4 주소.",
-    "form_error_ip4_range_start_format": "잘못된 범위 시작 IPv4 주소.",
-    "form_error_ip4_range_end_format": "잘못된 범위 종료 IPv4 주소.",
-    "form_error_ip4_gateway_format": "잘못된 게이트웨이 IPv4 주소.",
-    "form_error_ip6_format": "잘못된 IPv6 주소.",
-    "form_error_ip_format": "잘못된 IP 주소.",
-    "form_error_mac_format": "잘못된 MAC 주소.",
-    "form_error_client_id_format": "ClientID는 숫자, 소문자 및 하이픈만 포함해야 합니다.",
-    "form_error_server_name": "유효하지 않은 서버 이름입니다.",
-    "form_error_subnet": "서브넷 '{{cidr}}'에 '{{ip}}' IP 주소가 없습니다.",
-    "form_error_positive": "0보다 커야 합니다.",
-    "out_of_range_error": "'{{start}}'-'{{end}}' 범위 밖이어야 합니다.",
-    "lower_range_start_error": "범위 시작보다 작은 값이어야 합니다.",
-    "greater_range_start_error": "범위 시작보다 큰 값이어야 합니다.",
-    "greater_range_end_error": "범위 종료보다 큰 값이어야 합니다.",
-    "subnet_error": "주소는 하나의 서브넷에 있어야 합니다.",
-    "gateway_or_subnet_invalid": "잘못된 서브넷 마스크.",
+    "form_error_required": "필수 영역",
+    "form_error_ip4_format": "잘못된 IPv4 형식",
+    "form_error_ip4_range_start_format": "잘못된 범위 시작 IPv4 형식",
+    "form_error_ip4_range_end_format": "잘못된 범위 종료 IPv4 형식",
+    "form_error_ip4_gateway_format": "잘못된 게이트웨이 IPv4 형식",
+    "form_error_ip6_format": "잘못된 IPv6 주소",
+    "form_error_ip_format": "잘못된 IP 주소",
+    "form_error_mac_format": "잘못된 MAC 주소",
+    "form_error_client_id_format": "ClientID는 숫자, 소문자 및 붙임표(-)만 포함해야 합니다",
+    "form_error_server_name": "유효하지 않은 서버 이름",
+    "form_error_subnet": "서브넷 \"{{cidr}}\"에 \"{{ip}}\" IP 주소가 없습니다",
+    "form_error_positive": "0보다 커야 합니다",
+    "out_of_range_error": "\"{{start}}\"-\"{{end}}\" 범위 밖이어야 합니다",
+    "lower_range_start_error": "범위 시작보다 작은 값이어야 합니다",
+    "greater_range_start_error": "범위 시작보다 큰 값이어야 합니다",
+    "greater_range_end_error": "범위 종료보다 큰 값이어야 합니다",
+    "subnet_error": "주소는 하나의 서브넷에 있어야 합니다",
+    "gateway_or_subnet_invalid": "잘못된 서브넷 마스크",
     "dhcp_form_gateway_input": "게이트웨이 IP",
     "dhcp_form_subnet_input": "서브넷 마스크",
     "dhcp_form_range_title": "IP 주소 범위",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "호스트 이름",
     "dhcp_table_expires": "만료",
     "dhcp_warning": "DHCP 서버를 사용하려면 네트워크에 다른 활성화된 DHCP 서버가 없는지 확인해 주세요. 다른 활성 DHCP 서버가 있다면, 연결된 장치의 인터넷이 끊길 수 있습니다.",
-    "dhcp_error": "AdGuard Home이 네트워크에 다른 활성 DHCP 서버가 있는지 확인할 수 없습니다.",
+    "dhcp_error": "AdGuard Home이 네트워크에 다른 활성 DHCP 서버가 있는지 확인할 수 없습니다",
     "dhcp_static_ip_error": "DHCP 서버를 사용하려면 고정 IP 주소를 설정해야 합니다. AdGuard Home이 이 네트워크 인터페이스가 고정 IP 주소를 사용하는지 확인할 수 없습니다. 고정 IP 주소를 수동으로 설정하십시오.",
     "dhcp_dynamic_ip_found": "시스템은 <0>{{interfaceName}}</0> 인터페이스에 동적 IP 주소를 사용합니다. DHCP 서버를 사용하려면 고정 IP 주소를 설정해야 합니다. 현재 IP 주소는 <0>{{ipAddress}}</0>입니다. 'DHCP 서버 활성화' 버튼을 누르면 AdGuard Home이 이 IP 주소를 고정 IP 주소로 자동 설정합니다.",
     "dhcp_lease_added": "\"{{key}}\" 고정 임대 정상적으로 추가되었습니다",
@@ -196,8 +196,8 @@
     "choose_allowlist": "허용 목록 선택",
     "enter_valid_blocklist": "차단 목록에 유효한 URL을 입력해주세요.",
     "enter_valid_allowlist": "허용 목록에 유효한 URL을 입력해주세요.",
-    "form_error_url_format": "잘못된 URL 형식.",
-    "form_error_url_or_path_format": "목록의 URL 또는 절대 경로가 잘못되었습니다.",
+    "form_error_url_format": "잘못된 URL 형식",
+    "form_error_url_or_path_format": "목록의 URL 또는 절대 경로가 잘못되었습니다",
     "custom_filter_rules": "커스텀 필터링 규칙",
     "custom_filter_rules_hint": "한 라인에 한 규칙만 입력하세요. 광고 차단 규칙과 호스트 파일 문법 중 하나를 사용할 수 있습니다",
     "system_host_files": "시스템 호스트 파일",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# 이것 또한 댓글입니다.",
     "example_regex_meaning": "특정 정규 표현식에 맞는 도메인 접근을 차단합니다.",
     "example_upstream_regular": "일반 DNS (UDP을 통한 접속);",
+    "example_upstream_udp": "일반 DNS (UDP를 통한, 호스트명);",
     "example_upstream_dot": "암호화된 <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "암호화된 <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "암호화된 <0>DNS-over-QUIC</0> (실험);",
     "example_upstream_sdns": "<1>DNSCrypt</1> 또는 <2>DNS-over-HTTPS</2> 리졸버를 위한 <0>DNS 스탬프</0>;",
     "example_upstream_tcp": "일반 DNS (TCP를 통한 접속);",
+    "example_upstream_tcp_hostname": "일반 DNS (TCP를 통한, 호스트명);",
     "all_lists_up_to_date_toast": "모든 리스트가 이미 최신입니다",
     "updated_upstream_dns_toast": "업스트림 서버가 성공적으로 저장되었습니다",
     "dns_test_ok_toast": "특정 DNS 서버들은 정상적으로 동작 중입니다",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "검색을 제한하려면 쌍따옴표를 사용해주세요",
     "query_log_retention_confirm": "정말로 쿼리 로그 저장 기간을 변경하시겠습니까? 저장 주기를 낮출 경우, 일부 데이터가 손실됩니다",
     "anonymize_client_ip": "클라이언트 IP 익명화",
-    "anonymize_client_ip_desc": "클라이언트의 전체 IP 주소를 로그와 통계에 저장하지 않습니다.",
+    "anonymize_client_ip_desc": "클라이언트의 전체 IP 주소를 로그와 통계에 저장하저장하지 마세요",
     "dns_config": "DNS 서버 설정",
     "dns_cache_config": "DNS 캐시 구성",
-    "dns_cache_config_desc": "여기에서 DNS 캐시를 구성할 수 있습니다.",
+    "dns_cache_config_desc": "여기에서 DNS 캐시를 구성 할 수 있습니다",
     "blocking_mode": "차단 모드",
     "default": "기본",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "한도 제한 입력하기",
     "rate_limit": "한도 제한",
     "edns_enable": "EDNS 클라이언트 서브넷 활성화",
-    "edns_cs_desc": "클라이언트의 서브넷을 DNS 서버에 전달합니다.",
+    "edns_cs_desc": "업스트림 요청에 EDNS 클라이언트 서브넷 옵션(ECS)을 추가하고 쿼리 로그에 클라이언트가 보낸 값을 기록합니다.",
     "rate_limit_desc": "단일 클라이언트에서 허용 가능한 초 당 요청 생성 숫자 (0: 무제한)",
     "blocking_ipv4_desc": "차단된 A 요청에 대해서 반환할 IP 주소",
     "blocking_ipv6_desc": "차단된 AAAA 요청에 대해서 반환할 IP 주소",
@@ -309,7 +311,7 @@
     "install_settings_listen": "네트워크 인터페이스",
     "install_settings_port": "포트",
     "install_settings_interface_link": "AdGuard Home 관리자 웹 인터페이스는 다음 주소로 제공됨:",
-    "form_error_port": "유효한 포트 번호를 입력하세요.",
+    "form_error_port": "유효한 포트 번호를 입력하세요",
     "install_settings_dns": "DNS 서버",
     "install_settings_dns_desc": "다음 주소의 DNS 서버를 사용하도록 장치 또는 라우터를 구성해야 합니다.",
     "install_settings_all_interfaces": "모든 인터페이스",
@@ -356,7 +358,7 @@
     "open_dashboard": "대시보드 열기",
     "install_saved": "성공적으로 저장되었습니다",
     "encryption_title": "암호화",
-    "encryption_desc": "DNS 및 관리 웹 인터페이스에 대한 암호화(HTTPS/TLS)를 지원합니다.",
+    "encryption_desc": "DNS 및 관리 웹 인터페이스에 대한 암호화(HTTPS/TLS)를 지원합니다",
     "encryption_config_saved": "암호화 구성이 저장되었습니다",
     "encryption_server": "서버 이름",
     "encryption_server_enter": "도메인 이름을 입력하세요.",
@@ -378,26 +380,26 @@
     "encryption_key_input": "PEM으로 인코딩된 개인 키를 여기에 복사/붙여넣기하세요.",
     "encryption_enable": "암호화 활성화 (HTTPS, DNS-over-HTTPS 및 DNS-over-TLS)",
     "encryption_enable_desc": "암호화가 활성화 된 경우 AdGuard Home 관리자 인터페이스는 HTTPS를 통해 작동하고 DNS 서버는 DNS-over-HTTPS 및 DNS-over-TLS를 통해 요청을 수신합니다.",
-    "encryption_chain_valid": "인증서 체인이 유효합니다.",
-    "encryption_chain_invalid": "인증서 체인이 유효하지 않습니다.",
-    "encryption_key_valid": "유효한 {{type}} 개인 키입니다.",
-    "encryption_key_invalid": "유효하지 않는 {{type}} 개인 키입니다.",
+    "encryption_chain_valid": "인증서 체인이 유효합니다",
+    "encryption_chain_invalid": "인증서 체인이 유효하지 않습니다",
+    "encryption_key_valid": "유효한 {{type}} 개인 키입니다",
+    "encryption_key_invalid": "유효하지 않는 {{type}} 개인 키입니다",
     "encryption_subject": "대상",
     "encryption_issuer": "발행자",
     "encryption_hostnames": "호스트 이름",
     "encryption_reset": "암호화 설정을 재설정하시겠습니까?",
     "topline_expiring_certificate": "SSL 인증서가 곧 만료됩니다. 업데이트<0> 암호화 설정</0>.",
     "topline_expired_certificate": "SSL 인증서가 만료되었습니다. 업데이트<0> 암호화 설정</0>.",
-    "form_error_port_range": "80-65535 범위의 포트 번호를 입력하세요.",
-    "form_error_port_unsafe": "안전하지 않은 포트입니다.",
-    "form_error_equal": "동일하지 않아야 함.",
-    "form_error_password": "비밀번호 불일치.",
+    "form_error_port_range": "80-65535 범위의 포트 번호를 입력하세요",
+    "form_error_port_unsafe": "안전하지 않은 포트입니다",
+    "form_error_equal": "동일하지 않아야 합니다",
+    "form_error_password": "비밀번호 불일치",
     "reset_settings": "설정 초기화",
     "update_announcement": "AdGuard Home {{version}} 사용 가능합니다! <0>이곳</0>을 클릭하여 더 많은 정보를 확인하세요.",
     "setup_guide": "설치 안내",
     "dns_addresses": "DNS 주소",
     "dns_start": "DNS 서버를 시작하고 있습니다",
-    "dns_status_error": "DNS 서버 상태를 확인하는 동안 오류가 발생했습니다.",
+    "dns_status_error": "DNS 서버 상태를 확인하는 동안 오류가 발생했습니다",
     "down": "다운로드",
     "fix": "수정",
     "dns_providers": "다음은 선택할 수 있는 <0>알려진 DNS 공급자 목록</0>입니다.",
@@ -406,7 +408,7 @@
     "manual_update": "<a>절차를 따라</a> 수동으로 업데이트하십시오.",
     "processing_update": "잠시만 기다려주세요, AdGuard Home가 업데이트 중입니다.",
     "clients_title": "영구 클라이언트",
-    "clients_desc": "AdGuard Home에 연결된 기기에 대한 영구 클라이언트 레코드를 설정합니다.",
+    "clients_desc": "AdGuard Home에 연결된 기기에 대한 영구 클라이언트 레코드를 설정합니다",
     "settings_global": "글로벌",
     "settings_custom": "사용자",
     "table_client": "클라이언트",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "정말 클라이언트 \"{{key}}\" 삭제하시겠습니까?",
     "list_confirm_delete": "정말로 이 목록을 제거하시겠습니까?",
     "auto_clients_title": "런타임 클라이언트",
-    "auto_clients_desc": "AdGuard Home을 계속 사용할 수 있는 영구 클라이언트 목록에 없는 디바이스입니다.",
+    "auto_clients_desc": "AdGuard Home을 계속 사용할 수 있는 영구 클라이언트 목록에 없는 디바이스입니다",
     "access_title": "접근 설정",
-    "access_desc": "여기에서 AdGuard Home DNS 서버에 대한 액세스 규칙을 구성할 수 있습니다.",
+    "access_desc": "여기에서 AdGuard Home DNS 서버에 대한 액세스 규칙을 설정할 수 있습니다",
     "access_allowed_title": "허용된 클라이언트",
     "access_allowed_desc": "CIDR, IP 주소 또는 <a>ClientID</a> 목록입니다. 이 목록에 항목이 있는 경우, AdGuard Home은 이러한 클라이언트의 요청만 수락합니다.",
     "access_disallowed_title": "차단된 클라이언트",
@@ -475,8 +477,8 @@
     "dns_rewrites": "DNS 변경",
     "form_domain": "도메인 이름 또는 와일드카드를 입력합니다",
     "form_answer": "IP 주소 또는 도메인 이름을 입력하세요",
-    "form_error_domain_format": "도메인 형식이 잘못되었습니다.",
-    "form_error_answer_format": "답변 형식이 잘못되었습니다. ",
+    "form_error_domain_format": "도메인 형식이 잘못되었습니다",
+    "form_error_answer_format": "답변 형식이 잘못되었습니다",
     "configure": "설정하기",
     "main_settings": "기본 설정",
     "block_services": "특정 서비스 차단",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} 일",
     "interval_days_plural": "{{count}} 일",
     "domain": "도메인",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "응답",
     "filter_added_successfully": "목록이 성공적으로 추가됨",
@@ -507,7 +510,7 @@
     "filter_updated": "필터가 성공적으로 업데이트됨",
     "statistics_configuration": "통계 구성",
     "statistics_retention": "통계 저장 기간",
-    "statistics_retention_desc": "간격 값을 줄이면 일부 데이터가 손실됩니다.",
+    "statistics_retention_desc": "주기를 줄이면, 일부 데이터가 손실됩니다",
     "statistics_clear": "통계 초기화",
     "statistics_clear_confirm": "통계를 정말로 초기화하시겠습니까?",
     "statistics_retention_confirm": "정말로 통계 저장 기간을 변경하시겠습니까? 저장 주기를 낮출 경우, 일부 데이터가 손실됩니다",
@@ -628,5 +631,5 @@
     "parental_control": "자녀 보호",
     "safe_browsing": "세이프 브라우징",
     "served_from_cache": "{{value}} <i>(캐시에서 제공)</i>",
-    "form_error_password_length": "비밀번호는 {{value}}자 이상이어야 합니다."
+    "form_error_password_length": "비밀번호는 {{value}}자 이상이어야 합니다"
 }
diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json
index 36478928..990f4b09 100644
--- a/client/src/__locales/nl.json
+++ b/client/src/__locales/nl.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "DHCP configuratie succesvol opgeslagen",
     "dhcp_ipv4_settings": "DHCP IPv4 instellingen",
     "dhcp_ipv6_settings": "DHCP IPv6 instellingen",
-    "form_error_required": "Vereist veld.",
-    "form_error_ip4_format": "Ongeldig IPv4-adres.",
-    "form_error_ip4_range_start_format": "Ongeldig IPv4-adres start bereik.",
-    "form_error_ip4_range_end_format": "Ongeldig IPv4-adres einde bereik.",
-    "form_error_ip4_gateway_format": "Ongeldig IPv4-adres van de gateway.",
-    "form_error_ip6_format": "Ongeldig IPv6-adres.",
-    "form_error_ip_format": "Ongeldig IP-adres.",
-    "form_error_mac_format": "Ongeldig MAC-adres.",
-    "form_error_client_id_format": "Client-ID mag alleen cijfers, kleine letters en koppeltekens bevatten.",
-    "form_error_server_name": "Ongeldige servernaam.",
-    "form_error_subnet": "Subnet “{{cidr}}” bevat niet het IP-adres “{{ip}}”.",
-    "form_error_positive": "Moet groter zijn dan 0.",
-    "out_of_range_error": "Moet buiten bereik zijn \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Moet lager zijn dan begin reeks.",
-    "greater_range_start_error": "Moet groter zijn dan begin reeks.",
-    "greater_range_end_error": "Moet groter zijn dan einde reeks.",
-    "subnet_error": "Adressen moeten in één subnet vallen.",
-    "gateway_or_subnet_invalid": "Subnetmasker ongeldig.",
+    "form_error_required": "Vereist veld",
+    "form_error_ip4_format": "Ongeldig IPv4-adres",
+    "form_error_ip4_range_start_format": "Ongeldig IPv4-adres start bereik",
+    "form_error_ip4_range_end_format": "Ongeldig IPv4-adres einde bereik",
+    "form_error_ip4_gateway_format": "Ongeldig IPv4-adres van de gateway",
+    "form_error_ip6_format": "Ongeldig IPv6-adres",
+    "form_error_ip_format": "Ongeldig IP-adres",
+    "form_error_mac_format": "Ongeldig MAC-adres",
+    "form_error_client_id_format": "Client-ID mag alleen cijfers, kleine letters en koppeltekens bevatten",
+    "form_error_server_name": "Ongeldige servernaam",
+    "form_error_subnet": "Subnet “{{cidr}}” bevat niet het IP-adres “{{ip}}”",
+    "form_error_positive": "Moet groter zijn dan 0",
+    "out_of_range_error": "Moet buiten bereik zijn \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Moet lager zijn dan begin reeks",
+    "greater_range_start_error": "Moet groter zijn dan begin reeks",
+    "greater_range_end_error": "Moet groter zijn dan einde reeks",
+    "subnet_error": "Adressen moeten in één subnet vallen",
+    "gateway_or_subnet_invalid": "Subnetmasker ongeldig",
     "dhcp_form_gateway_input": "Gateway IP",
     "dhcp_form_subnet_input": "Subnet mask",
     "dhcp_form_range_title": "Bereik van IP adressen",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Host naam",
     "dhcp_table_expires": "Verloopt op",
     "dhcp_warning": "Indien je de ingebouwde DHCP server wilt inschakelen, let dan op dat er geen andere actieve DHCP server aanwezig is in je netwerk. Dit kan de internetverbinding instabiel maken voor sommige apparaten in je netwerk!",
-    "dhcp_error": "AdGuard Home kon niet bepalen of er een andere actieve DHCP server in je netwerk aanwezig is.",
+    "dhcp_error": "AdGuard Home kon niet bepalen of er een andere actieve DHCP server op het netwerk aanwezig is",
     "dhcp_static_ip_error": "Om de DHCP server te gebruiken, moet een statisch IP-adres worden ingesteld. AdGuard Home heeft niet kunnen vaststellen of de netwerkinterface is geconfigureerd met een statisch IP-adres. Stel handmatig een statisch IP-adres in.",
     "dhcp_dynamic_ip_found": "Je systeem gebruikt dynamische IP-adres configuratie voor interface <0>{{interfaceName}}</0>. Om de DHCP server te gebruiken moet er een statisch IP-adres worden ingesteld. Je huidige IP-adres is <0>{{ipAddress}}</0>. AdGuard Home zal automatisch dit IP-adres als statisch IP-adres instellen wanneer je op de knop \"DHCP inschakelen\" drukt.",
     "dhcp_lease_added": "Statische uitgifte \"{{key}}\" met succes toegevoegd",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Toestemmingslijsten selecteren",
     "enter_valid_blocklist": "Voer een geldige URL in voor de blokkeerlijst.",
     "enter_valid_allowlist": "Voer een geldige URL in voor de toestemmingslijst.",
-    "form_error_url_format": "Ongeldig URL-opmaak.",
-    "form_error_url_or_path_format": "Ongeldig URL of pad van de lijst.",
+    "form_error_url_format": "Ongeldig URL-opmaak",
+    "form_error_url_or_path_format": "Ongeldig URL of pad van de lijst",
     "custom_filter_rules": "Aangepaste filterregels",
     "custom_filter_rules_hint": "Voer één regel op een regel in. U kunt adblock-regels gebruiken of de syntaxis van hosts-bestanden gebruiken.",
     "system_host_files": "Systeem host-bestanden",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Ook een opmerking.",
     "example_regex_meaning": "toegang blokkeren tot de domeinen die overeenkomen met de opgegeven reguliere expressie.",
     "example_upstream_regular": "standaard DNS (over UDP);",
+    "example_upstream_udp": "standaard DNS (via UDP, hostnaam);",
     "example_upstream_dot": "versleutelde <0>DNS-via-TLS</0>;",
     "example_upstream_doh": "versleutelde <0>DNS-via-HTTPS</0>;",
     "example_upstream_doq": "versleutelde <0>DNS-via-QUIC</0> (experimenteel);",
     "example_upstream_sdns": "<0>DNS Stamps</0> voor <1>DNSCrypt</1> of <2>DNS-via-HTTPS</2> oplossingen;",
     "example_upstream_tcp": "standaard DNS (over TCP);",
+    "example_upstream_tcp_hostname": "standaard DNS (via TCP, hostnaam);",
     "all_lists_up_to_date_toast": "Alle lijsten zijn reeds actueel",
     "updated_upstream_dns_toast": "Upstream-servers succesvol opgeslagen",
     "dns_test_ok_toast": "Opgegeven DNS-servers werken correct",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Gebruik dubbele aanhalingstekens voor strikt zoeken",
     "query_log_retention_confirm": "Weet u zeker dat u de bewaartermijn van het query logboek wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren",
     "anonymize_client_ip": "Cliënt IP anonimiseren",
-    "anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in logboeken en statistiekbestanden.",
+    "anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in logboeken en statistiekbestanden",
     "dns_config": "DNS-server configuratie",
     "dns_cache_config": "DNS cache configuratie",
-    "dns_cache_config_desc": "Hier kan de DNS cache geconfigureerd worden.",
+    "dns_cache_config_desc": "Hier kan de DNS cache geconfigureerd worden",
     "blocking_mode": "Blocking modus",
     "default": "Standaard",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Voer ratio limiet in",
     "rate_limit": "Ratio limiet",
     "edns_enable": "EDNS client subnet inschakelen",
-    "edns_cs_desc": "Indien ingeschakeld stuurt AdGuard Home het subnet van de client naar de DNS-servers.",
+    "edns_cs_desc": "De EDNS Client Subnet-optie (ECS) toevoegen aan upstream-verzoeken en de waarden die door de clients zijn verzonden registreren in het querylogboek.",
     "rate_limit_desc": "Het aantal verzoeken per seconde toegelaten per toestel. 0 betekent onbeperkt.",
     "blocking_ipv4_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek",
     "blocking_ipv6_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Luister interface",
     "install_settings_port": "Poort",
     "install_settings_interface_link": "De webinterface van AdGuard Home admin is beschikbaar op de volgende adressen:",
-    "form_error_port": "Geldig poortnummer invoeren.",
+    "form_error_port": "Geldig poortnummer invoeren",
     "install_settings_dns": "DNS-server",
     "install_settings_dns_desc": "Je moet jouw apparaten of router configureren om de DNS-server te gebruiken op de volgende adressen:",
     "install_settings_all_interfaces": "Alle interfaces",
@@ -356,7 +358,7 @@
     "open_dashboard": "Open Dashboard",
     "install_saved": "Succesvol opgeslagen",
     "encryption_title": "Encryptie",
-    "encryption_desc": "Encryptie (HTTPS/TLS) ondersteuning voor DNS en admin web interface.",
+    "encryption_desc": "Encryptie (HTTPS/TLS) ondersteuning voor DNS en admin web interface",
     "encryption_config_saved": "Versleuteling configuratie opgeslagen",
     "encryption_server": "Server naam",
     "encryption_server_enter": "Voer domein naam in",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Kopieër en plak je PEM-gecodeerde prive sleutel voor je certificaat hier.",
     "encryption_enable": "Activeer encryptie (HTTPS, DNS-via-HTTPS, en DNS-via-TLS)",
     "encryption_enable_desc": "Als encryptie is geactiveerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en de DNS-server zal luisteren naar aanvragen via DNS-via-HTTPS en DNS-via-TLS.",
-    "encryption_chain_valid": "Certificaatketen is geldig.",
-    "encryption_chain_invalid": "Certificaatketen is ongeldig.",
-    "encryption_key_valid": "Dit is een geldige {{type}} privésleutel.",
-    "encryption_key_invalid": "Dit is een ongeldige {{type}} privésleutel.",
+    "encryption_chain_valid": "Certificaatketen is geldig",
+    "encryption_chain_invalid": "Certificaatketen is ongeldig",
+    "encryption_key_valid": "Dit is een geldige {{type}} privésleutel",
+    "encryption_key_invalid": "Dit is een ongeldige {{type}} privésleutel",
     "encryption_subject": "Onderwerp",
     "encryption_issuer": "Uitgever",
     "encryption_hostnames": "Hostnamen",
     "encryption_reset": "Ben je zeker dat je de encryptie instellingen wil resetten?",
     "topline_expiring_certificate": "Jouw SSL-certificaat vervalt binnenkort. Werk de <0>encryptie-instellingen</0> bij.",
     "topline_expired_certificate": "Jouw SSL-certificaat is vervallen. Werk de <0>encryptie-instellingen</0> bij.",
-    "form_error_port_range": "Poortnummer invoeren tussen 80 en 65535.",
-    "form_error_port_unsafe": "Dit is een onveilige poort.",
-    "form_error_equal": "Mag niet gelijk zijn.",
-    "form_error_password": "Wachtwoord komt niet overeen.",
+    "form_error_port_range": "Poortnummer invoeren tussen 80 en 65535",
+    "form_error_port_unsafe": "Onveilige poort",
+    "form_error_equal": "Mag niet gelijk zijn",
+    "form_error_password": "Wachtwoord komt niet overeen",
     "reset_settings": "Reset Instellingen",
     "update_announcement": "AdGuard Home{{version}} is nu beschikbaar! <0>klik hier</0> voor meer info.",
     "setup_guide": "Installatie gids",
     "dns_addresses": "DNS adressen",
     "dns_start": "DNS-server aan het opstarten",
-    "dns_status_error": "Fout bij het controleren van de DNS-server status.",
+    "dns_status_error": "Fout bij het controleren van de DNS-server status",
     "down": "Uitgeschakeld",
     "fix": "Los op",
     "dns_providers": "hier is een <0>lijst of gekende DNS providers</0> waarvan je kan kiezen.",
@@ -406,7 +408,7 @@
     "manual_update": "<a>Volg deze stappen</a> om handmatig bij te werken.",
     "processing_update": "Even geduld, AdGuard Home wordt bijgewerkt",
     "clients_title": "Permanente clients",
-    "clients_desc": "Permanente client-records configureren voor apparaten verboden met AdGuard Home.",
+    "clients_desc": "Permanente client-records configureren voor apparaten verboden met AdGuard Home",
     "settings_global": "Globaal",
     "settings_custom": "Aangepast",
     "table_client": "Gebruiker",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Ben je zeker dat je deze gebruiker \"{{key}}\" wilt verwijderen?",
     "list_confirm_delete": "Ben je zeker om deze lijst te verwijderen?",
     "auto_clients_title": "Runtime-clients",
-    "auto_clients_desc": "Apparaten die niet op de lijst van permanente clients staan die mogelijk nog steeds AdGuard Home gebruiken.",
+    "auto_clients_desc": "Apparaten die niet op de lijst van permanente clients staan die mogelijk nog steeds AdGuard Home gebruiken",
     "access_title": "Toegangs instellingen",
-    "access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen.",
+    "access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen",
     "access_allowed_title": "Toegestane gebruikers",
     "access_allowed_desc": "Een lijst met CIDR's, IP-adressen of <a>Client-ID's</a>. Indien geconfigureerd, accepteert AdGuard Home alleen verzoeken van deze cliënts.",
     "access_disallowed_title": "Verworpen gebruikers",
@@ -475,8 +477,8 @@
     "dns_rewrites": "DNS herschrijvingen",
     "form_domain": "Vul domein of wildcard in",
     "form_answer": "Vul IP adres of domeinnaam in",
-    "form_error_domain_format": "Ongeldige opmaak domein.",
-    "form_error_answer_format": "Ongeldig opmaak antwoord.",
+    "form_error_domain_format": "Ongeldige opmaak domein",
+    "form_error_answer_format": "Ongeldige opmaak antwoord",
     "configure": "Bewerk",
     "main_settings": "Algemene instellingen",
     "block_services": "Specifieke services blokkeren",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} dagen",
     "interval_days_plural": "{{count}} dagen",
     "domain": "Domein",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Antwoord",
     "filter_added_successfully": "De lijst is succesvol toegevoegd",
@@ -507,7 +510,7 @@
     "filter_updated": "De lijst is succesvol geüpdatet",
     "statistics_configuration": "Statistieken configuratie",
     "statistics_retention": "Statistieken retentie",
-    "statistics_retention_desc": "Als je de intervalwaarde vermindert, zullen sommige gegevens verloren gaan.",
+    "statistics_retention_desc": "Als je de intervalwaarde vermindert, zullen sommige gegevens verloren gaan",
     "statistics_clear": "Statistieken wissen",
     "statistics_clear_confirm": "Alle statistieken werkelijk wissen?",
     "statistics_retention_confirm": "Weet u zeker dat u de bewaartermijn van de statistieken wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren",
@@ -517,7 +520,7 @@
     "interval_hours_plural": "{{count}} uren",
     "filters_configuration": "Filters instellingen",
     "filters_enable": "Filters inschakelen",
-    "filters_interval": "Filters update frequentie",
+    "filters_interval": "Filter update frequentie",
     "disabled": "Uitgeschakeld",
     "username_label": "Gebruikersnaam",
     "username_placeholder": "Voer gebruikersnaam in",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Maximum TTL invoeren (seconden)",
     "cache_ttl_min_override_desc": "Uitbreiden van korte Time-To-Live waardes (seconden) ontvangen van de upstream server bij het cachen van DNS antwoorden.",
     "cache_ttl_max_override_desc": "Instellen van maximum time-to-live waarde (seconden) voor opslag in de DNS cache.",
-    "ttl_cache_validation": "Minimale waarde TTL-cache moet kleiner dan of gelijk zijn aan de maximale waarde.",
+    "ttl_cache_validation": "Minimale waarde TTL-cache moet kleiner dan of gelijk zijn aan de maximale waarde",
     "cache_optimistic": "Optimistisch cachen",
     "cache_optimistic_desc": "Laat AdGuard Home reageren vanuit de cache, zelfs als de vermeldingen zijn verlopen en probeer deze ook te vernieuwen.",
     "filter_category_general": "Algemeen",
@@ -628,5 +631,5 @@
     "parental_control": "Ouderlijk toezicht",
     "safe_browsing": "Veilig browsen",
     "served_from_cache": "{{value}} <i>(geleverd vanuit cache)</i>",
-    "form_error_password_length": "Wachtwoord moet minimaal {{value}} tekens lang zijn."
+    "form_error_password_length": "Wachtwoord moet minimaal {{value}} tekens lang zijn"
 }
diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json
index 253a8cac..1d1f5f18 100644
--- a/client/src/__locales/pl.json
+++ b/client/src/__locales/pl.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Konfiguracja DHCP została pomyślnie zapisana",
     "dhcp_ipv4_settings": "Ustawienia serwera DHCP IPv4",
     "dhcp_ipv6_settings": "Ustawienia serwera DHCP IPv6",
-    "form_error_required": "Pole wymagane.",
-    "form_error_ip4_format": "Nieprawidłowy adres IPv4.",
-    "form_error_ip4_range_start_format": "Nieprawidłowy adres IPv4 początku zakresu.",
-    "form_error_ip4_range_end_format": "Nieprawidłowy adres IPv4 końca zakresu.",
-    "form_error_ip4_gateway_format": "Nieprawidłowy adres IPv4 bramy.",
-    "form_error_ip6_format": "Nieprawidłowy adres IPv6.",
-    "form_error_ip_format": "Nieprawidłowy adres IP.",
-    "form_error_mac_format": "Nieprawidłowy adres MAC.",
-    "form_error_client_id_format": "ClientID musi zawierać tylko cyfry, małe litery i myślniki.",
-    "form_error_server_name": "Nieprawidłowa nazwa serwera.",
-    "form_error_subnet": "Podsieć \"{{cidr}}\" nie zawiera adresu IP \"{{ip}}\".",
-    "form_error_positive": "Musi być większa niż 0.",
-    "out_of_range_error": "Musi być spoza zakresu \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Musi być niższy niż początek zakresu.",
-    "greater_range_start_error": "Musi być większy niż początek zakresu.",
-    "greater_range_end_error": "Musi być większy niż koniec zakresu.",
-    "subnet_error": "Adresy muszą należeć do jednej podsieci.",
-    "gateway_or_subnet_invalid": "Nieprawidłowa maska podsieci.",
+    "form_error_required": "Pole wymagane",
+    "form_error_ip4_format": "Nieprawidłowy adres IPv4",
+    "form_error_ip4_range_start_format": "Nieprawidłowy adres IPv4 początku zakresu",
+    "form_error_ip4_range_end_format": "Nieprawidłowy adres IPv4 końca zakresu",
+    "form_error_ip4_gateway_format": "Nieprawidłowy adres IPv4 bramy",
+    "form_error_ip6_format": "Nieprawidłowy adres IPv6",
+    "form_error_ip_format": "Nieprawidłowy adres IP",
+    "form_error_mac_format": "Nieprawidłowy adres MAC",
+    "form_error_client_id_format": "ClientID musi zawierać tylko cyfry, małe litery i myślniki",
+    "form_error_server_name": "Nieprawidłowa nazwa serwera",
+    "form_error_subnet": "Podsieć \"{{cidr}}\" nie zawiera adresu IP \"{{ip}}\"",
+    "form_error_positive": "Musi być większa niż 0",
+    "out_of_range_error": "Musi być spoza zakresu \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Musi być niższy niż początek zakresu",
+    "greater_range_start_error": "Musi być większy niż początek zakresu",
+    "greater_range_end_error": "Musi być większy niż koniec zakresu",
+    "subnet_error": "Adresy muszą należeć do jednej podsieci",
+    "gateway_or_subnet_invalid": "Nieprawidłowa maska podsieci",
     "dhcp_form_gateway_input": "Adres IP bramy",
     "dhcp_form_subnet_input": "Maska podsieci",
     "dhcp_form_range_title": "Zakres adresów IP",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Nazwa hosta",
     "dhcp_table_expires": "Wygasa",
     "dhcp_warning": "Jeśli mimo wszystko chcesz włączyć serwer DHCP, upewnij się, że w Twojej sieci nie ma innego aktywnego serwera DHCP, ponieważ może to spowodować przerwanie łączności z Internetem dla urządzeń w sieci!",
-    "dhcp_error": "AdGuard Home nie mógł określić, czy w sieci jest inny aktywny serwer DHCP.",
+    "dhcp_error": "AdGuard Home nie mógł określić, czy w sieci jest inny aktywny serwer DHCP",
     "dhcp_static_ip_error": "Aby korzystać z serwera DHCP musi być ustawiony statyczny adres IP. AdGuard Home nie udało się ustalić, czy ten interfejs sieciowy jest skonfigurowany przy użyciu statycznego adresu IP. Proszę ustawić statyczny adres IP ręcznie.",
     "dhcp_dynamic_ip_found": "Twój system używa dynamicznej konfiguracji adresu IP dla interfejsu <0>{{interfaceName}}</0>. Aby można było korzystać z serwera DHCP, należy ustawić statyczny adres IP. Twój obecny adres IP to <0>{{ipAddress}}</0>. AdGuard Home automatycznie ustawi ten adres IP jako statyczny, jeśli naciśniesz przycisk \"Włącz serwer DHCP\".",
     "dhcp_lease_added": "Dzierżawa statyczna \"{{key}}\" pomyślnie dodana",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Wybierz listy dozwolonych",
     "enter_valid_blocklist": "Wpisz prawidłowy adres URL do listy zablokowanych.",
     "enter_valid_allowlist": "Wpisz prawidłowy adres URL do listy dozwolonych.",
-    "form_error_url_format": "Nieprawidłowy format URL.",
-    "form_error_url_or_path_format": "Nieprawidłowy adres URL lub bezwzględna ścieżka listy.",
+    "form_error_url_format": "Nieprawidłowy format URL",
+    "form_error_url_or_path_format": "Nieprawidłowy adres URL lub bezwzględna ścieżka listy",
     "custom_filter_rules": "Niestandardowe reguły filtrowania",
     "custom_filter_rules_hint": "Wpisz jedną regułę w jednej linii. Możesz użyć reguł adblock lub składni plików hostów.",
     "system_host_files": "Pliki hosts systemu",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Również komentarz.",
     "example_regex_meaning": "zablokuj dostęp do domen pasujących do określonego wyrażenia regularnego.",
     "example_upstream_regular": "normalny DNS (przez UDP);",
+    "example_upstream_udp": "zwykły DNS (przez UDP, nazwa hosta);",
     "example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "zaszyfrowany <0>DNS-over-QUIC</0> (eksperymentalny);",
     "example_upstream_sdns": "<0>Stempel DNS</0> dla resolwerów <1>DNSCrypt</1> lub <2>DNS-over-HTTPS</2>;",
     "example_upstream_tcp": "zwykły DNS (przez TCP);",
+    "example_upstream_tcp_hostname": "zwykły DNS (przez TCP, nazwa hosta);",
     "all_lists_up_to_date_toast": "Wszystkie listy są już aktualne",
     "updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane",
     "dns_test_ok_toast": "Określone serwery DNS działają poprawnie",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Używaj podwójnych cudzysłowów do ścisłego wyszukiwania",
     "query_log_retention_confirm": "Czy na pewno chcesz zmienić sposób przechowywania dziennika zapytań? Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone",
     "anonymize_client_ip": "Anonimizuj adres IP klienta",
-    "anonymize_client_ip_desc": "Nie zapisuj pełnego adresu IP w dziennikach i statystykach.",
+    "anonymize_client_ip_desc": "Nie zapisuj pełnego adresu IP w dziennikach i statystykach",
     "dns_config": "Konfiguracja serwera DNS",
     "dns_cache_config": "Konfiguracja pamięci podręcznej DNS",
-    "dns_cache_config_desc": "Tutaj możesz skonfigurować pamięć podręczną DNS.",
+    "dns_cache_config_desc": "Tutaj możesz skonfigurować pamięć podręczną DNS",
     "blocking_mode": "Tryb blokowania",
     "default": "Domyślny",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Wpisz limit ilościowy",
     "rate_limit": "Limit ilościowy",
     "edns_enable": "Włącz podsieć klienta EDNS",
-    "edns_cs_desc": "Wyślij podsieci klientów do serwerów DNS.",
+    "edns_cs_desc": "Dodaj opcję podsieci klienta EDNS (ECS) do żądań nadrzędnych i rejestruj wartości wysyłane przez klientów w dzienniku zapytań.",
     "rate_limit_desc": "Liczba żądań na sekundę dozwolona na klienta. Ustawienie wartości 0 oznacza brak ograniczeń.",
     "blocking_ipv4_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania A",
     "blocking_ipv6_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania AAAA",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Interfejs sieciowy",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Twój interfejs www AdGuard Home Admin będzie dostępny pod następującymi adresami:",
-    "form_error_port": "Wprowadź prawidłowy numer portu.",
+    "form_error_port": "Wprowadź prawidłowy numer portu",
     "install_settings_dns": "Serwer DNS",
     "install_settings_dns_desc": "Konieczne będzie skonfigurowanie urządzenia lub routera do korzystania z serwera DNS pod następującymi adresami:",
     "install_settings_all_interfaces": "Wszystkie interfejsy",
@@ -356,7 +358,7 @@
     "open_dashboard": "Otwórz panel sterowania",
     "install_saved": "Pomyślnie zapisany",
     "encryption_title": "Szyfrowanie",
-    "encryption_desc": "Obsługa szyfrowania (HTTPS/TLS) dla interfejsu sieciowego DNS i administratora.",
+    "encryption_desc": "Obsługa szyfrowania (HTTPS/TLS) dla interfejsu sieciowego DNS i administratora",
     "encryption_config_saved": "Konfiguracja szyfrowania została zapisana",
     "encryption_server": "Nazwa serwera",
     "encryption_server_enter": "Wpisz swoją nazwę domeny",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Tutaj kopiuj/wklej klucze prywatne zakodowane w PEM do swojego certyfikatu.",
     "encryption_enable": "Włącz szyfrowanie (HTTPS, DNS-over-HTTPS i DNS-over-TLS)",
     "encryption_enable_desc": "Jeśli szyfrowanie jest włączone, interfejs administracyjny AdGuard Home będzie działał przez HTTPS, a serwer DNS będzie nasłuchiwał żądań przez DNS-over-HTTPS i DNS-over-TLS.",
-    "encryption_chain_valid": "Łańcuch certyfikatów jest prawidłowy.",
-    "encryption_chain_invalid": "Łańcuch certyfikatu jest nieprawidłowy.",
-    "encryption_key_valid": "Poprawny {{type}} klucz prywatny.",
-    "encryption_key_invalid": "Nieprawidłowy {{type}} klucz prywatny.",
+    "encryption_chain_valid": "Łańcuch certyfikatów jest prawidłowy",
+    "encryption_chain_invalid": "Łańcuch certyfikatu jest nieprawidłowy",
+    "encryption_key_valid": "Poprawny {{type}} klucz prywatny",
+    "encryption_key_invalid": "Nieprawidłowy {{type}} klucz prywatny",
     "encryption_subject": "Temat",
     "encryption_issuer": "Zgłaszający",
     "encryption_hostnames": "Nazwy hostów",
     "encryption_reset": "Czy na pewno chcesz zresetować ustawienia szyfrowania?",
     "topline_expiring_certificate": "Twój certyfikat SSL wkrótce wygaśnie. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
     "topline_expired_certificate": "Twój certyfikat SSL wygasł. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
-    "form_error_port_range": "Wpisz numer portu z zakresu 80-65535.",
-    "form_error_port_unsafe": "To jest niebezpieczny port.",
-    "form_error_equal": "Nie mogą być równe.",
-    "form_error_password": "Niezgodne hasło.",
+    "form_error_port_range": "Wpisz numer portu z zakresu 80-65535",
+    "form_error_port_unsafe": "Niebezpieczny port",
+    "form_error_equal": "Nie mogą być równe",
+    "form_error_password": "Niezgodne hasło",
     "reset_settings": "Resetowanie ustawień",
     "update_announcement": "AdGuard Home {{version}} jest już dostępny! <0>Kliknij tutaj</0> aby uzyskać więcej informacji.",
     "setup_guide": "Przewodnik instalacji",
     "dns_addresses": "Adresy DNS",
     "dns_start": "Serwer DNS uruchamia się",
-    "dns_status_error": "Błąd podczas sprawdzania stanu serwera DNS.",
+    "dns_status_error": "Błąd podczas sprawdzania stanu serwera DNS",
     "down": "Utrata połączenia",
     "fix": "Napraw",
     "dns_providers": "Oto lista <0>znanych dostawców DNS</0> do wyboru.",
@@ -406,7 +408,7 @@
     "manual_update": "Proszę <a>wykonać te czynności</a>, aby zaktualizować ręcznie.",
     "processing_update": "Poczekaj, trwa aktualizacja AdGuard Home",
     "clients_title": "Trwali klienci",
-    "clients_desc": "Skonfiguruj trwałe rekordy klienta dla urządzeń podłączonych do AdGuard Home.",
+    "clients_desc": "Skonfiguruj trwałe rekordy klienta dla urządzeń podłączonych do AdGuard Home",
     "settings_global": "Globalny",
     "settings_custom": "Własne",
     "table_client": "Klient",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Czy na pewno chcesz usunąć klienta \"{{key}}\"?",
     "list_confirm_delete": "Czy na pewno chcesz usunąć tę listę?",
     "auto_clients_title": "Uruchomieni klienci",
-    "auto_clients_desc": "Urządzenia, których nie ma na liście stałych klientów, które mogą nadal korzystać z AdGuard Home.",
+    "auto_clients_desc": "Urządzenia, których nie ma na liście stałych klientów, które mogą nadal korzystać z AdGuard Home",
     "access_title": "Ustawienia dostępu",
-    "access_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home.",
+    "access_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home",
     "access_allowed_title": "Dozwoleni klienci",
     "access_allowed_desc": "Lista identyfikatorów CIDR, adresów IP lub <a>identyfikatorów klienta</a>. Jeśli ta lista zawiera wpisy, AdGuard Home zaakceptuje żądania tylko od tych klientów.",
     "access_disallowed_title": "Niedozwoleni klienci",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Przepisywanie DNS",
     "form_domain": "Wpisz nazwę domeny lub symbol wieloznaczny",
     "form_answer": "Wpisz adres IP lub nazwę domeny",
-    "form_error_domain_format": "Niepoprawny format domeny.",
-    "form_error_answer_format": "Nieprawidłowy format odpowiedzi.",
+    "form_error_domain_format": "Niepoprawny format domeny",
+    "form_error_answer_format": "Nieprawidłowy format odpowiedzi",
     "configure": "Skonfiguruj",
     "main_settings": "Ustawienia główne",
     "block_services": "Zablokuj określone usługi",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} dni",
     "interval_days_plural": "{{count}} dni",
     "domain": "Domena",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Odpowiedź",
     "filter_added_successfully": "Lista została pomyślnie dodana",
@@ -507,7 +510,7 @@
     "filter_updated": "Filtr został pomyślnie zaktualizowany",
     "statistics_configuration": "Konfiguracja statystyk",
     "statistics_retention": "Przechowywanie statystyk",
-    "statistics_retention_desc": "Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone.",
+    "statistics_retention_desc": "Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone",
     "statistics_clear": "Wyczyść statystyki",
     "statistics_clear_confirm": "Czy na pewno chcesz wyczyścić statystyki?",
     "statistics_retention_confirm": "Czy chcesz zmienić sposób przechowania statystyk? Jeżeli obniżysz wartość interwału, niektóre dane będą utracone",
@@ -517,7 +520,7 @@
     "interval_hours_plural": "{{count}} godziny",
     "filters_configuration": "Konfiguracja filtrów",
     "filters_enable": "Włącz filtry",
-    "filters_interval": "Częstotliwość aktualizacji filtrów",
+    "filters_interval": "Aktualizuj filtry co",
     "disabled": "Wyłączone",
     "username_label": "Nazwa użytkownika",
     "username_placeholder": "Wpisz nazwę użytkownika",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Wpisz maksymalną wartość TTL (w sekundach)",
     "cache_ttl_min_override_desc": "Przedłuż najkrótszą wartość TTL (w sekundach) otrzymaną od serwera wychodzącego podczas buforowania odpowiedzi DNS.",
     "cache_ttl_max_override_desc": "Ustaw maksymalną wartość czasu życia (w sekundach) dla wpisów w pamięci podręcznej DNS.",
-    "ttl_cache_validation": "Minimalne nadpisanie pamięci podręcznej TTL musi być mniejsze lub równe maksimum.",
+    "ttl_cache_validation": "Minimalne nadpisanie pamięci podręcznej TTL musi być mniejsze lub równe maksimum",
     "cache_optimistic": "Optymistyczne buforowanie",
     "cache_optimistic_desc": "Spraw, aby AdGuard Home odpowiadał z pamięci podręcznej, nawet gdy wpisy wygasły, a także spróbuj je odświeżyć.",
     "filter_category_general": "Ogólne",
@@ -628,5 +631,5 @@
     "parental_control": "Kontrola rodzicielska",
     "safe_browsing": "Bezpieczne przeglądanie",
     "served_from_cache": "{{value}} <i>(podawane z pamięci podręcznej)</i>",
-    "form_error_password_length": "Hasło musi mieć przynajmniej {{value}} znaków."
+    "form_error_password_length": "Hasło musi mieć co najmniej {{value}} znaków"
 }
diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json
index 56c91880..48d82e0d 100644
--- a/client/src/__locales/pt-br.json
+++ b/client/src/__locales/pt-br.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Configurações DHCP salvas com sucesso",
     "dhcp_ipv4_settings": "Configurações DHCP IPv4",
     "dhcp_ipv6_settings": "Configurações DHCP IPv6",
-    "form_error_required": "Campo obrigatório.",
-    "form_error_ip4_format": "Endereço de IPv4 inválido.",
-    "form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido.",
+    "form_error_required": "Campo obrigatório",
+    "form_error_ip4_format": "Endereço de IPv4 inválido",
+    "form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido",
     "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido.",
-    "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido.",
-    "form_error_ip6_format": "Endereço de IPv6 inválido.",
-    "form_error_ip_format": "Endereço de IP inválido.",
-    "form_error_mac_format": "Endereço de MAC inválido.",
+    "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido",
+    "form_error_ip6_format": "Endereço de IPv6 inválido",
+    "form_error_ip_format": "Endereço de IP inválido",
+    "form_error_mac_format": "Endereço de MAC inválido",
     "form_error_client_id_format": "O ID do cliente deve conter apenas números, letras minúsculas e hifens",
-    "form_error_server_name": "Nome de servidor inválido.",
-    "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\".",
-    "form_error_positive": "Deve ser maior que 0.",
-    "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Deve ser inferior ao início do intervalo.",
-    "greater_range_start_error": "Deve ser maior que o início do intervalo.",
-    "greater_range_end_error": "Deve ser maior que o fim do intervalo.",
-    "subnet_error": "Endereços devem estar em uma sub-rede.",
-    "gateway_or_subnet_invalid": "Máscara de sub-rede inválida.",
+    "form_error_server_name": "Nome de servidor inválido",
+    "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
+    "form_error_positive": "Deve ser maior que 0",
+    "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Deve ser inferior ao início do intervalo",
+    "greater_range_start_error": "Deve ser maior que o início do intervalo",
+    "greater_range_end_error": "Deve ser maior que o fim do intervalo",
+    "subnet_error": "Endereços devem estar em uma sub-rede",
+    "gateway_or_subnet_invalid": "Máscara de sub-rede inválida",
     "dhcp_form_gateway_input": "IP do gateway",
     "dhcp_form_subnet_input": "Máscara de sub-rede",
     "dhcp_form_range_title": "Faixa de endereços IP",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Nome do servidor",
     "dhcp_table_expires": "Expira",
     "dhcp_warning": "Se você quiser ativar o servidor DHCP de qualquer maneira, certifique-se de que não haja outro servidor DHCP ativo em sua rede, pois isso pode quebrar a conectividade com a Internet para dispositivos na rede!",
-    "dhcp_error": "O AdGuard Home não conseguiu determinar se há outro servidor DHCP ativo na rede.",
+    "dhcp_error": "O AdGuard Home não conseguiu determinar se há outro servidor DHCP ativo na rede",
     "dhcp_static_ip_error": "Para usar o servidor DHCP, você deve definir um endereço IP estático. AdGuard Home não conseguiu determinar se essa interface de rede está configurada usando o endereço de IP estático. Por favor, defina um endereço IP estático manualmente.",
     "dhcp_dynamic_ip_found": "Seu sistema usa a configuração de endereço IP dinâmico para a interface <0>{{interfaceName}}</0>. Para usar o servidor DHCP, você deve definir um endereço de IP estático. Seu endereço IP atual é <0> {{ipAddress}} </ 0>. AdGuard Home irá definir automaticamente este endereço IP como estático se você pressionar o botão \"Ativar servidor DHCP\".",
     "dhcp_lease_added": "Concessão estática \"{{key}}\" adicionada com sucesso",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Escolher as listas de permissões",
     "enter_valid_blocklist": "Digite um URL válido para a lista de bloqueio.",
     "enter_valid_allowlist": "Digite uma URL válida para a lista de permissões.",
-    "form_error_url_format": "Formato da URL inválida.",
-    "form_error_url_or_path_format": "URL ou local da lista inválida.",
+    "form_error_url_format": "Formato da URL inválida",
+    "form_error_url_or_path_format": "URL ou local da lista inválida",
     "custom_filter_rules": "Regras de filtragem personalizadas",
     "custom_filter_rules_hint": "Digite uma regra por linha. Você pode usar regras de bloqueio de anúncios ou a sintaxe de arquivos de hosts.",
     "system_host_files": "Arquivos hosts do sistema",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Também um comentário.",
     "example_regex_meaning": "bloqueia o acesso aos domínios que correspondem à expressão regular especificada.",
     "example_upstream_regular": "dNS regular (através do UDP);",
+    "example_upstream_udp": "DNS normal (através do UDP, nome do servidor);",
     "example_upstream_dot": "<0>DNS-sobre-TLS</0> criptografado;",
     "example_upstream_doh": "<0>DNS-sobre-HTTPS</0> criptografado;",
     "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado (experimental);",
     "example_upstream_sdns": "<0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>;",
     "example_upstream_tcp": "DNS regular (através do TCP);",
+    "example_upstream_tcp_hostname": "DNS normal (através do TCP, nome do servidor);",
     "all_lists_up_to_date_toast": "Todas as listas já estão atualizadas",
     "updated_upstream_dns_toast": "Servidores DNS primário salvos com sucesso",
     "dns_test_ok_toast": "Os servidores DNS especificados estão funcionando corretamente",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Use aspas duplas para uma pesquisa mais criteriosa",
     "query_log_retention_confirm": "Você tem certeza de que deseja alterar o arquivamento do registro de consulta? Se diminuir o valor de intervalo, alguns dados serão perdidos",
     "anonymize_client_ip": "Tornar anônimo o IP do cliente",
-    "anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros ou estatísticas.",
+    "anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros ou estatísticas",
     "dns_config": "Configuração do servidor DNS",
     "dns_cache_config": "Configuração de cache DNS",
-    "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS.",
+    "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS",
     "blocking_mode": "Modo de bloqueio",
     "default": "Padrão",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Insira a taxa limite",
     "rate_limit": "Taxa limite",
     "edns_enable": "Ativar a sub-rede do cliente EDNS",
-    "edns_cs_desc": "Envia as sub-redes dos clientes para os servidores DNS.",
+    "edns_cs_desc": "Adicione a opção de sub-rede de cliente EDNS (ECS) às solicitações de servidor DNS primário e registre os valores enviados pelos clientes no registro de consulta.",
     "rate_limit_desc": "O número de solicitações por segundo permitidas por cliente. Definir como 0 significa que não há limite.",
     "blocking_ipv4_desc": "Endereço de IP a ser retornado para uma solicitação bloqueada",
     "blocking_ipv6_desc": "Endereço de IP a ser retornado para uma solicitação AAAA bloqueada",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Interface de escuta",
     "install_settings_port": "Porta",
     "install_settings_interface_link": "A interface web de administrador do AdGuard estará disponível nos seguintes endereços:",
-    "form_error_port": "Digite um numero de porta válida.",
+    "form_error_port": "Digite um numero de porta válida",
     "install_settings_dns": "Servidor DNS",
     "install_settings_dns_desc": "Você precisa configurar seu dispositivo ou roteador para usar o servidor DNS nos seguintes endereços:",
     "install_settings_all_interfaces": "Todas interfaces",
@@ -356,7 +358,7 @@
     "open_dashboard": "Abrir painel",
     "install_saved": "Salvo com sucesso",
     "encryption_title": "Criptografia",
-    "encryption_desc": "Suporte a criptografia (HTTPS/TLS) para DNS e interface de administração web.",
+    "encryption_desc": "Suporte a criptografia (HTTPS/QUIC/TLS) para DNS e interface de administração web",
     "encryption_config_saved": "Configuração de criptografia salva",
     "encryption_server": "Nome do servidor",
     "encryption_server_enter": "Digite seu nome de domínio",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Copie/cole aqui a chave privada codificada em PEM para seu certificado.",
     "encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
     "encryption_enable_desc": "Se a criptografia estiver ativada, a interface administrativa do AdGuard Home funcionará em HTTPS, o servidor DNS irá capturar as solicitações por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.",
-    "encryption_chain_valid": "Cadeia de chave válida.",
-    "encryption_chain_invalid": "A cadeia de certificado é inválida.",
-    "encryption_key_valid": "Esta é uma chave privada {{type}} válida.",
-    "encryption_key_invalid": "Esta é uma chave privada {{type}} inválida.",
+    "encryption_chain_valid": "Cadeia de chave válida",
+    "encryption_chain_invalid": "A cadeia de certificado é inválida",
+    "encryption_key_valid": "Esta é uma chave privada {{type}} válida",
+    "encryption_key_invalid": "Esta é uma chave privada {{type}} inválida",
     "encryption_subject": "Assunto",
     "encryption_issuer": "Emissor",
     "encryption_hostnames": "Nomes dos servidores",
     "encryption_reset": "Você tem certeza de que deseja redefinir a configuração de criptografia?",
     "topline_expiring_certificate": "Seu certificado SSL está prestes a expirar. Atualize suas <0>configurações de criptografia</]0>",
     "topline_expired_certificate": "Seu certificado SSL está expirado. Atualize suas <0>configurações de criptografia</0>",
-    "form_error_port_range": "Digite um número de porta entre 80 e 65535.",
-    "form_error_port_unsafe": "Esta porta não é segura.",
-    "form_error_equal": "Não deve ser igual.",
-    "form_error_password": "Senhas não coincidem.",
+    "form_error_port_range": "Digite um número de porta entre 80 e 65535",
+    "form_error_port_unsafe": "Porta não é segura",
+    "form_error_equal": "Não deve ser igual",
+    "form_error_password": "Senhas não coincidem",
     "reset_settings": "Redefinir configurações",
     "update_announcement": "AdGuard Home {{version}} está disponível!<0>Clique aqui</0> para mais informações.",
     "setup_guide": "Guia de configuração",
     "dns_addresses": "Endereços DNS",
     "dns_start": "O servidor DNS está iniciando",
-    "dns_status_error": "Ocorreu um erro ao verificar o status do servidor DNS.",
+    "dns_status_error": "Ocorreu um erro ao verificar o status do servidor DNS",
     "down": "Caiu",
     "fix": "Corrigido",
     "dns_providers": "Aqui está uma <0>lista de provedores de DNS conhecidos</0> para escolher.",
@@ -406,7 +408,7 @@
     "manual_update": "Por favor, <a>siga estes passos</a> para atualizar manualmente.",
     "processing_update": "Por favor, aguarde enquanto o AdGuard Home está sendo atualizado",
     "clients_title": "Clientes persistentes",
-    "clients_desc": "Configure registros de cliente persistentes para dispositivos conectados ao AdGuard Home.",
+    "clients_desc": "Configure registros de cliente persistentes para dispositivos conectados ao AdGuard Home",
     "settings_global": "Global",
     "settings_custom": "Personalizado",
     "table_client": "Cliente",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Você tem certeza de que deseja excluir o cliente \"{{key}}\"?",
     "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?",
     "auto_clients_title": "Clientes ativos",
-    "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home.",
+    "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home",
     "access_title": "Configurações de acessos",
-    "access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home.",
+    "access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home",
     "access_allowed_title": "Clientes permitidos",
     "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou <a>IDs de cliente</a>. Se esta lista tiver entradas, o AdGuard Home do aceitará solicitações apenas desses clientes.",
     "access_disallowed_title": "Clientes não permitidos",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Reescritas de DNS",
     "form_domain": "Digite o nome do domínio ou wildcard",
     "form_answer": "Digite o endereço de IP ou nome de domínio",
-    "form_error_domain_format": "Formato de domínio inválido.",
-    "form_error_answer_format": "Formato de resposta inválido.",
+    "form_error_domain_format": "Formato de domínio inválido",
+    "form_error_answer_format": "Formato de resposta inválido",
     "configure": "Configurar",
     "main_settings": "Configurações principais",
     "block_services": "Bloquear serviços específicos",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} dias",
     "interval_days_plural": "{{count}} dias",
     "domain": "Domínio",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Resposta",
     "filter_added_successfully": "O filtro foi adicionado com sucesso",
@@ -507,7 +510,7 @@
     "filter_updated": "O filtro atualizado com sucesso",
     "statistics_configuration": "Configurações de estatísticas",
     "statistics_retention": "Permanência das estatísticas",
-    "statistics_retention_desc": "Se você diminuir o valor do intervalo, alguns dados serão perdidos.",
+    "statistics_retention_desc": "Se você diminuir o valor do intervalo, alguns dados serão perdidos",
     "statistics_clear": " Limpar estatísticas",
     "statistics_clear_confirm": "Você tem certeza de que deseja limpar as estatísticas?",
     "statistics_retention_confirm": "Você tem certeza que quer alterar o arquivamento das estatísticas? Se diminuir o valor do intervalo, alguns dados serão perdidos",
@@ -517,7 +520,7 @@
     "interval_hours_plural": "{{count}} horas",
     "filters_configuration": "Configuração de filtros",
     "filters_enable": "Ativar filtros",
-    "filters_interval": "Intervalo de atualização de filtros",
+    "filters_interval": "Intervalo de atualização de filtro",
     "disabled": "Desativado",
     "username_label": "Nome de usuário",
     "username_placeholder": "Digite o nome de usuário",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Digite o TTL máximo (segundos)",
     "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS.",
     "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS.",
-    "ttl_cache_validation": "O substituto mínimo de cache TTL deve ser menor ou igual ao máximo.",
+    "ttl_cache_validation": "O substituto mínimo de cache TTL deve ser menor ou igual ao máximo",
     "cache_optimistic": "Cache otimista",
     "cache_optimistic_desc": "Faz o AdGuard Home responder a partir do cache mesmo quando as entradas expirarem e também tenta atualizá-las.",
     "filter_category_general": "Geral",
@@ -628,5 +631,5 @@
     "parental_control": "Controle parental",
     "safe_browsing": "Navegação segura",
     "served_from_cache": "{{value}} <i>(servido do cache)</i>",
-    "form_error_password_length": "A senha deve ter pelo menos {{value}} caracteres."
+    "form_error_password_length": "A senha deve ter pelo menos {{value}} caracteres"
 }
diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json
index 068ebc03..1a2d5c7c 100644
--- a/client/src/__locales/pt-pt.json
+++ b/client/src/__locales/pt-pt.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Definições DHCP guardadas com sucesso",
     "dhcp_ipv4_settings": "Definições DHCP IPv4",
     "dhcp_ipv6_settings": "Definições DHCP IPv6",
-    "form_error_required": "Campo obrigatório.",
-    "form_error_ip4_format": "Endereço de IPv4 inválido.",
-    "form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido.",
-    "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido.",
-    "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido.",
-    "form_error_ip6_format": "Endereço de IPv6 inválido.",
-    "form_error_ip_format": "Endereço de IP inválido.",
-    "form_error_mac_format": "Endereço de MAC inválido.",
+    "form_error_required": "Campo obrigatório",
+    "form_error_ip4_format": "Endereço de IPv4 inválido",
+    "form_error_ip4_range_start_format": "Endereço IPv4 de início de intervalo inválido",
+    "form_error_ip4_range_end_format": "Endereço IPv4 de fim de intervalo inválido",
+    "form_error_ip4_gateway_format": "Endereço IPv4 de gateway inválido",
+    "form_error_ip6_format": "Endereço de IPv6 inválido",
+    "form_error_ip_format": "Endereço de email inválido",
+    "form_error_mac_format": "Endereço de MAC inválido",
     "form_error_client_id_format": "O ID do cliente deve conter apenas números, letras minúsculas e hifens",
-    "form_error_server_name": "Nome de servidor inválido.",
-    "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\".",
-    "form_error_positive": "Deve ser maior que 0.",
-    "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Deve ser inferior ao início do intervalo.",
-    "greater_range_start_error": "Deve ser maior que o início do intervalo.",
-    "greater_range_end_error": "Deve ser maior que o fim do intervalo.",
-    "subnet_error": "Os endereços devem estar em uma sub-rede.",
-    "gateway_or_subnet_invalid": "Máscara de sub-rede inválida.",
+    "form_error_server_name": "Nome de servidor inválido",
+    "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
+    "form_error_positive": "Deve ser maior que 0",
+    "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Deve ser inferior ao início do intervalo",
+    "greater_range_start_error": "Deve ser maior que o início do intervalo",
+    "greater_range_end_error": "Deve ser maior que o fim do intervalo",
+    "subnet_error": "Os endereços devem estar em uma sub-rede",
+    "gateway_or_subnet_invalid": "Máscara de sub-rede inválida",
     "dhcp_form_gateway_input": "IP do gateway",
     "dhcp_form_subnet_input": "Máscara de sub-rede",
     "dhcp_form_range_title": "Faixa de endereços IP",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Nome do servidor",
     "dhcp_table_expires": "Expira",
     "dhcp_warning": "Se tu quiser ativar o servidor DHCP de qualquer maneira, certifique-se de que não haja outro servidor DHCP ativo em tua rede, pois isso pode quebrar a conectividade com a Internet para dispositivos na rede!",
-    "dhcp_error": "O AdGuard Home não conseguiu determinar se há noutro servidor DHCP ativo na rede.",
+    "dhcp_error": "O AdGuard Home não conseguiu determinar se há noutro servidor DHCP ativo na rede",
     "dhcp_static_ip_error": "Para usar o servidor DHCP, deve definir um endereço IP estático. AdGuard Home não conseguiu determinar se essa interface de rede está configurada usando o endereço de IP estático. Por favor, defina um endereço IP estático manualmente.",
     "dhcp_dynamic_ip_found": "O seu sistema usa a configuração de endereço IP dinâmico para a interface <0>{{interfaceName}}</0>. Para usar o servidor DHCP, deve definir um endereço de IP estático. O seu endereço IP atual é <0> {{ipAddress}} </ 0>. AdGuard Home irá definir automaticamente este endereço IP como estático se pressionar o botão \"Ativar servidor DHCP\".",
     "dhcp_lease_added": "Concessão estática \"{{key}}\" adicionada com sucesso",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Escolher as listas de permissões",
     "enter_valid_blocklist": "Digite uma URL válida para a lista de bloqueio.",
     "enter_valid_allowlist": "Digite uma URL válida para a lista de permissões.",
-    "form_error_url_format": "Formato da URL inválida.",
-    "form_error_url_or_path_format": "URL ou local da lista inválida.",
+    "form_error_url_format": "Formato da URL inválida",
+    "form_error_url_or_path_format": "URL ou local da lista inválida",
     "custom_filter_rules": "Regras de filtragem personalizadas",
     "custom_filter_rules_hint": "Insira uma regra por linha. Pode usar regras de bloqueio de anúncios ou a sintaxe de ficheiros de hosts.",
     "system_host_files": "Arquivos hosts do sistema",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Também um comentário.",
     "example_regex_meaning": "bloquear o acesso aos domínios que correspondam à expressão regular especificada.",
     "example_upstream_regular": "DNS regular (através do UDP)",
+    "example_upstream_udp": "DNS normal (através do UDP, nome do servidor);",
     "example_upstream_dot": "<0>DNS-sobre-TLS</0> criptografado;",
     "example_upstream_doh": "<0>DNS-sobre-HTTPS</0> criptografado;",
     "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado (experimental);",
     "example_upstream_sdns": "<0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>;",
     "example_upstream_tcp": "DNS regular (através do TCP);",
+    "example_upstream_tcp_hostname": "DNS normal (através do TCP, nome do servidor);",
     "all_lists_up_to_date_toast": "Todas as listas já estão atualizadas",
     "updated_upstream_dns_toast": "Servidores DNS primário guardados com sucesso",
     "dns_test_ok_toast": "Os servidores DNS especificados estão a funcionar corretamente",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Usar aspas duplas para uma pesquisa rigorosa",
     "query_log_retention_confirm": "Tem a certeza de que deseja alterar a retenção do registo de consulta? Se diminuir o valor do intervalo, alguns dados serão perdidos",
     "anonymize_client_ip": "Tornar anónimo o IP do cliente",
-    "anonymize_client_ip_desc": "Não gurda o endereço de IP completo do cliente em registros ou estatísticas.",
+    "anonymize_client_ip_desc": "Não gurda o endereço de IP completo do cliente em registros ou estatísticas",
     "dns_config": "Definição do servidor DNS",
     "dns_cache_config": "Definição de cache DNS",
-    "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS.",
+    "dns_cache_config_desc": "Aqui você pode configurar o cache do DNS",
     "blocking_mode": "Modo de bloqueio",
     "default": "Predefinido",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Insira o limite de taxa",
     "rate_limit": "Limite de taxa",
     "edns_enable": "Ativar a sub-rede do cliente EDNS",
-    "edns_cs_desc": "Envia as sub-redes dos clientes para os servidores DNS.",
+    "edns_cs_desc": "Adicione a opção de sub-rede de cliente EDNS (ECS) às solicitações de servidor DNS primário e registre os valores enviados pelos clientes no registro de consulta.",
     "rate_limit_desc": "O número de solicitações por segundo permitido por cliente. Configurando para 0 significa sem limite.",
     "blocking_ipv4_desc": "Endereço IP a ser devolvido para uma solicitação A bloqueada",
     "blocking_ipv6_desc": "Endereço IP a ser devolvido para uma solicitação AAAA bloqueada",
@@ -356,7 +358,7 @@
     "open_dashboard": "Abrir Painel",
     "install_saved": "Guardado com sucesso",
     "encryption_title": "Encriptação",
-    "encryption_desc": "Suporta a criptografia (HTTPS/TLS) para DNS e interface de administração web.",
+    "encryption_desc": "Suporta a criptografia (HTTPS/QUIC/TLS) para DNS e interface de administração web",
     "encryption_config_saved": "Definição de criptografia guardada",
     "encryption_server": "Nome do servidor",
     "encryption_server_enter": "Insira o seu nome de domínio",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Copie/cole aqui a chave privada codificada em PEM para o seu certificado.",
     "encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
     "encryption_enable_desc": "Se a criptografia estiver ativada, a interface administrativa do AdGuard Home funcionará em HTTPS, o servidor DNS irá capturar as solicitações por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.",
-    "encryption_chain_valid": "Cadeia de certificado válida.",
-    "encryption_chain_invalid": "A cadeia de certificado é inválida.",
-    "encryption_key_valid": "Esta é uma chave privada {{type}} válida.",
-    "encryption_key_invalid": "Esta é uma chave privada {{type}} inválida.",
+    "encryption_chain_valid": "Cadeia de certificado válida",
+    "encryption_chain_invalid": "A cadeia de certificado é inválida",
+    "encryption_key_valid": "Esta é uma chave privada {{type}} válida",
+    "encryption_key_invalid": "Esta é uma chave privada {{type}} inválida",
     "encryption_subject": "Assunto",
     "encryption_issuer": "Emissor",
     "encryption_hostnames": "Nomes dos servidores",
     "encryption_reset": "Tem a certeza de que deseja repor a definição de criptografia?",
     "topline_expiring_certificate": "O seu certificado SSL está prestes a expirar. Atualize as suas <0>definições de criptografia</0>.",
     "topline_expired_certificate": "O seu certificado SSL está expirado. Atualize as suas <0>definições de criptografia</0>.",
-    "form_error_port_range": "Digite um numero de porta entre 80 e 65535.",
-    "form_error_port_unsafe": "Esta porta não é segura.",
-    "form_error_equal": "Não deve ser igual.",
-    "form_error_password": "As palavras-passe não coincidem.",
+    "form_error_port_range": "Digite um numero de porta entre 80 e 65535",
+    "form_error_port_unsafe": "Porta não é segura",
+    "form_error_equal": "Não deve ser igual",
+    "form_error_password": "As palavras-passe não coincidem",
     "reset_settings": "Repor definições",
     "update_announcement": "AdGuard Home {{version}} está disponível!<0>Clique aqui</0> para mais informações.",
     "setup_guide": "Guia de instalação",
     "dns_addresses": "Endereços DNS",
     "dns_start": "O servidor DNS está a iniciar",
-    "dns_status_error": "Ocorreu um erro ao verificar o estado do servidor DNS.",
+    "dns_status_error": "Ocorreu um erro ao verificar o estado do servidor DNS",
     "down": "Caiu",
     "fix": "Corrigido",
     "dns_providers": "Aqui está uma <0>lista de provedores de DNS conhecidos</0> para escolher.",
@@ -406,7 +408,7 @@
     "manual_update": "Por favor, <a>siga estes passos</a> para atualizar manualmente.",
     "processing_update": "Por favor espere, o AdGuard Home está a atualizar-se",
     "clients_title": "Clientes persistentes",
-    "clients_desc": "Configure registros de cliente persistentes para dispositivos conectados ao AdGuard Home.",
+    "clients_desc": "Configure registros de cliente persistentes para dispositivos conectados ao AdGuard Home",
     "settings_global": "Global",
     "settings_custom": "Personalizar",
     "table_client": "Cliente",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Tem a certeza de que deseja excluir o cliente \"{{key}}\"?",
     "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?",
     "auto_clients_title": "Clientes ativos",
-    "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home.",
+    "auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home",
     "access_title": "Definições de acesso",
-    "access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home.",
+    "access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home",
     "access_allowed_title": "Clientes permitidos",
     "access_allowed_desc": "Uma lista de CIDRs, endereços IP ou <a>IDs de cliente</a>. Se esta lista tiver entradas, o AdGuard Home do aceitará solicitações apenas desses clientes.",
     "access_disallowed_title": "Clientes não permitidos",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Reescritas de DNS",
     "form_domain": "Inserir domínio",
     "form_answer": "Insira o endereço de IP ou nome de domínio",
-    "form_error_domain_format": "Formato de domínio inválido.",
-    "form_error_answer_format": "Formato de resposta inválido.",
+    "form_error_domain_format": "Formato de domínio inválido",
+    "form_error_answer_format": "Formato de resposta inválido",
     "configure": "Configurar",
     "main_settings": "Definições principais",
     "block_services": "Bloquear serviços específicos",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} dias",
     "interval_days_plural": "{{count}} dias",
     "domain": "Domínio",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Resposta",
     "filter_added_successfully": "O filtro foi adicionado com sucesso",
@@ -507,7 +510,7 @@
     "filter_updated": "O filtro atualizado com sucesso",
     "statistics_configuration": "Definição das estatísticas",
     "statistics_retention": "Retenção de estatísticas",
-    "statistics_retention_desc": "Se diminuir o valor do intervalo, alguns dados serão perdidos.",
+    "statistics_retention_desc": "Se diminuir o valor do intervalo, alguns dados serão perdidos",
     "statistics_clear": "Limpar estatísticas",
     "statistics_clear_confirm": "Tem a certeza de que deseja limpar as estatísticas?",
     "statistics_retention_confirm": "Tem a certeza que quer alterar a retenção de estatísticas? Se diminuir o valor do intervalo, alguns dados serão perdidos",
@@ -517,7 +520,7 @@
     "interval_hours_plural": "{{count}} horas",
     "filters_configuration": "Definição dos filtros",
     "filters_enable": "Ativar filtros",
-    "filters_interval": "Intervalo de atualização de filtros",
+    "filters_interval": "Intervalo de atualização de filtro",
     "disabled": "Desativado",
     "username_label": "Nome do utilizador",
     "username_placeholder": "Insira o nome de utilizador",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Digite o TTL máximo (segundos)",
     "cache_ttl_min_override_desc": "Prolongue os valores de curta duração (segundos) recebidos do servidor primário ao armazenar em cache as respostas DNS.",
     "cache_ttl_max_override_desc": "Defina um valor máximo de tempo de vida (segundos) para entradas no cache DNS.",
-    "ttl_cache_validation": "O substituto mínimo de cache TTL deve ser menor ou igual ao máximo.",
+    "ttl_cache_validation": "O substituto mínimo de cache TTL deve ser menor ou igual ao máximo",
     "cache_optimistic": "Cache otimista",
     "cache_optimistic_desc": "Faz o AdGuard Home responder a partir do cache mesmo quando as entradas expirarem e também tenta atualizá-las.",
     "filter_category_general": "Geral",
@@ -628,5 +631,5 @@
     "parental_control": "Controlo parental",
     "safe_browsing": "Navegação segura",
     "served_from_cache": "{{value}} <i>(servido do cache)</i>",
-    "form_error_password_length": "A palavra-passe deve ter pelo menos {{value}} caracteres."
+    "form_error_password_length": "A palavra-passe deve ter pelo menos {{value}} caracteres"
 }
diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json
index c4b304b2..b19b8576 100644
--- a/client/src/__locales/ro.json
+++ b/client/src/__locales/ro.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Configurare DHCP salvată cu succes",
     "dhcp_ipv4_settings": "Setări DHCP IPv4",
     "dhcp_ipv6_settings": "Setări DHCP IPv6",
-    "form_error_required": "Câmp obligatoriu.",
-    "form_error_ip4_format": "Adresă IPv4 nevalidă.",
-    "form_error_ip4_range_start_format": "Adresă IPv4 nevalidă pentru începutul intervalului.",
-    "form_error_ip4_range_end_format": "Adresă IPv4 nevalidă a sfârșitului intervalului.",
-    "form_error_ip4_gateway_format": "Adresă IPv4 nevalidă a gateway-ului.",
-    "form_error_ip6_format": "Adresa IPv6 nevalidă.",
-    "form_error_ip_format": "Adresă IP nevalidă.",
-    "form_error_mac_format": "Adresă MAC nevalidă.",
-    "form_error_client_id_format": "ClientID-ul trebuie să conțină numai numere, litere minuscule și cratime.",
-    "form_error_server_name": "Nume de server nevalid.",
-    "form_error_subnet": "Subrețeaua „{{cidr}}” nu conține adresa IP „{{ip}}”.",
-    "form_error_positive": "Trebuie să fie mai mare de 0.",
-    "out_of_range_error": "Trebuie să fie în afara intervalului „{{start}}”-„{{end}}”.",
-    "lower_range_start_error": "Trebuie să fie mai mică decât începutul intervalului.",
-    "greater_range_start_error": "Trebuie să fie mai mare decât începutul intervalului.",
-    "greater_range_end_error": "Trebuie să fie mai mare decât sfârșitul intervalului.",
-    "subnet_error": "Adresele trebuie să fie în aceeași subrețea.",
-    "gateway_or_subnet_invalid": "Mască de subrețea nevalidă.",
+    "form_error_required": "Câmp obligatoriu",
+    "form_error_ip4_format": "Adresă IPv4 nevalidă",
+    "form_error_ip4_range_start_format": "Adresă IPv4 nevalidă pentru începutul intervalului",
+    "form_error_ip4_range_end_format": "Adresă IPv4 nevalidă a sfârșitului intervalului",
+    "form_error_ip4_gateway_format": "Adresă IPv4 nevalidă a gateway-ului",
+    "form_error_ip6_format": "Adresa IPv6 nevalidă",
+    "form_error_ip_format": "Adresă IP nevalidă",
+    "form_error_mac_format": "Adresă MAC nevalidă",
+    "form_error_client_id_format": "ClientID-ul trebuie să conțină numai numere, litere minuscule și cratime",
+    "form_error_server_name": "Nume de server nevalid",
+    "form_error_subnet": "Subrețeaua „{{cidr}}” nu conține adresa IP „{{ip}}”",
+    "form_error_positive": "Trebuie să fie mai mare de 0",
+    "out_of_range_error": "Trebuie să fie în afara intervalului „{{start}}”-„{{end}}”",
+    "lower_range_start_error": "Trebuie să fie mai mică decât începutul intervalului",
+    "greater_range_start_error": "Trebuie să fie mai mare decât începutul intervalului",
+    "greater_range_end_error": "Trebuie să fie mai mare decât sfârșitul intervalului",
+    "subnet_error": "Adresele trebuie să fie în aceeași subrețea",
+    "gateway_or_subnet_invalid": "Mască de subrețea nevalidă",
     "dhcp_form_gateway_input": "IP Gateway",
     "dhcp_form_subnet_input": "Mască subnet",
     "dhcp_form_range_title": "Interval de adrese IP",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Hostname",
     "dhcp_table_expires": "Expiră",
     "dhcp_warning": "Dacă doriți să activați serverul DHCP oricum, asigurați-vă că nu există nici un alt server DHCP activ în rețeaua dvs., deoarece acest lucru poate rupe conectivitatea la Internet a dispozitivelor din rețea!",
-    "dhcp_error": "AdGuard Home nu a putut determina dacă există un alt server DHCP activ în rețea.",
+    "dhcp_error": "AdGuard Home nu a putut determina dacă există un alt server DHCP activ în rețea",
     "dhcp_static_ip_error": "Pentru a utiliza serverul DHCP, trebuie setată o adresă IP statică. AdGuard Home nu a reușit să determine dacă această interfață de rețea este configurată utilizând o adresă IP statică. Setați manual o adresă IP statică.",
     "dhcp_dynamic_ip_found": "Sistemul dvs. folosește configurația dinamică a adreselor IP pentru interfața <0>{{interfaceName}}</0>. Pentru a utiliza serverul DHCP, trebuie setată o adresă IP statică. Adresa IP curentă este <0>{{ipAddress}}</0>. AdGuard Home o va configura automat ca adresă IP statică, dacă apăsați butonul \"Activați serverul DHCP\".",
     "dhcp_lease_added": "\"{{key}}\" statică închiriată adăugată cu succes",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Selectați liste de autorizări",
     "enter_valid_blocklist": "Introduceți un URL valid pentru blocare.",
     "enter_valid_allowlist": "Introduceți un URL valid pentru autorizare.",
-    "form_error_url_format": "Format URL nevalid.",
-    "form_error_url_or_path_format": "URL nevalabil sau calea absolută a listei.",
+    "form_error_url_format": "Format URL nevalid",
+    "form_error_url_or_path_format": "URL nevalabil sau calea absolută a listei",
     "custom_filter_rules": "Reguli de filtrare personalizate",
     "custom_filter_rules_hint": "Introduceți o regulă pe linie. Puteți utiliza reguli de blocare sau sintaxa de fișiere hosts.",
     "system_host_files": "Fișiere de sistem hosts",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# De asemenea, un comentariu.",
     "example_regex_meaning": "blochează accesul la domeniile care corespund expresiei regulate specificate.",
     "example_upstream_regular": "DNS clasic (over UDP);",
+    "example_upstream_udp": "DNS obișnuit (over UDP, nume de gazdă);",
     "example_upstream_dot": "<0>DNS-over-TLS</0> criptat;",
     "example_upstream_doh": "<0>DNS-over-HTTPS</0> criptat;",
     "example_upstream_doq": "<0>DNS-over-QUIC</0> criptat (experimental);",
     "example_upstream_sdns": "<0>DNS Stamps</0> pentru <1>DNSCrypt</1> sau rezolvere <2>DNS-over-HTTPS</2>;",
     "example_upstream_tcp": "DNS clasic (over TCP);",
+    "example_upstream_tcp_hostname": "DNS obișnuit (over TCP, nume de gazdă);",
     "all_lists_up_to_date_toast": "Toate listele sunt deja la zi",
     "updated_upstream_dns_toast": "Serverele din amonte au fost salvate cu succes",
     "dns_test_ok_toast": "Serverele DNS specificate funcționează corect",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Utilizați ghilimele duble pentru căutare strictă",
     "query_log_retention_confirm": "Sunteți sigur că doriți să schimbați retenția jurnalului de interogare? Reducând valoarea intervalului, unele date vor fi pierdute",
     "anonymize_client_ip": "Anonimizare client IP",
-    "anonymize_client_ip_desc": "Nu salvați adresa IP completă a clientului în jurnale și statistici.",
+    "anonymize_client_ip_desc": "Nu salvați adresa IP completă a clientului în jurnale și statistici",
     "dns_config": "Configurația serverului DNS",
     "dns_cache_config": "Configurare cache DNS",
-    "dns_cache_config_desc": "Aici puteți configura cache-ul DNS.",
+    "dns_cache_config_desc": "Aici puteți configura cache-ul DNS",
     "blocking_mode": "Modul de blocare",
     "default": "Implicit",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Introduceți limita ratei",
     "rate_limit": "Limita ratei",
     "edns_enable": "Activați subrețeaua de clienți EDNS",
-    "edns_cs_desc": "Trimite subrețelele clienților la serverele DNS.",
+    "edns_cs_desc": "Adaugă opțiunea EDNS Client Subnet (ECS) la solicitările în amonte și înregistrează valorile trimise de clienți în jurnalul de interogare.",
     "rate_limit_desc": "Numărul de interogări pe secundă permise pe client. Setarea la 0 înseamnă că nu există limită.",
     "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",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Interfață de ascultare",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Interfața dvs. de administrare AdGuard Home va fi disponibilă pe următoarele adrese:",
-    "form_error_port": "Introduceți un număr de port valid.",
+    "form_error_port": "Introduceți un număr de port valid",
     "install_settings_dns": "Server DNS",
     "install_settings_dns_desc": "Va trebui să configurați aparatele sau routerul pentru a utiliza serverul DNS pe următoarele adrese:",
     "install_settings_all_interfaces": "Toate interfețele",
@@ -356,7 +358,7 @@
     "open_dashboard": "Deschideți Tabloul de bord",
     "install_saved": "Salvat cu succes",
     "encryption_title": "Criptare",
-    "encryption_desc": "Suport pentru criptare (HTTPS/TLS) atât pentru DNS, cât și pentru interfața web de administrare.",
+    "encryption_desc": "Suport pentru criptare (HTTPS/TLS) atât pentru DNS, cât și pentru interfața web de administrare",
     "encryption_config_saved": "Configurația de criptare salvată",
     "encryption_server": "Nume de server",
     "encryption_server_enter": "Introduceți numele domeniului",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Copiați/lipiți cheia dvs. privată PEM-codată pentru certificatul dvs. aici.",
     "encryption_enable": "Activați criptarea (HTTPS, DNS-over-HTTPS, și DNS-over-TLS)",
     "encryption_enable_desc": "Dacă este activată criptarea, interfața administrator AdGuard Home va lucra peste HTTPS, și serverul DNS va asculta pentru cereri peste DNS-over-HTTPS și DNS-over-TLS.",
-    "encryption_chain_valid": "Lanț de certificate valid.",
-    "encryption_chain_invalid": "Lanț de certificate invalid.",
-    "encryption_key_valid": "Aceasta este o cheie privată {{type}} validă.",
-    "encryption_key_invalid": "Aceasta este o cheie privată {{type}} invalidă.",
+    "encryption_chain_valid": "Lanț de certificate valid",
+    "encryption_chain_invalid": "Lanț de certificate invalid",
+    "encryption_key_valid": "Aceasta este o cheie privată {{type}} validă",
+    "encryption_key_invalid": "Aceasta este o cheie privată {{type}} invalidă",
     "encryption_subject": "Obiect",
     "encryption_issuer": "Emitent",
     "encryption_hostnames": "Nume de host",
     "encryption_reset": "Sunteți sigur că doriți să resetați setările de criptare?",
     "topline_expiring_certificate": "Certificatul dvs. SSL este pe cale să expire. Actualizați <0>Setările de criptare</0>.",
     "topline_expired_certificate": "Certificatul dvs. SSL a expirat. Actualizați <0>Setările de criptare</0>.",
-    "form_error_port_range": "Introduceți valoarea portului între 80-65535.",
-    "form_error_port_unsafe": "Acesta este un port nesigur.",
-    "form_error_equal": "Nu trebuie să fie egale.",
-    "form_error_password": "Parolele nu corespund.",
+    "form_error_port_range": "Introduceți valoarea portului între 80-65535",
+    "form_error_port_unsafe": "Port nesigur",
+    "form_error_equal": "Nu trebuie să fie egale",
+    "form_error_password": "Parolele nu corespund",
     "reset_settings": "Resetare setări",
     "update_announcement": "AdGuard Home {{version}} este disponibil! <0>Clicați aici</0> pentru mai multe informații.",
     "setup_guide": "Ghid de instalare",
     "dns_addresses": "Adrese DNS",
     "dns_start": "Serverul DNS demarează",
-    "dns_status_error": "Eroare la verificare statut server DNS.",
+    "dns_status_error": "Eroare la verificarea stării serverului DNS",
     "down": "Down",
     "fix": "Fix",
     "dns_providers": "Iată o <0>listă de furnizori DNS cunoscuți</0> ce pot fi aleși.",
@@ -406,7 +408,7 @@
     "manual_update": "Vă rugăm <a>să urmați etapele următoare</a> pentru a actualiza manual.",
     "processing_update": "Vă rugăm să așteptați, AdGuard Home se actualizează...",
     "clients_title": "Clienți persistenți",
-    "clients_desc": "Configură înregistrările clientului permanent pentru dispozitivele conectate la AdGuard Home.",
+    "clients_desc": "Configurează înregistrările persistente ale clienților pentru dispozitivele conectate la AdGuard Home",
     "settings_global": "General",
     "settings_custom": "Personalizat",
     "table_client": "Client",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Sunteți sigur că doriți să ștergeți clientul \"{{key}}\"?",
     "list_confirm_delete": "Sigur doriți să ștergeți această listă?",
     "auto_clients_title": "Clienți runtime",
-    "auto_clients_desc": "Dispozitivele care nu se află pe lista de clienți Persistent care pot utiliza în continuare AdGuard Home.",
+    "auto_clients_desc": "Dispozitivele care nu se află pe lista de clienți Persistent care pot utiliza în continuare AdGuard Home",
     "access_title": "Setări de acces",
-    "access_desc": "Aici puteți configura regulile de acces pentru serverul DNS AdGuard Home.",
+    "access_desc": "Aici puteți configura regulile de acces pentru serverul DNS AdGuard Home",
     "access_allowed_title": "Clienți autorizați",
     "access_allowed_desc": "O listă de CIDR-uri, adrese IP sau <a>ClientID-uri</a>. Dacă această listă are intrări, AdGuard Home va accepta cereri numai de la acești clienți.",
     "access_disallowed_title": "Clienți neautorizați",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Rescrieri DNS",
     "form_domain": "Introduceți un nume de domeniu sau wildcard",
     "form_answer": "Introduceți adresa IP sau numele de domeniu",
-    "form_error_domain_format": "Format de domeniu invalid.",
-    "form_error_answer_format": "Format de răspuns invalid.",
+    "form_error_domain_format": "Format de domeniu invalid",
+    "form_error_answer_format": "Format de răspuns invalid",
     "configure": "Configurați",
     "main_settings": "Setări principale",
     "block_services": "Blochează anumite servicii",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} zi",
     "interval_days_plural": "{{count}} zile",
     "domain": "Domeniu",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Răspuns",
     "filter_added_successfully": "Filtrul a fost adăugat cu succes",
@@ -507,7 +510,7 @@
     "filter_updated": "Filtrul a fost actualizat cu succes",
     "statistics_configuration": "Configurația statisticilor",
     "statistics_retention": "Păstrarea statisticilor",
-    "statistics_retention_desc": "Dacă reduceți valoarea intervalului, unele date vor fi pierdute.",
+    "statistics_retention_desc": "Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
     "statistics_clear": " Șterge statisticile",
     "statistics_clear_confirm": "Sunteți sigur că doriți să ștergeți statisticile?",
     "statistics_retention_confirm": "Sunteți sigur că doriți să schimbați păstrarea statisticilor? Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
@@ -517,7 +520,7 @@
     "interval_hours_plural": "{{count}} ore",
     "filters_configuration": "Configurația filtrelor",
     "filters_enable": "Activați filtrele",
-    "filters_interval": "Interval de actualizare filtre",
+    "filters_interval": "Intervalul de actualizare a filtrului",
     "disabled": "Dezactivat",
     "username_label": "Nume utilizator",
     "username_placeholder": "Introduceți nume utilizator",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Introduceți maximum TTL (secunde)",
     "cache_ttl_min_override_desc": "Extinde valorile timp-de-viață scurte (secunde) primite de la serverul din amonte la stocarea în cache a răspunsurilor DNS.",
     "cache_ttl_max_override_desc": "Setează o valoare maximă a timpului-de-viață (secunde) pentru intrările din memoria cache DNS.",
-    "ttl_cache_validation": "Valoarea TTL cache minimă trebuie să fie mai mică sau egală cu valoarea maximă.",
+    "ttl_cache_validation": "Valoarea TTL cache minimă trebuie să fie mai mică sau egală cu valoarea maximă",
     "cache_optimistic": "Caching optimistic",
     "cache_optimistic_desc": "Face ca AdGuard Home să răspundă din cache chiar și atunci când intrările au expirate și de asemenea, încearcă să le reîmprospăteze.",
     "filter_category_general": "General",
@@ -628,5 +631,5 @@
     "parental_control": "Control Parental",
     "safe_browsing": "Navigare în siguranță",
     "served_from_cache": "{{value}} <i>(furnizat din cache)</i>",
-    "form_error_password_length": "Parola trebuie să aibă cel puțin {{value}} caractere."
+    "form_error_password_length": "Parola trebuie să aibă cel puțin {{value}} caractere"
 }
diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json
index dc73fded..214169fd 100644
--- a/client/src/__locales/ru.json
+++ b/client/src/__locales/ru.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Конфигурация DHCP-сервера успешно сохранена",
     "dhcp_ipv4_settings": "Настройки DHCP IPv4",
     "dhcp_ipv6_settings": "Настройки DHCP IPv6",
-    "form_error_required": "Обязательное поле.",
-    "form_error_ip4_format": "Некорректный IPv4-адрес.",
-    "form_error_ip4_range_start_format": "Некорректный IPv4-адрес начала диапазона.",
-    "form_error_ip4_range_end_format": "Некорректный IPv4-адрес конца диапазона.",
-    "form_error_ip4_gateway_format": "Некорректный IPv4-адрес шлюза.",
-    "form_error_ip6_format": "Некорректный IPv6-адрес.",
-    "form_error_ip_format": "Некорректный IP-адрес.",
-    "form_error_mac_format": "Некорректный MAC-адрес.",
-    "form_error_client_id_format": "ClientID может содержать только цифры, строчные латинские буквы и дефисы.",
-    "form_error_server_name": "Некорректное имя сервера.",
-    "form_error_subnet": "Подсеть «{{cidr}}» не содержит IP-адрес «{{ip}}».",
-    "form_error_positive": "Должно быть больше 0.",
-    "out_of_range_error": "Должно быть вне диапазона «{{start}}»-«{{end}}».",
-    "lower_range_start_error": "Должно быть меньше начала диапазона.",
-    "greater_range_start_error": "Должно быть больше начала диапазона.",
-    "greater_range_end_error": "Должно быть больше конца диапазона.",
-    "subnet_error": "Адреса должны быть внутри одной подсети.",
-    "gateway_or_subnet_invalid": "Некорректная маска подсети.",
+    "form_error_required": "Обязательное поле",
+    "form_error_ip4_format": "Некорректный IPv4-адрес",
+    "form_error_ip4_range_start_format": "Некорректный IPv4-адрес начала диапазона",
+    "form_error_ip4_range_end_format": "Некорректный IPv4-адрес конца диапазона",
+    "form_error_ip4_gateway_format": "Некорректный IPv4-адрес шлюза",
+    "form_error_ip6_format": "Некорректный IPv6-адрес",
+    "form_error_ip_format": "Некорректный IP-адрес",
+    "form_error_mac_format": "Некорректный MAC-адрес",
+    "form_error_client_id_format": "ClientID может содержать только цифры, строчные латинские буквы и дефисы",
+    "form_error_server_name": "Некорректное имя сервера",
+    "form_error_subnet": "Подсеть «{{cidr}}» не содержит IP-адрес «{{ip}}»",
+    "form_error_positive": "Должно быть больше 0",
+    "out_of_range_error": "Должно быть вне диапазона «{{start}}»-«{{end}}»",
+    "lower_range_start_error": "Должно быть меньше начала диапазона",
+    "greater_range_start_error": "Должно быть больше начала диапазона",
+    "greater_range_end_error": "Должно быть больше конца диапазона",
+    "subnet_error": "Адреса должны быть внутри одной подсети",
+    "gateway_or_subnet_invalid": "Некорректная маска подсети",
     "dhcp_form_gateway_input": "IP-адрес шлюза",
     "dhcp_form_subnet_input": "Маска подсети",
     "dhcp_form_range_title": "Диапазон IP-адресов",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Имя хоста",
     "dhcp_table_expires": "Истекает",
     "dhcp_warning": "Если вы все равно хотите включить DHCP-сервер, убедитесь, что в сети больше нет активных DHCP-серверов. Иначе это может сломать доступ в сеть для подключённых устройств!",
-    "dhcp_error": "AdGuard Home не смог определить присутствие других DHCP-серверов в сети.",
+    "dhcp_error": "AdGuard Home не смог определить присутствие других DHCP-серверов в сети",
     "dhcp_static_ip_error": "Чтобы использовать DHCP-сервер, должен быть установлен статический IP-адрес. AdGuard Home не смог определить, использует ли этот сетевой интерфейс статический IP-адрес. Пожалуйста, установите его вручную.",
     "dhcp_dynamic_ip_found": "Ваша система использует динамический IP-адрес для интерфейса <0>{{interfaceName}}</0>. Чтобы использовать DHCP-сервер, необходимо установить статический IP-адрес. Ваш текущий IP-адрес – <0>{{ipAddress}}</0>. Мы автоматически установим его как статический, если вы нажмёте кнопку «Включить DHCP-сервер».",
     "dhcp_lease_added": "Статическая аренда «{{key}}» успешно добавлена",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Выберите списки разрешённых",
     "enter_valid_blocklist": "Добавьте действующий URL-адрес в чёрный список.",
     "enter_valid_allowlist": "Добавьте действующий URL-адрес в белый список.",
-    "form_error_url_format": "Неверный формат URL.",
-    "form_error_url_or_path_format": "Неверный URL или абсолютный путь к списку.",
+    "form_error_url_format": "Неверный формат URL",
+    "form_error_url_or_path_format": "Неверный URL или абсолютный путь к списку",
     "custom_filter_rules": "Пользовательское правило фильтрации",
     "custom_filter_rules_hint": "Вводите по одному правилу на строчку. Вы можете использовать правила блокировки или синтаксис файлов hosts.",
     "system_host_files": "Системные hosts-файлы",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# И вот так тоже.",
     "example_regex_meaning": "блокировать доступ к доменам, соответствующим заданному регулярному выражению.",
     "example_upstream_regular": "обычный DNS (поверх UDP);",
+    "example_upstream_udp": "обычный DNS (поверх UDP, с именем хоста);",
     "example_upstream_dot": "зашифрованный <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "зашифрованный <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "зашифрованный <0>DNS-over-QUIC</0> (эксперементальный);",
     "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt</1> или <2>DNS-over-HTTPS</2> серверов;",
     "example_upstream_tcp": "обычный DNS (поверх TCP);",
+    "example_upstream_tcp_hostname": "обычный DNS (поверх TCP, с именем хоста);",
     "all_lists_up_to_date_toast": "Все списки уже обновлены",
     "updated_upstream_dns_toast": "DNS-серверы успешно обновлены",
     "dns_test_ok_toast": "Указанные серверы DNS работают корректно",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Используйте двойные кавычки для строгого поиска",
     "query_log_retention_confirm": "Вы уверены, что хотите изменить срок хранения запросов? При сокращении интервала данные могут быть утеряны",
     "anonymize_client_ip": "Анонимизировать IP-адрес клиента",
-    "anonymize_client_ip_desc": "Не сохранять полный IP-адрес клиента в журналах и статистике.",
+    "anonymize_client_ip_desc": "Не сохранять полный IP-адрес клиента в журналах и статистике",
     "dns_config": "Настройки DNS-сервера",
     "dns_cache_config": "Настройка кеша DNS",
-    "dns_cache_config_desc": "Здесь можно настроить кеш DNS.",
+    "dns_cache_config_desc": "Здесь можно настроить кеш DNS",
     "blocking_mode": "Режим блокировки",
     "default": "Стандартный",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Введите rate limit",
     "rate_limit": "Rate limit",
     "edns_enable": "Включить отправку EDNS Client Subnet",
-    "edns_cs_desc": "Отправлять подсети клиентов на DNS-серверы.",
+    "edns_cs_desc": "Добавлять опцию EDNS Client Subnet (ECS) к запросам к upstream-серверам, а также записывать присланные клиентами значения в журнал.",
     "rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно).",
     "blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса",
     "blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Сетевой интерфейс",
     "install_settings_port": "Порт",
     "install_settings_interface_link": "Ваш веб-интерфейс администрирования AdGuard Home будет доступен по следующим адресам:",
-    "form_error_port": "Введите корректный порт.",
+    "form_error_port": "Введите корректный порт",
     "install_settings_dns": "DNS-сервер",
     "install_settings_dns_desc": "Вам будет нужно настроить свои устройства или роутер на использование DNS-сервера на одном из следующих адресов:",
     "install_settings_all_interfaces": "Все интерфейсы",
@@ -356,7 +358,7 @@
     "open_dashboard": "Открыть Панель управления",
     "install_saved": "Успешно сохранено",
     "encryption_title": "Шифрование",
-    "encryption_desc": "Поддержка шифрования (HTTPS/TLS) для DNS и веб-интерфейса администрирования.",
+    "encryption_desc": "Поддержка шифрования (HTTPS/QUIC/TLS) для DNS и веб-интерфейса администрирования",
     "encryption_config_saved": "Настройки шифрования сохранены",
     "encryption_server": "Имя сервера",
     "encryption_server_enter": "Введите ваше доменное имя",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Скопируйте сюда приватный ключ в PEM-кодировке.",
     "encryption_enable": "Включить шифрование (HTTPS, DNS-over-HTTPS и DNS-over-TLS)",
     "encryption_enable_desc": "Если шифрование включено, веб-интерфейс AdGuard Home будет работать по HTTPS, а DNS-сервер будет также работать по DNS-over-HTTPS и DNS-over-TLS.",
-    "encryption_chain_valid": "Цепочка сертификатов прошла проверку.",
-    "encryption_chain_invalid": "Цепочка сертификатов не прошла проверку.",
-    "encryption_key_valid": "Корректный {{type}} приватный ключ.",
-    "encryption_key_invalid": "Некорректный {{type}} приватный ключ.",
+    "encryption_chain_valid": "Цепочка сертификатов прошла проверку",
+    "encryption_chain_invalid": "Цепочка сертификатов не прошла проверку",
+    "encryption_key_valid": "Корректный {{type}} приватный ключ",
+    "encryption_key_invalid": "Некорректный {{type}} приватный ключ",
     "encryption_subject": "Субъект",
     "encryption_issuer": "Издатель",
     "encryption_hostnames": "Имена хостов",
     "encryption_reset": "Вы уверены, что хотите сбросить настройки шифрования?",
     "topline_expiring_certificate": "Ваш SSL-сертификат скоро истекает. Обновите <0>Настройки шифрования</0>.",
     "topline_expired_certificate": "Ваш SSL-сертификат истёк. Обновите <0>Настройки шифрования</0>.",
-    "form_error_port_range": "Введите номер порта из интервала 80-65535.",
-    "form_error_port_unsafe": "Это небезопасный порт.",
-    "form_error_equal": "Не должны быть равны.",
-    "form_error_password": "Пароли не совпадают.",
+    "form_error_port_range": "Введите номер порта из интервала 80-65535",
+    "form_error_port_unsafe": "Небезопасный порт",
+    "form_error_equal": "Не должны быть равны",
+    "form_error_password": "Пароли не совпадают",
     "reset_settings": "Сбросить настройки",
     "update_announcement": "AdGuard Home {{version}} уже доступна! <0>Нажмите сюда</0>, чтобы узнать больше.",
     "setup_guide": "Инструкция по настройке",
     "dns_addresses": "Адреса DNS",
     "dns_start": "DNS-сервер запускается",
-    "dns_status_error": "Ошибка при получении состояния DNS-сервера.",
+    "dns_status_error": "Ошибка при получении состояния DNS-сервера",
     "down": "Вниз",
     "fix": "Исправить",
     "dns_providers": "<0>Список известных DNS-провайдеров</0> на выбор.",
@@ -406,7 +408,7 @@
     "manual_update": "Пожалуйста, <a>следуйте инструкции</a> для обновления вручную.",
     "processing_update": "Пожалуйста, подождите, AdGuard Home обновляется",
     "clients_title": "Сохранённые клиенты",
-    "clients_desc": "Настройте устройства, использующие AdGuard Home.",
+    "clients_desc": "Настройте устройства, использующие AdGuard Home",
     "settings_global": "Глобальные",
     "settings_custom": "Свои",
     "table_client": "Клиент",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Вы уверены, что хотите удалить клиента «{{key}}»?",
     "list_confirm_delete": "Вы уверены, что хотите удалить этот список?",
     "auto_clients_title": "Клиенты (runtime)",
-    "auto_clients_desc": "Несохранённые клиенты, которые могут пользоваться AdGuard Home.",
+    "auto_clients_desc": "Несохранённые клиенты, которые могут пользоваться AdGuard Home",
     "access_title": "Настройки доступа",
-    "access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home.",
+    "access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home",
     "access_allowed_title": "Разрешённые клиенты",
     "access_allowed_desc": "Список CIDR, IP-адресов или <a>ClientID</a>. Если в списке есть записи, AdGuard Home будет принимать запросы только от этих клиентов.",
     "access_disallowed_title": "Запрещённые клиенты",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Перезапись DNS-запросов",
     "form_domain": "Введите домен",
     "form_answer": "Введите IP адрес или домен",
-    "form_error_domain_format": "Некорректный домен.",
-    "form_error_answer_format": "Некорректный ответ.",
+    "form_error_domain_format": "Некорректный домен",
+    "form_error_answer_format": "Некорректный ответ",
     "configure": "Настроить",
     "main_settings": "Основные настройки",
     "block_services": "Выбрать заблокированные сервисы",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} день",
     "interval_days_plural": "{{count}} дней",
     "domain": "Домен",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Ответ",
     "filter_added_successfully": "Список успешно добавлен",
@@ -507,7 +510,7 @@
     "filter_updated": "Список успешно обновлён",
     "statistics_configuration": "Конфигурация статистики",
     "statistics_retention": "Сохранение статистики",
-    "statistics_retention_desc": "Если вы уменьшите значение интервала, некоторые данные могут быть потеряны.",
+    "statistics_retention_desc": "Если вы уменьшите значение интервала, некоторые данные могут быть потеряны",
     "statistics_clear": "Очистить статистику",
     "statistics_clear_confirm": "Вы уверены, что хотите очистить статистику?",
     "statistics_retention_confirm": "Вы уверены, что хотите изменить срок хранения статистики? При сокращении интервала данные могут быть утеряны",
@@ -599,14 +602,14 @@
     "milliseconds_abbreviation": "мс",
     "cache_size": "Размер кеша",
     "cache_size_desc": "Размера кеша DNS (в байтах).",
-    "cache_ttl_min_override": "Переопределить минимальный TTL (в секундах)",
-    "cache_ttl_max_override": "Переопределить максимальный TTL (в секундах)",
+    "cache_ttl_min_override": "Переопределить минимальный TTL",
+    "cache_ttl_max_override": "Переопределить максимальный TTL",
     "enter_cache_size": "Введите размер кеша (в байтах)",
     "enter_cache_ttl_min_override": "Введите минимальный TTL (в секундах)",
     "enter_cache_ttl_max_override": "Введите максимальный TTL (в секундах)",
     "cache_ttl_min_override_desc": "Расширить короткие TTL-значения (в секундах), полученные с upstream-сервера при кешировании DNS-ответов.",
     "cache_ttl_max_override_desc": "Установить максимальное TTL-значение (в секундах) для записей в DNS-кеше.",
-    "ttl_cache_validation": "Минимальное значение TTL-кеша должно быть меньше или равно максимальному значению.",
+    "ttl_cache_validation": "Значение для переопределения минимального TTL должно быть меньше или равно значению для переопределения максимального",
     "cache_optimistic": "Оптимистическое кеширование",
     "cache_optimistic_desc": "AdGuard Home будет отвечать из кеша, даже если ответы в нём неактуальны, и попытается обновить их.",
     "filter_category_general": "Общие",
@@ -628,5 +631,5 @@
     "parental_control": "Родительский контроль",
     "safe_browsing": "Безопасный интернет",
     "served_from_cache": "{{value}} <i>(получено из кеша)</i>",
-    "form_error_password_length": "Пароль должен быть длиной не меньше {{value}} символов."
+    "form_error_password_length": "Пароль должен быть длиной не меньше {{value}} символов"
 }
diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json
index d2a4f5b9..ea6d29b1 100644
--- a/client/src/__locales/si-lk.json
+++ b/client/src/__locales/si-lk.json
@@ -1,15 +1,19 @@
 {
-    "client_settings": "අනුග්‍රාහක සැකසුම්",
-    "example_upstream_comment": "ඔබට අදහසක් සඳහන් කළ හැකිය",
+    "client_settings": "අනුග්‍රාහකයේ සැකසුම්",
+    "example_upstream_comment": "අදහසක්.",
     "parallel_requests": "සමාන්තර ඉල්ලීම්",
     "load_balancing": "ධාරිතාව තුලනය",
     "local_ptr_title": "පෞද්ගලික ප්‍රතිවර්ත ව.නා.ප. සේවාදායක",
+    "local_ptr_desc": "ස්ථානීය PTR විමසුම් සඳහා ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන ව.නා.ප. සේවාදායක. මෙම සේවාදායක පුද්ගලික අ.ජා.කෙ. ලිපින සහිත අනුග්‍රාහකවල සත්කාරක නාම විසඳීමට භාවිතා කරයි, උදාහරණයක් ලෙස ප්‍රතිවර්ත ව.නා.ප. භාවිතයෙන් \"192.168.12.34\". නැති නම්, ඇඩ්ගාර්ඩ් හෝම් හි ලිපින සඳහා හැරුනු විට ඔබගේ මෙහෙයුම් පද්ධතියේ පෙරනිමි ව.නා.ප. විසදුම්වල ලිපින භාවිතා කරයි.",
+    "local_ptr_default_resolver": "පෙරනිමි ලෙස, ඇඩ්ගාර්ඩ් හෝම් පහත ප්‍රතිවර්තත ව.නා.ප. විසඳුම් භාවිතා කරයි: {{ip}}.",
+    "local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්‍රතිවර්ත ව.නා.ප. විසඳුම් නිශ්චය කරගත නොහැකි විය.",
     "local_ptr_placeholder": "පේළියකට එක් සේවාදායක ලිපිනය බැගින් යොදන්න",
     "resolve_clients_title": "අනුග්‍රාහකවල අ.ජා.කෙ. ලිපින ප්‍රතිවර්ත විසඳීම සබල කරන්න",
     "check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක සඳහා පරීක්‍ෂා කරන්න",
     "save_config": "වින්‍යාසය සුරකින්න",
     "enabled_dhcp": "ග.ධා.වි.කෙ. සේවාදායකය සබල කෙරිණි",
     "disabled_dhcp": "ග.ධා.වි.කෙ. සේවාදායකය අබල කෙරිණි",
+    "unavailable_dhcp": "ග.ධා.වි.කෙ. නැත",
     "unavailable_dhcp_desc": "ඇඩ්ගාර්ඩ් හෝම් හට ඔබගේ මෙහෙයුම් පද්ධතියේ ග.ධා.වි.කෙ. සේවාදායකයක් ධාවනය කිරීමට නොහැකිය",
     "dhcp_title": "ග.ධා.වි.කෙ. සේවාදායකය (පර්යේෂණාත්මක!)",
     "dhcp_description": "ඔබගේ මාර්ගකාරකය ග.ධා.වි.කෙ. (DHCP) සැකසුම් ලබා නොදෙන්නේ නම්, ඔබට ඇඩ්ගාර්ඩ් හි ඇති ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කළ හැකිය.",
@@ -18,16 +22,21 @@
     "dhcp_config_saved": "ග.ධා.වි.කෙ. වින්‍යාසය සාර්ථකව සුරකින ලදි",
     "dhcp_ipv4_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 4 සැකසුම්",
     "dhcp_ipv6_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 6 සැකසුම්",
-    "form_error_required": "අවශ්‍ය ක්ෂේත්‍රයකි",
-    "form_error_ip4_format": "වලංගු නොවන IPv4 ලිපිනයකි",
-    "form_error_ip4_range_start_format": "පරාසය ආරම්භයේ අ.ජා.කෙ.4 ලිපිනය වලංගු නොවේ",
-    "form_error_ip4_range_end_format": "පරාසය අවසානයේ අ.ජා.කෙ.4 ලිපිනය වලංගු නොවේ",
+    "form_error_required": "ඇවැසි ක්‍ෂේත්‍රයකි",
+    "form_error_ip4_format": "IPv4 ලිපිනය වලංගු නොවේ",
+    "form_error_ip4_range_start_format": "පරාසය ආරම්භයේ වලංගු නොවන අ.ජා.කෙ.4 ලිපිනයකි",
+    "form_error_ip4_range_end_format": "පරාසය අවසානයේ වලංගු නොවන අ.ජා.කෙ.4 ලිපිනයකි",
     "form_error_ip6_format": "වලංගු නොවන අ.ජා.කෙ.6 ලිපිනයකි",
     "form_error_ip_format": "අ.ජා.කෙ. (IP) ලිපිනය වලංගු නොවේ",
     "form_error_mac_format": "මා.ප්‍ර.පා. ලිපිනය වලංගු නොවේ",
     "form_error_client_id_format": "අනුග්‍රාහකයේ හැඳු. වලංගු නොවේ",
-    "form_error_server_name": "වලංගු නොවන සේවාදායක නාමයකි",
+    "form_error_server_name": "සේවාදායකයේ නම වලංගු නොවේ",
     "form_error_positive": "0 ට වඩා වැඩි විය යුතුය",
+    "out_of_range_error": "\"{{start}}\"-\"{{end}}\" පරාසයෙන් පිට විය යුතුය",
+    "lower_range_start_error": "පරාසය ආරම්භයට වඩා අඩු විය යුතුය",
+    "greater_range_start_error": "පරාසය ආරම්භයට වඩා වැඩි විය යුතුය",
+    "greater_range_end_error": "පරාසය අවසානයට වඩා වැඩි විය යුතුය",
+    "subnet_error": "ලිපින එක් අනුජාලයක තිබිය යුතුය",
     "dhcp_form_range_title": "අ.ජා. කෙ. (IP) ලිපින පරාසය",
     "dhcp_form_range_start": "පරාසය ආරම්භය",
     "dhcp_form_range_end": "පරාසය අවසානය",
@@ -38,7 +47,7 @@
     "dhcp_table_hostname": "ධාරක නාමය",
     "dhcp_table_expires": "කල් ඉකුත් වීම",
     "dhcp_warning": "ඔබට කෙසේ හෝ ග.ධා.වි.කෙ. සේවාදායකය සබල කිරීමට අවශ්‍ය නම්, ඔබගේ ජාලයේ වෙනත් ක්‍රියාකාරී ග.ධා.වි.කෙ. සේවාදායකයක් නැති බව තහවුරු කරගන්න. මෙය සම්බන්ධිත උපාංග සඳහා අන්තර්ජාලය බිඳ දැමිය හැකිය!",
-    "dhcp_error": "ජාලයේ තවත් ග.ධා.වි.කෙ. සේවාදායකයක් තිබේද යන්න නිශ්චය කළ නොහැකි විය.",
+    "dhcp_error": "ජාලයේ තවත් ක්‍රියාත්මක ග.ධා.වි.කෙ. සේවාදායකයක් තිබේද යන්න නිශ්චය කළ නොහැකි විය",
     "dhcp_static_ip_error": "ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් සැකසිය යුතුය. මෙම ජාල අතුරුමුහුණත ස්ථිතික අ.ජා. කෙ. ලිපිනයක් භාවිතයෙන් වින්‍යාසගත කර තිබේද යන්න තීරණය කිරීමට ඇඩ්ගාර්ඩ් හෝම් අසමත් විය. කරුණාකර ස්ථිතික අ.ජා. කෙ. ලිපිනයක් අතින් සකසන්න.",
     "dhcp_dynamic_ip_found": "ඔබගේ පද්ධතිය <0>{{interfaceName}}</0> අතුරු මුහුණත සඳහා ගතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපින වින්‍යාසය භාවිතා කරයි. ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අ.ජා. කෙ. ලිපිනයක් සැකසිය යුතුය. ඔබගේ වර්තමාන අ.ජා. කෙ. ලිපිනය <0>{{ipAddress}}</0> වේ. ඔබ \"ග.ධා.වි.කෙ. සබල කරන්න\" බොත්තම එබුවහොත් ඇඩ්ගාර්ඩ් හෝම් ස්වයංක්‍රීයව මෙම අ.ජා. කෙ. ලිපිනය ස්ථිතික ලෙස සකසනු ඇත.",
     "dhcp_reset": "ග.ධා.වි.කෙ. වින්‍යාසය යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
@@ -56,7 +65,7 @@
     "settings": "සැකසුම්",
     "filters": "පෙරහන්",
     "filter": "පෙරහන",
-    "query_log": "විමසුම් ලොගය",
+    "query_log": "විමසුම් සටහන",
     "compact": "සංක්ෂිප්ත",
     "nothing_found": "කිසිවක් හමු නොවිණි",
     "faq": "නිති පැණ",
@@ -78,7 +87,7 @@
     "blocked_by": "<0>පෙරහන් මගින් අවහිර කළ</0>",
     "stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්",
     "stats_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි",
-    "stats_query_domain": "ජනප්‍රිය විමසන ලද වසම්",
+    "stats_query_domain": "ප්‍රචලිත විමසන ලද වසම්",
     "for_last_24_hours": "පසුගිය පැය 24 සඳහා",
     "for_last_days": "පසුගිය දවස් {{count}} සඳහා",
     "for_last_days_plural": "පසුගිය දවස් {{count}} සඳහා",
@@ -86,8 +95,8 @@
     "stats_disabled_short": "සංඛ්‍යාලේඛන අබල කර ඇත",
     "no_domains_found": "වසම් කිසිවක් හමු නොවිණි",
     "requests_count": "ඉල්ලීම් ගණන",
-    "top_blocked_domains": "ජනප්‍රිය අවහිර කළ වසම්",
-    "top_clients": "ජනප්‍රිය අනුග්‍රාහක",
+    "top_blocked_domains": "ප්‍රචලිත අවහිර කළ වසම්",
+    "top_clients": "ප්‍රචලිත අනුග්‍රාහක",
     "no_clients_found": "අනුග්‍රාහක හමු නොවිණි",
     "general_statistics": "පොදු සංඛ්‍යාලේඛන",
     "number_of_dns_query_days": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
@@ -157,23 +166,26 @@
     "enter_valid_blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුල් කරන්න.",
     "enter_valid_allowlist": "ඉඩ දීමේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුල් කරන්න.",
     "form_error_url_format": "වලංගු නොවන ඒ.ස.නි.(URL) ආකෘතියකි",
-    "form_error_url_or_path_format": "ලැයිස්තුවක වලංගු නොවන ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයකි",
+    "form_error_url_or_path_format": "වලංගු නොවන ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයකි",
     "custom_filter_rules": "අභිරුචි පෙරීමේ නීති",
     "custom_filter_rules_hint": "පේළියකට එක් නීතියක් බැගින් ඇතුල් කරන්න. ඔබට දැන්වීම් අවහිර කිරීමේ නීති හෝ ධාරක ගොනු පද ගැලපුම් භාවිතා කළ හැකිය.",
     "system_host_files": "පද්ධතියේ සත්කාරක ගොනු",
     "examples_title": "උදාහරණ",
-    "example_meaning_filter_block": "උදාහරණය.ලංකා වසමට සහ එහි සියළුම උප වසම් වලට ප්‍රවේශය අවහිර කරයි",
-    "example_meaning_filter_whitelist": "උදාහරණය.ලංකා වසමට සහ එහි සියළුම උප වසම් වලට ප්‍රවේශය අනවහිර කරයි",
-    "example_meaning_host_block": "ඇඩ්ගාර්ඩ් හෝම් දැන් උදාහරණය.ලංකා වසම සඳහා 127.0.0.1 ලිපිනය ලබා දෙනු ඇත (නමුත් එහි උප ලිපින නොවේ).",
-    "example_comment": "! මෙතැන අදහස් දැක්වීමක්",
-    "example_comment_meaning": "අදහසක්",
-    "example_comment_hash": "# එසේම අදහස් දැක්වීමක්",
-    "example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැලපෙන වසම් වෙත ප්‍රවේශය අවහිර කරයි",
-    "example_upstream_regular": "සාමාන්‍ය ව.නා.ප. (UDP හරහා)",
+    "example_meaning_filter_block": "උදාහරණය.ලංකා වසමට සහ එහි සියළු උප වසම් වලට ප්‍රවේශය අවහිර කරයි;",
+    "example_meaning_filter_whitelist": "උදාහරණය.ලංකා වසමට සහ එහි සියළු උප වසම් වලට ප්‍රවේශය අනවහිර කරයි;",
+    "example_meaning_host_block": "උදාහරණය.ලංකා වසම සඳහා 127.0.0.1 සමඟ ප්‍රතිචාර දක්වයි (නමුත් එහි උප ලිපින සඳහා නොවේ);",
+    "example_comment": "! මෙතැන අදහස් දැක්වීමක්.",
+    "example_comment_meaning": "අදහසක්;",
+    "example_comment_hash": "# එසේම අදහස් දැක්වීමක්.",
+    "example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැළපෙන වසම් වෙත ප්‍රවේශය අවහිර කරයි.",
+    "example_upstream_regular": "සාමාන්‍ය ව.නා.ප. (UDP හරහා);",
+    "example_upstream_udp": "සාමාන්‍ය ව.නා.ප. (UDP, සත්කාරක නම හරහා);",
     "example_upstream_dot": "සංකේතිත <0>DNS-over-TLS</0>",
     "example_upstream_doh": "සංකේතිත <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "සංකේතිත <0>DNS-over-QUIC</0>",
-    "example_upstream_tcp": "සාමාන්‍ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා) ",
+    "example_upstream_doq": "සංකේතිත <0>DNS-over-QUIC</0>;",
+    "example_upstream_sdns": "<1>DNSCrypt</1> හෝ <2>HTTPS-හරහා-ව.නා.ප.</2> විසඳුම් සඳහා <0>ව.නා.ප. මුද්දර</0>;",
+    "example_upstream_tcp": "සාමාන්‍ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා);",
+    "example_upstream_tcp_hostname": "සාමාන්‍ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක නම හරහා);",
     "all_lists_up_to_date_toast": "සියළුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි",
     "dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායක නිවැරදිව ක්‍රියා කරයි",
     "dns_test_not_ok_toast": "\"{{key}}\" සේවාදායක(ය): භාවිතා කිරීමට නොහැකි විය, ඔබ එය නිවැරදිව ලියා ඇතිදැයි පරීක්‍ෂා කරන්න",
@@ -194,6 +206,7 @@
     "empty_response_status": "හිස්",
     "show_all_filter_type": "සියල්ල පෙන්වන්න",
     "show_filtered_type": "පෙරූ දෑ පෙන්වන්න",
+    "no_logs_found": "සටහන් හමු නොවිණි",
     "refresh_btn": "නැවුම් කරන්න",
     "previous_btn": "පෙර",
     "next_btn": "ඊළඟ",
@@ -205,20 +218,21 @@
     "rule_added_to_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති තුළට මෙම නීතිය එකතු කෙරිණි: {{rule}}",
     "query_log_response_status": "තත්වය: {{value}}",
     "query_log_filtered": "{{filter}} මගින් පෙරිණි",
-    "query_log_confirm_clear": "සම්පූර්ණ විමසුම් ලොගය ඉවත් කිරීමට අවශ්‍ය යැයි ඔබට විශ්වාසද?",
-    "query_log_updated": "විමසුම් ලොගය සාර්ථකව යාවත්කාලීන කරන ලදි",
-    "query_log_clear": "විමසුම් ලොග ඉවත් කරන්න",
-    "query_log_retention": "විමසුම් ලොග රඳවා තබා ගැනීම",
-    "query_log_enable": "ලොගය සබල කරන්න",
-    "query_log_configuration": "ලොග වින්‍යාසය",
-    "query_log_disabled": "විමසුම් ලොගය අබල කර ඇති අතර එය <0>සැකසුම්</0> තුළ වින්‍යාසගත කළ හැකිය",
+    "query_log_confirm_clear": "සමස්ථ විමසුම් සටහන හිස් කිරීමට ඇවැසි බව ඔබට විශ්වාසද?",
+    "query_log_cleared": "විමසුම් සටහන සාර්ථකව හිස් කර ඇත",
+    "query_log_updated": "විමසුම් සටහන සාර්ථකව යාවත්කාල කෙරිණි",
+    "query_log_clear": "විමසුම් සටහන් හිස් කරන්න",
+    "query_log_retention": "විමසුම් සටහන් රඳවා තබා ගැනීම",
+    "query_log_enable": "සටහන සබල කරන්න",
+    "query_log_configuration": "සටහන් වින්‍යාසය",
+    "query_log_disabled": "විමසුම් සටහන අබල කර ඇති අතර එය <0>සැකසුම්</0> තුළ වින්‍යාසගත කළ හැකිය",
     "query_log_strict_search": "ඉතා නිවැරදිව සෙවීමට ද්විත්ව උද්ධෘතය භාවිතා කරන්න",
-    "query_log_retention_confirm": "විමසුම් ලොගය රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
+    "query_log_retention_confirm": "විමසුම් සටහන රඳවා තබා ගැනීම වෙනස් කිරීමට ඇවැසි බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
     "anonymize_client_ip": "අනුග්‍රාහකයෙහි අ.ජා.කෙ. (IP) නිර්නාමික කරන්න",
-    "anonymize_client_ip_desc": "ලොග සහ සංඛ්‍යාලේඛන තුළ අනුග්‍රාහකයේ සම්පූර්ණ අ.ජා.කෙ. ලිපිනය සුරැකීමෙන් වලකින්න",
+    "anonymize_client_ip_desc": "සටහන් සහ සංඛ්‍යාලේඛන තුළ අනුග්‍රාහකයේ පූර්ණ අ.ජා.කෙ. ලිපිනය සුරකින්න එපා",
     "dns_config": "ව.නා.ප. සේවාදායක වින්‍යාසය",
     "dns_cache_config": "ව.නා.ප. නිහිත වින්‍යාසය",
-    "dns_cache_config_desc": "මෙහිදී ඔබට ව.නා.ප. නිහිතය වින්‍යාසගත කළ හැකිය",
+    "dns_cache_config_desc": "මෙතැන ඔබට ව.නා.ප. නිහිතය වින්‍යාසගත කළ හැකිය",
     "blocking_mode": "අවහිර කරන ආකාරය",
     "default": "සුපුරුදු",
     "nxdomain": "නොපවතින වසම",
@@ -227,8 +241,15 @@
     "custom_ip": "අභිරුචි අ.ජා.කෙ.",
     "blocking_ipv4": "අ.ජා.කෙ.4 අවහිර කිරීම",
     "blocking_ipv6": "අ.ජා.කෙ.6 අවහිර කිරීම",
-    "client_id": "අනුග්‍රාහකයේ හැඳුනුම",
-    "client_id_placeholder": "අනුග්‍රාහකයේ හැඳුනුම යොදන්න",
+    "dnscrypt": "DNSCrypt",
+    "dns_over_https": "HTTPS-හරහා-ව.නා.ප.",
+    "dns_over_tls": "TLS-හරහා-ව.නා.ප.",
+    "dns_over_quic": "QUIC-හරහා-ව.නා.ප.",
+    "client_id": "අනුග්‍රාහකයේ හැඳු.",
+    "client_id_placeholder": "අනුග්‍රාහකයක හැඳු. යොදන්න",
+    "client_id_desc": "අනුග්‍රාහක හැඳු. මගින් අනුග්‍රාහක හඳුනාගත හැකිය. කෙසේදැයි <a>මෙතැනින්</a> දැන ගන්න.",
+    "download_mobileconfig_doh": "HTTPS-හරහා-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න",
+    "download_mobileconfig_dot": "TLS-හරහා-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න",
     "download_mobileconfig": "වින්‍යාසගත ගොනුව බාගන්න",
     "plain_dns": "සරල ව.නා.ප.",
     "form_enter_rate_limit": "අනුපාත සීමාව ඇතුල් කරන්න",
@@ -255,12 +276,12 @@
     "install_welcome_desc": "ඇඩ්ගාර්ඩ් හෝම් යනු ජාලය පුරා ඇති දැන්වීම් සහ ලුහුබැඳීම අවහිර කරන ව.නා.ප. සේවාදායකයකි. ඔබගේ මුළු ජාලය සහ සියළුම උපාංග පාලනය කිරීමට ඉඩ සලසා දීම එහි පරමාර්ථය යි, එයට අනුග්‍රාහක පාර්ශවීය වැඩසටහනක් භාවිතා කිරීම අවශ්‍ය නොවේ.",
     "install_settings_title": "පරිපාලක වියමන අතුරු මුහුණත",
     "install_settings_listen": "සවන් දෙන අතුරු මුහුණත",
-    "install_settings_port": "කෙවෙනිය",
+    "install_settings_port": "තොට",
     "install_settings_interface_link": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලක වියමන අතුරු මුහුණතට පහත ලිපින වලින් ප්‍රවේශ වීමට හැකිය:",
-    "form_error_port": "වලංගු කෙවෙනියක අගයක් යොදන්න",
+    "form_error_port": "වලංගු තොටක අගයක් යොදන්න",
     "install_settings_dns": "ව.නා.ප. සේවාදායකය",
     "install_settings_dns_desc": "පහත ලිපිනයන්හි ව.නා.ප. සේවාදායකය භාවිතා කිරීම සඳහා ඔබගේ උපාංග හෝ මාර්ගකාරකය වින්‍යාසගත කිරීමට අවශ්‍ය වනු ඇත:",
-    "install_settings_all_interfaces": "සියලුම අතුරුමුහුණත්",
+    "install_settings_all_interfaces": "සියළු අතුරුමුහුණත්",
     "install_auth_title": "සත්‍යාපනය",
     "install_auth_desc": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් පරිපාලන වියමන අතුරු මුහුණතට මුරපද සත්‍යාපනය වින්‍යාසගත කළ යුතුය. එය ඔබගේ ස්ථානීය ජාල‌යෙන් පමණක් ප්‍රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්‍රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.",
     "install_auth_username": "පරිශීලක නාමය",
@@ -274,14 +295,15 @@
     "install_submit_title": "සුභ පැතුම්!",
     "install_submit_desc": "පිහිටුවීමේ ක්‍රියා පටිපාටිය අවසන් වී ඇති අතර ඔබ දැන් ඇඩ්ගාර්ඩ් හෝම් භාවිතය ආරම්භ කිරීමට සූදානම්ය.",
     "install_devices_router": "මාර්ගකාරකය",
-    "install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධ සියලුම උපාංග ස්වයංක්‍රීයව ආවරණය කරන අතර ඔබට ඒ සෑම එකක්ම අතින් වින්‍යාසගත කිරීමට අවශ්‍ය නොවේ.",
+    "install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධ සියළුම උපාංග ස්වයංක්‍රීයව ආවරණය කරන අතර ඔබට ඒ සෑම එකක්ම අතින් වින්‍යාසගත කිරීමට අවශ්‍ය නොවේ.",
     "install_devices_address": "ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය පහත ලිපිනයන්ට සවන් දෙමින් පවතී",
+    "install_devices_router_list_1": "ඔබගේ මාර්ගකාරකය සඳහා වූ මනාපයන් විවෘත කරන්න. සාමාන්‍යයෙන්, එය ඔබගේ අතිරික්සුවෙන් ඒ.ස.නි.(URL) ක් හරහා (http://192.168.0.1/ හෝ http://192.168.1.1/ වැනි) ප්‍රවේශ විය හැකිය. මුරපදය ඇතුල් කිරීමට සිදු විය හැකි නමුත් එය මතක නැතිනම් බොහෝ විට මාර්ගකාරකයේ බොත්තමක් එබීමෙන් මුරපදය නැවත සැකසිය හැකිය. නමුත් මෙම ක්‍රියා පටිපාටිය තෝරා ගන්නේ නම්, බොහෝ විට ඔබගේ මාර්ගකාරකයේ සමස්ථ වින්‍යාසය අහිමි වනු ඇති බව මතක තබා ගන්න.එය පිහිටුවීමට ඔබගේ මාර්ගකාරකයට යෙදුමක් ඇවැසි නම්, කරුණාකර එය ඔබගේ පරිගණකයේ හෝ දුරකථනයේ ස්ථාපනය කර මාර්ගකාරකයේ සැකසුම් වෙත ප්‍රවේශ වීමට භාවිතා කරන්න.",
     "install_devices_router_list_2": "ග.ධා.වි.කෙ. (DHCP)/ ව.නා.ප. (DNS) සැකසුම් සොයා ගන්න. අංක කට්ටල දෙකකට හෝ තුනකට ඉඩ දෙන ක්ෂේත්‍රයක් අසල ඇති ව.නා.ප. අකුරු බලන්න, සෑම එකක්ම ඉලක්කම් එකේ සිට තුන දක්වා කාණ්ඩ හතරකට බෙදා ඇත.",
     "install_devices_router_list_3": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින එහි ඇතුල් කරන්න.",
     "install_devices_router_list_4": "සමහර වර්ගයේ මාර්ගකාරක වල අභිරුචි ව.නා.ප. සේවාදායකයක් සැකසීමට නොහැකිය. මෙම අවස්ථාවේදී ඇඩ්ගාර්ඩ් හෝම් <0>ග.ධා.වි.කෙ. සේවාදායකයක්</0> ලෙස පිහිටුවන්නේ නම් එය උපකාර වනු ඇත. එසේ නැතිනම්, ඔබගේ විශේෂිත මාර්ගකාරකය සඳහා වූ ව.නා.ප. සේවාදායක රිසිකරණය කරන්නේ කෙසේද යන්න පිළිබඳ අත්පොත පරීක්‍ෂා කළ යුතුය.",
     "install_devices_windows_list_2": "ජාල සහ අන්තර්ජාල ප්‍රවර්ගයට ගොස් පසුව ජාල සහ බෙදාගැනීමේ මධ්‍යස්ථානය වෙත යන්න.",
-    "install_devices_windows_list_3": "\"උපයුක්තක‌‌‌යෙහි සැකසුම් වෙනස් කිරීම\" තිරයේ වම් පසින් සොයාගෙන එය මත ඔබන්න.",
-    "install_devices_windows_list_4": "ඔබගේ ක්‍රියාකාරී සම්බන්ධතාවය තෝරන්න, එය මත දකුණු-ක්ලික් කර ගුණාංග තෝරන්න.",
+    "install_devices_windows_list_3": "වම් තීරුවෙහි \"උපයුක්තක‌‌‌යෙහි සැකසුම් වෙනස් කිරීම\" ඔබන්න.",
+    "install_devices_windows_list_4": "ඔබගේ ක්‍රියාකාරී සම්බන්ධතාවය මත දකුණු-ක්ලික් කර ගුණාංග තෝරන්න.",
     "install_devices_windows_list_5": "ලැයිස්තුවෙන් \"අන්තර්ජාල කෙටුම්පත් අනුවාදය 4 (TCP/IPv4)\" (හෝ, IPv6 සඳහා, \"අන්තර්ජාල කෙටුම්පත් අනුවාදය 6 (TCP/IPv6)\") සොයාගෙන එය තෝරා ඉන්පසු ගුණාංග මත නැවත ඔබන්න.",
     "install_devices_windows_list_6": "'පහත සඳහන් ව.නා.ප. සේවාදායක ලිපින භාවිතා කරන්න' යන්න තෝරා ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින ඇතුල් කරන්න.",
     "install_devices_macos_list_1": "ඇපල් නිරූපකය එබීමෙන් පසු පද්ධතියේ මනාප වෙත යන්න.",
@@ -302,18 +324,18 @@
     "open_dashboard": "උපකරණ පුවරුව විවෘත කරන්න",
     "install_saved": "සාර්ථකව සුරකින ලදි",
     "encryption_title": "සංකේතනය",
-    "encryption_desc": "ගුප්තකේතනය (HTTPS/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වියමන අතුරු මුහුණත සහය දක්වයි",
+    "encryption_desc": "ගුප්තකේතනය (HTTPS/QUIC/TLS) සඳහා ව.නා.ප. සහ පරිපාලක වියමන අතුරු මුහුණත සහය දක්වයි",
     "encryption_config_saved": "සංකේතන වින්‍යාසය සුරකින ලදි",
     "encryption_server": "සේවාදායක‌‌‌‌යේ නම",
     "encryption_server_enter": "ඔබගේ වසම් නාමය ඇතුල් කරන්න",
     "encryption_redirect": "ස්වයංක්‍රීයව HTTPS වෙත හරවා යවන්න",
     "encryption_redirect_desc": "සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් ඔබව ස්වයංක්‍රීයව HTTP සිට HTTPS ලිපින වෙත හරවා යවනු ඇත.",
-    "encryption_https": "HTTPS කෙවෙනිය",
-    "encryption_https_desc": "HTTPS කෙවෙනිය වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ප්‍රවේශ විය හැකි අතර එය '/dns-query' ස්ථානයේ DNS-over-HTTPS ද ලබා දෙනු ඇත.",
-    "encryption_dot": "DNS-over-TLS කෙවෙනිය",
-    "encryption_dot_desc": "මෙම කෙවෙනිය වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා DNS-over-TLS සේවාදායකයක් ධාවනය කරනු ඇත.",
-    "encryption_doq": "DNS-over-QUIC කෙවෙනිය",
-    "encryption_doq_desc": "මෙම කෙවෙනිය වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කෙවෙනිය හරහා DNS-over-QUIC සේවාදායකයක් ධාවනය කරනු ඇත. එය පර්යේෂණාත්මක වන අතර විශ්වාසදායක නොවිය හැකිය. එසේම, මේ වන විට එයට සහාය දක්වන බොහෝ අනුග්‍රාහකයින් නැත.",
+    "encryption_https": "HTTPS තොට",
+    "encryption_https_desc": "HTTPS තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ප්‍රවේශ විය හැකි අතර එය '/dns-query' ස්ථානයේ HTTPS-හරහා-ව.නා.ප. ද ලබා දෙනු ඇත.",
+    "encryption_dot": "TLS-හරහා-ව.නා.ප. තොට",
+    "encryption_dot_desc": "මෙම තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා TLS-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත.",
+    "encryption_doq": "QUIC-හරහා-ව.නා.ප. තොට",
+    "encryption_doq_desc": "මෙම තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම තොට හරහා QUIC-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත. එය පරීක්‍ෂාත්මක වන අතර විශ්වාසදායක නොවිය හැකිය. එසේම, මේ වන විට එයට සහාය දක්වන බොහෝ අනුග්‍රාහක නැත.",
     "encryption_certificates": "සහතික",
     "encryption_certificates_input": "ඔබගේ PEM-කේතනය කළ සහතික පිටපත් කර මෙහි අලවන්න.",
     "encryption_status": "තත්වය",
@@ -332,24 +354,25 @@
     "encryption_reset": "සංකේතාංකන සැකසුම් යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
     "topline_expiring_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත්වීමට ආසන්න වී ඇත. <0>සංකේතන සැකසුම්</0> යාවත්කාල කරන්න.",
     "topline_expired_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත් වී ඇත. <0>සංකේතන සැකසුම්</0> යාවත්කාල කරන්න.",
-    "form_error_port_range": "80-65535 පරාසය හි කෙවෙනියක අගයක් ඇතුල් කරන්න",
-    "form_error_port_unsafe": "මෙය අනාරක්ෂිත කෙවෙනියකි",
+    "form_error_port_range": "80-65535 පරාසය හි තොටක අගයක් ඇතුල් කරන්න",
+    "form_error_port_unsafe": "මෙය අනාරක්‍ෂිත තොටකි",
     "form_error_equal": "සමාන නොවිය යුතුය",
-    "form_error_password": "මුරපදය නොගැලපුණි",
+    "form_error_password": "මුරපදය නොගැළපේ",
     "reset_settings": "සැකසුම් යළි පිහිටුවන්න",
     "update_announcement": "ඇඩ්ගාර්ඩ් හෝම් {{version}} දැන් ලබා ගත හැකිය! වැඩි විස්තර සඳහා <0>මෙය ඔබන්න</0>.",
     "setup_guide": "පිහිටුවීමේ මාර්ගෝපදේශය",
     "dns_addresses": "ව.නා.ප. ලිපින",
     "dns_start": "ව.නා.ප. සේවාදායකය ආරම්භ වෙමින්",
-    "dns_status_error": "ව.නා.ප. සේවාදායකයේ තත්වය පරීක්‍ෂා කිරීමේදී දෝෂයකි",
+    "dns_status_error": "ව.නා.ප. සේවාදායකයේ තත්‍වය පරීක්‍ෂා කිරීමේ දෝෂයකි",
     "down": "බිඳ වැටී",
     "fix": "නිරාකරණය",
     "dns_providers": "මෙහි තෝරා ගැනීමට <0>දැනුවත් ව.නා.ප. සපයන්නන්ගේ ලැයිස්තුවක්</0> ඇත.",
     "update_now": "යාවත්කාල කරන්න",
     "update_failed": "ස්වයං යාවත්කාලය අසමත් විය. අතින් යාවත්කාල කිරීමට කරුණාකර <a>පියවර අනුගමනය කරන්න</a>.",
+    "manual_update": "අතින් යාවත්කාල කිරීමට <a>මෙම පියවර</a> අනුගමනය කරන්න.",
     "processing_update": "රැඳී සිටින්න, ඇඩ්ගාර්ඩ් හෝම් යාවත්කාල වෙමින්",
-    "clients_title": "අනුග්‍රාහක",
-    "clients_desc": "ඇඩ්ගාර්ඩ් හෝම් වෙත සම්බන්ධිත උපාංග වින්‍යාසගත කරන්න",
+    "clients_title": "නිබැඳි අනුග්‍රාහක",
+    "clients_desc": "ඇඩ්ගාර්ඩ් හෝම් වෙත සම්බන්ධිත උපාංග සඳහා නිබැඳි අනුග්‍රාහක වාර්තා වින්‍යාසගත කරන්න",
     "settings_global": "ගෝලීය",
     "settings_custom": "අභිරුචි",
     "table_client": "අනුග්‍රාහකය",
@@ -374,9 +397,9 @@
     "clients_not_found": "අනුග්‍රාහක හමු නොවිණි",
     "client_confirm_delete": "\"{{key}}\" අනුග්‍රාහකය ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?",
     "list_confirm_delete": "මෙම ලැයිස්තුව ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
-    "auto_clients_desc": "ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන අනුග්‍රාහක දත්ත, නමුත් වින්‍යාසය තුළ ගබඩා කර නැති",
+    "auto_clients_desc": "ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන නිබැඳි අනුග්‍රාහක තුළ නැති උපාංග",
     "access_title": "ප්‍රවේශවීමට සැකසුම්",
-    "access_desc": "මෙහිදී ඔබට ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය සඳහා ප්‍රවේශ වී‌‌‌‌මේ නීති වින්‍යාසගත කළ හැකිය.",
+    "access_desc": "මෙහිදී ඔබට ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකයට ප්‍රවේශ වී‌‌‌‌මේ නීති වින්‍යාසගත කළ හැකිය",
     "access_allowed_title": "ඉඩ ලත් අනුග්‍රාහකයින්",
     "access_allowed_desc": "CIDR හෝ අ.ජා. කෙ. ලිපින ලැයිස්තුවක් වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අ.ජා. කෙ. ලිපින වලින් පමණක් ඉල්ලීම් පිළිගනු ඇත.",
     "access_disallowed_title": "නොඉඩ ලත් අනුග්‍රාහකයින්",
@@ -388,6 +411,7 @@
     "check_updates_now": "දැන් යාවත්කාල පරීක්‍ෂා කරන්න",
     "dns_privacy": "ව.නා.ප. රහස්‍යතා",
     "setup_dns_privacy_3": "<0>මෙහි ඔබට භාවිතා කළ හැකි මෘදුකාංග ලැයිස්තුවක් ඇත.</0>",
+    "setup_dns_privacy_android_2": "<1>HTTPS-හරහා-ව.නා.ප.</1> සහ <1>TLS-හරහා-ව.නා.ප.</1> සඳහා <0>ඇන්ඩ්‍රොයිඩ් සඳහා ඇඩ්ගාර්ඩ්</0> සහාය දක්වයි.",
     "setup_dns_privacy_other_title": "වෙනත් ක්‍රියාවට නැංවූ දෑ",
     "setup_dns_privacy_other_2": "<0>ඩීඑන්එස්ප්‍රොක්සි</0> දන්නා සියලුම ආරක්‍ෂිත ව.නා.ප. කෙටුම්පත් සඳහා සහාය දක්වයි.",
     "setup_dns_privacy_other_3": "<1>DNS-over-HTTPS</1> සඳහා <0>dnscrypt-පෙරකලාසිය</0> සහාය දක්වයි.",
@@ -429,6 +453,7 @@
     "interval_days": "දවස් {{count}}",
     "interval_days_plural": "දවස් {{count}}",
     "domain": "වසම",
+    "ecs": "ECS",
     "answer": "උත්තරය",
     "filter_added_successfully": "පෙරහන සාර්ථකව එකතු කෙරිණි",
     "filter_removed_successfully": "ලැයිස්තුව සාර්ථකව ඉවත් කෙරිණි",
@@ -436,7 +461,7 @@
     "statistics_configuration": "සංඛ්‍යාලේඛන වින්‍යාසය",
     "statistics_retention": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම",
     "statistics_retention_desc": "ඔබ කාල පරතරය අඩු කළහොත් සමහර දත්ත නැති වනු ඇත",
-    "statistics_clear": " සංඛ්‍යාලේඛන ඉවත් කරන්න",
+    "statistics_clear": "සංඛ්‍යාලේඛන හිස් කරන්න",
     "statistics_clear_confirm": "සංඛ්‍යාලේඛන ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
     "statistics_retention_confirm": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
     "statistics_cleared": "සංඛ්‍යාලේඛන සාර්ථකව ඉවත් කෙරිණි",
@@ -475,11 +500,11 @@
     "fastest_addr_desc": "සියළුම ව.නා.ප. සේවාදායක වලින් විමසා සියළු ප්‍රතිචාර අතරින් වේගවත්ම අ.ජා.කෙ. ලිපිනය ලබා දෙයි. සියළුම ව.නා.ප. ප්‍රතිචාර සඳහා ඇඩ්ගාර්ඩ් හෝම් රැඳී සිටිය යුතු බැවින් මෙය ව.නා.ප. විමසුම් මන්දගාමී කරන නමුත් සමස්ත සම්බන්ධතාවය වැඩි දියුණු කරයි.",
     "autofix_warning_text": "ඔබ \"නිරාකරණය\" යන්න එබුවහොත්, ඔබගේ පද්ධතිය ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය භාවිතයට වින්‍යාසගත කෙරෙනු ඇත.",
     "autofix_warning_result": "ප්‍රතිඵලයක් ලෙස ඔබගේ පද්ධතියෙන් ලැබෙන සියළුම ව.නා.ප. ඉල්ලීම් මූලිකවම ඇඩ්ගාර්ඩ් හෝම් විසින් සකසනු ඇත.",
-    "tags_title": "හැඳුනුම් සංකේත",
-    "tags_desc": "අනුග්‍රාහකයට අනුරූප වන හැඳුනුම් සංකේත ඔබට තෝරා ගත හැකිය. පෙරහන් නීති වලට හැඳුනුම් සංකේත ඇතුළත් කළ හැකි අතර ඒවා වඩාත් නිවැරදිව යෙදීමට ඔබට ඉඩ සලසයි. <0>තව දැන ගන්න</0>",
-    "form_select_tags": "අනුග්‍රාහක හැඳුනුම් සංකේත",
+    "tags_title": "අනන්‍යන",
+    "tags_desc": "අනුග්‍රාහකයට අනුරූපව අනන්‍යන ඔබට තෝරා ගත හැකිය. ඒවා වඩාත් නිවැරදිව යෙදීමට \nඅනන්‍යන පෙරහන් නීති වලට ඇතුළත් කරන්න. <0>තව දැන ගන්න</0>.",
+    "form_select_tags": "අනුග්‍රාහක අනන්‍යන තෝරන්න",
     "check_title": "පෙරීම පරීක්‍ෂා කරන්න",
-    "check_desc": "ධාරක නාමය පෙරහන් වේ දැයි පරීක්‍ෂා කරන්න",
+    "check_desc": "සත්කාරක නාමය පෙරෙනවා දැයි පරීක්‍ෂා කරන්න.",
     "check": "පරීක්‍ෂාව",
     "form_enter_host": "ධාරක නාමයක් ඇතුල් කරන්න",
     "filtered_custom_rules": "අභිරුචි පෙරීමේ නීති මගින් පෙරහන් කරන ලදි",
@@ -505,6 +530,7 @@
     "confirm_static_ip": "ඇඩ්ගාර්ඩ් හෝම් ඔබගේ ස්ථිතික අ.ජා.කෙ. (IP) ලිපිනය ලෙස {{ip}} වින්‍යාසගත කරනු ඇත. ඔබට ඉදිරියට යාමට අවශ්‍යද?",
     "list_updated": "ලැයිස්තු {{count}} ක් යාවත්කාල කෙරිණි",
     "list_updated_plural": "ලැයිස්තු {{count}} ක් යාවත්කාල කෙරිණි",
+    "dnssec_enable": "DNSSEC සබල කරන්න",
     "all_queries": "සියළුම විමසුම්",
     "show_blocked_responses": "අවහිර කර ඇත",
     "show_whitelisted_responses": "ඉඩ දී ඇත",
@@ -519,14 +545,15 @@
     "blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුව",
     "milliseconds_abbreviation": "මිලි තත්.",
     "cache_size": "නිහිතයෙහි ප්‍රමාණය",
-    "cache_size_desc": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය (බයිට වලින්)",
+    "cache_size_desc": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය (බයිට)",
     "cache_ttl_min_override": "අවම පව. කා. අභිබවන්න",
     "cache_ttl_max_override": "උපරිම පව. කා. අභිබවන්න",
     "enter_cache_size": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය යොදන්න (බයිට)",
     "enter_cache_ttl_min_override": "අවම පව. කා. (TTL) ඇතුල් කරන්න",
     "enter_cache_ttl_max_override": "උපරිම පව. කා. (TTL) ඇතුල් කරන්න",
-    "cache_ttl_max_override_desc": "ව.නා.ප. නිහිතයෙහි නිවේශිත සඳහා ඉතා වැඩි පවත්නා කාලයක අගයක් (තත්.) සකසන්න",
-    "ttl_cache_validation": "නිහිතයෙහි අවම පව. කා. (TTL) අගය උපරිම අගයට වඩා අඩු හෝ සමාන විය යුතුය",
+    "cache_ttl_max_override_desc": "ව.නා.ප. නිහිතයෙහි නිවේශිත සඳහා උපරිම පවත්නා කාලයක අගයක් (තත්.) සකසන්න.",
+    "ttl_cache_validation": "නිහිතයෙහි පාගාගෙන යන අවම පව. කා. (TTL) උපරිමයට වඩා අඩු හෝ සමාන විය යුතුය",
+    "cache_optimistic": "සර්වශුභවාදී නිහිතගතය",
     "cache_optimistic_desc": "නිවේශිත කල් ඉකුත් වූ විට පවා ඇඩ්ගාර්ඩ් හෝම් ට නිහිතයෙන් ප්‍රතිචාර දැක්වීමට සලස්වයි එමෙන්ම ඒවා නැවත නැවුම් කිරීමට ද උත්සාහ කරයි.",
     "filter_category_general": "පොදු",
     "filter_category_security": "ආරක්‍ෂණ",
@@ -539,10 +566,13 @@
     "setup_config_to_enable_dhcp_server": "ග.ධා.වි.කෙ. සේවාදායකය සබල කිරීමට වින්‍යාසය පිහිටුවන්න",
     "original_response": "මුල් ප්‍රතිචාරය",
     "click_to_view_queries": "විමසුම් බැලීමට ඔබන්න",
-    "port_53_faq_link": "53 වන කෙවෙනිය බොහෝ විට \"DNSStubListener\" හෝ \"systemd-resolved\" සේවා භාවිතයට ගනු ලැබේ. කරුණාකර මෙය විසඳන්නේ කෙසේද යන්න පිළිබඳ <0>මෙම උපදෙස්</0> කියවන්න.",
+    "port_53_faq_link": "53 වන තොට බොහෝ විට \"DNSStubListener\" හෝ \"systemd-resolved\" සේවා භාවිතයට ගනු ලැබේ. කරුණාකර මෙය විසඳන්නේ කෙසේද යන්න පිළිබඳ <0>මෙම උපදෙස්</0> කියවන්න.",
     "adg_will_drop_dns_queries": "ඇඩ්ගාර්ඩ් හෝම් මෙම අනුග්‍රාහකයේ සියළුම ව.නා.ප. විමසුම් අතහැර දමනු ඇත.",
+    "filter_allowlist": "අවවාදයයි: මෙම ක්‍රියාමාර්ගය ඉඩලත් අනුග්‍රාහක ලේඛන වලින්ද \"{{disallowed_rule}}\" නීතිය බැහැර කරයි.",
+    "last_rule_in_allowlist": "\"{{disallowed_rule}}\" නීතිය බැහැර කිරීම \"ඉඩලත් අනුග්‍රාහක\" ලේඛනය අබල කරන බැවින් මෙම අනුග්‍රාහකය ඉඩ නොදීමට නොහැකිය.",
     "use_saved_key": "පෙර සුරැකි යතුර භාවිතා කරන්න",
     "parental_control": "දෙමාපිය පාලනය",
     "safe_browsing": "ආරක්‍ෂිත පිරික්සුම",
-    "served_from_cache": "{{value}} <i>(නිහිතයෙන් ගැනිණි)</i>"
+    "served_from_cache": "{{value}} <i>(නිහිතයෙන් ගැනිණි)</i>",
+    "form_error_password_length": "මුරපදය අවම වශයෙන් අකුරු {{value}} ක් දිගු විය යුතුමයි"
 }
diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json
index 118a0578..52048c04 100644
--- a/client/src/__locales/sk.json
+++ b/client/src/__locales/sk.json
@@ -36,23 +36,23 @@
     "dhcp_ipv4_settings": "Nastavenia DHCP IPv4",
     "dhcp_ipv6_settings": "Nastavenia DHCP IPv6",
     "form_error_required": "Povinná položka.",
-    "form_error_ip4_format": "Neplatná IPv4 adresa.",
-    "form_error_ip4_range_start_format": "Neplatný začiatok rozsahu IPv4 formátu.",
-    "form_error_ip4_range_end_format": "Neplatná IPv4 adresa konca rozsahu.",
-    "form_error_ip4_gateway_format": "Neplatná IPv4 adresa brány.",
-    "form_error_ip6_format": "Neplatná IPv6 adresa.",
-    "form_error_ip_format": "Neplatná IP adresa.",
-    "form_error_mac_format": "Neplatná MAC adresa.",
-    "form_error_client_id_format": "Id klienta musí obsahovať iba čísla, malé písmená a spojovníky.",
+    "form_error_ip4_format": "Neplatná IPv4 adresa",
+    "form_error_ip4_range_start_format": "Neplatný začiatok rozsahu IPv4 formátu",
+    "form_error_ip4_range_end_format": "Neplatný koniec rozsahu IPv4 formátu",
+    "form_error_ip4_gateway_format": "Neplatná IPv4 adresa brány",
+    "form_error_ip6_format": "Neplatná IPv6 adresa",
+    "form_error_ip_format": "Neplatná IP adresa",
+    "form_error_mac_format": "Neplatná MAC adresa",
+    "form_error_client_id_format": "ID klienta musí obsahovať iba čísla, malé písmená a spojovníky",
     "form_error_server_name": "Neplatné meno servera",
-    "form_error_subnet": "Podsieť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\".",
-    "form_error_positive": "Musí byť väčšie ako 0.",
-    "out_of_range_error": "Musí byť mimo rozsahu \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Musí byť nižšie ako začiatok rozsahu.",
-    "greater_range_start_error": "Musí byť väčšie ako začiatok rozsahu.",
-    "greater_range_end_error": "Musí byť väčšie ako koniec rozsahu.",
-    "subnet_error": "Adresy musia byť v spoločnej podsieti.",
-    "gateway_or_subnet_invalid": "Maska podsiete je neplatná.",
+    "form_error_subnet": "Podsieť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
+    "form_error_positive": "Musí byť väčšie ako 0",
+    "out_of_range_error": "Musí byť mimo rozsahu \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Musí byť nižšie ako začiatok rozsahu",
+    "greater_range_start_error": "Musí byť väčšie ako začiatok rozsahu",
+    "greater_range_end_error": "Musí byť väčšie ako koniec rozsahu",
+    "subnet_error": "Adresy musia byť v spoločnej podsieti",
+    "gateway_or_subnet_invalid": "Maska podsiete je neplatná",
     "dhcp_form_gateway_input": "IP brána",
     "dhcp_form_subnet_input": "Maska podsiete",
     "dhcp_form_range_title": "Rozsah IP adries",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Meno hostiteľa",
     "dhcp_table_expires": "Vyprší",
     "dhcp_warning": "Ak chcete server DHCP napriek tomu zapnúť, uistite sa, že v sieti nie je žiadny iný aktívny DHCP server. V opačnom prípade sa môže prerušiť internet pre už pripojené zariadenia!",
-    "dhcp_error": "AdGuard Home nevie určiť, či je v sieti iný aktívny DHCP server.",
+    "dhcp_error": "AdGuard Home nevie určiť, či je v sieti iný aktívny DHCP server",
     "dhcp_static_ip_error": "Aby bolo možné používať DHCP server, musí byť nastavená statická IP adresa. AdGuard Home nedokázal určiť, či je toto sieťové rozhranie nakonfigurované pomocou statickej adresy IP. Nastavte statickú IP adresu manuálne.",
     "dhcp_dynamic_ip_found": "Váš systém používa pre rozhranie <0>{{interfaceName}}</0> dynamickú konfiguráciu IP adresy. Aby bolo možné používať DHCP server, musí byť nastavená statická IP adresa. Vaša aktuálna IP adresa je <0>{{ipAddress}}</0>. ak Ak stlačíte tlačidlo \"Povoliť DHCP server\", AdGuard Home automaticky nastaví túto IP adresu ako statickú.",
     "dhcp_lease_added": "Statický  \"{{key}}\" prenájmu bol úspešne pridaný",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Vybrať povolený zoznam",
     "enter_valid_blocklist": "Zadajte platnú URL adresu do zoznamu blokovaných DNS.",
     "enter_valid_allowlist": "Zadajte platnú URL adresu do zoznamu povolených DNS.",
-    "form_error_url_format": "Neplatný URL formát.",
-    "form_error_url_or_path_format": "Neplatná URL adresa alebo absolútna adresa zoznamu.",
+    "form_error_url_format": "Neplatný URL formát",
+    "form_error_url_or_path_format": "Neplatná URL adresa alebo absolútna adresa zoznamu",
     "custom_filter_rules": "Vlastné filtračné pravidlá",
     "custom_filter_rules_hint": "Zadajte na každý riadok jedno pravidlo. Môžete použiť buď adblock pravidlá alebo syntax host súborov.",
     "system_host_files": "Systémové súbory hosts",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Tiež komentár.",
     "example_regex_meaning": "zablokovať prístup k doménam zodpovedajúcim zadanému regulárnemu výrazu.",
     "example_upstream_regular": "obyčajná DNS (cez UDP);",
+    "example_upstream_udp": "štandardné DNS (cez UDP, hostname);",
     "example_upstream_dot": "šifrované <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "šifrované <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "šifrované <0>DNS-over-QUIC</0> (experimentálne);",
     "example_upstream_sdns": "<0>DNS pečiatky</0> pre <1>DNSCrypt</1> alebo <2>DNS-over-HTTPS</2> rezolvery;",
     "example_upstream_tcp": "obyčajná DNS (cez TCP);",
+    "example_upstream_tcp_hostname": "štandardné DNS (cez TCP, hostname);",
     "all_lists_up_to_date_toast": "Všetky zoznamy sú už aktuálne",
     "updated_upstream_dns_toast": "Upstream servery boli úspešne uložené",
     "dns_test_ok_toast": "Špecifikované DNS servery pracujú korektne",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Na prísne vyhľadávanie použite dvojité úvodzovky",
     "query_log_retention_confirm": "Naozaj chcete zmeniť uchovávanie denníku dopytov? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
     "anonymize_client_ip": "Anonymizujte IP klienta",
-    "anonymize_client_ip_desc": "Neukladať úplnú IP adresu klienta do protokolov a štatistík.",
+    "anonymize_client_ip_desc": "Neukladať úplnú IP adresu klienta do protokolov a štatistík",
     "dns_config": "Konfigurácia DNS servera",
     "dns_cache_config": "Konfigurácia DNS cache",
-    "dns_cache_config_desc": "Tu môžete nakonfigurovať DNS cache.",
+    "dns_cache_config_desc": "Tu môžete nakonfigurovať DNS cache",
     "blocking_mode": "Spôsob blokovania",
     "default": "Predvolené",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Zadajte rýchlostný limit",
     "rate_limit": "Rýchlostný limit",
     "edns_enable": "Povoliť klientsku podsiete EDNS",
-    "edns_cs_desc": "Posiela podsiete klientov na DNS servery.",
+    "edns_cs_desc": "Pridáva možnosť EDNS Client Subnet (ECS) do upstream požiadaviek a zapíše hodnoty odoslané klientmi do denníka dopytov.",
     "rate_limit_desc": "Počet požiadaviek za sekundu, ktoré môže jeden klient vykonať. Nastavenie na hodnotu 0 znamená neobmedzene.",
     "blocking_ipv4_desc": "IP adresa, ktorá sa má vrátiť v prípade blokovanej žiadosti A",
     "blocking_ipv6_desc": "IP adresa, ktorá sa má vrátiť v prípade blokovanej žiadosti AAAA",
@@ -356,7 +358,7 @@
     "open_dashboard": "Otvoriť riadiaci panel",
     "install_saved": "Úspešne uložené",
     "encryption_title": "Šifrovanie",
-    "encryption_desc": "Podpora šifrovania (HTTPS/TLS) pre webové rozhranie DNS aj administrátora.",
+    "encryption_desc": "Podpora šifrovania (HTTPS/TLS) pre webové rozhranie DNS aj administrátora",
     "encryption_config_saved": "Konfigurácia šifrovania uložená",
     "encryption_server": "Meno servera",
     "encryption_server_enter": "Zadajte meno Vašej domény",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Skopírujte a prilepte sem svoj súkromný kľúč vo formáte PEM pre Váš certifikát.",
     "encryption_enable": "Zapnite šifrovanie (HTTPS, DNS-cez-HTTPS a DNS-cez-TLS)",
     "encryption_enable_desc": "Ak je šifrovanie zapnuté, AdGuard Home administrátorské rozhranie bude pracovať cez HTTPS a DNS server bude počúvať požiadavky cez DNS-cez-HTTPS a DNS-cez-TLS.",
-    "encryption_chain_valid": "Certifikačný reťazec je platný.",
-    "encryption_chain_invalid": "Certifikačný reťazec je neplatný.",
-    "encryption_key_valid": "Toto je platný {{type}} súkromný kľúč.",
-    "encryption_key_invalid": "Toto je neplatný {{type}} súkromný kľúč.",
+    "encryption_chain_valid": "Certifikačný reťazec je platný",
+    "encryption_chain_invalid": "Certifikačný reťazec je neplatný",
+    "encryption_key_valid": "Toto je platný {{type}} súkromný kľúč",
+    "encryption_key_invalid": "Toto je neplatný {{type}} súkromný kľúč",
     "encryption_subject": "Predmet",
     "encryption_issuer": "Vydavateľ",
     "encryption_hostnames": "Názvy hostiteľov",
     "encryption_reset": "Naozaj chcete obnoviť nastavenia šifrovania?",
     "topline_expiring_certificate": "Váš SSL certifikát čoskoro vyprší. Aktualizujte <0>Nastavenia šifrovania</0>.",
     "topline_expired_certificate": "Váš SSL certifikát vypršal. Aktualizujte <0>Nastavenia šifrovania</0>.",
-    "form_error_port_range": "Zadajte číslo portu v rozsahu 80-65535.",
-    "form_error_port_unsafe": "Toto nie je bezpečný port.",
-    "form_error_equal": "Nesmie byť rovnaká.",
-    "form_error_password": "Heslo sa nezhoduje.",
+    "form_error_port_range": "Zadajte číslo portu v rozsahu 80-65535",
+    "form_error_port_unsafe": "Nezabezpečený port",
+    "form_error_equal": "Nesmie byť rovnaká",
+    "form_error_password": "Heslo sa nezhoduje",
     "reset_settings": "Obnoviť nastavenia",
     "update_announcement": "AdGuard Home {{version}} je teraz k dispozícii! <0>Viac informácií nájdete tu</0>.",
     "setup_guide": "Sprievodca nastavením",
     "dns_addresses": "DNS adresy",
     "dns_start": "Spúšťa sa DNS server",
-    "dns_status_error": "Chyba pri zisťovaní stavu DNS servera.",
+    "dns_status_error": "Chyba pri zisťovaní stavu DNS servera",
     "down": "Nadol",
     "fix": "Opraviť",
     "dns_providers": "Tu je <0>zoznam známych poskytovateľov DNS</0>, z ktorého si vyberiete.",
@@ -406,7 +408,7 @@
     "manual_update": "Pre manuálnu aktualizáciu prosím <a>sledujte tento postup</a>.",
     "processing_update": "Čakajte prosím, AdGuard Home sa aktualizuje",
     "clients_title": "Permanentní klienti",
-    "clients_desc": "Nakonfigurujte trvalé záznamy klientov pre zariadenia pripojené k domovskej stránke AdGuard.",
+    "clients_desc": "Nakonfigurujte trvalé záznamy klientov pre zariadenia pripojené k AdGuard Home",
     "settings_global": "Globálne",
     "settings_custom": "Vlastné",
     "table_client": "Klient",
@@ -433,7 +435,7 @@
     "client_confirm_delete": "Naozaj chcete vymazať \"{{key}}\" klienta?",
     "list_confirm_delete": "Naozaj chcete vymazať tento zoznam?",
     "auto_clients_title": "Runtime klienti",
-    "auto_clients_desc": "Zariadenia, ktoré nie sú na zozname trvalých klientov, ktorí môžu stále používať AdGuard Home.",
+    "auto_clients_desc": "Zariadenia, ktoré nie sú na zozname trvalých klientov, ktorí môžu stále používať AdGuard Home",
     "access_title": "Nastavenia prístupu",
     "access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.",
     "access_allowed_title": "Povolení klienti",
@@ -475,8 +477,8 @@
     "dns_rewrites": "DNS prepisovanie",
     "form_domain": "Zadajte meno domény alebo zástupný znak",
     "form_answer": "Zadajte IP adresu alebo meno domény",
-    "form_error_domain_format": "Neplatný formát domény.",
-    "form_error_answer_format": "Neplatný formát odpovede.",
+    "form_error_domain_format": "Neplatný formát domény",
+    "form_error_answer_format": "Neplatný formát odpovede",
     "configure": "Konfigurovať",
     "main_settings": "Hlavné nastavenia",
     "block_services": "Blokovať vybrané služby",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} deň",
     "interval_days_plural": "{{count}} dní",
     "domain": "Doména",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Odpoveď",
     "filter_added_successfully": "Filter bol úspešne pridaný",
@@ -507,7 +510,7 @@
     "filter_updated": "Filter bol úspešne aktualizovaný",
     "statistics_configuration": "Konfigurácia štatistiky",
     "statistics_retention": "Štatistika za obdobie",
-    "statistics_retention_desc": "Ak znížite hodnotu intervalu, niektoré údaje sa stratia.",
+    "statistics_retention_desc": "Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
     "statistics_clear": "Vynulovať štatistiku",
     "statistics_clear_confirm": "Naozaj chcete vynulovať štatistiku?",
     "statistics_retention_confirm": "Naozaj chcete zmeniť uchovávanie štatistík? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Zadať maximálne TTL (v sekundách)",
     "cache_ttl_min_override_desc": "Predĺži krátke hodnoty TTL (v sekundách) prijaté od servera typu upstream pri ukladaní odpovedí DNS do cache pamäte.",
     "cache_ttl_max_override_desc": "Nastaví maximálnu hodnotu TTL (v sekundách) pre záznamy v DNS cache pamäti.",
-    "ttl_cache_validation": "Minimálna hodnota TTL cache musí byť menšia alebo rovná maximálnej hodnote.",
+    "ttl_cache_validation": "Minimálna hodnota TTL cache musí byť menšia alebo rovná maximálnej hodnote",
     "cache_optimistic": "Optimistické nastavenie",
     "cache_optimistic_desc": "Nechajte AdGuard Home odpovedať z vyrovnávacej pamäte, aj keď už platnosť položiek skončila, a tiež sa pokúste ich obnoviť.",
     "filter_category_general": "Všeobecné",
@@ -628,5 +631,5 @@
     "parental_control": "Rodičovská kontrola",
     "safe_browsing": "Bezpečné prehliadanie",
     "served_from_cache": "{{value}} <i>(prevzatá z cache pamäte)</i>",
-    "form_error_password_length": "Heslo musí mať dĺžku aspoň {{value}} znakov."
+    "form_error_password_length": "Heslo musí mať dĺžku aspoň {{value}} znakov"
 }
diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json
index 8df19766..8dffefc7 100644
--- a/client/src/__locales/sl.json
+++ b/client/src/__locales/sl.json
@@ -37,22 +37,22 @@
     "dhcp_ipv6_settings": "Nastavitve DHCP IPv6",
     "form_error_required": "Zahtevano polje.",
     "form_error_ip4_format": "Neveljaven naslov IPv4.",
-    "form_error_ip4_range_start_format": "Neveljaven naslov IPv4 začetka razpona.",
-    "form_error_ip4_range_end_format": "Neveljaven naslov IPv4 konca razpona.",
-    "form_error_ip4_gateway_format": "Neveljaven naslov IPv4 prehoda.",
-    "form_error_ip6_format": "Neveljaven naslov IPv6.",
-    "form_error_ip_format": "Neveljaven naslov IP.",
-    "form_error_mac_format": "Neveljaven naslov MAC.",
-    "form_error_client_id_format": "ID odjemalca mora vsebovati samo številke, male črke in vezaje.",
-    "form_error_server_name": "Neveljavno ime strežnika.",
-    "form_error_subnet": "Podomrežje \"{{cidr}}\" ne vsebuje naslova IP \"{{ip}}\".",
-    "form_error_positive": "Mora biti večja od 0.",
-    "out_of_range_error": "Mora biti izven razpona \"{{start}}\"-\"{{end}}\".",
-    "lower_range_start_error": "Mora biti manjši od začetka razpona.",
-    "greater_range_start_error": "Mora biti večji od začetka razpona.",
-    "greater_range_end_error": "Mora biti večji od konca razpona.",
-    "subnet_error": "Naslovi morajo biti v enem podomrežju.",
-    "gateway_or_subnet_invalid": "Maska podomrežja ni veljavna.",
+    "form_error_ip4_range_start_format": "Neveljaven začetek razpona naslova IPv4",
+    "form_error_ip4_range_end_format": "Neveljaven konec razpona naslova IPv4",
+    "form_error_ip4_gateway_format": "Neveljaven naslov IPv4 prehoda",
+    "form_error_ip6_format": "Neveljaven naslov IPv6",
+    "form_error_ip_format": "Neveljaven naslov IP",
+    "form_error_mac_format": "Neveljaven naslov MAC",
+    "form_error_client_id_format": "ID odjemalca mora vsebovati samo številke, male črke in vezaje",
+    "form_error_server_name": "Neveljavno ime strežnika",
+    "form_error_subnet": "Podomrežje \"{{cidr}}\" ne vsebuje naslova IP \"{{ip}}\"",
+    "form_error_positive": "Mora biti večja od 0",
+    "out_of_range_error": "Mora biti izven razpona \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Mora biti manjši od začetka razpona",
+    "greater_range_start_error": "Mora biti večji od začetka razpona",
+    "greater_range_end_error": "Mora biti večji od konca razpona",
+    "subnet_error": "Naslovi morajo biti v enem podomrežju",
+    "gateway_or_subnet_invalid": "Maska podomrežja ni veljavna",
     "dhcp_form_gateway_input": "IP prehoda",
     "dhcp_form_subnet_input": "Maska podomrežja",
     "dhcp_form_range_title": "Razpon naslovov IP",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Ime gostitelja",
     "dhcp_table_expires": "Poteče",
     "dhcp_warning": "Če želite vseeno omogočiti strežnik DHCP, se prepričajte, da v vašem omrežju ni nobenega drugega aktivnega strežnika DHCP, saj lahko to prekine internetno povezljivost naprav v omrežju!",
-    "dhcp_error": "AdGuard Home ni mogel ugotoviti, ali je v omrežju še en aktivni strežnik DHCP.",
+    "dhcp_error": "AdGuard Home ni mogel ugotoviti, ali je v omrežju še en aktivni strežnik DHCP",
     "dhcp_static_ip_error": "Za uporabo strežnika DHCP mora biti nastavljen statični naslov IP. AdGuard Home ni uspel ugotoviti, ali je ta omrežni vmesnik nastavljen s statičnim naslovom IP. Prosimo, nastavite statični naslov IP ročno.",
     "dhcp_dynamic_ip_found": "Vaš sistem uporablja dinamično nastavitev naslova IP kartice <0>{{interfaceName}}</0>. Za uporabo strežnika DHCP morate nastaviti statični naslov IP. Vaš trenutni naslov IP je<0>{{ipAddress}}</0>. AdGuard Home bo samodejno nastavil ta naslov IP kot statičen, če pritisnete gumb 'Omogoči strežnik DHCP'.",
     "dhcp_lease_added": "Statičen najem \"{{key}}\" je uspešno dodan",
@@ -196,7 +196,7 @@
     "choose_allowlist": "Izberite sezname dovoljenih",
     "enter_valid_blocklist": "Vnesite veljaven URL naslov seznama nedovoljenih.",
     "enter_valid_allowlist": "Vnesite veljaven URL naslov seznama dovoljenih.",
-    "form_error_url_format": "Neveljaven format URL naslova.",
+    "form_error_url_format": "Neveljavna oblika URL naslova",
     "form_error_url_or_path_format": "Neveljaven URL ali absolutna pot seznama",
     "custom_filter_rules": "Pravila filtriranja po meri",
     "custom_filter_rules_hint": "V vrstico vnesite eno pravilo. Uporabite lahko pravila zaviranja oglasov ali sintakso gostiteljskih datotek.",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Tudi komentar.",
     "example_regex_meaning": "onemogoča dostop do domen, ki se ujemajo z določenim regularnim izrazom.",
     "example_upstream_regular": "redni DNS (nad UDP);",
+    "example_upstream_udp": "redni DNS (nad UDP, ime gostitelja);",
     "example_upstream_dot": "šifriran <0>DNS-prek-TLS</0>;",
     "example_upstream_doh": "šifriran <0>DNS-prek-HTTPS</0>;",
     "example_upstream_doq": "šifriran <0>DNS-prek-QUIC</0> (eksperimentalno);",
     "example_upstream_sdns": "lahko uporabite <0>DNS Žige</0> za reševalce <1>DNSCrypt</1> ali <2>DNS-prek-HTTPS</2>;",
     "example_upstream_tcp": "redni DNS (nad TCP);",
+    "example_upstream_tcp_hostname": "redni DNS (nad TCP, ime gostitelja);",
     "all_lists_up_to_date_toast": "Vsi seznami so že posodobljeni",
     "updated_upstream_dns_toast": "Gorvodni trežniki so uspešno shranjeni",
     "dns_test_ok_toast": "Navedeni strežniki DNS delujejo pravilno",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Za strogo iskanje uporabite dvojne narekovaje",
     "query_log_retention_confirm": "Ali ste prepričani, da želite spremeniti zadrževanje dnevnika poizvedb? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
     "anonymize_client_ip": "Anonimiziraj odjemalca IP",
-    "anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih ali statistiki.",
+    "anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih ali statistiki",
     "dns_config": "Konfiguracija strežnika DNS",
     "dns_cache_config": "Konfiguracija strežnika DNS",
-    "dns_cache_config_desc": "Tu lahko nastavite predpomnilnik DNS.",
+    "dns_cache_config_desc": "Tu lahko nastavite predpomnilnik DNS",
     "blocking_mode": "Način zaviranja",
     "default": "Privzeto",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Vnesite omejitev hitrosti",
     "rate_limit": "Omejitev hitrosti",
     "edns_enable": "Omogoči odjemalsko podomrežje EDNS",
-    "edns_cs_desc": "Pošlji podomrežja odjemalcev strežnikom DNS.",
+    "edns_cs_desc": "Dodaj možnost podomrežja odjemalca EDNS (ECS) zahtevam v gorvodnem toku in zabeleži vrednosti, ki jih pošljejo odjemalci, v dnevnik poizvedb.",
     "rate_limit_desc": "Dovoljeno število zahtev na sekundo na odjemalca. Nastavitev na 0 pomeni brez omejitve.",
     "blocking_ipv4_desc": "IP naslov, ki mora biti vrnjen za onemogočeno zahtevo A",
     "blocking_ipv6_desc": "IP naslov, ki mora biti vrnjen za onemogočeno zahtevo AAAA",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Poslušaj vmesnik",
     "install_settings_port": "Vrata",
     "install_settings_interface_link": "Vaš AdGuard Home Skrbniški spletni vmesnik bo na voljo na naslednjih naslovih:",
-    "form_error_port": "Vnesite veljavno številko vrat.",
+    "form_error_port": "Vnesite veljavno številko vrat",
     "install_settings_dns": "DNS strežnik",
     "install_settings_dns_desc": "Vaše naprave ali usmerjevalnik boste morali konfigurirati za uporabo strežnika DNS na naslednjih naslovih:",
     "install_settings_all_interfaces": "Vsi vmesniki",
@@ -356,7 +358,7 @@
     "open_dashboard": "Odpri nadzorno ploščo",
     "install_saved": "Shranjeno uspešno",
     "encryption_title": "Šifriranje",
-    "encryption_desc": "Podpora za šifriranje (HTTPS/TLS) za DNS in skrbniški spletni vmesnik.",
+    "encryption_desc": "Podpora za šifriranje (HTTPS/TLS) za DNS in skrbniški spletni vmesnik",
     "encryption_config_saved": "Nastavitve šifriranja so shranjene",
     "encryption_server": "Ime strežnika",
     "encryption_server_enter": "Vnesite ime vaše domene",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Tukaj kopirajte/prilepite PEM-kodiran zasebni ključ za vaše digitalno potrdilo.",
     "encryption_enable": "Omogoči šifriranje (HTTPS, DNS-prek-HTTPS in DNS-prek-TLS)",
     "encryption_enable_desc": "Če je omogočeno šifriranje, bo skrbniški vmesnik AdGuard Home deloval prek HTTPS, strežnik DNS pa bo poslušal zahteve prek DNS-prek-HTTPS in DNS-prek-TLS.",
-    "encryption_chain_valid": "Veriga digitalih potrdil je veljavna.",
-    "encryption_chain_invalid": "Veriga digitalih potrdil ni veljavna.",
-    "encryption_key_valid": "To je veljaven zasebni ključ {{type}}.",
-    "encryption_key_invalid": "To je neveljaven zasebni ključ {{type}}.",
+    "encryption_chain_valid": "Veriga digitalih potrdil je veljavna",
+    "encryption_chain_invalid": "Veriga digitalih potrdil ni veljavna",
+    "encryption_key_valid": "To je veljaven zasebni ključ {{type}}",
+    "encryption_key_invalid": "To je neveljaven zasebni ključ {{type}}",
     "encryption_subject": "Predmet",
     "encryption_issuer": "Izdajatelj",
     "encryption_hostnames": "Imena gostiteljev",
     "encryption_reset": "Ali ste prepričani, da želite ponastaviti nastavitve šifriranja?",
     "topline_expiring_certificate": "Vaš e digitalno potrdilo SSL bo kmalu poteklol. Posodobite <0>Nastavitve šifriranja</0>.",
     "topline_expired_certificate": "Vaše digitalno potrdilo SSL je poteklo. Posodobi <0>Nastavitve šifriranja</0>.",
-    "form_error_port_range": "Vnesite številko vrat v razponu med 80-65535.",
-    "form_error_port_unsafe": "To so nevarna vrata.",
-    "form_error_equal": "Ne sme biti enako.",
-    "form_error_password": "Geslo se ne ujema.",
+    "form_error_port_range": "Vnesite številko vrat v razponu med 80-65535",
+    "form_error_port_unsafe": "Nevarna vrata",
+    "form_error_equal": "Ne sme biti enako",
+    "form_error_password": "Geslo se ne ujema",
     "reset_settings": "Ponastavi nastavitve",
     "update_announcement": "Zdaj je na voljo AdGuard Home {{version}}! <0>Klinite tukaj</0> za več informacij.",
     "setup_guide": "Navodila za nastavitev",
     "dns_addresses": "DNS naslovi",
     "dns_start": "Zaganja se strežnik DNS",
-    "dns_status_error": "Napaka pri preverjanju stanja strežnika DNS.",
+    "dns_status_error": "Napaka pri preverjanju stanja strežnika DNS",
     "down": "Navzdol",
     "fix": "Popravi",
     "dns_providers": "Tukaj je  <0>seznam znanih ponudnikov DNS</0>, med katerimi lahko izbirate.",
@@ -406,7 +408,7 @@
     "manual_update": "Za ročno posodobitev <a>sledite tem korakom</a>.",
     "processing_update": "Prosimo, počakajte. AdGuard Home se posodablja!",
     "clients_title": "Trajni odjemalci",
-    "clients_desc": "Nastavite trajne zapise odjemalca za povezane naprave z AdGuard Home.",
+    "clients_desc": "Nastavite trajne zapise odjemalca za povezane naprave z AdGuard Home",
     "settings_global": "Splošno",
     "settings_custom": "Po meri",
     "table_client": "Odjemalec",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "Ali ste prepričani, da želite izbrisati odjemalca \"{{key}}\"?",
     "list_confirm_delete": "Ali ste prepričani, da želite izbrisati ta seznam?",
     "auto_clients_title": "Odjemalci izvajanja",
-    "auto_clients_desc": "Naprave, ki niso na seznamu trajnih odjemalcev, ki morda še vedno uporabljajo AdGuard Home.",
+    "auto_clients_desc": "Naprave, ki niso na seznamu trajnih odjemalcev, ki morda še vedno uporabljajo AdGuard Home",
     "access_title": "Nastavitve dostopa",
-    "access_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home.",
+    "access_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home",
     "access_allowed_title": "Dovoljeni odjemalci",
     "access_allowed_desc": "Seznam CIDR-jev, naslovov IP ali <a>ID-jev odjemalcev</a>. Če ta seznam vsebuje vnose, bo AdGuard Home sprejel zahteve samo teh odjemalcev.",
     "access_disallowed_title": "Zavrnjeni odjemalci",
@@ -475,8 +477,8 @@
     "dns_rewrites": "Prepisovanja NDS",
     "form_domain": "Vnesite domeno ali nadomestni znak",
     "form_answer": "Vnesite IP naslov ali ime domene",
-    "form_error_domain_format": "Neveljavna oblika domene.",
-    "form_error_answer_format": "Neveljavna oblika odgovora.",
+    "form_error_domain_format": "Neveljavna oblika domene",
+    "form_error_answer_format": "Neveljavna oblika odgovora",
     "configure": "Konfiguriraj",
     "main_settings": "Glavne nastavitve",
     "block_services": "Onemogoči določene storitve",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} dan",
     "interval_days_plural": "{{count}} dni",
     "domain": "Domena",
+    "ecs": "ECS",
     "punycode": "Slaba koda",
     "answer": "Odgovor",
     "filter_added_successfully": "Seznam je bil uspešno dodan",
@@ -507,7 +510,7 @@
     "filter_updated": "Filter je bil uspešno posodobljen",
     "statistics_configuration": "Nastavitve statistike",
     "statistics_retention": "Statistika zadrževanja",
-    "statistics_retention_desc": "Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni.",
+    "statistics_retention_desc": "Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
     "statistics_clear": " Počisti statistiko",
     "statistics_clear_confirm": "Ali ste prepričani, da želite počistiti statistiko?",
     "statistics_retention_confirm": "Ali ste prepričani, da želite spremeniti zadrževanje statistike? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Vnesite največji TTL (v sekundah)",
     "cache_ttl_min_override_desc": "Podaljšajte kratke življenjske vrednosti (sekunde), prejete od gorvodnega strežnika pri predpomnjenju odgovorov DNS.",
     "cache_ttl_max_override_desc": "Nastavite največjo vrednost življenjske dobe (sekunde) za vnose v predpomnilniku DNS.",
-    "ttl_cache_validation": "Najmanjša preglasitev TTL predpomnilnika mora biti manjša ali enaka najvišji.",
+    "ttl_cache_validation": "Najmanjša preglasitev TTL predpomnilnika mora biti manjša ali enaka najvišji",
     "cache_optimistic": "Optimistično predpomnjenje",
     "cache_optimistic_desc": "Poskrbi, da se AdGuard Home odzove iz predpomnilnika, tudi ko vnosi potečejo, in jih tudi poskusi osvežiti.",
     "filter_category_general": "Splošno",
@@ -628,5 +631,5 @@
     "parental_control": "Starševski nadzor",
     "safe_browsing": "Varno brskanje",
     "served_from_cache": "{{value}} <i>(postreženo iz predpomnilnika)</i>",
-    "form_error_password_length": "Geslo mora vsebovati najmanj {{value}} znakov."
+    "form_error_password_length": "Geslo mora vsebovati najmanj {{value}} znakov"
 }
diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json
index 3c6a19f2..1fff431a 100644
--- a/client/src/__locales/sr-cs.json
+++ b/client/src/__locales/sr-cs.json
@@ -1,10 +1,24 @@
 {
     "client_settings": "Postavke klijenta",
+    "example_upstream_reserved": "upstream <0>za određene domene</0>;",
+    "example_upstream_comment": "komentar.",
+    "upstream_parallel": "Koristite paralelne upite da biste ubrzali rešavanje tako što ćete istovremeno ispitati sve uzvodne servere.",
     "parallel_requests": "Paralelni zahtevi",
     "load_balancing": "Load-balancing",
+    "load_balancing_desc": "Koristi jedan upstream server. AdGuard Home koristi najnoviji nasumični algoritam da izabere server tako da se najbrži server češće koristi.",
     "bootstrap_dns": "Bootstrap DNS serveri",
     "bootstrap_dns_desc": "Bootstrap DNS serveri se koriste da reše IP adrese od DoH/DoT razrešivača koje ste odredili kao upstream.",
+    "local_ptr_title": "Private reverse DNS serveri",
+    "local_ptr_desc": "DNS serveri koje AdGuard Home koristi za lokalne PTR upite. Ovi serveri se koriste za rešavanje imena domaćina klijenata sa privatnim IP adresama, na primer \"192.168.12.34\", koristeći obrnuti DNS. Ako nije podešen, AdGuard Home koristi adrese podrazumevanih DNS razrešivača vašeg OS-a osim adresa samog AdGuard Home-a.",
+    "local_ptr_default_resolver": "Podrazumevano, AdGuard Home koristi sledeće obrnute DNS razrešivače: {{ip}}.",
+    "local_ptr_no_default_resolver": "AdGuard Home ne može da odredi pogodne privatne obrnute DNS razrešivače za ovaj sistem.",
+    "local_ptr_placeholder": "Unesite jednu adresu servera po redu",
+    "resolve_clients_title": "Uključi obrnuto razrešavanje klijentskih IP adresa",
+    "resolve_clients_desc": "Obrnuto razrešite IP adrese klijenata u njihova imena domaćina slanjem PTR upita odgovarajućim razrešivačima (privatni DNS serveri za lokalne klijente, uzvodni serveri za klijente sa javnim IP adresama).",
+    "use_private_ptr_resolvers_title": "Koristi privatne obrnute razrešivače",
+    "use_private_ptr_resolvers_desc": "Izvršavanje obrnutih DNS izgleda za lokalno servirane adrese pomoću ovih uzvodnih servera. Ako je onemogućen, AdGuard Home odgovara sa NXDOMAIN na sve takve PTR zahteve osim klijenata poznatih iz DHCP- a, /etc/hosts itd.",
     "check_dhcp_servers": "Proveri DHCP servere",
+    "save_config": "Sačuvaj konfiguraciju",
     "enabled_dhcp": "DHCP server uključen",
     "disabled_dhcp": "DHCP server isključen",
     "unavailable_dhcp": "DHCP nije dostupan",
@@ -13,19 +27,32 @@
     "dhcp_description": "Ako vaš ruter nema DHCP postavke, možete koristiti AdGuard' ugrađen DHCP server.",
     "dhcp_enable": "Uključi DHCP server",
     "dhcp_disable": "Isključi DHCP server",
+    "dhcp_not_found": "Bezbedno je da uključite ugrađeni DHCP server. Nismo pronašli nijedan aktivan DHCP server na mreži. međutim, ohrabrujemo vas da to ponovo proverite ručno, jer naš automatski test trenutno nije 100% pouzdan.",
     "dhcp_found": "Pronađen je aktivan DHCP server na mreži. Nije bezbedno da uključite ugrađeni DHCP server.",
     "dhcp_leases": "DHCP pozajmljivanja",
     "dhcp_static_leases": "DHCP statička pozajmljivanja",
     "dhcp_leases_not_found": "DHCP pozajmljivanja nisu pronađena",
+    "dhcp_config_saved": "Sačuvaj DHCP konfiguraciju servera",
     "dhcp_ipv4_settings": "DHCP IPv4 postavke",
     "dhcp_ipv6_settings": "DHCP IPv6 postavke",
     "form_error_required": "Obavezno polje",
-    "form_error_ip4_format": "Nevažeći IPv4 format",
-    "form_error_ip6_format": "Nevažeći IPv6 format",
-    "form_error_ip_format": "Pogrešna IP adresa",
-    "form_error_mac_format": "Nevažeći MAC format",
-    "form_error_client_id_format": "Nevažeći format klijenta",
+    "form_error_ip4_format": "Nevažeća IPv4 adresa",
+    "form_error_ip4_range_start_format": "Nevažeća IPv4 addresa početnog opsega",
+    "form_error_ip4_range_end_format": "Nevažeća IPv4 addresa završnog opsega",
+    "form_error_ip4_gateway_format": "Nevažeća IPv4 addresa prozala",
+    "form_error_ip6_format": "Nevažeća IPv6 adresa",
+    "form_error_ip_format": "Nevažeća IP adresa",
+    "form_error_mac_format": "Nevažeća MAC adresa",
+    "form_error_client_id_format": "ClientID mora da sadrži samo brojeve, malim slovima i crticama",
+    "form_error_server_name": "Nevažeće ime servera",
+    "form_error_subnet": "Subnet \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"",
     "form_error_positive": "Mora biti veće od 0",
+    "out_of_range_error": "Mora biti izvan opsega \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Mora biti manje od početnog opsega",
+    "greater_range_start_error": "Mora biti veće od početnog opsega",
+    "greater_range_end_error": "Mora biti veće od završnog opsega",
+    "subnet_error": "Asrese moraju biti u jednoj subnet",
+    "gateway_or_subnet_invalid": "Subnet mask nevažeća",
     "dhcp_form_gateway_input": "IP mrežnog prolaza",
     "dhcp_form_subnet_input": "Subnet mask",
     "dhcp_form_range_title": "Opseg IP adresa",
@@ -39,11 +66,19 @@
     "ip": "IP",
     "dhcp_table_hostname": "Ime hosta",
     "dhcp_table_expires": "Ističe",
+    "dhcp_warning": "Ako ipak želite da omogućite DHCP server, uverite se da u mreži ne postoji drugi aktivni DHCP server, jer to može da prekine Internet vezu za uređaje na mreži!",
+    "dhcp_error": "AdGuard Home nije mogao da utvrdi da li postoji još jedan aktivni DHCP server na mreži",
+    "dhcp_static_ip_error": "Da biste koristili DHCP server, morate postaviti statičnu IP adresu. AdGuard Home nije uspeo da utvrdi da li je ovaj mrežni interfejs konfigurisan pomoću statične IP adrese. Postavite statičnu IP adresu ručno.",
+    "dhcp_dynamic_ip_found": "Vaš sistem koristi dinamičku IP adresu za okruženje <0>{{interfaceName}}</0>. Kako biste koristili DHCP server, morate podesiti statičku IP adresu. Vaša trenutna IP adresa je <0>{{ipAddress}}</0>. Automatski ćemo podesiti ovu IP adresu kao statičku ako pritisnete Uključi DHCP dugme.",
     "dhcp_lease_added": "Statičko iznajmljivanje \"{{key}}\" uspešno dodato",
     "dhcp_lease_deleted": "Statičko iznajmljivanje lease \"{{key}}\" uspešno izbrisano",
     "dhcp_new_static_lease": "Novo statičko iznajmljivanje",
     "dhcp_static_leases_not_found": "Nisu pronađena statička DHCP iznajmljivanja",
     "dhcp_add_static_lease": "Dodaj statičko iznajmljivanje",
+    "dhcp_reset_leases": "Resetuj sva unajmljivanja",
+    "dhcp_reset_leases_confirm": "Jeste li sigurni da želite da resetujete sva pozajmljivanja?",
+    "dhcp_reset_leases_success": "DHCP unajmljivanja uspešno resetovana",
+    "dhcp_reset": "Jeste li sigurni da želite da resetujete DHCP konfiguraciju?",
     "country": "Zemlja",
     "city": "Grad",
     "delete_confirm": "Jeste li sigurni da želite da izbrišete \"{{key}}\"?",
@@ -84,13 +119,22 @@
     "for_last_24_hours": "u poslednja 24 časa",
     "for_last_days": "u poslednjih {{count}} dana",
     "for_last_days_plural": "u poslednjih {{count}} dana",
+    "stats_disabled": "Statistika je isključena. Možete ga uključiti sa stranice <0>sa postavkama</0>.",
+    "stats_disabled_short": "Statistika je isključena",
     "no_domains_found": "Domeni nisu pronađeni",
     "requests_count": "Broj zahteva",
     "top_blocked_domains": "Najčešće blokirani domeni",
     "top_clients": "Najčešći klijenti",
     "no_clients_found": "Nema pronađenih klijenata",
     "general_statistics": "Opšte statistike",
+    "number_of_dns_query_days": "Broj obrađenih DNS unosa u poslednjih {{count}} dan",
+    "number_of_dns_query_days_plural": "Broj obrađenih DNS unosa u poslednjih {{count}} dana",
+    "number_of_dns_query_24_hours": "Broj obrađenih DNS unosa u poslednja 24 časa",
+    "number_of_dns_query_blocked_24_hours": "Broj DNS zahteva blokiranih od filtera blokatora reklama i blok liste hostova",
+    "number_of_dns_query_blocked_24_hours_by_sec": "Broj DNS zahteva blokiranih od AdGuard-ovog podprograma za bezbedno pregledanje",
+    "number_of_dns_query_blocked_24_hours_adult": "Broj blokiranih sajtova za odrasle",
     "enforced_save_search": "Nametni sigurno pretraživanje",
+    "number_of_dns_query_to_safe_search": "Broj DNS zahteva ka pretraživačima za koje je nametnuto sigurno pretraživanje",
     "average_processing_time": "Prosečno vreme obrade",
     "average_processing_time_hint": "Prosečno vreme u milisekundama za obradu DNS zahteva",
     "block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtere i hosts datoteke",
@@ -100,6 +144,7 @@
     "use_adguard_parental": "Koristi AdGuard-ovu uslugu roditeljske kontrole",
     "use_adguard_parental_hint": "AdGuard Home će proveriti da li domen sadrži sadržaj za odrasle. Koristi se isti privatni prijateljski API kao i kod usluge bezbednog pregledanja.",
     "enforce_safe_search": "Nametni sigurno pretraživanje",
+    "enforce_save_search_hint": "AdGuard Home može nametnuti sigurno pretraživanje u sledećim pretraživačima: Google, Youtube, Bing, DuckDuckGo i Yandex.",
     "no_servers_specified": "Serveri nisu određeni",
     "general_settings": "Opšte postavke",
     "dns_settings": "DNS postavke",
@@ -111,6 +156,7 @@
     "encryption_settings": "Postavke šifrovanja",
     "dhcp_settings": "DHCP postavke",
     "upstream_dns": "Upstream DNS serveri",
+    "upstream_dns_help": "Unesite adrese servera, jednu po redu. <a>Saznajte više</a> o konfigurisanju upstream DNS servera.",
     "upstream_dns_configured_in_file": "Konfiguriši u {{path}}",
     "test_upstream_btn": "Testiraj upstreams",
     "upstreams": "Upstreams",
@@ -150,25 +196,29 @@
     "choose_allowlist": "Izaberite liste dozvoljenih",
     "enter_valid_blocklist": "Unesite važeći URL do blok liste.",
     "enter_valid_allowlist": "Unesite važeći URL do liste dozvoljenih.",
-    "form_error_url_format": "Nevažeći format Url-a",
+    "form_error_url_format": "Nevažeći format URL-a",
     "form_error_url_or_path_format": "URL ili apsolutna putanja do liste nije valjana",
     "custom_filter_rules": "Prilagođena pravila filtriranja",
     "custom_filter_rules_hint": "Unesite jedno pravilo po redu. Možete koristiti pravila blokatora reklama ili sintaksu hosts datoteke.",
+    "system_host_files": "System hosts datoteke",
     "examples_title": "Primeri",
-    "example_meaning_filter_block": "blokirajte pristup ka domenu primer.org i svim njegovim poddomenima",
-    "example_meaning_filter_whitelist": "dozvolite pristup ka domenu primer.org i svim njegovim poddomenima",
-    "example_meaning_host_block": "AdGuard Home će sada vratiti adresu 127.0.0.1 za domen primer.org (ali ne i za njegove poddomene).",
-    "example_comment": "! Ovde ide komentar",
-    "example_comment_meaning": "samo komentar",
-    "example_comment_hash": "# Takođe komentar",
-    "example_regex_meaning": "blokiranje pristupa domenima koji odgovaraju određenom uobičajenom izrazu",
-    "example_upstream_regular": "uobičajeno DNS (preko UDP)",
-    "example_upstream_dot": "šifrovano <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "šifrovano <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "šifrovano <0>DNS-over-QUIC</0>",
-    "example_upstream_sdns": "možete koristiti <0>DNS brojeve</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2>",
-    "example_upstream_tcp": "uobičajeni DNS (preko TCP)",
+    "example_meaning_filter_block": "blokirajte pristup ka primer.org i svim njegovim poddomenima;",
+    "example_meaning_filter_whitelist": "dozvolite pristup ka primer.org i svim njegovim poddomenima;",
+    "example_meaning_host_block": "vratiti adresu 127.0.0.1 za primer.org (ali ne i za njegove poddomene);",
+    "example_comment": "! Ovde ide komentar.",
+    "example_comment_meaning": "samo komentar;",
+    "example_comment_hash": "# Takođe komentar.",
+    "example_regex_meaning": "blokiranje pristupa domenima koji odgovaraju određenom uobičajenom izrazu.",
+    "example_upstream_regular": "uobičajeno DNS (preko UDP);",
+    "example_upstream_udp": "uobičajen DNS (preko UDP, imena domaćina);",
+    "example_upstream_dot": "šifrovano <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "šifrovano <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "šifrovano <0>DNS-over-QUIC</0> (eksperimentalno);",
+    "example_upstream_sdns": "<0>DNS brojeve</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2> razrešivače;",
+    "example_upstream_tcp": "uobičajeni DNS (preko TCP);",
+    "example_upstream_tcp_hostname": "uobičajen DNS (preko TCP, imena domaćina);",
     "all_lists_up_to_date_toast": "Sve liste su već ažurirane",
+    "updated_upstream_dns_toast": "Upstream serveri su uspešno sačuvani",
     "dns_test_ok_toast": "Dati DNS serveri rade ispravno",
     "dns_test_not_ok_toast": "Server \"{{key}}\": se ne može koristiti. Proverite da li ste ga ispravno uneli",
     "unblock": "Odblokiraj",
@@ -195,7 +245,7 @@
     "loading_table_status": "Učitavanje...",
     "page_table_footer_text": "Stranica",
     "rows_table_footer_text": "redovi",
-    "updated_custom_filtering_toast": "Ažurirana prilagođena pravila filtriranja",
+    "updated_custom_filtering_toast": "Prilagođena pravila su uspešno sačuvana",
     "rule_removed_from_custom_filtering_toast": "Pravilo uklonjeno iz prilagođenih pravila filtriranja: {{rule}}",
     "rule_added_to_custom_filtering_toast": "Pravilo dodato u prilagođena pravila filtriranja: {{rule}}",
     "query_log_response_status": "Stanje: {{value}}",
@@ -227,13 +277,18 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Unesite ClientID",
+    "client_id_desc": "Različiti klijenti mogu biti prepoznati posebnim ClientID. <a>Ovde</a> možete saznati više o tome kako da prepoznate klijente.",
     "download_mobileconfig_doh": "Preuzimanja",
     "download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS",
+    "download_mobileconfig": "Preuzmi konfiguracionu datoteku",
     "plain_dns": "Plain DNS",
     "form_enter_rate_limit": "Unesite ograničenje brzine",
     "rate_limit": "Ograničenje brzine",
     "edns_enable": "Uključi EDNS Client Subnet",
-    "edns_cs_desc": "Ako je uključeno, AdGuard Home će slati klijente na DNS servere.",
+    "edns_cs_desc": "Dodajte opciju podmreži EDNS klijenta (ECS) uzvodnim zahtevima i evidentirajte vrednosti koje klijenti šalju u evidenciji upita.",
+    "rate_limit_desc": "Broj zahteva u sekundi dozvoljen po klijentu. Postavljanje na 0 znači da nema ograničenja.",
     "blocking_ipv4_desc": "IP adresa koja će biti vraćena za blokirane zahteve",
     "blocking_ipv6_desc": "IP adresa koja će biti vraćena za blokirane AAAA zahteve",
     "blocking_mode_default": "Podrazumevano: Odgovara sa REFUSED kada je blokirano od Adblock-style pravila; odgovara sa IP adresom koja je određena u pravilu kada je blokiran od /etc/hosts-style pravila",
@@ -256,6 +311,7 @@
     "install_settings_listen": "Okruženje slušanja",
     "install_settings_port": "Port",
     "install_settings_interface_link": "Vaše AdGuard Home administratorsko web okruženje će biti dostupno na sledećim adresama:",
+    "form_error_port": "Unesite važeći broj porta",
     "install_settings_dns": "DNS server",
     "install_settings_dns_desc": "Potrebno je da konfigurišete vaše uređaje ili ruter da koristi DNS server sa sledećim adresama:",
     "install_settings_all_interfaces": "Sva okruženja",
@@ -274,14 +330,16 @@
     "install_devices_router": "Ruter",
     "install_devices_router_desc": "Ovo postavljanje će automatski pokriti sve uređaje koji su povezani na vaš kućni ruter pa nećete morati da konfigurišete svaki uređaj posebno.",
     "install_devices_address": "AdGuard Home DNS server sluša na sledećim adresama",
+    "install_devices_router_list_1": "Otvorite željene postavke mrežne skretnice. Obično mu možete pristupiti iz pregledača putem URL adrese, kao što su http://192.168.0.1/ ili http://192.168.1.1/. Od vas će možda biti zatraženo da unesete lozinku. Ako je se ne sećate, često možete da poništite lozinku pritiskom na dugme na samoj mrežnoj skretnici, ali imajte na umu da ćete, ako se ova procedura izabere, verovatno izgubiti celu konfiguraciju rutera. Ako ruter zahteva aplikaciju za podešavanje, instalirajte aplikaciju na telefonu ili računaru i koristite je za pristup postavkama rutera.",
     "install_devices_router_list_2": "Pronađite DHCP ili DNS postavke. Potražite DNS slova pored polja koje dozvoljava dve ili tri skupine brojeva, a svaka može da sadrži četiri grupe od jedne do tri cifre.",
     "install_devices_router_list_3": "Tamo unesite adrese AdGuard home servera.",
+    "install_devices_router_list_4": "Na nekim tipovima mrežnih skretnica nije moguće podesiti prilagođeni DNS server. U tom slučaju, podešavanje AdGuard Home-a kao <0>DHCP servera</0> može da pomogne. U suprotnom, trebalo bi da proverite uputstvo mrežne skretnice o prilagođavanju DNS servera na određenom modelu rutera.",
     "install_devices_windows_list_1": "Otvorite kontrolnu tablu iz startnog menija ili kroz Windows pretragu.",
     "install_devices_windows_list_2": "Otvorite kategoriju mreža i internet a onda otiđite u centar za mrežu i deljenje.",
-    "install_devices_windows_list_3": "Na levoj strani ekrana pronađite Promena postavke adaptera i kliknite tu.",
-    "install_devices_windows_list_4": "Izaberite vašu aktivnu vezu, desnim tasterom kliknite na nju i izaberite Svojstva.",
-    "install_devices_windows_list_5": "Na listi pronađite Internet Protokol verzija 4 (TCP/IP), izaberite ga pa kliknite ponovo na Svojstva.",
-    "install_devices_windows_list_6": "Izaberite Koristi sledeće adrese DNS servera pa unesite vaše adrese AdGuard Home servera.",
+    "install_devices_windows_list_3": "Na levoj tabli kliknite na dugme \"Promeni postavke adaptera\".",
+    "install_devices_windows_list_4": "Kliknite desnim tasterom miša na aktivnu vezu i izaberite stavku Svojstva.",
+    "install_devices_windows_list_5": "Na listi pronađite Internet Protokol verzija 4 (TCP/IP) (ili, za IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\"), izaberite ga pa kliknite ponovo na Svojstva.",
+    "install_devices_windows_list_6": "Izaberite \"Koristi sledeće adrese DNS servera\" pa unesite vaše adrese AdGuard Home servera.",
     "install_devices_macos_list_1": "Kliknite na ikonicu jabuke pa otiđite na postavke sistema.",
     "install_devices_macos_list_2": "Kliknite na mrežu.",
     "install_devices_macos_list_3": "Izaberite prvu vezu sa liste pa kliknite na više opcija.",
@@ -300,16 +358,18 @@
     "open_dashboard": "Otvori kontrolnu tablu",
     "install_saved": "Uspešno sačuvano",
     "encryption_title": "Šifrovanje",
-    "encryption_desc": "Šifrovanje (HTTPS/TLS) podrška za oba DNS i administratorsko okruženje",
+    "encryption_desc": "Šifrovanje (HTTPS/QUIC/TLS) podrška za oba DNS i administratorsko okruženje",
+    "encryption_config_saved": "Konfiguracija šifrovanja je sačuvana",
     "encryption_server": "Ime servera",
     "encryption_server_enter": "Unesite vaše ime domena",
+    "encryption_server_desc": "Da biste koristili HTTPS, potrebno je da unesete ime servera koje se podudara sa SSL certifikatom ili džoker certifikatom. Ako polje nije postavljeno, prihvatiće TLS veze za bilo koji domen.",
     "encryption_redirect": "Automatski preusmeri na HTTPS",
     "encryption_redirect_desc": "Ako je označeno, AdGuard Home će vas automatski preusmeravati sa HTTP na HTTPS adrese.",
     "encryption_https": "HTTPS port",
     "encryption_https_desc": "Ako je HTTPS port konfigurisan, AdGuard Home administratorskom okruženju će se moći pristupati preko HTTPS, a to će takođe omogućiti DNS-over-HTTPS na '/dns-query' lokaciji.",
     "encryption_dot": "DNS-over-TLS port",
     "encryption_dot_desc": "Ako je ovaj port konfigurisan, AdGuard Home će pokretati DNS-over-TLS server na ovom portu.",
-    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq": "DNS-over-QUIC port (eksperimentalno)",
     "encryption_doq_desc": "Ako je ovaj port konfigurisan, AdGuard Home će pokrenuti DNS-over-QUIC server na tom portu. To je eksperiment i možda neće biti stabilno. Takođe, u ovom trenutku ne postoji puno klijenata koji ovo podržavaju.",
     "encryption_certificates": "Sertifikati",
     "encryption_certificates_desc": "Da biste koristili šifrovanje, morate obezbediti važeći lanac SSL sertifikata za vaš domen. Besplatan sertifikat možete nabaviti na <0>{{link}}</0> ili ga možete kupiti od nekog od pouzdanih izdavalaca sertifikata.",
@@ -330,21 +390,25 @@
     "encryption_reset": "Jeste li sigurni da želite dda resetujete postavke šifrovanja?",
     "topline_expiring_certificate": "Vaš SSL sertifikat uskoro ističe. Ažurirajte <0>postavke šifrovanja</0>.",
     "topline_expired_certificate": "Vaš SSL sertifikat je istekao. Ažurirajte <0>postavke šifrovanja</0>.",
-    "form_error_port_unsafe": "Ovo nije siguran port",
+    "form_error_port_range": "Unesite vrednost porta u opsegu od 80-65535",
+    "form_error_port_unsafe": "Nije siguran port",
+    "form_error_equal": "Ne smije biti jednako",
     "form_error_password": "Lozinke se ne podudaraju",
     "reset_settings": "Vrati postavke na podrazumevano",
     "update_announcement": "AdGuard Home {{version}} je sada dostupan! <0>Kliknite ovde</0> za više informacija.",
+    "setup_guide": "Uputstvo za podešavanje",
     "dns_addresses": "DNS adrese",
     "dns_start": "DNS server se pokreće",
-    "dns_status_error": "Greška prilikom pribavljanja stanja DNS servera",
+    "dns_status_error": "Greška pri proveri statusa DNS servera",
     "down": "Dole",
     "fix": "Popravi",
     "dns_providers": "Ovo je a <0>lista poznatih DNS dobavljača</0> sa koje možete da izaberete.",
     "update_now": "Ažuriraj sada",
     "update_failed": "Automatsko ažuriranje nije uspelo. Molimo vas <a>pratite korake</a> za ručno ažuriranje.",
+    "manual_update": "Molimo vas <a>pratite korake</a> za ručno ažuriranje.",
     "processing_update": "Molimo sačekajte. AdGuard Home se ažurira",
-    "clients_title": "Klijenti",
-    "clients_desc": "Konfigurišite uređaje koji su povezani na AdGuard Home",
+    "clients_title": "Uporni klijenti",
+    "clients_desc": "Konfigurisanje stalnih klijenata za uređaje povezane sa AdGuard Home",
     "settings_global": "Globalno",
     "settings_custom": "Prilagođeno",
     "table_client": "Klijent",
@@ -355,7 +419,9 @@
     "client_edit": "Izmeni klijent",
     "client_identifier": "Identifikator",
     "ip_address": "IP adresa",
+    "client_identifier_desc": "Klijenti se mogu identifikovati po IP adresi, CIDR-u, MAC adresi ili ClientID (može se koristiti za DoT/DoH/DoQ). Saznajte više o tome kako <0>da identifikujete klijente</0>.",
     "form_enter_ip": "Unesite IP",
+    "form_enter_subnet_ip": "Unesite IP adresu subnet \"{{cidr}}\"",
     "form_enter_mac": "Unesite MAC",
     "form_enter_id": "Unesite identifikator",
     "form_add_id": "Dodaj identifikator",
@@ -369,14 +435,15 @@
     "client_confirm_delete": "Jeste li sigurni da želite da izbrišete klijenta \"{{key}}\"?",
     "list_confirm_delete": "Jeste li sigurni da želite da izbrišete ovu listu?",
     "auto_clients_title": "Klijenti (runtime)",
-    "auto_clients_desc": "Podaci o klijentima koji koriste AdGuard Home, ali nisu sačuvani u konfiguraciji",
+    "auto_clients_desc": "Uređaji koji nisu na listi upornih klijenata koji i dalje mogu da koriste AdGuard Home",
     "access_title": "Postavke pristupa",
-    "access_desc": "Ovde možete konfigurisati pravila pristupa za AdGuard home DNS server.",
+    "access_desc": "Ovde možete konfigurisati pravila pristupa za AdGuard Home DNS server",
     "access_allowed_title": "Dozvoljeni klijenti",
-    "access_allowed_desc": "Spisak CIDR ili IP adresa. Ako je podešeno, AdGuard Home će prihvatiti zahteve samo od ovih IP adresa.",
+    "access_allowed_desc": "Spisak CIDR, IP adresa ili <a>ClientIDs</a>. Ako ova lista ima stavke, AdGuard Home će prihvatiti zahteve samo ovih klijenata.",
     "access_disallowed_title": "Zabranjeni klijenti",
-    "access_disallowed_desc": "Lista CIDR ili IP adresa.. Ako je podešeno, AdGuard Home će odbijati zahteve od ovih IP adresa.",
+    "access_disallowed_desc": "Spisak CIDR, IP adresa ili <a>ClientIDs</a>. Ako ova lista ima stavke, AdGuard Home će otpustiti zahteve ovih klijenata. Ovo polje se zanemaruje ako postoje stavke u dozvoljenim klijentima.",
     "access_blocked_title": "Blokirani domeni",
+    "access_blocked_desc": "Da ne bude zabune sa filterima. AdGuard Home odustaje od DNS upita koji se podudaraju sa ovim domenima, a ovi upiti se čak i ne pojavljuju u evidenciji upita. Možete da navedete tačna imena domena, džoker znakove ili pravila URL filtera, npr. \"example.org\", \"*.example.org\" ili \"|| example.org^\" dopisno.",
     "access_settings_saved": "Postavke pristupa su uspešno sačuvane",
     "updates_checked": "Ažuriranja su uspešno proverena",
     "updates_version_equal": "AdGuard Home je ažuriran na najnoviju verziju",
@@ -397,6 +464,7 @@
     "setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podržava <1>DNS-over-HTTPS</1>.",
     "setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podržava <1>DNS-over-HTTPS</1>.",
     "setup_dns_privacy_other_5": "Više implementacija ćete pronaći <0>ovde</0> i <1>ovde</1>.",
+    "setup_dns_privacy_ioc_mac": "iOS i macOS konfiguracija",
     "setup_dns_notice": "Kako biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, potrebno je da <0>konfigurišete šifrovanje</0> u AdGuard Home postavkama.",
     "rewrite_added": "DNS prepisivanje za \"{{key}}\" je uspešno dodato",
     "rewrite_deleted": "DNS prepisivanje za \"{{key}}\" uspešno izbrisano",
@@ -429,11 +497,13 @@
     "encryption_key_source_content": "Nalepi sadržaj privatnog ključa",
     "stats_params": "Konfiguracija statistike",
     "config_successfully_saved": "Konfiguracija je uspešno sačuvana",
-    "interval_6_hour": "6 sati",
+    "interval_6_hour": "6 časa",
     "interval_24_hour": "24 časa",
     "interval_days": "{{count}} dan",
     "interval_days_plural": "{{count}} dana",
     "domain": "Domen",
+    "ecs": "ECS",
+    "punycode": "Punycode",
     "answer": "Odgovor",
     "filter_added_successfully": "Filter je uspešno dodat",
     "filter_removed_successfully": "Lista je uspešno uklonjena",
@@ -445,6 +515,7 @@
     "statistics_clear_confirm": "Jeste li sigurni da želite da očistite statistiku?",
     "statistics_retention_confirm": "Jeste li sigurni da želite da promenite zadržavanje statistike? Ako smanjite vrednost intervala, neki podaci će biti izgubljeni",
     "statistics_cleared": "Statistika je uspešno očišćena",
+    "statistics_enable": "Uključi statistiku",
     "interval_hours": "{{count}} čas",
     "interval_hours_plural": "{{count}} časova",
     "filters_configuration": "Konfiguracija filtera",
@@ -464,7 +535,7 @@
     "netname": "Ime mreže",
     "network": "Mreža",
     "descr": "Opis",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Saznajte više</0> o stvaranju vaše lične blokliste hostova.",
     "blocked_by_response": "Blokirano od CNAME ili IP u odgovoru",
     "blocked_by_cname_or_ip": "Blokirano od CNAME ili IP",
@@ -476,17 +547,18 @@
     "rewrite_domain_name": "Ime domena: dodajte CNAME zapis",
     "rewrite_A": "<0>A</0>: posebna vrednost, zadrži <0>A</0> records iz apstrima",
     "rewrite_AAAA": "<0>AAAA</0>: posebna vrednost, zadržite <0>AAAA</0> records iz apstrima",
-    "disable_ipv6": "Isključi IPv6",
-    "disable_ipv6_desc": "Ako je ovo uključeno, svi DNS unosi za IPv6 adrese (type AAAA) će biti odbačeni.",
+    "disable_ipv6": "Onemogući rešavanje IPv6 adresa",
+    "disable_ipv6_desc": "Otpustite sve DNS upite za IPv6 adrese (otkucajte AAAA).",
     "fastest_addr": "Najbrža IP adresa",
+    "fastest_addr_desc": "Pretražuje sve DNS servere i vraća najbržu IP adresu među svim odgovorima. Ovo će usporiti DNS pretragu jer moramo da čekamo na odgovore od svih DNS servera, ali će poboljšati sveukupnu povezanost.",
     "autofix_warning_text": "Ako kliknete \"Popravi\", AdGuardHome će konfigurisati vaš sistem da koristi AdGuardHome DNS server.",
     "autofix_warning_list": "To će izvršiti sledeće zadatke: <0>Deaktiviranje system DNSStubListener</0> <0>Set DNS server address to 127.0.0.1</0> <0>Replace symbolic link target of /etc/resolv.conf to /run/systemd/resolve/resolv.conf</0> <0>Stop DNSStubListener (reload systemd-resolved service)</0>",
     "autofix_warning_result": "Kao rezultat, svi DNS zahtevi sa vašeg sistema će biti obrađeni od AdGuardHome.",
     "tags_title": "Oznake",
-    "tags_desc": "Možete izabrati oznake koje odgovaraju klijentu. Oznake mogu biti uključene u pravila filtriranja i dozvoljavaju vam da ih preciznije primenite. <0>Saznajte više</0>",
+    "tags_desc": "Možete izabrati oznake koje odgovaraju klijentu. Uključite oznake u pravila filtriranja da biste ih preciznije primenili. <0>Saznajte više</0>.",
     "form_select_tags": "Izaberite oznake klijenta",
     "check_title": "Proverite filtriranje",
-    "check_desc": "Proverite da li je host filtriran",
+    "check_desc": "Proverite da li je host filtriran.",
     "check": "Proveri",
     "form_enter_host": "Unesite host",
     "filtered_custom_rules": "Filtrirano od strane prilagođenog pravila",
@@ -508,35 +580,38 @@
     "set_static_ip": "Postavite statičku IP adresu",
     "install_static_ok": "Dobre vesti! Statička IP adresa je već konfigurisana",
     "install_static_error": "AdGuard Home se ne može automatski konfigurisati za ovo mrežno okruženje. Pogledajte uputstvo kako da to ručno uradite.",
+    "install_static_configure": "Otkrili smo da se koristi dinamička IP adresa — <0>{{ip}}</0>. Želite li da je koristite kao vašu statičku adresu?",
     "confirm_static_ip": "AdGuard Home će konfigurisati {{ip}} da bude vaša statička IP adresa. Želite li da nastavite?",
     "list_updated": "{{count}} lista ažurirana",
     "list_updated_plural": "{{count}} lista ažurirano",
     "dnssec_enable": "Uključi DNSSEC",
-    "dnssec_enable_desc": "Postavlja DNSSEC zastavicu u odlaznim DNS zahtevima i proverava rezultat (DNSSEC rešavač je potreban)",
+    "dnssec_enable_desc": "Postavlja DNSSEC zastavicu u odlaznim DNS zahtevima i proverava rezultat (DNSSEC rešavač je potreban).",
     "validated_with_dnssec": "Potvrđeno sa DNSSEC",
     "all_queries": "Svi zahtevi",
     "show_blocked_responses": "Blokirano",
     "show_whitelisted_responses": "Na beloj listi",
     "show_processed_responses": "Obrađeno",
     "blocked_safebrowsing": "Blokiralo bezbedno pregledanje",
-    "blocked_adult_websites": "Blokirala roditeljska kontrola",
+    "blocked_adult_websites": "Blokiraj sajtove za odrasle",
     "blocked_threats": "Blokiranih pretnji",
     "allowed": "Dozvoljeno",
     "filtered": "Filtrirano",
     "rewritten": "Prepisano",
-    "safe_search": "uključi sigurno pretraživanje",
+    "safe_search": "Sigurna pretraga",
     "blocklist": "Lista blokiranih",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Veličina predmemorije",
-    "cache_size_desc": "Veličina DNS predmemorije (u bitovima)",
+    "cache_size_desc": "Veličina DNS predmemorije (u bitovima).",
     "cache_ttl_min_override": "Prepiši najmanji TTL",
     "cache_ttl_max_override": "Prepiši najveći TTL",
     "enter_cache_size": "Unesite veličinu predmemorije",
     "enter_cache_ttl_min_override": "Unesite najmanji TTL",
     "enter_cache_ttl_max_override": "Unesite najveći TTL",
-    "cache_ttl_min_override_desc": "Prepiši TTL vrednost (minimum) dobijen od apstrim servera. Ova vrednost ne može biti veća od 3600 (1 sat)",
-    "cache_ttl_max_override_desc": "Prepiši TTL vrednost (maksimum) dobijen od apstrim servera",
+    "cache_ttl_min_override_desc": "Proširivanje kratkih vrednosti vremena na život (sekundi) primljenih sa uzvodnog servera prilikom keširanje DNS odgovora.",
+    "cache_ttl_max_override_desc": "Prepiši TTL vrednost (maksimum) dobijen od apstrim servera.",
     "ttl_cache_validation": "Minimalna TTL vrednost mora biti manja ili jednaka najvišij vrednosti",
+    "cache_optimistic": "Optimistično keširanje",
+    "cache_optimistic_desc": "Neka AdGuard Home odgovara iz predmemorije čak i kada su unosi istekli pa pokušaj da ih osvežiš.",
     "filter_category_general": "Opšte",
     "filter_category_security": "Bezbednost",
     "filter_category_regional": "Region",
@@ -545,9 +620,16 @@
     "filter_category_security_desc": "Lista specijalizovana za blokiranje štetnog softvera, štetnih i fišing domena",
     "filter_category_regional_desc": "Lista koja se usredsređuje na regionalne reklame i servere praćenja",
     "filter_category_other_desc": "Ostale liste blokiranja",
+    "setup_config_to_enable_dhcp_server": "Podesite konfiguraciju kako biste omogućili DHCP server",
     "original_response": "Izvorni odgovor",
     "click_to_view_queries": "Kliknite da pogledate zahteve",
     "port_53_faq_link": "Port 53 je najčešće zauzet od \"DNSStubListener\" ili \"systemd-resolved\" usluga. Pročitajte <0>ovo uputstvo</0> kako da to rešite.",
     "adg_will_drop_dns_queries": "AdGuard Home će odbacivati sve DNS unose od ovog klijenta.",
-    "parental_control": "Roditeljska kontrola"
+    "filter_allowlist": "UPOZORENJE: Ova radnja će takođe izuzeti pravilo \"{{disallowed_rule}}\" sa the spiska dozvoljenih klijenata.",
+    "last_rule_in_allowlist": "Ne mogu da zabranim ovog klijenta zato što će izuzimanje pravila \"{{disallowed_rule}}\" onemogućiti \"dozvoljene klijente\".",
+    "use_saved_key": "Koristi prethodno sačuvan ključ",
+    "parental_control": "Roditeljska kontrola",
+    "safe_browsing": "Sigurno pregledanje",
+    "served_from_cache": "{{value}} <i>(posluženo iz predmemorije)</i>",
+    "form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova"
 }
diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json
index 8ab55d81..90ddd608 100644
--- a/client/src/__locales/sv.json
+++ b/client/src/__locales/sv.json
@@ -36,15 +36,15 @@
     "dhcp_ipv4_settings": "DHCP IPv4 inställningar",
     "dhcp_ipv6_settings": "DHCP IPv6 inställningar",
     "form_error_required": "Obligatoriskt fält",
-    "form_error_ip4_format": "Ogiltig IPv4-adress.",
+    "form_error_ip4_format": "Ogiltig IPv4-adress",
     "form_error_ip4_range_start_format": "Ogiltig IPv4-adress för starten av intervallet",
     "form_error_ip4_range_end_format": "Ogiltig IPv4-adress för slutet av intervallet",
     "form_error_ip4_gateway_format": "Ogiltig IPv4 adress för gatewayen",
-    "form_error_ip6_format": "Ogiltig IPv6-adress.",
-    "form_error_ip_format": "Ogiltig IP-adress.",
-    "form_error_mac_format": "Ogiltig MAC-adress.",
+    "form_error_ip6_format": "Ogiltig IPv6-adress",
+    "form_error_ip_format": "Ogiltig IP-adress",
+    "form_error_mac_format": "Ogiltig MAC-adress",
     "form_error_client_id_format": "Ogiltigt klient-ID",
-    "form_error_server_name": "Ogiltigt servernamn.",
+    "form_error_server_name": "Ogiltigt servernamn",
     "form_error_subnet": "Subnätet \"{{cidr}}\" innehåller inte IP-adressen \"{{ip}}\"",
     "form_error_positive": "Måste vara större än noll",
     "out_of_range_error": "Måste vara utanför intervallet \"{{start}}\"-\"{{end}}\"",
@@ -95,7 +95,7 @@
     "filter": "Filter",
     "query_log": "Förfrågningslogg",
     "compact": "Kompakt",
-    "nothing_found": "Inget hittades",
+    "nothing_found": "Ingenting hittades",
     "faq": "FAQ",
     "version": "version",
     "address": "Adress",
@@ -367,7 +367,7 @@
     "encryption_https_desc": "Om en HTTPS-port är inställd kommer gränssnittet till AdGuard Home administrering att kunna nås via HTTPS och kommer också att erbjuda DNS-over-HTTPS på '/dns-query' plats.",
     "encryption_dot": "DNS-över-TLS port",
     "encryption_dot_desc": "Om den här porten ställs in kommer AdGuard Home att använda DNS-over-TLS-server på porten.",
-    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq": "DNS-over-QUIC port (experimentell)",
     "encryption_doq_desc": "Om denna port är konfigurerad kommer AdGuard Home att köra en DNS-over-QUIC-server på denna port. Det är experimentellt och kanske inte är tillförlitligt. Dessutom finns det inte så många klienter som stödjer det för tillfället.",
     "encryption_certificates": "Certifikat",
     "encryption_certificates_desc": "För att använda kryptering måste du ange ett giltigt SSL-certifikat för din domän. Du kan skaffa ett certifikat gratis på <0>{{link}}</0> eller köpa ett från någon av de godkända certifikatutfärdare.",
@@ -389,7 +389,7 @@
     "topline_expiring_certificate": "Ditt SSL-certifikat håller på att gå ut. <0>Krypteringsinställningar</0>.",
     "topline_expired_certificate": "Ditt SSL-certifikat har gått ut. Uppdatera <0>Krypteringsinställningar</0>-",
     "form_error_port_range": "Ange ett portnummer inom värdena 80-65535",
-    "form_error_port_unsafe": "Det här är en osäker port",
+    "form_error_port_unsafe": "Osäker port",
     "form_error_equal": "Får inte vara samma",
     "form_error_password": "Lösenorden överensstämmer inte",
     "reset_settings": "Återställ inställningar",
@@ -405,8 +405,8 @@
     "update_failed": "Automatisk uppdatering misslyckad. Var god <a>följ stegen</a> för att uppdatera manuellt.",
     "manual_update": "Vänligen <a>följ dessa steg</a> för att uppdatera manuellt.",
     "processing_update": "Vänta, AdGuard Home uppdateras",
-    "clients_title": "Klienter",
-    "clients_desc": "Konfigurera enheter uppkopplade mot AdGuard Home",
+    "clients_title": "Uthålliga klienter",
+    "clients_desc": "Konfigurera beständiga klientposter för enheter som är anslutna till AdGuard Home",
     "settings_global": "Global",
     "settings_custom": "Anpassade",
     "table_client": "Klient",
@@ -417,7 +417,7 @@
     "client_edit": "Redigera klient",
     "client_identifier": "Identifikator",
     "ip_address": "IP-adress",
-    "client_identifier_desc": "Klienter kan identifieras med IP-adressen, CIDR, MAC-adressen eller ett speciellt klient-ID (kan användas för DoT/DoH/DoQ). <0>Här</0> kan du lära dig mer om hur du identifierar klienter.",
+    "client_identifier_desc": "Klienter kan identifieras med IP-adressen, CIDR, MAC-adressen eller ett ClientID (kan användas för DoT/DoH/DoQ). <0>Här</0> kan du lära dig mer om hur du identifierar klienter.",
     "form_enter_ip": "Skriv in IP",
     "form_enter_subnet_ip": "Ange en IP adress i subnätet \"{{cidr}}\"",
     "form_enter_mac": "Skriv in MAC",
@@ -433,13 +433,13 @@
     "client_confirm_delete": "Är du säker på att du vill ta bort klient \"{{key}}\"?",
     "list_confirm_delete": "Är du säker på att du vill ta bort den här listan?",
     "auto_clients_title": "Klienter (körtid)",
-    "auto_clients_desc": "Data från klienter som använder AdGuard Home, men inte är sparade i konfigurationen",
+    "auto_clients_desc": "Enheter som inte finns på listan över beständiga klienter som fortfarande kan använda AdGuard Home",
     "access_title": "Åtkomstinställningar",
-    "access_desc": "Här kan du konfigurera åtkomstregler för AdGuard Homes DNS-server.",
+    "access_desc": "Här kan du konfigurera åtkomstregler för AdGuard Homes DNS-server",
     "access_allowed_title": "Tillåtna klienter",
-    "access_allowed_desc": "En lista över CIDR, IP-adresser eller klient-ID. Om det är konfigurerat accepterar AdGuard Home endast förfrågningar från dessa klienter.",
+    "access_allowed_desc": "En lista över CIDR, IP-adresser eller <a>ClientID</a>. Om den här listan har poster accepterar AdGuard Home endast förfrågningar från dessa clienter.",
     "access_disallowed_title": "Otillåtna klienter",
-    "access_disallowed_desc": "En lista över CIDR, IP-adresser eller klient-ID. Om det är konfigurerat kommer AdGuard Home att kasta förfrågningar från dessa klienter. Om tillåtna klienter är konfigurerade ignoreras detta fält.",
+    "access_disallowed_desc": "En lista över CIDR, IP-adresser eller <a>ClientID</a>. Om den här listan har poster kommer AdGuard Home att ta bort förfrågningar från dessa klienter. Detta fält ignoreras om det finns poster i Tillåtna klienter.",
     "access_blocked_title": "Blockerade domäner",
     "access_blocked_desc": "Ej att förväxla med filter. AdGuard Home kastar DNS-frågor som matchar dessa domäner, och dessa frågor visas inte ens i frågeloggen. Du kan ange exakta domännamn, jokertecken eller URL-filterregler, t.ex. \"example.org\", \"*.example.org\" eller \"||example.org^\" på motsvarande sätt.",
     "access_settings_saved": "Åtkomstinställningar sparade",
@@ -500,6 +500,7 @@
     "interval_days": "{{count}} dag",
     "interval_days_plural": "{{count}} dagar",
     "domain": "Domän",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Svar",
     "filter_added_successfully": "Listan har lagts till",
@@ -552,10 +553,10 @@
     "autofix_warning_list": "Den kommer att utföra följande uppgifter: <0>Avaktivera system DNSStubListener</0> <0>Sätt DNS serveradress till 127.0.0.1</0> <0>Ersätt symboliskt länkmål för /etc/resolv.conf med /run/systemd /resolve/resolv.conf</0> <0>Stoppa DNSStubListener (ladda om systemd-resolved tjänst)</0>",
     "autofix_warning_result": "Som ett resultat kommer alla DNS-förfrågningar från ditt system att behandlas av AdGuard Home som standard.",
     "tags_title": "Taggar",
-    "tags_desc": "Du kan välja de taggar som motsvarar klienten. Taggar kan inkluderas i filtreringsreglerna och låter dig tillämpa dem mer exakt. <0>Läs mer</0>",
+    "tags_desc": "Du kan välja de taggar som motsvarar klienten. Inkludera taggar i filtreringsregler för att tillämpa dem mer exakt. <0>Läs mer</0>.",
     "form_select_tags": "Välj klienttaggar",
     "check_title": "Kontrollera filtreringen",
-    "check_desc": "Kontrollera om värdnamnet är filtrerat",
+    "check_desc": "Kontrollera om värdnamnet är filtrerat.",
     "check": "Kontrollera",
     "form_enter_host": "Ange ett värdnamn",
     "filtered_custom_rules": "Filtrerat efter anpassade filtreringsregler",
@@ -598,14 +599,14 @@
     "blocklist": "Blocklista",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Cachestorlek",
-    "cache_size_desc": "DNS cachestorlek (i byte)",
+    "cache_size_desc": "DNS cachestorlek (i byte).",
     "cache_ttl_min_override": "Åsidosätt minsta TTL",
     "cache_ttl_max_override": "Åsidosätt maximal TTL",
     "enter_cache_size": "Ange cachestorlek (byte)",
     "enter_cache_ttl_min_override": "Ange minsta TTL (sekunder)",
     "enter_cache_ttl_max_override": "Ange maximal TTL (sekunder)",
-    "cache_ttl_min_override_desc": "Förläng värden för korta time-to-live värden (sekunder) som tas emot från uppströms server när DNS svar cachelagras",
-    "cache_ttl_max_override_desc": "Ställ in ett maximalt värde för time-to-live (sekunder) för poster i DNS cachen",
+    "cache_ttl_min_override_desc": "Förläng värden för korta time-to-live värden (sekunder) som tas emot från uppströms server när DNS svar cachelagras.",
+    "cache_ttl_max_override_desc": "Ställ in ett maximalt värde för time-to-live (sekunder) för poster i DNS cachen.",
     "ttl_cache_validation": "Minsta cache TTL-värde måste vara mindre än eller lika med maxvärdet",
     "cache_optimistic": "Optimistisk cachning",
     "cache_optimistic_desc": "Få AdGuard Home att svara från cachen även när posterna har gått ut och försök även uppdatera dem.",
@@ -627,5 +628,6 @@
     "use_saved_key": "Använd den tidigare sparade nyckeln",
     "parental_control": "Föräldrakontroll",
     "safe_browsing": "Säker surfning",
-    "served_from_cache": "{{value}} <i>(levereras från cache)</i>"
+    "served_from_cache": "{{value}} <i>(levereras från cache)</i>",
+    "form_error_password_length": "Lösenordet måste vara minst {{value}} tecken långt"
 }
diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json
index 86683857..5aa53ec1 100644
--- a/client/src/__locales/tr.json
+++ b/client/src/__locales/tr.json
@@ -9,12 +9,12 @@
     "bootstrap_dns": "DNS Önyükleme sunucuları",
     "bootstrap_dns_desc": "DNS Önyükleme sunucuları, belirttiğiniz üst sunucuların DoH/DoT çözümleyicilerine ait IP adreslerinin çözümlemek için kullanılır.",
     "local_ptr_title": "Özel ters DNS sunucuları",
-    "local_ptr_desc": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, rDNS kullanarak \"192.168.12.34\" gibi özel IP adreslerine sahip istemcilerin ana bilgisayar adlarını çözmek için kullanılır. Ayarlanmadığı durumda AdGuard Home, işletim sisteminizin varsayılan DNS çözümleme adreslerini kullanır.",
+    "local_ptr_desc": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, rDNS kullanarak \"192.168.12.34\" gibi özel IP adreslerine sahip istemcilerin ana makine adlarını çözmek için kullanılır. Ayarlanmadığı durumda AdGuard Home, işletim sisteminizin varsayılan DNS çözümleme adreslerini kullanır.",
     "local_ptr_default_resolver": "AdGuard Home, varsayılan olarak aşağıdaki ters DNS çözümleyicilerini kullanır: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home, bu sistem için uygun olan özel ters DNS çözümleyicilerini belirleyemedi.",
     "local_ptr_placeholder": "Her satıra bir sunucu adresi girin",
     "resolve_clients_title": "İstemcilerin IP adreslerinin ters çözümlenmesini etkinleştir",
-    "resolve_clients_desc": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunucuları) PTR sorguları göndererek istemcilerin IP adreslerini ana bilgisayar adlarının tersine çözün.",
+    "resolve_clients_desc": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunucuları) PTR sorguları göndererek istemcilerin IP adreslerini ana makine adlarının tersine çözün.",
     "use_private_ptr_resolvers_title": "Özel ters DNS çözümleyicileri kullan",
     "use_private_ptr_resolvers_desc": "Bu üst kaynak sunucularını kullanarak yerel olarak sunulan adresler için ters DNS aramaları yapın. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts, vb. tarafından bilinen istemciler dışında bu tür tüm PTR isteklerine NXDOMAIN ile yanıt verir.",
     "check_dhcp_servers": "DHCP sunucularını denetle",
@@ -35,23 +35,23 @@
     "dhcp_config_saved": "DHCP yapılandırması başarıyla kaydedildi",
     "dhcp_ipv4_settings": "DHCP IPv4 Ayarları",
     "dhcp_ipv6_settings": "DHCP IPv6 Ayarları",
-    "form_error_required": "Gerekli alan.",
-    "form_error_ip4_format": "IPv4 adresi geçersiz.",
-    "form_error_ip4_range_start_format": "Başlangıç aralığı IPv4 adresi geçersiz.",
-    "form_error_ip4_range_end_format": "Bitiş aralığı IPv4 adresi geçersiz.",
-    "form_error_ip4_gateway_format": "Ağ geçidi IPv4 adresi geçersiz.",
-    "form_error_ip6_format": "IPv6 adresi geçersiz.",
-    "form_error_ip_format": "IP adresi geçersiz.",
-    "form_error_mac_format": "MAC adresi geçersiz.",
-    "form_error_client_id_format": "İstemci Kimliği yalnızca sayılar, küçük harfler ve kısa çizgiler içermelidir.",
-    "form_error_server_name": "Sunucu adı geçersiz.",
-    "form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor.",
-    "form_error_positive": "0'dan büyük olmalıdır.",
-    "out_of_range_error": "\"{{start}}\"-\"{{end}}\" aralığının dışında olmalıdır.",
-    "lower_range_start_error": "Başlangıç aralığından daha düşük olmalıdır.",
-    "greater_range_start_error": "Başlangıç aralığından daha büyük olmalıdır.",
-    "greater_range_end_error": "Bitiş aralığından daha büyük olmalıdır.",
-    "subnet_error": "Adresler bir alt ağda olmalıdır.",
+    "form_error_required": "Gerekli alan",
+    "form_error_ip4_format": "Geçersiz IPv4 adresi",
+    "form_error_ip4_range_start_format": "Geçersiz başlangıç aralığı IPv4 biçimi",
+    "form_error_ip4_range_end_format": "Geçersiz bitiş aralığı IPv4 adresi",
+    "form_error_ip4_gateway_format": "Geçersiz ağ geçidi IPv4 adresi",
+    "form_error_ip6_format": "Geçersiz IPv6 adresi",
+    "form_error_ip_format": "Geçersiz IP adresi",
+    "form_error_mac_format": "Geçersiz MAC adresi",
+    "form_error_client_id_format": "İstemci Kimliği yalnızca sayılar, küçük harfler ve kısa çizgiler içermelidir",
+    "form_error_server_name": "Geçersiz sunucu adı",
+    "form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor",
+    "form_error_positive": "0'dan büyük olmalıdır",
+    "out_of_range_error": "\"{{start}}\"-\"{{end}}\" aralığının dışında olmalıdır",
+    "lower_range_start_error": "Başlangıç aralığından daha düşük olmalıdır",
+    "greater_range_start_error": "Başlangıç aralığından daha büyük olmalıdır",
+    "greater_range_end_error": "Bitiş aralığından daha büyük olmalıdır",
+    "subnet_error": "Adresler bir alt ağda olmalıdır",
     "gateway_or_subnet_invalid": "Alt ağ maskesi geçersiz.",
     "dhcp_form_gateway_input": "Ağ geçidi IP",
     "dhcp_form_subnet_input": "Alt ağ maskesi",
@@ -64,10 +64,10 @@
     "dhcp_hardware_address": "Donanım adresi",
     "dhcp_ip_addresses": "IP adresleri",
     "ip": "IP",
-    "dhcp_table_hostname": "Bilgisayar Adı",
+    "dhcp_table_hostname": "Ana makine Adı",
     "dhcp_table_expires": "Bitiş tarihi",
     "dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların İnternet bağlantısı kesilebilir!",
-    "dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi.",
+    "dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi",
     "dhcp_static_ip_error": "DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. AdGuard Home, bu ağ arayüzünün sabit bir IP adresi kullanılarak yapılandırılıp yapılandırılmadığını belirleyemedi. Lütfen sabit IP adresini elle ayarlayın.",
     "dhcp_dynamic_ip_found": "Sisteminiz, <0>{{interfaceName}}</0> arayüzü için dinamik IP adresi yapılandırması kullanıyor. DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. Geçerli olan IP adresiniz <0>{{ipAddress}}</0>. \"DHCP sunucusunu etkinleştir\" düğmesine basarsanız, AdGuard Home bu IP adresini otomatik bir şekilde sabit olarak ayarlayacaktır.",
     "dhcp_lease_added": "Sabit kiralama \"{{key}}\" başarıyla eklendi",
@@ -82,7 +82,7 @@
     "country": "Ülke",
     "city": "Şehir",
     "delete_confirm": "\"{{key}}\" öğesini silmek istediğinizden emin misiniz?",
-    "form_enter_hostname": "Ana bilgisayar adı girin",
+    "form_enter_hostname": "Ana makine adı girin",
     "error_details": "Hata ayrıntıları",
     "response_details": "Yanıt ayrıntıları",
     "request_details": "İstek ayrıntıları",
@@ -130,14 +130,14 @@
     "number_of_dns_query_days": "Son {{count}} gün boyunca işlenen DNS sorgularının sayısı",
     "number_of_dns_query_days_plural": "Son {{count}} gün boyunca işlenen DNS sorgularının sayısı",
     "number_of_dns_query_24_hours": "Son 24 saat içinde işlenen DNS sorgularının sayısı",
-    "number_of_dns_query_blocked_24_hours": "Reklam engelleme filtreleri ve engel listeleri tarafından engellenen DNS isteklerinin sayısı",
+    "number_of_dns_query_blocked_24_hours": "Reklam engelleme filtreleri ve ana makine engel listeleri tarafından engellenen DNS isteklerinin sayısı",
     "number_of_dns_query_blocked_24_hours_by_sec": "AdGuard gezinti koruması modülü tarafından engellenen DNS isteklerinin sayısı",
     "number_of_dns_query_blocked_24_hours_adult": "Engellenen yetişkin içerikli sitelerin sayısı",
     "enforced_save_search": "Uygulanan güvenli arama",
     "number_of_dns_query_to_safe_search": "Güvenli Aramanın uygulandığı arama motorlarına gönderilen DNS isteklerinin sayısı",
     "average_processing_time": "Ortalama işlem süresi",
     "average_processing_time_hint": "Bir DNS isteğinin milisaniye cinsinden ortalama işlem süresi",
-    "block_domain_use_filters_and_hosts": "Filtre ve ana bilgisayar listelerini kullanarak alan adlarını engelle",
+    "block_domain_use_filters_and_hosts": "Filtre ve ana makine listelerini kullanarak alan adlarını engelle",
     "filters_block_toggle_hint": "<a>Filtreler</a> ayarlarında engelleme kuralları oluşturabilirsiniz.",
     "use_adguard_browsing_sec": "AdGuard gezinti koruması web hizmetini kullan",
     "use_adguard_browsing_sec_hint": "AdGuard Home, alan adının gezinti koruması web hizmeti tarafından engellenip engellenmediğini kontrol eder. Kontrolü gerçekleştirmek için gizlilik dostu arama API'sini kullanır: sunucuya yalnızca SHA256 karma alan adının kısa bir ön eki gönderilir.",
@@ -151,7 +151,7 @@
     "dns_blocklists": "DNS engel listeleri",
     "dns_allowlists": "DNS izin listeleri",
     "dns_blocklists_desc": "AdGuard Home, engel listeleriyle eşleşen alan adlarını engeller.",
-    "dns_allowlists_desc": "DNS izin listesindeki alan adlarına, engel listesinde olsa bile izin verilir.",
+    "dns_allowlists_desc": "DNS izin listesindeki alan adlarına, engel listesinde olsa bile izin verilecektir.",
     "custom_filtering_rules": "Özel filtreleme kuralları",
     "encryption_settings": "Şifreleme ayarları",
     "dhcp_settings": "DHCP ayarları",
@@ -179,14 +179,14 @@
     "edit_table_action": "Düzenle",
     "delete_table_action": "Sil",
     "elapsed": "Geçen süre",
-    "filters_and_hosts_hint": "AdGuard Home, temel reklam engelleme kurallarını ve ana bilgisayar engelleme dosyalarının söz dizimini anlar.",
+    "filters_and_hosts_hint": "AdGuard Home, temel reklam engelleme kurallarını ve ana makine engelleme dosyalarının söz dizimini anlar.",
     "no_blocklist_added": "Engel listesi eklenmedi",
     "no_whitelist_added": "İzin listesi eklenmedi",
     "add_blocklist": "Engel listesi ekle",
     "add_allowlist": "İzin listesi ekle",
     "cancel_btn": "İptal",
     "enter_name_hint": "İsim girin",
-    "enter_url_or_path_hint": "Listenin URL adresini veya dosya konumunu girin",
+    "enter_url_or_path_hint": "Listenin URL adresini veya dosya yolunu girin",
     "check_updates_btn": "Güncellemeleri denetle",
     "new_blocklist": "Yeni engel listesi",
     "new_allowlist": "Yeni izin listesi",
@@ -196,11 +196,11 @@
     "choose_allowlist": "İzin listelerini seçin",
     "enter_valid_blocklist": "Engel listesine geçerli bir URL girin.",
     "enter_valid_allowlist": "İzin listesine geçerli bir URL girin.",
-    "form_error_url_format": "URL biçimi geçersiz.",
-    "form_error_url_or_path_format": "Listenin URL adresi veya dosya konumu geçersiz.",
+    "form_error_url_format": "Geçersiz URL biçimi",
+    "form_error_url_or_path_format": "Geçersiz URL adresi veya dosya yolu",
     "custom_filter_rules": "Özel filtreleme kuralları",
-    "custom_filter_rules_hint": "Her satıra bir kural girin. Reklam engelleme kuralı veya ana bilgisayar dosyası söz dizimi kullanabilirsiniz.",
-    "system_host_files": "Sistem ana bilgisayar dosyaları",
+    "custom_filter_rules_hint": "Her satıra bir kural girin. Reklam engelleme kuralı veya ana makine dosyası söz dizimi kullanabilirsiniz.",
+    "system_host_files": "Sistem ana makine dosyaları",
     "examples_title": "Örnekler",
     "example_meaning_filter_block": "example.org'a ve tüm alt alanlarına erişimi engeller;",
     "example_meaning_filter_whitelist": "example.org'a ve tüm alt alanlarına erişimin engelini kaldırır;",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Ayrıca bir yorum.",
     "example_regex_meaning": "belirtilen düzenli ifadelerle eşleşen alan adlarına erişimi engelle.",
     "example_upstream_regular": "normal DNS (UDP üzerinden);",
+    "example_upstream_udp": "normal DNS (UDP üzerinden, ana makine adı);",
     "example_upstream_dot": "şifrelenmiş <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "şifrelenmiş <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "şifrelenmiş <0>DNS-over-QUIC</0> (deneysel);",
     "example_upstream_sdns": "<1>DNSCrypt</1> veya <2>DNS-over-HTTPS</2> çözümleyicileri için <0>DNS Damgaları</0>;",
     "example_upstream_tcp": "normal DNS (TCP üzerinden);",
+    "example_upstream_tcp_hostname": "normal DNS (TCP üzerinden, ana makine adı);",
     "all_lists_up_to_date_toast": "Tüm listeler güncel durumda",
     "updated_upstream_dns_toast": "Üst sunucular başarıyla kaydedildi",
     "dns_test_ok_toast": "Belirtilen DNS sunucuları düzgün çalışıyor",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Tam arama için çift tırnak işareti kullanın",
     "query_log_retention_confirm": "Sorgu günlüğü saklama süresini değiştirmek istediğinize emin misiniz? Aralık değerini azaltırsanız, bazı veriler kaybolacaktır",
     "anonymize_client_ip": "İstemcinin IP adresini gizle",
-    "anonymize_client_ip_desc": "İstemcinin tam IP adresini günlüklere veya istatistiklere kaydetmeyin.",
+    "anonymize_client_ip_desc": "İstemcinin tam IP adresini günlüklere veya istatistiklere kaydetmeyin",
     "dns_config": "DNS sunucu yapılandırması",
     "dns_cache_config": "DNS önbellek yapılandırması",
-    "dns_cache_config_desc": "Burada DNS önbelleğini yapılandırabilirsiniz.",
+    "dns_cache_config_desc": "Burada DNS önbelleğini yapılandırabilirsiniz",
     "blocking_mode": "Engelleme modu",
     "default": "Varsayılan",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Sıklık limitini girin",
     "rate_limit": "Sıklık limiti",
     "edns_enable": "EDNS istemci alt ağını etkinleştir",
-    "edns_cs_desc": "İstemcilerin alt ağlarını DNS sunucularına gönderin.",
+    "edns_cs_desc": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı seçeneğini (ECS) ekleyin ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydedin.",
     "rate_limit_desc": "İstemci başına izin verilen saniyedeki istek sayısı. 0 olarak ayarlamak, sınır olmadığı anlamına gelir.",
     "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",
@@ -308,8 +310,8 @@
     "install_settings_title": "Yönetici Web Arayüzü",
     "install_settings_listen": "Dinleme arayüzü",
     "install_settings_port": "Bağlantı noktası",
-    "install_settings_interface_link": "AdGuard Home yönetici web arayüzü sayfanız şu adresten erişilebilir olacaktır:",
-    "form_error_port": "Geçerli bir bağlantı noktası değeri girin.",
+    "install_settings_interface_link": "AdGuard Home yönetici web arayüzünüz aşağıdaki adreslerde bulunacaktır:",
+    "form_error_port": "Geçerli bir bağlantı noktası değeri girin",
     "install_settings_dns": "DNS sunucusu",
     "install_settings_dns_desc": "Cihazlarınızı veya yönlendiricinizi şu adresteki DNS sunucusunu kullanması için ayarlamanız gerekecek:",
     "install_settings_all_interfaces": "Tüm arayüzler",
@@ -334,7 +336,7 @@
     "install_devices_router_list_4": "Bazı yönlendirici türlerinde özel bir DNS sunucusu ayarlanamaz. Bu durumda, AdGuard Home'u <0>DHCP sunucusu</0> olarak ayarlamak yardımcı olabilir. Aksi takdirde, yönlendirici modeliniz için DNS sunucularını nasıl ayarlayacağınız konusunda yönlendirici kılavuzuna bakmalısınız.",
     "install_devices_windows_list_1": "Başlat menüsünden veya Windows araması aracılığıyla Denetim Masası'nı açın.",
     "install_devices_windows_list_2": "Ağ ve İnternet kategorisine girin ve ardından Ağ ve Paylaşım Merkezi'ne girin.",
-    "install_devices_windows_list_3": "Ekranın sol tarafında bulunan \"Adaptör ayarlarını değiştir\" öğesini bulun ve tıklayın.",
+    "install_devices_windows_list_3": "Sol panelde \"Bağdaştırıcı ayarlarını değiştirin'e\" tıklayın.",
     "install_devices_windows_list_4": "Kullandığınız aktif bağlantının üzerine sağ tıklayın ve Özellikler öğesine tıklayın.",
     "install_devices_windows_list_5": "Listede \"İnternet Protokolü Sürüm 4 (TCP/IPv4)\" (veya IPv6 için \"İnternet Protokolü Sürüm 6 (TCP/IPv6)\") öğesini bulun, seçin ve ardından tekrar Özellikler'e tıklayın.",
     "install_devices_windows_list_6": "\"Aşağıdaki DNS sunucu adreslerini kullan\"ı seçin ve AdGuard Home sunucu adreslerinizi girin.",
@@ -356,7 +358,7 @@
     "open_dashboard": "Ana Sayfayı Aç",
     "install_saved": "Başarıyla kaydedildi",
     "encryption_title": "Şifreleme",
-    "encryption_desc": "DNS ve yönetici web arayüzü için şifreleme (HTTPS/TLS) desteği.",
+    "encryption_desc": "DNS ve yönetici web arayüzü için şifreleme (HTTPS/TLS) desteği",
     "encryption_config_saved": "Şifreleme yapılandırması kaydedildi",
     "encryption_server": "Sunucu adı",
     "encryption_server_enter": "Alan adınızı girin",
@@ -364,7 +366,7 @@
     "encryption_redirect": "Otomatik olarak HTTPS'e yönlendir",
     "encryption_redirect_desc": "Etkinleştirirseniz, AdGuard Home sizi HTTP adresi yerine HTTPS adresine yönlendirir.",
     "encryption_https": "HTTPS bağlantı noktası",
-    "encryption_https_desc": "HTTPS bağlantı noktası yapılandırılırsa, AdGuard Home yönetici arayüzüne HTTPS aracılığıyla erişilebilir olacak ve ayrıca '/dns-query' üzerinden DNS-over-HTTPS bağlantısı sağlanır.",
+    "encryption_https_desc": "HTTPS bağlantı noktası yapılandırılırsa, AdGuard Home yönetici arayüzüne HTTPS aracılığıyla erişilebilir olacak ve ayrıca '/dns-query' üzerinden DNS-over-HTTPS bağlantısı sağlayacaktır.",
     "encryption_dot": "DNS-over-TLS bağlantı noktası",
     "encryption_dot_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, DNS-over-TLS sunucusunu bu bağlantı noktası üzerinden çalıştıracaktır.",
     "encryption_doq": "DNS-over-QUIC bağlantı noktası (deneysel)",
@@ -378,26 +380,26 @@
     "encryption_key_input": "Sertifikanızın PEM biçimli özel anahtarını kopyalayıp buraya yapıştırın.",
     "encryption_enable": "Şifrelemeyi etkinleştir (HTTPS, DNS-over-HTTPS ve DNS-over-TLS)",
     "encryption_enable_desc": "Şifrelemeyi etkinleştirirseniz, AdGuard Home yönetici arayüzü HTTPS üzerinden çalışır ve DNS sunucusu, DNS-over-HTTPS ve DNS-over-TLS üzerinden gelen istekleri dinler.",
-    "encryption_chain_valid": "Sertifika zinciri geçerli.",
+    "encryption_chain_valid": "Sertifika zinciri geçerli",
     "encryption_chain_invalid": "Sertifika zinciri geçersiz.",
     "encryption_key_valid": "Bu geçerli bir {{type}} özel anahtar.",
     "encryption_key_invalid": "Bu geçersiz bir {{type}} özel anahtar.",
     "encryption_subject": "Konu",
     "encryption_issuer": "Sağlayan",
-    "encryption_hostnames": "Ana bilgisayar adları",
+    "encryption_hostnames": "Ana makine adları",
     "encryption_reset": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?",
     "topline_expiring_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını</0> güncelleyin.",
     "topline_expired_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını</0> güncelleyin.",
-    "form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin.",
-    "form_error_port_unsafe": "Bu bağlantı noktası güvenli değil.",
-    "form_error_equal": "Aynı olmamalı.",
-    "form_error_password": "Parolalar uyuşmuyor.",
+    "form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin",
+    "form_error_port_unsafe": "Güvenli olmayan bağlantı noktası",
+    "form_error_equal": "Aynı olmamalıdır",
+    "form_error_password": "Parolalar uyuşmuyor",
     "reset_settings": "Ayarları sıfırla",
     "update_announcement": "AdGuard Home {{version}} sürümü mevcut! Daha fazla bilgi için <0>buraya tıklayın.</0>",
     "setup_guide": "Kurulum Rehberi",
     "dns_addresses": "DNS adresleri",
     "dns_start": "DNS sunucusu başlatılıyor",
-    "dns_status_error": "DNS sunucusunun durumu denetlenirken bir hata oluştu.",
+    "dns_status_error": "DNS sunucusunun durumu denetlenirken bir hata oluştu",
     "down": "Kapalı",
     "fix": "Düzelt",
     "dns_providers": "Aralarından seçim yapabileceğiniz, bilinen <0>DNS sağlayıcıların listesi</0>.",
@@ -406,7 +408,7 @@
     "manual_update": "Elle güncellemek için lütfen <a>bu adımları uygulayın</a>.",
     "processing_update": "Lütfen bekleyin, AdGuard Home güncelleniyor",
     "clients_title": "Kalıcı istemciler",
-    "clients_desc": "AdGuard Home'a bağlı cihazlar için kalıcı istemci kayıtlarını yapılandırın.",
+    "clients_desc": "AdGuard Home'a bağlı cihazlar için kalıcı istemci kayıtlarını yapılandırın",
     "settings_global": "Genel",
     "settings_custom": "Özel",
     "table_client": "İstemci",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?",
     "list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?",
     "auto_clients_title": "Çalışma zamanı istemcileri",
-    "auto_clients_desc": "Henüz AdGuard Home'u kullanabilecek Kalıcı istemciler listesinde olmayan cihazlar.",
+    "auto_clients_desc": "Henüz AdGuard Home'u kullanabilecek Kalıcı istemciler listesinde olmayan cihazlar",
     "access_title": "Erişim ayarları",
-    "access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz.",
+    "access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz",
     "access_allowed_title": "İzin verilen istemciler",
     "access_allowed_desc": "CIDR'lerin, IP adreslerinin veya <a>İstemci Kimliklerin</a> listesi. Bu listede girişler varsa, AdGuard Home yalnızca bu istemcilerden gelen istekleri kabul eder.",
     "access_disallowed_title": "İzin verilmeyen istemciler",
@@ -471,12 +473,12 @@
     "rewrite_confirm_delete": "\"{{key}}\" için DNS yeniden yazımını silmek istediğinize emin misiniz?",
     "rewrite_desc": "Belirli bir alan adı için özel DNS yanıtını kolayca yapılandırmanızı sağlar.",
     "rewrite_applied": "Yeniden yazım kuralı uygulandı",
-    "rewrite_hosts_applied": "Ana bilgisayar dosyası kuralı tarafından yeniden yazıldı",
+    "rewrite_hosts_applied": "Ana makine dosyası kuralı tarafından yeniden yazıldı",
     "dns_rewrites": "DNS yeniden yazımları",
     "form_domain": "Alan adı veya joker karakter girin",
     "form_answer": "IP adresi veya alan adı girin",
-    "form_error_domain_format": "Alan adı biçimi geçersiz.",
-    "form_error_answer_format": "Yanıt biçimi geçersiz.",
+    "form_error_domain_format": "Geçersiz alan adı biçimi",
+    "form_error_answer_format": "Geçersiz yanıt biçimi",
     "configure": "Yapılandır",
     "main_settings": "Ana ayarlar",
     "block_services": "Belirli hizmetleri engelle",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} gün",
     "interval_days_plural": "{{count}} gün",
     "domain": "Alan adı",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Yanıt",
     "filter_added_successfully": "Liste başarıyla eklendi",
@@ -507,7 +510,7 @@
     "filter_updated": "Liste başarıyla güncellendi",
     "statistics_configuration": "İstatistik yapılandırması",
     "statistics_retention": "İstatistikleri sakla",
-    "statistics_retention_desc": "Zaman değerini azaltırsanız, bazı veriler kaybolacaktır.",
+    "statistics_retention_desc": "Zaman değerini azaltırsanız, bazı veriler kaybolacaktır",
     "statistics_clear": " İstatistikleri temizle",
     "statistics_clear_confirm": "İstatistikleri temizlemek istediğinizden emin misiniz?",
     "statistics_retention_confirm": "İstatistik saklama süresini değiştirmek istediğinizden emin misiniz? Aralık değerini azaltırsanız, bazı veriler kaybolacaktır",
@@ -524,7 +527,7 @@
     "password_label": "Parola",
     "password_placeholder": "Parolayı girin",
     "sign_in": "Giriş yap",
-    "sign_out": "Oturumu kapat",
+    "sign_out": "Çıkış yap",
     "forgot_password": "Parolanızı mı unuttunuz?",
     "forgot_password_desc": "Kullanıcı hesabınız için yeni bir parola oluşturmak istiyorsanız lütfen <0>bu adımları</0> uygulayın.",
     "location": "Konum",
@@ -533,7 +536,7 @@
     "network": "Ağ",
     "descr": "Açıklama",
     "whois": "WHOIS",
-    "filtering_rules_learn_more": "Kendi ana bilgisayar listelerinizi oluşturma hakkında <0>daha fazla bilgi edinin</0>.",
+    "filtering_rules_learn_more": "Kendi ana makine listelerinizi oluşturma hakkında <0>daha fazla bilgi edinin</0>.",
     "blocked_by_response": "Yanıt olarak CNAME veya IP tarafından engellendi",
     "blocked_by_cname_or_ip": "CNAME veya IP tarafından engellendi",
     "try_again": "Tekrar dene",
@@ -555,13 +558,13 @@
     "tags_desc": "İstemciye karşılık gelen etiketleri seçebilirsiniz. Etiketleri daha kesin olarak uygulamak için filtreleme kurallarına dahil edin. <0>Daha fazla bilgi edinin</0>.",
     "form_select_tags": "İstemci etiketlerini seçin",
     "check_title": "Filtrelemeyi denetleyin",
-    "check_desc": "Ana bilgisayar adının filtreleme durumunu kontrol edin.",
+    "check_desc": "Ana makine adının filtreleme durumunu kontrol edin.",
     "check": "Denetle",
-    "form_enter_host": "Ana bilgisayar adı girin",
+    "form_enter_host": "Ana makine adı girin",
     "filtered_custom_rules": "Özel filtreleme kurallarına göre filtrelendi",
     "choose_from_list": "Listeden seç",
     "add_custom_list": "Özel liste ekle",
-    "host_whitelisted": "Ana bilgisayara izin verildi",
+    "host_whitelisted": "Ana makineye izin verildi",
     "check_ip": "IP adresleri: {{ip}}",
     "check_cname": "CNAME: {{cname}}",
     "check_reason": "Sebep: {{reason}}",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "Maksimum TTL değerini girin (saniye olarak)",
     "cache_ttl_min_override_desc": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan kullanım süresi değerini uzatın (saniye olarak).",
     "cache_ttl_max_override_desc": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak).",
-    "ttl_cache_validation": "Minimum önbellek TTL geçersiz kılma, maksimuma eşit veya bundan küçük olmalıdır.",
+    "ttl_cache_validation": "Minimum önbellek TTL geçersiz kılma, maksimuma eşit veya bundan küçük olmalıdır",
     "cache_optimistic": "İyimser önbelleğe alma",
     "cache_optimistic_desc": "Girişlerin süresi dolduğunda bile AdGuard Home'un önbellekten yanıt vermesini sağlayın ve bunları yenilemeye çalışın.",
     "filter_category_general": "Genel",
@@ -621,12 +624,12 @@
     "original_response": "Gerçek yanıt",
     "click_to_view_queries": "Sorguları görmek için tıklayın",
     "port_53_faq_link": "53 numaralı bağlantı noktası genellikle \"DNSStubListener\" veya \"systemd-resolved\" hizmetleri tarafından kullanılır. Lütfen bu sorunun nasıl çözüleceğine ilişkin <0>bu talimatı</0> okuyun.",
-    "adg_will_drop_dns_queries": "AdGuard Home, bu istemciden gelen tüm DNS sorgularını yok sayar.",
+    "adg_will_drop_dns_queries": "AdGuard Home, bu istemciden gelen tüm DNS sorgularını yok sayacaktır.",
     "filter_allowlist": "UYARI: Bu işlem ayrıca \"{{disallowed_rule}}\" kuralını izin verilen istemciler listesinden hariç tutacaktır.",
     "last_rule_in_allowlist": "\"{{disallowed_rule}}\" kuralı hariç tutulduğunda \"İzin verilen istemciler\" listesi DEVRE DIŞI bırakılacağı için bu istemciye izin verilemez.",
     "use_saved_key": "Önceden kaydedilmiş anahtarı kullan",
     "parental_control": "Ebeveyn Denetimi",
     "safe_browsing": "Güvenli Gezinti",
     "served_from_cache": "{{value}} <i>(önbellekten kullanıldı)</i>",
-    "form_error_password_length": "Parola en az {{value}} karakter uzunluğunda olmalı."
+    "form_error_password_length": "Parola en az {{value}} karakter uzunluğunda olmalıdır"
 }
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index 716d784c..5d349618 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "Конфігурацію DHCP-сервера успішно збережено",
     "dhcp_ipv4_settings": "Налаштування DHCP IPv4",
     "dhcp_ipv6_settings": "Налаштування DHCP IPv6",
-    "form_error_required": "Обов'язкове поле.",
-    "form_error_ip4_format": "Неправильна IPv4-адреса.",
-    "form_error_ip4_range_start_format": "Неправильна IPv4-адреса початку діапазону.",
-    "form_error_ip4_range_end_format": "Неправильна IPv4-адреса кінця діапазону.",
-    "form_error_ip4_gateway_format": "Неправильна IPv4-адреса шлюзу.",
-    "form_error_ip6_format": "Неправильна IPv6-адреса.",
-    "form_error_ip_format": "Неправильна IP-адреса.",
-    "form_error_mac_format": "Неправильна MAC-адреса.",
-    "form_error_client_id_format": "ClientID має містити лише цифри, малі латинські букви та дефіси.",
-    "form_error_server_name": "Неправильна назва сервера.",
-    "form_error_subnet": "Підмережа «{{cidr}}» не містить IP-адресу «{{ip}}».",
-    "form_error_positive": "Повинно бути більше ніж 0.",
-    "out_of_range_error": "Не повинна бути в діапазоні «{{start}}»−«{{end}}».",
-    "lower_range_start_error": "Має бути меншим за початкову адресу.",
-    "greater_range_start_error": "Має бути більшим за початкову адресу.",
-    "greater_range_end_error": "Має бути більшим за кінцеву адресу.",
-    "subnet_error": "Адреси повинні бути в одній підмережі.",
-    "gateway_or_subnet_invalid": "Неправильна маска підмережі.",
+    "form_error_required": "Обов'язкове поле",
+    "form_error_ip4_format": "Неправильна IPv4-адреса",
+    "form_error_ip4_range_start_format": "Неправильна IPv4-адреса початку діапазону",
+    "form_error_ip4_range_end_format": "Неправильна IPv4-адреса кінця діапазону",
+    "form_error_ip4_gateway_format": "Неправильна IPv4-адреса шлюзу",
+    "form_error_ip6_format": "Неправильна IPv6-адреса",
+    "form_error_ip_format": "Неправильна IP-адреса",
+    "form_error_mac_format": "Неправильна MAC-адреса",
+    "form_error_client_id_format": "ID клієнта має містити лише цифри, малі букви та дефіси",
+    "form_error_server_name": "Неправильна назва сервера",
+    "form_error_subnet": "Підмережа «{{cidr}}» не містить IP-адресу «{{ip}}»",
+    "form_error_positive": "Повинно бути більше за 0",
+    "out_of_range_error": "Не повинна бути в діапазоні «{{start}}»−«{{end}}»",
+    "lower_range_start_error": "Має бути меншим за початкову адресу",
+    "greater_range_start_error": "Має бути більшим за початкову адресу",
+    "greater_range_end_error": "Має бути більшим за кінцеву адресу",
+    "subnet_error": "Адреси повинні бути в одній підмережі",
+    "gateway_or_subnet_invalid": "Неправильна маска підмережі",
     "dhcp_form_gateway_input": "IP-адреса шлюзу",
     "dhcp_form_subnet_input": "Маска підмережі",
     "dhcp_form_range_title": "Діапазон IP-адрес",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "Назва вузла",
     "dhcp_table_expires": "Термін дії",
     "dhcp_warning": "Якщо ви однаково хочете увімкнути DHCP-сервер, переконайтеся, що у вашій мережі немає інших активних DHCP-серверів. Інакше, це може порушити роботу інтернету на під'єднаних пристроях!",
-    "dhcp_error": "Не вдалося визначити, чи є в мережі інший DHCP-сервер.",
+    "dhcp_error": "AdGuard Home не зміг визначити, чи є в мережі інший DHCP-сервер",
     "dhcp_static_ip_error": "Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Нам не вдалося визначити, чи цей мережевий інтерфейс налаштовано для використання статичної IP-адреси. Встановіть статичну IP-адресу вручну.",
     "dhcp_dynamic_ip_found": "Ваша система використовує конфігурацію з динамічною IP-адресою для інтерфейсу <0>{{interfaceName}}</0>. Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Ваша поточна IP-адреса <0>{{ipAddress}}</0>. Ми автоматично встановимо цю IP-адресу як статичну, якщо ви натиснете кнопку «Увімкнути DHCP-сервер».",
     "dhcp_lease_added": "Статичну оренду «{{key}}» успішно додано",
@@ -196,8 +196,8 @@
     "choose_allowlist": "Обрати списки дозволених сайтів",
     "enter_valid_blocklist": "Введіть дійсну URL-адресу в список блокування.",
     "enter_valid_allowlist": "Введіть дійсну URL-адресу в список дозволів.",
-    "form_error_url_format": "Неправильний формат URL.",
-    "form_error_url_or_path_format": "Неправильна URL-адреса або абсолютний шлях до списку.",
+    "form_error_url_format": "Неправильний формат URL",
+    "form_error_url_or_path_format": "Неправильна URL-адреса або абсолютний шлях до списку",
     "custom_filter_rules": "Власні правила фільтрування",
     "custom_filter_rules_hint": "Вводьте одне правило на рядок. Ви можете використовувати правила блокування чи синтаксис файлів hosts.",
     "system_host_files": "Системні hosts-файли",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Також коментар.",
     "example_regex_meaning": "блокувати доступ до доменів, що відповідають вказаному регулярному виразу.",
     "example_upstream_regular": "звичайний DNS (через UDP);",
+    "example_upstream_udp": "звичайний DNS (поверх UDP, з назвою вузла);",
     "example_upstream_dot": "зашифрований <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "зашифрований <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "зашифрований <0>DNS-over-QUIC</0> (експериментальний);",
     "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt</1> або <2>DNS-over-HTTPS</2> серверів;",
     "example_upstream_tcp": "звичайний DNS (через TCP);",
+    "example_upstream_tcp_hostname": "звичайний DNS (поверх TCP, з назвою вузла);",
     "all_lists_up_to_date_toast": "Всі списки вже оновлені",
     "updated_upstream_dns_toast": "DNS-сервери оновлено",
     "dns_test_ok_toast": "Вказані DNS сервери працюють правильно",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "Використовуйте подвійні лапки для точного пошуку",
     "query_log_retention_confirm": "Ви дійсно хочете змінити час зберігання журналу? Якщо ви зменшите значення, деякі дані будуть втрачені",
     "anonymize_client_ip": "Анонімізація IP-адреси клієнта",
-    "anonymize_client_ip_desc": "Не зберігати повну IP-адресу клієнта в журналах і статистиці.",
+    "anonymize_client_ip_desc": "Не зберігати повну IP-адресу клієнта в журналах і статистиці",
     "dns_config": "Конфігурація DNS-сервера",
     "dns_cache_config": "Конфігурація кешу DNS",
-    "dns_cache_config_desc": "Тут ви можете налаштувати кеш DNS.",
+    "dns_cache_config_desc": "Тут ви можете налаштувати DNS-кеш",
     "blocking_mode": "Режим блокування",
     "default": "Типовий",
     "nxdomain": "NXDOMAIN",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "Уведіть обмеження швидкості",
     "rate_limit": "Обмеження швидкості",
     "edns_enable": "Увімкнути відправку EDNS Client Subnet",
-    "edns_cs_desc": "Надсилати підмережі клієнтів на DNS-сервери.",
+    "edns_cs_desc": "Додавати параметр EDNS Client Subnet (ECS) до запитів до upstream-серверів, а також записувати в журнал значення, що надсилаються клієнтами.",
     "rate_limit_desc": "Кількість запитів в секунду, які може робити один клієнт. Встановлене значення «0» означатиме необмежену кількість.",
     "blocking_ipv4_desc": "IP-адреса, яку потрібно видати для заблокованого A запиту",
     "blocking_ipv6_desc": "IP-адреса, яку потрібно видати для заблокованого АААА запиту",
@@ -309,7 +311,7 @@
     "install_settings_listen": "Мережевий інтерфейс",
     "install_settings_port": "Порт",
     "install_settings_interface_link": "Веб-інтерфейс адміністратора AdGuard Home буде доступний за такими адресами:",
-    "form_error_port": "Уведіть правильне значення порту.",
+    "form_error_port": "Уведіть правильне значення порту",
     "install_settings_dns": "DNS-сервер",
     "install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:",
     "install_settings_all_interfaces": "Усі інтерфейси",
@@ -356,7 +358,7 @@
     "open_dashboard": "Відкрити інформаційну панель",
     "install_saved": "Збережено успішно",
     "encryption_title": "Шифрування",
-    "encryption_desc": "Підтримка шифрування (HTTPS/TLS) як для DNS, так і для вебінтерфейсу адміністратора.",
+    "encryption_desc": "Підтримка шифрування (HTTPS/TLS) як для DNS, так і для вебінтерфейсу адміністратора",
     "encryption_config_saved": "Конфігурацію шифрування збережено",
     "encryption_server": "Назва сервера",
     "encryption_server_enter": "Введіть ваше доменне ім'я",
@@ -378,33 +380,34 @@
     "encryption_key_input": "Скопіюйте/вставте сюди свій приватний ключ кодований PEM для вашого сертифіката.",
     "encryption_enable": "Увімкнути шифрування (HTTPS, DNS-over-HTTPS і DNS-over-TLS)",
     "encryption_enable_desc": "Якщо ввімкнено шифрування, інтерфейс адміністратора AdGuard Home буде працювати через HTTPS, а DNS-сервер буде прослуховувати запити через DNS-over-HTTPS і DNS-over-TLS.",
-    "encryption_chain_valid": "Ланцюжок довіри сертифікатів дійсний.",
-    "encryption_chain_invalid": "Ланцюжок довіри сертифікатів не дійсний.",
-    "encryption_key_valid": "Дійсний {{type}} приватний ключ.",
-    "encryption_key_invalid": "Недійсний {{type}} приватний ключ.",
+    "encryption_chain_valid": "Ланцюжок довіри сертифікатів дійсний",
+    "encryption_chain_invalid": "Ланцюжок довіри сертифікатів не дійсний",
+    "encryption_key_valid": "Дійсний {{type}} приватний ключ",
+    "encryption_key_invalid": "Недійсний {{type}} приватний ключ",
     "encryption_subject": "Обє'кт",
     "encryption_issuer": "Видавець",
     "encryption_hostnames": "Назви вузлів",
     "encryption_reset": "Ви впевнені, що хочете скинути налаштування шифрування?",
     "topline_expiring_certificate": "Ваш сертифікат SSL скоро закінчиться. Оновіть <0>Налаштування шифрування</0>.",
     "topline_expired_certificate": "Термін дії вашого сертифіката SSL закінчився. Оновіть <0>Налаштування шифрування</0>.",
-    "form_error_port_range": "Введіть значення порту в діапазоні 80−65535.",
-    "form_error_port_unsafe": "Це небезпечний порт.",
-    "form_error_equal": "Мають бути різні значення.",
-    "form_error_password": "Паролі не збігаються.",
+    "form_error_port_range": "Введіть значення порту в діапазоні 80−65535",
+    "form_error_port_unsafe": "Небезпечний порт",
+    "form_error_equal": "Мають бути різні значення",
+    "form_error_password": "Паролі не збігаються",
     "reset_settings": "Скинути налаштування",
     "update_announcement": "AdGuard Home {{version}} тепер доступний! <0>Докладніше</0>.",
     "setup_guide": "Посібник з налаштування",
     "dns_addresses": "DNS-адреси",
     "dns_start": "DNS-сервер запускається",
-    "dns_status_error": "Помилка перевірки стану сервера DNS",
+    "dns_status_error": "Помилка перевірки стану DNS-сервера",
     "down": "Недоступний",
     "fix": "Виправити",
     "dns_providers": "<0>Список відомих DNS-провайдерів</0> на вибір.",
     "update_now": "Оновити зараз",
     "update_failed": "Помилка автоматичного оновлення. Будь ласка, <a>виконайте ці кроки</a> аби оновити вручну.",
+    "manual_update": "Щоб оновити самостійно, <a>виконайте ці кроки</a>.",
     "processing_update": "Зачекайте будь ласка, AdGuard Home оновлюється",
-    "clients_title": "Клієнти",
+    "clients_title": "Постійні клієнти",
     "clients_desc": "Налаштуйте пристрої, під'єднані до AdGuard Home",
     "settings_global": "Загальні",
     "settings_custom": "Власні",
@@ -416,7 +419,7 @@
     "client_edit": "Редагувати Клієнта",
     "client_identifier": "Ідентифікатор",
     "ip_address": "IP-адреса",
-    "client_identifier_desc": "Клієнтів можна ідентифікувати за IP-, CIDR-, MAC-адресами або ж за спеціальним клієнтським ідентифікатором (можливий для DoT, DoH та DoQ). <0>Докладніше про ідентифікацію клієнтів</0>.",
+    "client_identifier_desc": "Клієнтів можна ідентифікувати за IP- чи MAC-адресами, CIDR або ж за спеціальним клієнтським ідентифікатором ClientID (можливий для DoT, DoH та DoQ). <0>Докладніше про ідентифікацію клієнтів</0>.",
     "form_enter_ip": "Введіть IP",
     "form_enter_subnet_ip": "Введіть IP-адресу в підмережі «{{cidr}}»",
     "form_enter_mac": "Введіть MAC",
@@ -432,13 +435,13 @@
     "client_confirm_delete": "Ви впевнені, що хочете видалити клієнта «{{key}}»?",
     "list_confirm_delete": "Ви впевнені, що хочете видалити цей список?",
     "auto_clients_title": "Runtime-клієнти",
-    "auto_clients_desc": "Дані про клієнтів, які використовують AdGuard Home, але не зберігаються в конфігурації",
+    "auto_clients_desc": "Клієнти, які використовують AdGuard Home, незалежно від того, чи збережені вони в списку постійних",
     "access_title": "Налаштування доступу",
-    "access_desc": "Тут ви можете налаштувати правила доступу для DNS-сервера AdGuard Home.",
+    "access_desc": "Тут ви можете налаштувати правила доступу для DNS-сервера AdGuard Home",
     "access_allowed_title": "Дозволені клієнти",
-    "access_allowed_desc": "Перелік CIDR-, IP-адрес та клієнтських ідентифікаторів. Якщо налаштовано, AdGuard Home прийматиме запити лише від цих клієнтів.",
+    "access_allowed_desc": "Перелік CIDR, IP-адрес та <a>ClientIDs</a>. Якщо налаштовано, AdGuard Home прийматиме запити лише від цих клієнтів.",
     "access_disallowed_title": "Заборонені клієнти",
-    "access_disallowed_desc": "Перелік CIDR-, IP-адрес та клієнтських ідентифікаторів. Якщо налаштовано, AdGuard Home буде скасовувати запити від цих клієнтів. Проте якщо налаштовано список дозволених клієнтів, то це поле проігнорується.",
+    "access_disallowed_desc": "Перелік CIDR, IP-адрес та <a>ClientIDs</a>. Якщо налаштовано, AdGuard Home буде скасовувати запити від цих клієнтів. Проте якщо налаштовано список Дозволених клієнтів, то це поле проігнорується.",
     "access_blocked_title": "Заборонені домени",
     "access_blocked_desc": "Не плутайте з фільтрами. AdGuard Home буде ігнорувати DNS-запити з цими доменами, такі запити навіть не будуть записані до журналу. Ви можете вказати точні доменні імена, замінні знаки та правила фільтрування URL-адрес, наприклад, 'example.org', '*.example.org' або '||example.org^' відповідно.",
     "access_settings_saved": "Налаштування доступу успішно збережено",
@@ -475,7 +478,7 @@
     "form_domain": "Введіть доменне ім’я або підстановний знак",
     "form_answer": "Введіть IP-адресу або доменне ім'я",
     "form_error_domain_format": "Неправильний формат домену",
-    "form_error_answer_format": "Неправильний формат відопвіді",
+    "form_error_answer_format": "Неправильний формат відповіді",
     "configure": "Налаштувати",
     "main_settings": "Головні налаштування",
     "block_services": "Блокувати конкретні сервіси",
@@ -499,6 +502,7 @@
     "interval_days": "{{count}} день",
     "interval_days_plural": "{{count}} дні(в)",
     "domain": "Домен",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Відповідь",
     "filter_added_successfully": "Фільтр успішно додано",
@@ -598,14 +602,14 @@
     "milliseconds_abbreviation": "мс",
     "cache_size": "Розмір кешу",
     "cache_size_desc": "Розмір кешу DNS (у байтах).",
-    "cache_ttl_min_override": "Замінити мінімальний TTL",
-    "cache_ttl_max_override": "Замінити максимальний TTL",
+    "cache_ttl_min_override": "Змінити мінімальний TTL",
+    "cache_ttl_max_override": "Змінити максимальний TTL",
     "enter_cache_size": "Введіть розмір кешу (байт)",
-    "enter_cache_ttl_min_override": "Введіть мінімальний TTL (секунди)",
-    "enter_cache_ttl_max_override": "Введіть максимальний TTL (секунди)",
-    "cache_ttl_min_override_desc": "Розширити короткі значення time-to-live (секунди) отримані від основного сервера під час кешування відповідей DNS",
-    "cache_ttl_max_override_desc": "Встановіть максимальне значення time-to-live (секунди) для записів у кеші DNS",
-    "ttl_cache_validation": "Мінімальне значення TTL кеш-пам'яті має бути меншим або рівним максимальному значенню",
+    "enter_cache_ttl_min_override": "Введіть мінімальний TTL (в секундах)",
+    "enter_cache_ttl_max_override": "Введіть максимальний TTL (в секундах)",
+    "cache_ttl_min_override_desc": "Збільшити малі TTL-значення (в секундах), отримані від основного сервера під час кешування DNS-відповідей.",
+    "cache_ttl_max_override_desc": "Встановіть максимальне TTL-значення (в секундах) для записів у DNS-кеші.",
+    "ttl_cache_validation": "Мінімальне TTL-значення має бути меншим або рівним максимальному значенню",
     "cache_optimistic": "Оптимістичне кешування",
     "cache_optimistic_desc": "AdGuard Home буде відповідати з кешу, навіть якщо відповіді в ньому застарілі, а також спробує оновити їх.",
     "filter_category_general": "Загальні",
@@ -627,5 +631,5 @@
     "parental_control": "Батьківський контроль",
     "safe_browsing": "Безпечний перегляд",
     "served_from_cache": "{{value}} <i>(отримано з кешу)</i>",
-    "form_error_password_length": "Пароль мусить мати принаймні {{value}} символів."
+    "form_error_password_length": "Пароль мусить мати принаймні {{value}} символів"
 }
diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json
index 28ec3b1b..7c9c2fd4 100644
--- a/client/src/__locales/vi.json
+++ b/client/src/__locales/vi.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Cài đặt máy khách",
-    "example_upstream_reserved": "bạn có thể chỉ định DNS ngược tuyến <0>cho một tên miền cụ thể(hoặc nhiều)</0>",
-    "example_upstream_comment": "Bạn có thể thêm chú thích cụ thể",
+    "example_upstream_reserved": "ngược dòng <0>cho các miền cụ thể</0>;",
+    "example_upstream_comment": "một lời bình luận.",
     "upstream_parallel": "Sử dụng truy vấn song song để tăng tốc độ giải quyết bằng cách truy vấn đồng thời tất cả các máy chủ ngược tuyến",
     "parallel_requests": "Yêu cầu song song",
     "load_balancing": "Cân bằng tải",
@@ -36,14 +36,23 @@
     "dhcp_ipv4_settings": "Cài đặt DHCP IPv4",
     "dhcp_ipv6_settings": "Cài đặt DHCP IPv6",
     "form_error_required": "Trường bắt buộc",
-    "form_error_ip4_format": "Định dạng IPv4 không hợp lệ",
-    "form_error_ip6_format": "Định dạng IPv6 không hợp lệ",
+    "form_error_ip4_format": "Địa chỉ IPv4 không hợp lệ",
+    "form_error_ip4_range_start_format": "Địa chỉ IPv4 không hợp lệ của phạm vi bắt đầu",
+    "form_error_ip4_range_end_format": "Địa chỉ IPv4 không hợp lệ của cuối phạm vi",
+    "form_error_ip4_gateway_format": "Địa chỉ IPv4 không hợp lệ của cổng kết nối",
+    "form_error_ip6_format": "Địa chỉ IPv6 không hợp lệ",
     "form_error_ip_format": "Địa chỉ IP không hợp lệ",
-    "form_error_mac_format": "Định dạng MAC không hợp lệ",
-    "form_error_client_id_format": "Định dạng client ID không hợp lệ",
+    "form_error_mac_format": "Địa chỉ MAC không hợp lệ",
+    "form_error_client_id_format": "ClientID chỉ được chứa số, chữ thường và dấu gạch nối",
     "form_error_server_name": "Tên máy chủ không hợp lệ",
     "form_error_subnet": "Mạng con \"{{cidr}}\" không chứa địa chỉ IP \"{{ip}}\"",
     "form_error_positive": "Phải lớn hơn 0",
+    "out_of_range_error": "Phải nằm ngoài phạm vi \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "Phải thấp hơn khởi động phạm vi",
+    "greater_range_start_error": "Phải lớn hơn khoảng bắt đầu",
+    "greater_range_end_error": "Phải lớn hơn phạm vi kết thúc",
+    "subnet_error": "Địa chỉ phải nằm trong một mạng con",
+    "gateway_or_subnet_invalid": "Mặt nạ mạng con không hợp lệ",
     "dhcp_form_gateway_input": "Cổng IP",
     "dhcp_form_subnet_input": "Mặt nạ mạng con",
     "dhcp_form_range_title": "Phạm vi của địa chỉ IP",
@@ -58,7 +67,7 @@
     "dhcp_table_hostname": "Tên máy chủ",
     "dhcp_table_expires": "Hết hạn",
     "dhcp_warning": "Nếu bạn vẫn muốn bật máy chủ DHCP, hãy đảm bảo rằng không có máy chủ DHCP hoạt động nào khác trong mạng của bạn. Nếu không, nó có thể phá vỡ Internet cho các thiết bị được kết nối!",
-    "dhcp_error": "Chúng tôi không thể xác định liệu có một máy chủ DHCP khác trong mạng hay không.",
+    "dhcp_error": "Chúng tôi không thể xác định liệu có một máy chủ DHCP khác trong mạng hay không",
     "dhcp_static_ip_error": "Để sử dụng máy chủ DHCP, phải đặt địa chỉ IP tĩnh. Chúng tôi không thể xác định xem giao diện mạng này có được cấu hình bằng địa chỉ IP tĩnh hay không. Vui lòng đặt địa chỉ IP tĩnh theo cách thủ công.",
     "dhcp_dynamic_ip_found": "Hệ thống của bạn sử dụng cấu hình địa chỉ IP động cho giao diện <0>{{interfaceName}}</0>. Để sử dụng máy chủ DHCP, phải đặt địa chỉ IP tĩnh. Địa chỉ IP hiện tại của bạn là <0>{{ipAddress}}</0>. Chúng tôi sẽ tự động đặt địa chỉ IP này thành tĩnh nếu bạn nhấn nút Bật DHCP.",
     "dhcp_lease_added": "Cho thuê tĩnh \"{{key}}\" đã được thêm thành công",
@@ -191,20 +200,23 @@
     "form_error_url_or_path_format": "Định dạng URL hoặc đường dẫn tới danh sách không hợp lệ",
     "custom_filter_rules": "Quy tắc lọc tuỳ chỉnh",
     "custom_filter_rules_hint": "Nhập mỗi quy tắc 1 dòng. Có thể sử dụng quy tắc chặn quảng cáo hoặc cú pháp file host",
+    "system_host_files": "Hệ thống lưu trữ tệp",
     "examples_title": "Ví dụ",
-    "example_meaning_filter_block": "Chặn truy cập tới tên miền example.org và tất cả tên miền con",
-    "example_meaning_filter_whitelist": "Không chặn truy cập tới tên miền example.org và tất cả tên miền con",
-    "example_meaning_host_block": "AdGuard Home sẽ phản hồi địa chỉ IP 127.0.0.1 cho tên miền example.org (không áp dụng tên miền con)",
-    "example_comment": "! Đây là một chú thích",
-    "example_comment_meaning": "Chỉ là một chú thích",
-    "example_comment_hash": "# Cũng là một chú thích",
-    "example_regex_meaning": "chặn quyền truy cập vào các miền khớp với <0>biểu thức chính được quy định</0>",
-    "example_upstream_regular": "DNS thông thường (dùng UDP)",
-    "example_upstream_dot": "được mã hoá <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "được mã hoá <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "được mã hoá <0>DNS-over-QUIC</0>",
+    "example_meaning_filter_block": "chặn truy cập tới tên miền example.org và tất cả tên miền con;",
+    "example_meaning_filter_whitelist": "không chặn truy cập tới tên miền example.org và tất cả tên miền con;",
+    "example_meaning_host_block": "hồi địa chỉ IP 127.0.0.1 cho tên miền example.org (không áp dụng tên miền con);",
+    "example_comment": "! Đây là một chú thích.",
+    "example_comment_meaning": "chỉ là một chú thích;",
+    "example_comment_hash": "# Cũng là một chú thích.",
+    "example_regex_meaning": "chặn quyền truy cập vào các miền khớp với biểu thức chính được quy định.",
+    "example_upstream_regular": "DNS thông thường (dùng UDP);",
+    "example_upstream_udp": "DNS thông thường (qua UDP, tên máy chủ);",
+    "example_upstream_dot": "được mã hoá <0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "được mã hoá <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "được mã hoá <0>DNS-over-QUIC</0> (thử nghiệm);",
     "example_upstream_sdns": "bạn có thể sử dụng <0>DNS Stamps</0> for <1>DNSCrypt</1> hoặc <2>DNS-over-HTTPS</2> ",
-    "example_upstream_tcp": "DNS thông thường(dùng TCP)",
+    "example_upstream_tcp": "DNS thông thường(dùng TCP);",
+    "example_upstream_tcp_hostname": "DNS thông thường (qua TCP, tên máy chủ);",
     "all_lists_up_to_date_toast": "Tất cả danh sách đã ở phiên bản mới nhất",
     "updated_upstream_dns_toast": "Các máy chủ thượng nguồn đã được lưu thành công",
     "dns_test_ok_toast": "Máy chủ DNS có thể sử dụng",
@@ -265,9 +277,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "ID khách hàng",
-    "client_id_placeholder": "Nhập ID khách hàng",
-    "client_id_desc": "Các khách hàng khác nhau có thể được xác định bằng một ID khách hàng đặc biệt. <a>Tại đây</a> bạn có thể tìm hiểu thêm về cách xác định khách hàng.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Nhập một ClientID",
+    "client_id_desc": "Khách hàng có thể được xác định bằng ClientID. Tìm hiểu thêm về cách xác định khách hàng <a> tại đây </a>.",
     "download_mobileconfig_doh": "Tải xuống .mobileconfig cho DNS-over-HTTPS",
     "download_mobileconfig_dot": "Tải xuống .mobileconfig cho DNS-over-TLS",
     "download_mobileconfig": "Tải xuống tệp cấu hình",
@@ -275,7 +287,7 @@
     "form_enter_rate_limit": "Nhập giới hạn yêu cầu",
     "rate_limit": "Giới hạn yêu cầu",
     "edns_enable": "Bật mạng con EDNS Client",
-    "edns_cs_desc": "Nếu được bật, AdGuard Home sẽ gửi các mạng con của khách hàng đến các máy chủ DNS.",
+    "edns_cs_desc": "Thêm tùy chọn EDNS Client Subnet (ECS) vào các yêu cầu ngược dòng và ghi lại các giá trị được gửi bởi các máy khách trong nhật ký truy vấn.",
     "rate_limit_desc": "Số lượng yêu cầu mỗi giây mà một khách hàng được phép thực hiện (0: không giới hạn)",
     "blocking_ipv4_desc": "Địa chỉ IP được trả lại cho một yêu cầu A bị chặn",
     "blocking_ipv6_desc": "Địa chỉ IP được trả lại cho một yêu cầu AAA bị chặn",
@@ -299,12 +311,12 @@
     "install_settings_listen": "Giao diện nghe",
     "install_settings_port": "Cổng",
     "install_settings_interface_link": "Giao diện web quản trị viên AdGuard Home của bạn sẽ có sẵn trên các địa chỉ sau:",
-    "form_error_port": "Nhập giá trị cổng hợp lệ",
+    "form_error_port": "Nhập số cổng hợp lệ",
     "install_settings_dns": "Máy chủ DNS",
     "install_settings_dns_desc": "Bạn sẽ cần định cấu hình thiết bị hoặc bộ định tuyến của mình để sử dụng máy chủ DNS trên các địa chỉ sau:",
     "install_settings_all_interfaces": "Tất cả các giao diện",
     "install_auth_title": "Xác thực",
-    "install_auth_desc": "Rất khuyến khích cấu hình xác thực mật khẩu cho giao diện web quản trị viên AdGuard Home của bạn. Ngay cả khi nó chỉ có thể truy cập được trong mạng cục bộ của bạn, điều quan trọng là phải bảo vệ nó khỏi sự truy cập không hạn chế.",
+    "install_auth_desc": "Xác thực mật khẩu cho giao diện web quản trị AdGuard Home của bạn phải được định cấu hình. Ngay cả khi AdGuard Home chỉ có thể truy cập được trong mạng cục bộ của bạn, điều quan trọng vẫn là bảo vệ nó khỏi quyền truy cập không hạn chế.",
     "install_auth_username": "Tên đăng nhập",
     "install_auth_password": "Mật khẩu",
     "install_auth_confirm": "Xác nhận mật khẩu",
@@ -346,7 +358,7 @@
     "open_dashboard": "Mở bảng điều khiển",
     "install_saved": "Lưu thành công",
     "encryption_title": "Mã hóa",
-    "encryption_desc": "Hỗ trợ mã hóa (HTTPS/TLS) cho cả giao diện web quản trị viên và DNS",
+    "encryption_desc": "Hỗ trợ mã hóa (HTTPS/QUIC/TLS) cho cả giao diện web quản trị viên và DNS",
     "encryption_config_saved": "Đã lưu cấu hình mã hóa",
     "encryption_server": "Tên máy chủ",
     "encryption_server_enter": "Nhập tên miền của bạn",
@@ -357,7 +369,7 @@
     "encryption_https_desc": "Nếu cổng HTTPS được định cấu hình, giao diện quản trị viên AdGuard Home sẽ có thể truy cập thông qua HTTPS và nó cũng sẽ cung cấp DNS-over-HTTPS trên vị trí '/dns-query'.",
     "encryption_dot": "Cổng DNS-over-TLS",
     "encryption_dot_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS-over-TLS trên cổng này.",
-    "encryption_doq": "Cổng DNS-over-QUIC",
+    "encryption_doq": "Cổng DNS-over-QUIC (thử nghiệm)",
     "encryption_doq_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS qua QUIC trên cổng này. Đó là thử nghiệm và có thể không đáng tin cậy. Ngoài ra, không có quá nhiều khách hàng hỗ trợ nó vào lúc này.",
     "encryption_certificates": "Chứng chỉ",
     "encryption_certificates_desc": "Để sử dụng mã hóa, bạn cần cung cấp chuỗi chứng chỉ SSL hợp lệ cho miền của mình. Bạn có thể nhận chứng chỉ miễn phí trên <0>{{link}}</0> hoặc bạn có thể mua chứng chỉ từ một trong các Cơ Quan Chứng Nhận tin cậy.",
@@ -370,8 +382,8 @@
     "encryption_enable_desc": "Nếu mã hóa được bật, giao diện quản trị viên AdGuard Home sẽ hoạt động trên HTTPS và máy chủ DNS sẽ lắng nghe các yêu cầu qua DNS-over-HTTPS và DNS-over-TLS.",
     "encryption_chain_valid": "Chứng chỉ hợp lệ",
     "encryption_chain_invalid": "Chứng chỉ không hợp lệ",
-    "encryption_key_valid": "Đây là khóa riêng {{type}} hợp lệ",
-    "encryption_key_invalid": "Đây là khóa riêng {{type}} không hợp lệ",
+    "encryption_key_valid": "Khóa riêng {{type}} hợp lệ",
+    "encryption_key_invalid": "Khóa riêng {{type}} không hợp lệ",
     "encryption_subject": "Chủ đề",
     "encryption_issuer": "Phát hành",
     "encryption_hostnames": "Tên máy chủ",
@@ -393,9 +405,10 @@
     "dns_providers": "Dưới đây là một <0>danh sách của các nhà cung cấp DNS đã biết</0> để lựa chọn.",
     "update_now": "Cập nhật ngay",
     "update_failed": "Tự động cập nhật thất bại. Vui lòng <a>làm theo các bước</a> để cập nhật thủ công.",
+    "manual_update": "Vui lòng <a>làm theo các bước này</a> để cập nhật thủ công.",
     "processing_update": "Xin vui lòng chờ, AdGuard Home đang được cập nhật",
-    "clients_title": "Máy khách",
-    "clients_desc": "Định cấu hình thiết bị được kết nối với AdGuard Home",
+    "clients_title": "Khách hàng lâu dài",
+    "clients_desc": "Định cấu hình hồ sơ khách hàng liên tục cho các thiết bị được kết nối với AdGuard Home",
     "settings_global": "Toàn cầu",
     "settings_custom": "Tùy chỉnh",
     "table_client": "Máy khách",
@@ -406,7 +419,7 @@
     "client_edit": "Chỉnh Sửa Máy Khách",
     "client_identifier": "Định danh",
     "ip_address": "Địa chỉ IP",
-    "client_identifier_desc": "Các máy khách có thể được xác định bằng địa chỉ IP, CIDR, địa chỉ MAC hoặc một ID khách đặc biệt(có thể được dùng cho DoT/DoH/DoQ). Xem thêm cách xác định máy khách tại <0>đây</0>.",
+    "client_identifier_desc": "Khách hàng có thể được xác định bằng địa chỉ IP, CIDR, địa chỉ MAC hoặc ClientID (có thể được sử dụng cho DoT / DoH / DoQ). Tìm hiểu thêm về cách xác định khách hàng <0>tại đây</0>.",
     "form_enter_ip": "Nhập IP",
     "form_enter_subnet_ip": "Nhập địa chỉ IP vào mạng con \"{{cidr}}\"",
     "form_enter_mac": "Nhập MAC",
@@ -422,13 +435,13 @@
     "client_confirm_delete": "Bạn có chắc chắn muốn xóa máy khách \"{{key}}\" không?",
     "list_confirm_delete": "Bạn có muốn xóa bộ lọc này?",
     "auto_clients_title": "Máy khách (thời gian chạy)",
-    "auto_clients_desc": "Dữ liệu trên các máy khách sử dụng AdGuard Home, nhưng không được lưu trong cấu hình",
+    "auto_clients_desc": "Các thiết bị không có trong danh sách khách hàng ổn định vẫn có thể sử dụng AdGuard Home",
     "access_title": "Cài đặt truy cập",
-    "access_desc": "Tại đây bạn có thể định cấu hình quy tắc truy cập cho máy chủ AdGuard Home DNS.",
+    "access_desc": "Tại đây bạn có thể định cấu hình quy tắc truy cập cho máy chủ AdGuard Home DNS",
     "access_allowed_title": "Máy chủ được phép",
-    "access_allowed_desc": "Một danh sách các địa chỉ CIDR hoặc IP. Nếu được định cấu hình, AdGuard Home sẽ chỉ chấp nhận các yêu cầu từ các địa chỉ IP này.",
+    "access_allowed_desc": "Danh sách CIDR, địa chỉ IP hoặc <a>ClientID</a>. Nếu danh sách này có các mục nhập, AdGuard Home sẽ chỉ chấp nhận yêu cầu từ những khách hàng này.",
     "access_disallowed_title": "Máy chủ không được phép",
-    "access_disallowed_desc": "Một danh sách các địa chỉ CIDR hoặc IP. Nếu được định cấu hình, AdGuard Home sẽ bỏ yêu cầu từ các địa chỉ IP này.",
+    "access_disallowed_desc": "Danh sách CIDR, địa chỉ IP hoặc <a>ClientID</a>. Nếu danh sách này có các mục nhập, AdGuard Home sẽ loại bỏ các yêu cầu từ những khách hàng này. Trường này bị bỏ qua nếu có các mục nhập trong máy khách Được phép.",
     "access_blocked_title": "Tên miền bị chặn",
     "access_blocked_desc": "Đừng nhầm lẫn điều này với các bộ lọc. AdGuard Home sẽ bỏ các truy vấn DNS với các tên miền này trong câu hỏi của truy vấn.",
     "access_settings_saved": "Cài đặt truy cập đã lưu thành công",
@@ -489,6 +502,7 @@
     "interval_days": "{{count}} ngày",
     "interval_days_plural": "{{count}} ngày",
     "domain": "Tên miền",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "Trả lời",
     "filter_added_successfully": "Thêm bộ lọc thành công",
@@ -521,7 +535,7 @@
     "netname": "Tên mạng",
     "network": "Mạng",
     "descr": "Mô tả",
-    "whois": "Whois",
+    "whois": "WHOIS",
     "filtering_rules_learn_more": "<0>Tìm hiểu thêm</0> về việc tạo danh sách chặn máy chủ của riêng bạn.",
     "blocked_by_response": "Chặn bởi CNAME hoặc địa IP ở phản hồi",
     "blocked_by_cname_or_ip": "Đã bị chặn bởi CNAME hoặc IP",
@@ -541,10 +555,10 @@
     "autofix_warning_list": "Nó sẽ thực hiện các tác vụ sau: <0> Hủy kích hoạt hệ thống DNSStubListener </0> <0> Đặt địa chỉ máy chủ DNS thành 127.0.0.1 </0> <0> Thay thế mục tiêu liên kết tượng trưng của /etc/resolv.conf bằng / run / systemd /resolve/resolv.conf </0> <0> Dừng DNSStubListener (tải lại dịch vụ do hệ thống phân giải) </0>",
     "autofix_warning_result": "Do đó, tất cả các yêu cầu DNS từ hệ thống của bạn sẽ được AdGuard Home xử lý theo mặc định.",
     "tags_title": "Thẻ",
-    "tags_desc": "Bạn có thể chọn các thẻ tương ứng với khách hàng. Thẻ có thể được bao gồm trong các quy tắc lọc và cho phép bạn áp dụng chúng chính xác hơn. <0>Tìm hiểu thêm</0>",
+    "tags_desc": "Bạn có thể chọn các thẻ tương ứng với máy khách. Bao gồm các thẻ trong các quy tắc lọc để áp dụng chúng chính xác hơn. <0>Tìm hiểu thêm</0>.",
     "form_select_tags": "Chọn thẻ khách hàng",
     "check_title": "Kiểm tra bộ lọc",
-    "check_desc": "Kiểm tra xem tên miền có tồn tại trong các bộ lọc không",
+    "check_desc": "Kiểm tra xem tên miền có tồn tại trong các bộ lọc không.",
     "check": "Kiểm tra",
     "form_enter_host": "Nhập tên máy chủ",
     "filtered_custom_rules": "Được lọc bởi các quy tắc lọc tùy chỉnh",
@@ -583,19 +597,21 @@
     "allowed": "Được phép",
     "filtered": "Đã lọc",
     "rewritten": "Đã viết lại",
-    "safe_search": "Kích hoạt Tìm kiếm An toàn",
+    "safe_search": "Tìm kiếm an toàn",
     "blocklist": "Danh sách chặn",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Kích thước cache",
-    "cache_size_desc": "Kích thước cache DNS (bytes)",
+    "cache_size_desc": "Kích thước cache DNS (bytes).",
     "cache_ttl_min_override": "Ghi đè TTL tối thiểu",
     "cache_ttl_max_override": "Ghi đè TTL tối đa",
     "enter_cache_size": "Nhập kích thước bộ nhớ cache (byte)",
     "enter_cache_ttl_min_override": "Nhập TTL tối thiểu (giây)",
     "enter_cache_ttl_max_override": "Nhập TTL tối đa (giây)",
-    "cache_ttl_min_override_desc": "Mở rộng giá trị thời gian tồn tại ngắn (giây) nhận được từ máy chủ ngược dòng khi phản hồi DNS vào bộ nhớ đệm",
-    "cache_ttl_max_override_desc": "Đặt giá trị thời gian tồn tại tối đa (giây) cho các mục nhập trong bộ nhớ cache DNS",
+    "cache_ttl_min_override_desc": "Mở rộng giá trị thời gian tồn tại ngắn (giây) nhận được từ máy chủ ngược dòng khi phản hồi DNS vào bộ nhớ đệm.",
+    "cache_ttl_max_override_desc": "Đặt giá trị thời gian tồn tại tối đa (giây) cho các mục nhập trong bộ nhớ cache DNS.",
     "ttl_cache_validation": "Giá trị TTL trong bộ nhớ cache tối thiểu phải nhỏ hơn hoặc bằng giá trị lớn nhất",
+    "cache_optimistic": "Bộ nhớ đệm lạc quan",
+    "cache_optimistic_desc": "Làm cho AdGuard Home phản hồi từ bộ nhớ cache ngay cả khi các mục nhập đã hết hạn và cố gắng làm mới chúng.",
     "filter_category_general": "Chung",
     "filter_category_security": "Bảo mật",
     "filter_category_regional": "Khu vực",
@@ -611,5 +627,9 @@
     "adg_will_drop_dns_queries": "AdGuard Home sẽ loại bỏ tất cả các truy vấn DNS từ ứng dụng khách này.",
     "filter_allowlist": "CẢNH BÁO: Hành động này cũng sẽ loại trừ quy tắc \"{{disallowed_rule}}\" khỏi danh sách các ứng dụng khách được phép.",
     "last_rule_in_allowlist": "Không thể không cho phép ứng dụng khách này vì việc loại trừ quy tắc \"{{disallowed_rule}}\" sẽ TẮT danh sách \"Ứng dụng khách được phép\".",
-    "parental_control": "Quản lý của phụ huynh"
+    "use_saved_key": "Sử dụng khóa đã lưu trước đó",
+    "parental_control": "Quản lý của phụ huynh",
+    "safe_browsing": "Duyệt web an toàn",
+    "served_from_cache": "{{value}} <i>(được phục vụ từ bộ nhớ cache)</i>",
+    "form_error_password_length": "Mật khẩu phải có ít nhất {{value}} ký tự"
 }
diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json
index dce4be31..69d5b8b0 100644
--- a/client/src/__locales/zh-cn.json
+++ b/client/src/__locales/zh-cn.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "已成功保存 DHCP 服务器配置",
     "dhcp_ipv4_settings": "DHCP IPv4设置",
     "dhcp_ipv6_settings": "DHCP IPv6设置",
-    "form_error_required": "必填字段。",
-    "form_error_ip4_format": "无效的 IPv4 地址。",
-    "form_error_ip4_range_start_format": "范围起始值的 IPv4 地址无效。",
-    "form_error_ip4_range_end_format": "范围终值的 IPv4 地址无效。",
-    "form_error_ip4_gateway_format": "网关 IPv4 格式无效。",
-    "form_error_ip6_format": "无效的 IPv6 地址。",
-    "form_error_ip_format": "无效的 IP 地址。",
-    "form_error_mac_format": "无效的 MAC 地址。",
-    "form_error_client_id_format": "客户端 ID 必须只包含数字、小写字母和连字符。",
-    "form_error_server_name": "无效的服务器名。",
-    "form_error_subnet": "子网 \"{{cidr}}\" 不包含 IP 地址 \"{{ip}}\"。",
-    "form_error_positive": "必须大于 0。",
-    "out_of_range_error": "必定超出了范围 \"{{start}}\"-\"{{end}}\"。",
-    "lower_range_start_error": "必须小于范围起始值。",
-    "greater_range_start_error": "必须大于范围起始值。",
-    "greater_range_end_error": "必须大于范围终值。",
-    "subnet_error": "地址必须在一个子网内。",
-    "gateway_or_subnet_invalid": "子网掩码无效。",
+    "form_error_required": "必填字段",
+    "form_error_ip4_format": "无效的 IPv4 地址",
+    "form_error_ip4_range_start_format": "范围起始值的 IPv4 地址无效",
+    "form_error_ip4_range_end_format": "范围终值的 IPv4 地址无效",
+    "form_error_ip4_gateway_format": "网关 IPv4 地址无效",
+    "form_error_ip6_format": "无效的 IPv6 地址",
+    "form_error_ip_format": "无效的 IP 地址",
+    "form_error_mac_format": "无效的 MAC 地址",
+    "form_error_client_id_format": "客户端 ID 必须只包含数字、小写字母和连字符",
+    "form_error_server_name": "无效的服务器名",
+    "form_error_subnet": "子网 \"{{cidr}}\" 不包含 IP 地址 \"{{ip}}\"",
+    "form_error_positive": "必须大于 0",
+    "out_of_range_error": "必定超出了范围 \"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "必须小于范围起始值",
+    "greater_range_start_error": "必须大于范围起始值",
+    "greater_range_end_error": "必须大于范围终值",
+    "subnet_error": "地址必须在一个子网内",
+    "gateway_or_subnet_invalid": "子网掩码无效",
     "dhcp_form_gateway_input": "网关 IP",
     "dhcp_form_subnet_input": "子网掩码",
     "dhcp_form_range_title": "IP 地址范围",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "主机名",
     "dhcp_table_expires": "到期",
     "dhcp_warning": "如果你想要启用内置的 DHCP 服务器,请确保在当前网络中没有其它起作用的 DHCP 服务器。否则,此操作可能会破坏已连接设备的网络连接!",
-    "dhcp_error": "AdGuard Home 无法确定在当前网络中是否存在其它 DHCP 服务器。",
+    "dhcp_error": "AdGuard Home 无法确定在当前网络中是否存在其它 DHCP 服务器",
     "dhcp_static_ip_error": "要使用 DHCP 服务器,则必须设置静态 IP 地址。AdGuard Home 无法确定此网络接口是否已被配置为使用静态 IP 地址。请手动为此网络接口设置静态 IP 地址。",
     "dhcp_dynamic_ip_found": "您的系统对网络接口 <0>{{interfaceName}}</0> 使用了动态 IP 地址配置。要使用 DHCP 服务器,则必须对此网络接口使用静态 IP 地址配置。此网络接口当前的 IP 地址为 <0>{{ipAddress}}</0>。如您点击“启用 DHCP 服务器” 按钮,AdGuard Home 将自动修改此网络接口以使用静态 IP 地址。",
     "dhcp_lease_added": "静态租约 \"{{key}}\" 已成功添加",
@@ -196,8 +196,8 @@
     "choose_allowlist": "选择允许列表",
     "enter_valid_blocklist": "输入有效的阻止列表URL",
     "enter_valid_allowlist": "输入有效的允许列表URL",
-    "form_error_url_format": "无效的 URL 格式。",
-    "form_error_url_or_path_format": "无效的 URL 或列表的绝对路径。",
+    "form_error_url_format": "无效的 URL 格式",
+    "form_error_url_or_path_format": "无效的 URL 或列表的绝对路径",
     "custom_filter_rules": "自定义过滤器规则",
     "custom_filter_rules_hint": "请确保每行只输入一条规则。你可以输入符合 adblock 语法或 Hosts 语法的规则。",
     "system_host_files": "系统主机文件",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# 这也是一行注释。",
     "example_regex_meaning": "阻止访问与指定的正则表达式匹配的域名。",
     "example_upstream_regular": "常规 DNS(基于 UDP);",
+    "example_upstream_udp": "常规 DNS(通过 UDP、主机名);",
     "example_upstream_dot": "加密 <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "加密 <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "加密 <0>DNS-over-QUIC</0>(实验性的);",
     "example_upstream_sdns": "<1>DNSCrypt</1> 的 <0>DNS Stamps</0> 或者 <2>DNS-over-HTTPS</2> 解析器;",
     "example_upstream_tcp": "常规 DNS(基于 TCP );",
+    "example_upstream_tcp_hostname": "常规 DNS(通过 TCP、主机名);",
     "all_lists_up_to_date_toast": "所有列表都是最新的",
     "updated_upstream_dns_toast": "上游服务器保存成功",
     "dns_test_ok_toast": "指定的 DNS 服务器现已正常运行",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "使用双引号进行严谨搜索",
     "query_log_retention_confirm": "您确定要更改查询记录保留时间吗? 如果您减少间隔时间的值, 某些数据可能会丢失。",
     "anonymize_client_ip": "匿名化客户端IP",
-    "anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整 IP 地址。",
+    "anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整 IP 地址",
     "dns_config": "DNS 服务配置",
     "dns_cache_config": "DNS缓存配置",
-    "dns_cache_config_desc": "您可以在此处配置 DNS 缓存。",
+    "dns_cache_config_desc": "您可以在此处配置 DNS 缓存",
     "blocking_mode": "拦截模式",
     "default": "默认",
     "nxdomain": "无效域名",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "输入限制速率",
     "rate_limit": "速度限制",
     "edns_enable": "启用 EDNS 客户端子网",
-    "edns_cs_desc": "将客户端子网发送至 DNS 服务器。",
+    "edns_cs_desc": "在上游请求中加入 EDNS 客户端子网(“EDNS Client Subnet”,即 ECS)选项,并在查询日志中记录客户端发送的数值。",
     "rate_limit_desc": "每个客户端每秒钟查询次数的限制。设置为 0 意味着不限制。",
     "blocking_ipv4_desc": "拦截 A 记录请求返回的 IP 地址",
     "blocking_ipv6_desc": "拦截 AAAA 记录请求返回的 IP 地址",
@@ -309,7 +311,7 @@
     "install_settings_listen": "监听接口",
     "install_settings_port": "端口",
     "install_settings_interface_link": "您可以通过以下地址访问您的 AdGuard Home 网页管理界面:",
-    "form_error_port": "输入有效的端口值。",
+    "form_error_port": "输入有效的端口值",
     "install_settings_dns": "DNS 服务器",
     "install_settings_dns_desc": "您将需要使用以下地址来设置您的设备或路由器的 DNS 服务器:",
     "install_settings_all_interfaces": "所有接口",
@@ -356,7 +358,7 @@
     "open_dashboard": "打开仪表盘",
     "install_saved": "保存成功",
     "encryption_title": "加密",
-    "encryption_desc": "为 DNS 与网页管理界面启用加密(HTTPS/TLS)。",
+    "encryption_desc": "为 DNS 与网页管理界面启用加密(HTTPS/QUIC/TLS)",
     "encryption_config_saved": "加密配置已保存",
     "encryption_server": "服务器名称",
     "encryption_server_enter": "输入您的域名",
@@ -378,26 +380,26 @@
     "encryption_key_input": "将您以 PEM 格式编码的证书私钥复制粘贴到此处。",
     "encryption_enable": "启用加密(HTTPS、DNS-over-HTTPS、DNS-over-TLS)",
     "encryption_enable_desc": "如果启用加密选项,AdGuard Home 的网页管理界面将通过 HTTPS 连接访问,同时 DNS 服务器将监听通过 DNS-over-HTTPS 与 DNS-over-TLS 发送的请求。",
-    "encryption_chain_valid": "证书链有效。",
-    "encryption_chain_invalid": "证书链无效。",
-    "encryption_key_valid": "该 {{type}} 私钥有效。",
-    "encryption_key_invalid": "该 {{type}} 私钥无效。",
+    "encryption_chain_valid": "证书链有效",
+    "encryption_chain_invalid": "证书链无效",
+    "encryption_key_valid": "该 {{type}} 私钥有效",
+    "encryption_key_invalid": "该 {{type}} 私钥无效",
     "encryption_subject": "使用者",
     "encryption_issuer": "颁发者",
     "encryption_hostnames": "主机名",
     "encryption_reset": "您确定想要重置加密设置?",
     "topline_expiring_certificate": "您的 SSL 证书即将过期。请更新 <0>加密设置</0> 。",
     "topline_expired_certificate": "您的 SSL 证书已过期。请更新 <0>加密设置</0> 。",
-    "form_error_port_range": "输入 80 - 65535 范围内的端口值。",
-    "form_error_port_unsafe": "这是一个不安全的端口。",
-    "form_error_equal": "不可相同。",
-    "form_error_password": "密码不匹配。",
+    "form_error_port_range": "输入 80 - 65535 范围内的端口值",
+    "form_error_port_unsafe": "这是一个不安全的端口",
+    "form_error_equal": "不可相同",
+    "form_error_password": "密码不匹配",
     "reset_settings": "重置设置",
     "update_announcement": "AdGuard Home {{version}} 现已发布! <0>点击此处</0> 以获取详细信息。",
     "setup_guide": "设置指导",
     "dns_addresses": "DNS 地址",
     "dns_start": "正在启动DNS服务",
-    "dns_status_error": "检查 DNS 服务器状态时出错。",
+    "dns_status_error": "检查 DNS 服务器状态时出错",
     "down": "下移",
     "fix": "修复",
     "dns_providers": "此为可从中选择的<0>已知 DNS 提供商列表</0>。",
@@ -406,7 +408,7 @@
     "manual_update": "请跟随<a>此步骤</a>以进行手动更新。",
     "processing_update": "正在更新 AdGuard Home,请稍侯",
     "clients_title": "持久客户端",
-    "clients_desc": "配置已连接到 AdGuard Home 的设备的持久客户端记录。",
+    "clients_desc": "配置已连接到 AdGuard Home 的设备的持久客户端记录",
     "settings_global": "全局",
     "settings_custom": "自定义",
     "table_client": "客户端",
@@ -435,7 +437,7 @@
     "auto_clients_title": "客户端(运行时间)",
     "auto_clients_desc": "不在可继续使用 AdGuard Home 的持久客户端列表中的设备。",
     "access_title": "访问设置",
-    "access_desc": "您可在此处配置 AdGuard Home DNS 服务器的访问规则。",
+    "access_desc": "您可以在此处配置 AdGuard Home 的 DNS 服务器的访问规则",
     "access_allowed_title": "允许的客户端",
     "access_allowed_desc": "CIDR、IP 地址或<a>客户端 ID</a> 的列表。如已配置,则 AdGuard Home 将仅接受来自这些客户端的请求。",
     "access_disallowed_title": "不允许的客户端",
@@ -475,8 +477,8 @@
     "dns_rewrites": "DNS 重写",
     "form_domain": "输入域",
     "form_answer": "输入 IP 地址或域名",
-    "form_error_domain_format": "无效的网域格式。",
-    "form_error_answer_format": "无效的响应格式。",
+    "form_error_domain_format": "无效的域格式",
+    "form_error_answer_format": "无效的响应格式",
     "configure": "配置",
     "main_settings": "主要设置",
     "block_services": "阻止特定服务",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} 天",
     "interval_days_plural": "{{count}} 天",
     "domain": "域名",
+    "ecs": "ECS",
     "punycode": "Punycode",
     "answer": "应答",
     "filter_added_successfully": "已成功添加过滤器",
@@ -507,7 +510,7 @@
     "filter_updated": "成功更新过滤器",
     "statistics_configuration": "统计配置",
     "statistics_retention": "统计保留",
-    "statistics_retention_desc": "如果您减少该间隔的数值, 某些数据可能会丢失。",
+    "statistics_retention_desc": "如果您减少该间隔的数值, 某些数据可能会丢失",
     "statistics_clear": " 清除统计数据",
     "statistics_clear_confirm": "您确定要清除统计数据?",
     "statistics_retention_confirm": "您确定要更改统计记录保留时间吗? 如果您减少间隔时间的值, 某些数据可能会丢失。",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "输入最大 TTL 值(秒)",
     "cache_ttl_min_override_desc": "缓存 DNS 响应时,延长从上游服务器接收到的 TTL 值 (秒)。",
     "cache_ttl_max_override_desc": "设定 DNS 缓存条目的最大 TTL 值(秒)。",
-    "ttl_cache_validation": "最小缓存 TTL 值必须小于或等于最大值。",
+    "ttl_cache_validation": "最小缓存 TTL 值必须小于或等于最大值",
     "cache_optimistic": "乐观缓存",
     "cache_optimistic_desc": "即使条目已过期,也让 AdGuard Home 从缓存中响应,并尝试刷新它们。",
     "filter_category_general": "常规",
@@ -628,5 +631,5 @@
     "parental_control": "家长控制",
     "safe_browsing": "安全浏览",
     "served_from_cache": "{{value}}<i>(由缓存提供)</i>",
-    "form_error_password_length": "密码必须至少有 {{value}} 个字符。"
+    "form_error_password_length": "密码必须至少有 {{value}} 个字符"
 }
diff --git a/client/src/__locales/zh-hk.json b/client/src/__locales/zh-hk.json
index cae94b69..99028a59 100644
--- a/client/src/__locales/zh-hk.json
+++ b/client/src/__locales/zh-hk.json
@@ -38,12 +38,18 @@
     "form_error_required": "必要欄位",
     "form_error_ip4_format": "無效的 IPv4 格式",
     "form_error_ip6_format": "無效的 IPv6 格式",
+    "form_error_ip4_range_start_format": "無效的 IPv4 範圍起始位址",
+    "form_error_ip4_range_end_format": "無效的 IPv4 範圍結束位址",
+    "form_error_ip4_gateway_format": "閘道的 IPv4 位址無效",
     "form_error_ip_format": "無效的 IP 位址",
     "form_error_mac_format": "無效的 「MAC 位址」格式",
     "form_error_client_id_format": "無效的「客戶端 ID」格式",
     "form_error_server_name": "無效伺服器名稱",
     "form_error_subnet": "子網路 \"{{cidr}}\" 不包含 IP 位址 \"{{ip}}\"",
     "form_error_positive": "數值必須大於 0",
+    "out_of_range_error": "必須介於 \"{{start}}\" - \"{{end}}\" 範圍之外",
+    "lower_range_start_error": "必須小於起始值",
+    "greater_range_start_error": "必須大於起始值",
     "dhcp_form_gateway_input": "閘道 IP 位址",
     "dhcp_form_subnet_input": "子網路遮罩",
     "dhcp_form_range_title": "IP 位址範圍",
diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json
index 52bbc4e0..8b1fab72 100644
--- a/client/src/__locales/zh-tw.json
+++ b/client/src/__locales/zh-tw.json
@@ -35,24 +35,24 @@
     "dhcp_config_saved": "動態主機設定協定(DHCP)配置被成功地儲存",
     "dhcp_ipv4_settings": "DHCP IPv4 設定",
     "dhcp_ipv6_settings": "DHCP IPv6 設定",
-    "form_error_required": "必填的欄位。",
-    "form_error_ip4_format": "無效的 IPv4 位址。",
-    "form_error_ip4_range_start_format": "無效起始範圍的 IPv4 位址。",
-    "form_error_ip4_range_end_format": "無效結束範圍的 IPv4 位址。",
-    "form_error_ip4_gateway_format": "無效閘道的 IPv4 位址。",
-    "form_error_ip6_format": "無效的 IPv6 位址。",
-    "form_error_ip_format": "無效的 IP 位址。",
-    "form_error_mac_format": "無效的媒體存取控制(MAC)位址。",
-    "form_error_client_id_format": "用戶端 ID 必須只包含數字、小寫字母和連字號。",
-    "form_error_server_name": "無效的伺服器名稱。",
-    "form_error_subnet": "子網路 \"{{cidr}}\" 不包含該 IP 位址 \"{{ip}}\"。",
-    "form_error_positive": "必須大於 0。",
-    "out_of_range_error": "必須在\"{{start}}\"-\"{{end}}\"範圍之外。",
-    "lower_range_start_error": "必須低於起始範圍。",
-    "greater_range_start_error": "必須大於起始範圍。",
-    "greater_range_end_error": "必須大於結束範圍。",
-    "subnet_error": "位址必須在子網路中。",
-    "gateway_or_subnet_invalid": "無效的子網路遮罩。",
+    "form_error_required": "必填的欄位",
+    "form_error_ip4_format": "無效的 IPv4 位址",
+    "form_error_ip4_range_start_format": "無效起始範圍的 IPv4 位址",
+    "form_error_ip4_range_end_format": "無效結束範圍的 IPv4 位址",
+    "form_error_ip4_gateway_format": "無效閘道的 IPv4 位址",
+    "form_error_ip6_format": "無效的 IPv6 位址",
+    "form_error_ip_format": "無效的 IP 位址",
+    "form_error_mac_format": "無效的媒體存取控制(MAC)位址",
+    "form_error_client_id_format": "用戶端 ID 必須只包含數字、小寫字母和連字號",
+    "form_error_server_name": "無效的伺服器名稱",
+    "form_error_subnet": "子網路 \"{{cidr}}\" 不包含該 IP 位址 \"{{ip}}\"",
+    "form_error_positive": "必須大於 0",
+    "out_of_range_error": "必須在\"{{start}}\"-\"{{end}}\"範圍之外",
+    "lower_range_start_error": "必須低於起始範圍",
+    "greater_range_start_error": "必須大於起始範圍",
+    "greater_range_end_error": "必須大於結束範圍",
+    "subnet_error": "位址必須在子網路中",
+    "gateway_or_subnet_invalid": "無效的子網路遮罩",
     "dhcp_form_gateway_input": "閘道 IP",
     "dhcp_form_subnet_input": "子網路遮罩",
     "dhcp_form_range_title": "IP 位址範圍",
@@ -67,7 +67,7 @@
     "dhcp_table_hostname": "主機名稱",
     "dhcp_table_expires": "到期",
     "dhcp_warning": "如果您無論如何想要啟用動態主機設定協定(DHCP)伺服器,確保在您的網路中無其它現行的 DHCP 伺服器,因為對於該網路上的裝置,這可能破壞其網際網路連線!",
-    "dhcp_error": "AdGuard Home 無法確定於該網路上是否有另外現行的動態主機設定協定(DHCP)伺服器。",
+    "dhcp_error": "AdGuard Home 無法確定於該網路上是否有另外現行的動態主機設定協定(DHCP)伺服器",
     "dhcp_static_ip_error": "為了使用動態主機設定協定(DHCP)伺服器,靜態 IP 位址必須被設定。AdGuard Home 未能確定此網路介面是否被配置使用靜態 IP 位址。請手動地設定靜態 IP 位址。",
     "dhcp_dynamic_ip_found": "您的系統使用動態 IP 位址配置供介面 <0>{{interfaceName}}</0>。為了使用動態主機設定協定(DHCP)伺服器,靜態 IP 位址必須被設定。您現行的 IP 位址為 <0>{{ipAddress}}</0>。如果您按\"啟用 DHCP 伺服器\" 按鈕,AdGuard Home 將自動地設定此 IP 位址作為靜態。",
     "dhcp_lease_added": "靜態租約 \"{{key}}\" 被成功地加入",
@@ -196,8 +196,8 @@
     "choose_allowlist": "選擇允許清單",
     "enter_valid_blocklist": "輸入一個到該封鎖清單之有效的網址。",
     "enter_valid_allowlist": "輸入一個到該允許清單之有效的網址。",
-    "form_error_url_format": "無效的網址格式。",
-    "form_error_url_or_path_format": "該清單之無效的網址或絕對的路徑。",
+    "form_error_url_format": "無效的網址格式",
+    "form_error_url_or_path_format": "該清單之無效的網址或絕對的路徑",
     "custom_filter_rules": "自訂的過濾規則",
     "custom_filter_rules_hint": "於一行上輸入一項規則。您可使用廣告封鎖規則或主機檔案語法。",
     "system_host_files": "系統主機檔案",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# 也是一個註解。",
     "example_regex_meaning": "封鎖至與該已明確指定的規則運算式(Regular Expression)相符的網域之存取。",
     "example_upstream_regular": "常規 DNS(透過 UDP);",
+    "example_upstream_udp": "常規 DNS(透過 UDP,主機名稱);",
     "example_upstream_dot": "加密的 <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "加密的 <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "加密的 <0>DNS-over-QUIC</0>(實驗性的);",
     "example_upstream_sdns": "關於 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2> 解析器之 <0>DNS 戳記</0>;",
     "example_upstream_tcp": "常規 DNS(透過 TCP);",
+    "example_upstream_tcp_hostname": "常規 DNS(透過 TCP,主機名稱);",
     "all_lists_up_to_date_toast": "所有的清單已是最新的",
     "updated_upstream_dns_toast": "上游的伺服器被成功地儲存",
     "dns_test_ok_toast": "已明確指定的 DNS 伺服器正在正確地運作",
@@ -259,10 +261,10 @@
     "query_log_strict_search": "使用雙引號於嚴謹的搜尋",
     "query_log_retention_confirm": "您確定您想要更改查詢記錄保留嗎?如果您減少該間隔值,某些資料將被丟失",
     "anonymize_client_ip": "將用戶端 IP 匿名",
-    "anonymize_client_ip_desc": "不要儲存用戶端之完整的 IP 位址到記錄或統計資料裡。",
+    "anonymize_client_ip_desc": "不要儲存用戶端之完整的 IP 位址到記錄或統計資料裡",
     "dns_config": "DNS 伺服器配置",
     "dns_cache_config": "DNS 快取配置",
-    "dns_cache_config_desc": "於此您可配置 DNS 快取。",
+    "dns_cache_config_desc": "於此您可配置 DNS 快取",
     "blocking_mode": "封鎖模式",
     "default": "預設",
     "nxdomain": "不存在的網域(NXDOMAIN)",
@@ -285,7 +287,7 @@
     "form_enter_rate_limit": "輸入速率限制",
     "rate_limit": "速率限制",
     "edns_enable": "啟用對於 DNS 的擴充機制(EDNS)用戶端子網路",
-    "edns_cs_desc": "傳送用戶端的子網路到該 DNS 伺服器。",
+    "edns_cs_desc": "新增對於 DNS 的擴充機制(EDNS)用戶端子網路選項到上游的請求,並在查詢記錄中記錄由用戶端傳送的數值。",
     "rate_limit_desc": "每個用戶端被允許的每秒請求之數量。設定它為 0 表示無限制。",
     "blocking_ipv4_desc": "要被返回給已封鎖的 A 請求之 IP 位址",
     "blocking_ipv6_desc": "要被返回給已封鎖的 AAAA 請求之 IP 位址",
@@ -309,7 +311,7 @@
     "install_settings_listen": "監聽介面",
     "install_settings_port": "連接埠",
     "install_settings_interface_link": "您的 AdGuard Home 管理員網路介面將於下列的位址上為可用的:",
-    "form_error_port": "輸入有效的連接埠號碼。",
+    "form_error_port": "輸入有效的連接埠號碼",
     "install_settings_dns": "DNS 伺服器",
     "install_settings_dns_desc": "您將需要配置您的裝置或路由器以使用於下列的位址上之 DNS 伺服器:",
     "install_settings_all_interfaces": "所有的介面",
@@ -356,7 +358,7 @@
     "open_dashboard": "開啟儀表板",
     "install_saved": "被成功地儲存",
     "encryption_title": "加密",
-    "encryption_desc": "供 DNS 和管理員網路介面兩者之加密(HTTPS/TLS)支援。",
+    "encryption_desc": "供 DNS 和管理員網路介面兩者之加密(HTTPS/TLS)支援",
     "encryption_config_saved": "加密配置被儲存",
     "encryption_server": "伺服器名稱",
     "encryption_server_enter": "輸入您的域名",
@@ -378,26 +380,26 @@
     "encryption_key_input": "於此複製/貼上您的隱私增強郵件編碼之(PEM-encoded)私密金鑰供您的憑證。",
     "encryption_enable": "啟用加密(HTTPS、DNS-over-HTTPS 和 DNS-over-TLS)",
     "encryption_enable_desc": "如果加密被啟用,AdGuard Home 管理員介面透過 HTTPS 將運作,且該 DNS 伺服器將留心監聽透過 DNS-over-HTTPS 和 DNS-over-TLS 之請求。",
-    "encryption_chain_valid": "憑證鏈結為有效的。",
-    "encryption_chain_invalid": "憑證鏈結為無效的。",
-    "encryption_key_valid": "此為有效的 {{type}} 私密金鑰。",
-    "encryption_key_invalid": "此為無效的 {{type}} 私密金鑰。",
+    "encryption_chain_valid": "憑證鏈結為有效的",
+    "encryption_chain_invalid": "憑證鏈結為無效的",
+    "encryption_key_valid": "此為有效的 {{type}} 私密金鑰",
+    "encryption_key_invalid": "此為無效的 {{type}} 私密金鑰",
     "encryption_subject": "物件",
     "encryption_issuer": "簽發者",
     "encryption_hostnames": "主機名稱",
     "encryption_reset": "您確定您想要重置加密設定嗎?",
     "topline_expiring_certificate": "您的安全通訊端層(SSL)憑證即將到期。更新<0>加密設定</0>。",
     "topline_expired_certificate": "您的安全通訊端層(SSL)憑證為已到期的。更新<0>加密設定</0>。",
-    "form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼。",
-    "form_error_port_unsafe": "此為不安全的連接埠。",
-    "form_error_equal": "必須不為相等的。",
-    "form_error_password": "不相符的密碼。",
+    "form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼",
+    "form_error_port_unsafe": "不安全的連接埠",
+    "form_error_equal": "必須為不相等的",
+    "form_error_password": "不相符的密碼",
     "reset_settings": "重置設定",
     "update_announcement": "AdGuard Home {{version}} 現為可用的!關於更多的資訊,<0>點擊這裡</0>。",
     "setup_guide": "設置指南",
     "dns_addresses": "DNS 位址",
     "dns_start": "DNS 伺服器正在啟動",
-    "dns_status_error": "檢查 DNS 伺服器狀態出錯。",
+    "dns_status_error": "檢查 DNS 伺服器狀態出錯",
     "down": "停止運作的",
     "fix": "修復",
     "dns_providers": "這裡是一個從中選擇之<0>已知的 DNS 供應商之清單</0>。",
@@ -406,7 +408,7 @@
     "manual_update": "請<a>遵循這些步驟</a>以手動地更新。",
     "processing_update": "請稍候,AdGuard Home 正被更新",
     "clients_title": "持續性用戶端",
-    "clients_desc": "配置關於被連線到 AdGuard Home 的裝置之持續性用戶端記錄。",
+    "clients_desc": "配置關於被連線到 AdGuard Home 的裝置之持續性用戶端記錄",
     "settings_global": "全域的",
     "settings_custom": "自訂的",
     "table_client": "用戶端",
@@ -433,9 +435,9 @@
     "client_confirm_delete": "您確定您想要刪除用戶端 \"{{key}}\" 嗎?",
     "list_confirm_delete": "您確定您想要刪除該清單嗎?",
     "auto_clients_title": "執行時期用戶端",
-    "auto_clients_desc": "未於可能仍然使用 AdGuard Home 的持續性用戶端之清單上的裝置。",
+    "auto_clients_desc": "未於可能仍然使用 AdGuard Home 的持續性用戶端之清單上的裝置",
     "access_title": "存取設定",
-    "access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則。",
+    "access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則",
     "access_allowed_title": "已允許的用戶端",
     "access_allowed_desc": "無類別網域間路由(CIDRs)、IP 位址或<a>用戶端 IDs</a> 之清單。如果此清單有項目,AdGuard Home 將接受僅來自這些用戶端的請求。",
     "access_disallowed_title": "未被允許的用戶端",
@@ -475,8 +477,8 @@
     "dns_rewrites": "DNS 改寫",
     "form_domain": "輸入域名或萬用字元(wildcard)",
     "form_answer": "輸入 IP 位址或域名",
-    "form_error_domain_format": "無效的網域格式。",
-    "form_error_answer_format": "無效的回應格式。",
+    "form_error_domain_format": "無效的網域格式",
+    "form_error_answer_format": "無效的回應格式",
     "configure": "配置",
     "main_settings": "主設定",
     "block_services": "封鎖特定的服務",
@@ -500,6 +502,7 @@
     "interval_days": "{{count}} 日",
     "interval_days_plural": "{{count}} 日",
     "domain": "網域",
+    "ecs": "對於 DNS 的擴充機制(EDNS)用戶端子網路",
     "punycode": "國際化域名代碼(Punycode)",
     "answer": "回應",
     "filter_added_successfully": "該清單已被成功地加入",
@@ -507,7 +510,7 @@
     "filter_updated": "該清單已被成功地更新",
     "statistics_configuration": "統計資料配置",
     "statistics_retention": "統計資料保留",
-    "statistics_retention_desc": "如果您減少該間隔值,某些資料將被丟失。",
+    "statistics_retention_desc": "如果您減少該間隔值,某些資料將被丟失",
     "statistics_clear": " 清除統計資料",
     "statistics_clear_confirm": "您確定您想要清除統計資料嗎?",
     "statistics_retention_confirm": "您確定您想要更改統計資料保留嗎?如果您減少該間隔值,某些資料將被丟失",
@@ -606,7 +609,7 @@
     "enter_cache_ttl_max_override": "輸入最大的存活時間(秒)",
     "cache_ttl_min_override_desc": "當快取 DNS 回應時,延長從上游的伺服器收到的短存活時間數值(秒)。",
     "cache_ttl_max_override_desc": "設定最大的存活時間數值(秒)供在 DNS 快取中的項目。",
-    "ttl_cache_validation": "最小的快取存活時間(TTL)覆寫必須小於或等於最大的。",
+    "ttl_cache_validation": "最小的快取存活時間(TTL)覆寫必須小於或等於最大的",
     "cache_optimistic": "樂觀快取",
     "cache_optimistic_desc": "即使當項目為已到期的,從快取使 AdGuard Home 回覆,並還嘗試重新整理它們。",
     "filter_category_general": "一般的",
@@ -628,5 +631,5 @@
     "parental_control": "家長控制",
     "safe_browsing": "安全瀏覽",
     "served_from_cache": "{{value}} <i>(由快取提供)</i>",
-    "form_error_password_length": "密碼必須為至少長 {{value}} 個字元。"
+    "form_error_password_length": "密碼必須為至少長 {{value}} 個字元"
 }

From 5cba78a8d5ca35e33c35ff68ef0b668d78826c7f Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 23 Mar 2022 16:00:32 +0300
Subject: [PATCH 021/143] Pull request: 4276 upd quic port

Merge in DNS/adguard-home from 4276-doq-port to master

Closes #4276.

Squashed commit of the following:

commit cbdde622b54d0d5d11d1b4809f95a41ace990a1b
Merge: d32c13e9 2c33ab6a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 15:47:43 2022 +0300

    Merge branch 'master' into 4276-doq-port

commit d32c13e98f0fed2c863160e4e2de02ae3038e3df
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 21 21:55:09 2022 +0300

    all: fix link

commit 0afd702f5192d727927df2f8d95b9317811a1be0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 21 21:47:38 2022 +0300

    all: imp docs, log changes

commit 9a77fc3daf78d32c577f1bc49aa1f8bc352d44e3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 21 21:41:30 2022 +0300

    home: upd quic port
---
 CHANGELOG.md                    |  9 +++++++--
 internal/home/config.go         | 36 +++++++++++++++++++++++----------
 internal/home/controlinstall.go |  4 ++--
 internal/home/dns.go            |  2 +-
 internal/home/home.go           | 14 ++++++-------
 internal/home/tls.go            | 28 ++++++++++++-------------
 6 files changed, 56 insertions(+), 37 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc986774..0a29640a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,8 @@ and this project adheres to
 
 ### Changed
 
+- The default DNS-over-QUIC port number is now `853` instead of `754` in
+  accoradance with the latest [RFC draft][doq-draft-10] ([#4276]).
 - Reverse DNS now has a greater priority as the source of runtime clients'
   informmation than ARP neighborhood.
 - Improved detection of runtime clients through more resilient ARP processing
@@ -103,8 +105,10 @@ In this release, the schema version has changed from 12 to 13.
 [#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
+[#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
 
-[repr]: https://reproducible-builds.org/docs/source-date-epoch/
+[repr]:         https://reproducible-builds.org/docs/source-date-epoch/
+[doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
 
 
 
@@ -234,7 +238,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
 - New possible value of `6h` for `querylog_interval` setting ([#2504]).
 - Blocking access using ClientIDs ([#2624], [#3162]).
 - `source` directives support in `/etc/network/interfaces` on Linux ([#3257]).
-- RFC 9000 support in DNS-over-QUIC.
+- [RFC 9000][rfc-9000] support in QUIC.
 - Completely disabling statistics by setting the statistics interval to zero
   ([#2141]).
 - The ability to completely purge DHCP leases ([#1691]).
@@ -459,6 +463,7 @@ In this release, the schema version has changed from 10 to 12.
 [#3933]: https://github.com/AdguardTeam/AdGuardHome/pull/3933
 
 [ms-v0.107.0]: https://github.com/AdguardTeam/AdGuardHome/milestone/23?closed=1
+[rfc-9000]:    https://datatracker.ietf.org/doc/html/rfc9000
 
 
 
diff --git a/internal/home/config.go b/internal/home/config.go
index c018f16e..c81de19e 100644
--- a/internal/home/config.go
+++ b/internal/home/config.go
@@ -292,18 +292,20 @@ func parseConfig() (err error) {
 	uc := aghalg.UniqChecker{}
 	addPorts(
 		uc,
-		config.BindPort,
-		config.BetaBindPort,
-		config.DNS.Port,
+		tcpPort(config.BindPort),
+		tcpPort(config.BetaBindPort),
+		udpPort(config.DNS.Port),
 	)
 
 	if config.TLS.Enabled {
 		addPorts(
 			uc,
-			config.TLS.PortHTTPS,
-			config.TLS.PortDNSOverTLS,
-			config.TLS.PortDNSOverQUIC,
-			config.TLS.PortDNSCrypt,
+			// TODO(e.burkov):  Consider adding a udpPort with the same value if
+			// we ever support the HTTP/3 for web admin interface.
+			tcpPort(config.TLS.PortHTTPS),
+			tcpPort(config.TLS.PortDNSOverTLS),
+			udpPort(config.TLS.PortDNSOverQUIC),
+			tcpPort(config.TLS.PortDNSCrypt),
 		)
 	}
 	if err = uc.Validate(aghalg.IntIsBefore); err != nil {
@@ -321,11 +323,23 @@ func parseConfig() (err error) {
 	return nil
 }
 
-// addPorts is a helper for ports validation.  It skips zero ports.
-func addPorts(uc aghalg.UniqChecker, ports ...int) {
+// udpPort is the port number for UDP protocol.
+type udpPort int
+
+// tcpPort is the port number for TCP protocol.
+type tcpPort int
+
+// addPorts is a helper for ports validation.  It skips zero ports.  Each of
+// ports should be either a udpPort or a tcpPort.
+func addPorts(uc aghalg.UniqChecker, ports ...interface{}) {
 	for _, p := range ports {
-		if p != 0 {
-			uc.Add(p)
+		switch p := p.(type) {
+		case tcpPort, udpPort:
+			if p != 0 {
+				uc.Add(p)
+			}
+		default:
+			// Go on.
 		}
 	}
 }
diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go
index 98cbf31a..0435651b 100644
--- a/internal/home/controlinstall.go
+++ b/internal/home/controlinstall.go
@@ -109,7 +109,7 @@ func (req *checkConfReq) validateWeb(uc aghalg.UniqChecker) (err error) {
 	defer func() { err = errors.Annotate(err, "validating ports: %w") }()
 
 	port := req.Web.Port
-	addPorts(uc, config.BetaBindPort, port)
+	addPorts(uc, tcpPort(config.BetaBindPort), tcpPort(port))
 	if err = uc.Validate(aghalg.IntIsBefore); err != nil {
 		// Avoid duplicating the error into the status of DNS.
 		uc[port] = 1
@@ -135,7 +135,7 @@ func (req *checkConfReq) validateDNS(uc aghalg.UniqChecker) (canAutofix bool, er
 	defer func() { err = errors.Annotate(err, "validating ports: %w") }()
 
 	port := req.DNS.Port
-	addPorts(uc, port)
+	addPorts(uc, udpPort(port))
 	if err = uc.Validate(aghalg.IntIsBefore); err != nil {
 		return false, err
 	}
diff --git a/internal/home/dns.go b/internal/home/dns.go
index d676f6af..c30e12ec 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -25,7 +25,7 @@ const (
 	defaultPortDNS   = 53
 	defaultPortHTTP  = 80
 	defaultPortHTTPS = 443
-	defaultPortQUIC  = 784
+	defaultPortQUIC  = 853
 	defaultPortTLS   = 853
 )
 
diff --git a/internal/home/home.go b/internal/home/home.go
index 7096be78..4e4d3aee 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -305,17 +305,17 @@ func setupConfig(args options) (err error) {
 		uc := aghalg.UniqChecker{}
 		addPorts(
 			uc,
-			args.bindPort,
-			config.BetaBindPort,
-			config.DNS.Port,
+			tcpPort(args.bindPort),
+			tcpPort(config.BetaBindPort),
+			udpPort(config.DNS.Port),
 		)
 		if config.TLS.Enabled {
 			addPorts(
 				uc,
-				config.TLS.PortHTTPS,
-				config.TLS.PortDNSOverTLS,
-				config.TLS.PortDNSOverQUIC,
-				config.TLS.PortDNSCrypt,
+				tcpPort(config.TLS.PortHTTPS),
+				tcpPort(config.TLS.PortDNSOverTLS),
+				udpPort(config.TLS.PortDNSOverQUIC),
+				tcpPort(config.TLS.PortDNSCrypt),
 			)
 		}
 		if err = uc.Validate(aghalg.IntIsBefore); err != nil {
diff --git a/internal/home/tls.go b/internal/home/tls.go
index eeffe3b6..fe595e98 100644
--- a/internal/home/tls.go
+++ b/internal/home/tls.go
@@ -253,13 +253,13 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
 		uc := aghalg.UniqChecker{}
 		addPorts(
 			uc,
-			config.BindPort,
-			config.BetaBindPort,
-			config.DNS.Port,
-			setts.PortHTTPS,
-			setts.PortDNSOverTLS,
-			setts.PortDNSOverQUIC,
-			setts.PortDNSCrypt,
+			tcpPort(config.BindPort),
+			tcpPort(config.BetaBindPort),
+			udpPort(config.DNS.Port),
+			tcpPort(setts.PortHTTPS),
+			tcpPort(setts.PortDNSOverTLS),
+			udpPort(setts.PortDNSOverQUIC),
+			tcpPort(setts.PortDNSCrypt),
 		)
 
 		err = uc.Validate(aghalg.IntIsBefore)
@@ -346,13 +346,13 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
 		uc := aghalg.UniqChecker{}
 		addPorts(
 			uc,
-			config.BindPort,
-			config.BetaBindPort,
-			config.DNS.Port,
-			data.PortHTTPS,
-			data.PortDNSOverTLS,
-			data.PortDNSOverQUIC,
-			data.PortDNSCrypt,
+			tcpPort(config.BindPort),
+			tcpPort(config.BetaBindPort),
+			udpPort(config.DNS.Port),
+			tcpPort(data.PortHTTPS),
+			tcpPort(data.PortDNSOverTLS),
+			udpPort(data.PortDNSOverQUIC),
+			tcpPort(data.PortDNSCrypt),
 		)
 
 		err = uc.Validate(aghalg.IntIsBefore)

From 9ce2a0fb341c89b1e047649f97f80cbcc088c8e0 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 23 Mar 2022 16:13:28 +0300
Subject: [PATCH 022/143] Pull request: all: upd chlog

Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 02b33e764d540868df19cd7359f114f98c9a66ca
Merge: 999a5f27 5cba78a8
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Mar 23 16:09:39 2022 +0300

    Merge branch 'master' into upd-chlog

commit 999a5f2718f5577e13ec59619652cc60ff7a7416
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Mar 23 16:04:35 2022 +0300

    all: fix chlog

commit 2fe68c5665ea63c3b3f73b8a6ace88de45f2aec1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Mar 23 15:59:36 2022 +0300

    all: imp chlog

commit d5af9db6365430cc7e96d7681231db24e57aa6a5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Mar 23 15:39:27 2022 +0300

    all: upd chlog
---
 CHANGELOG.md | 72 ++++++++++++++++++++++++++++++++--------------------
 1 file changed, 44 insertions(+), 28 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0a29640a..65e8a64b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,8 +17,9 @@ and this project adheres to
 
 ### Added
 
-- The ability to customize the set of networks considered private through the
-  new `private_networks` setting ([#3142]).
+- The ability to customize the set of networks that are considered private
+  through the new `dns.private_networks` property in the configuration file
+  ([#3142]).
 - EDNS Client-Subnet information in the request details section of a query log
   record ([#3978]).
 - Support for hostnames for plain UDP upstream servers using the `udp://` scheme
@@ -32,7 +33,7 @@ and this project adheres to
 - The default DNS-over-QUIC port number is now `853` instead of `754` in
   accoradance with the latest [RFC draft][doq-draft-10] ([#4276]).
 - Reverse DNS now has a greater priority as the source of runtime clients'
-  informmation than ARP neighborhood.
+  information than ARP neighborhood.
 - Improved detection of runtime clients through more resilient ARP processing
   ([#3597]).
 - The TTL of responses served from the optimistic cache is now lowered to 10
@@ -49,16 +50,16 @@ and this project adheres to
   of the commit from which the binary was built ([#4221]).  This should simplify
   reproducible builds for package maintainers and those who compile their own
   AdGuard Home.
-- The setting `local_domain_name` is now in the `dhcp` block in the
+- The property `local_domain_name` is now in the `dhcp` object in the
   configuration file to avoid confusion ([#3367]).
-- The `dns.bogus_nxdomain` configuration file parameter now supports CIDR
+- The `dns.bogus_nxdomain` property in the configuration file now supports CIDR
   notation alongside IP addresses ([#1730]).
 
 #### Configuration Changes
 
 In this release, the schema version has changed from 12 to 13.
 
-- Parameter `local_domain_name`, which in schema versions 12 and earlier used to
+- Property `local_domain_name`, which in schema versions 12 and earlier used to
   be a part of the `dns` object, is now a part of the `dhcp` object:
 
   ```yaml
@@ -73,8 +74,8 @@ In this release, the schema version has changed from 12 to 13.
     'local_domain_name': 'lan'
   ```
 
-  To rollback this change, move the parameter back into `dns` and change the
-  `schema_version` back to `12`.
+  To rollback this change, move the property back into the `dns` object and
+  change the `schema_version` back to `12`.
 
 ### Deprecated
 
@@ -86,7 +87,7 @@ In this release, the schema version has changed from 12 to 13.
 
 ### Security
 
-- `User-Agent` HTTP header removed from outcoming DNS-over-HTTPS requests.
+- `User-Agent` HTTP header removed from outgoing DNS-over-HTTPS requests.
 - Enforced password strength policy ([#3503]).
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
@@ -113,15 +114,29 @@ In this release, the schema version has changed from 12 to 13.
 
 
 <!--
-## [v0.107.5] - 2022-04-04 (APPROX.)
+## [v0.107.6] - 2022-04-04 (APPROX.)
 
-See also the [v0.107.5 GitHub milestone][ms-v0.107.5].
+See also the [v0.107.6 GitHub milestone][ms-v0.107.6].
 
-[ms-v0.107.5]: https://github.com/AdguardTeam/AdGuardHome/milestone/42?closed=1
+[ms-v0.107.6]: https://github.com/AdguardTeam/AdGuardHome/milestone/42?closed=1
 -->
 
 
 
+## [v0.107.5] - 2022-03-04
+
+This is a security update.  There is no GitHub milestone, since no GitHub issues
+were resolved.
+
+### Security
+
+- Go version was updated to prevent the possibility of exploiting the
+  [CVE-2022-24921] vulnerability.
+
+[CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921
+
+
+
 ## [v0.107.4] - 2022-03-01
 
 See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
@@ -135,8 +150,8 @@ See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
 
 ### Security
 
-- Go version was updated to prevent the possibility of exploiting
-  [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773].
+- Go version was updated to prevent the possibility of exploiting the
+  [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] vulnerabilities.
 
 [#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
 [#4254]: https://github.com/AdguardTeam/AdGuardHome/issues/4254
@@ -235,7 +250,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
   through the new `fastest_timeout` field in the configuration file ([#1992]).
 - Static IP address detection on FreeBSD ([#3289]).
 - Optimistic cache ([#2145]).
-- New possible value of `6h` for `querylog_interval` setting ([#2504]).
+- New possible value of `6h` for `querylog_interval` property ([#2504]).
 - Blocking access using ClientIDs ([#2624], [#3162]).
 - `source` directives support in `/etc/network/interfaces` on Linux ([#3257]).
 - [RFC 9000][rfc-9000] support in QUIC.
@@ -286,22 +301,22 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
   proxy ([#2799]).
 - Clients who are blocked by access settings now receive a `REFUSED` response
   when a protocol other than DNS-over-UDP and DNSCrypt is used.
-- `querylog_interval` setting is now formatted in hours.
+- `dns.querylog_interval` property is now formatted in hours.
 - Query log search now supports internationalized domains ([#3012]).
 - Internationalized domains are now shown decoded in the query log with the
   original encoded version shown in request details ([#3013]).
 - When /etc/hosts-type rules have several IPs for one host, all IPs are now
   returned instead of only the first one ([#1381]).
-- The setting `rlimit_nofile` is now in the `os` block of the configuration
-  file, together with the new `group` and `user` settings ([#2763]).
+- Property `rlimit_nofile` is now in the `os` object of the configuration file,
+  together with the new `group` and `user` properties ([#2763]).
 - Permissions on filter files are now `0o644` instead of `0o600` ([#3198]).
 
 #### Configuration Changes
 
 In this release, the schema version has changed from 10 to 12.
 
-- Parameter `dns.querylog_interval`, which in schema versions 11 and earlier
-  used to be an integer number of days, is now a string with a human-readable
+- Property `dns.querylog_interval`, which in schema versions 11 and earlier used
+  to be an integer number of days, is now a string with a human-readable
   duration:
 
   ```yaml
@@ -316,10 +331,10 @@ In this release, the schema version has changed from 10 to 12.
     'querylog_interval': '2160h'
   ```
 
-  To rollback this change, convert the parameter back into days and change the
+  To rollback this change, convert the property back into days and change the
   `schema_version` back to `11`.
 
-- Parameter `rlimit_nofile`, which in schema versions 10 and earlier used to be
+- Property `rlimit_nofile`, which in schema versions 10 and earlier used to be
   on the top level, is now moved to the new `os` object:
 
   ```yaml
@@ -333,7 +348,7 @@ In this release, the schema version has changed from 10 to 12.
     'user': ''
   ```
 
-  To rollback this change, move the parameter on the top level and change the
+  To rollback this change, move the property on the top level and change the
   `schema_version` back to `10`.
 
 ### Deprecated
@@ -686,8 +701,8 @@ See also the [v0.105.1 GitHub milestone][ms-v0.105.1].
 - Occasional crashes during startup.
 - The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response
   is now correctly named again ([#2678]).
-- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` settings aren't reset to
-  `false` on update anymore ([#2653]).
+- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` properties aren't reset
+  to `false` on update anymore ([#2653]).
 - The `Vary` header is now added along with `Access-Control-Allow-Origin` to
   prevent cache-related and other issues in browsers ([#2658]).
 - The request body size limit is now set for HTTPS requests as well.
@@ -864,11 +879,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
 
 
 <!--
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...HEAD
-[v0.107.5]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...HEAD
+[v0.107.6]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...v0.107.6
 -->
 
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...HEAD
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...HEAD
+[v0.107.5]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5
 [v0.107.4]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.3...v0.107.4
 [v0.107.3]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.2...v0.107.3
 [v0.107.2]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.1...v0.107.2

From 82505566f87fe10b80b2961456ed0150baed716c Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 23 Mar 2022 20:47:45 +0300
Subject: [PATCH 023/143] Pull request: 2846 cover aghnet vol.2

Merge in DNS/adguard-home from 2846-cover-aghnet-vol.2 to master

Updates #2846.
Closes #4408.

Squashed commit of the following:

commit 8d62b29d5b5be875cb71e518e479e321d853eb1a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 20:42:04 2022 +0300

    home: recover panic

commit 1d98109e910830bec712c7aecbbbcb8f659d823d
Merge: ac11d751 9ce2a0fb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 20:32:05 2022 +0300

    Merge branch 'master' into 2846-cover-aghnet-vol.2

commit ac11d751fb7951e3dd0940bf425a893223c32789
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 20:29:41 2022 +0300

    aghnet: use iotest

commit 7c923df7bafd5d4b91c4b4a01e75ab161944f949
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 20:17:19 2022 +0300

    aghnet: cover more

commit 3bfd4d587e4b887b5527d60c0eb6027da15c7e37
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 14:13:59 2022 +0300

    aghnet: cover arpdb more

commit cd5cf7bbdecceeab6d3abee10a5572e1e907cc67
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 13:05:35 2022 +0300

    all: rm arpdb initial refresh

commit 0fb8d9e44a4d130ca4e8fc2ea5d595ec08555302
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 22 21:13:16 2022 +0300

    aghnet: cover arpdb
---
 internal/aghnet/arpdb.go                      | 11 +---
 internal/aghnet/arpdb_bsd_test.go             |  8 +++
 internal/aghnet/arpdb_linux_test.go           |  7 +++
 internal/aghnet/arpdb_openbsd_test.go         |  2 +
 internal/aghnet/arpdb_test.go                 | 53 +++++++++++++++++++
 internal/aghnet/hostscontainer.go             |  4 +-
 internal/aghnet/net.go                        |  3 +-
 internal/aghnet/net_test.go                   | 25 +++++++++
 internal/aghnet/systemresolvers.go            | 40 ++------------
 internal/aghnet/systemresolvers_others.go     | 23 ++++----
 .../aghnet/systemresolvers_others_test.go     | 21 +++-----
 internal/aghnet/systemresolvers_test.go       | 14 +++--
 internal/aghnet/systemresolvers_windows.go    |  3 +-
 internal/aghnet/testdata/proc_net_arp         |  4 +-
 internal/dnsforward/dnsforward.go             |  2 +-
 internal/home/clients.go                      |  2 +
 internal/home/home.go                         |  9 +---
 17 files changed, 143 insertions(+), 88 deletions(-)

diff --git a/internal/aghnet/arpdb.go b/internal/aghnet/arpdb.go
index 759a688a..afb880c3 100644
--- a/internal/aghnet/arpdb.go
+++ b/internal/aghnet/arpdb.go
@@ -27,15 +27,8 @@ type ARPDB interface {
 }
 
 // NewARPDB returns the ARPDB properly initialized for the OS.
-func NewARPDB() (arp ARPDB, err error) {
-	arp = newARPDB()
-
-	err = arp.Refresh()
-	if err != nil {
-		return nil, fmt.Errorf("arpdb initial refresh: %w", err)
-	}
-
-	return arp, nil
+func NewARPDB() (arp ARPDB) {
+	return newARPDB()
 }
 
 // Empty ARPDB implementation
diff --git a/internal/aghnet/arpdb_bsd_test.go b/internal/aghnet/arpdb_bsd_test.go
index bbadc600..3404af69 100644
--- a/internal/aghnet/arpdb_bsd_test.go
+++ b/internal/aghnet/arpdb_bsd_test.go
@@ -8,8 +8,12 @@ import (
 )
 
 const arpAOutput = `
+invalid.mac (1.2.3.4) at 12:34:56:78:910 on el0 ifscope [ethernet]
+invalid.ip  (1.2.3.4.5) at ab:cd:ef:ab:cd:12 on ek0 ifscope [ethernet]
+invalid.fmt 1 at 12:cd:ef:ab:cd:ef on er0 ifscope [ethernet]
 hostname.one (192.168.1.2) at ab:cd:ef:ab:cd:ef on en0 ifscope [ethernet]
 hostname.two (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 1198 seconds [ethernet]
+? (::1234) at aa:bb:cc:dd:ee:ff on ej0 expires in 1918 seconds [ethernet]
 `
 
 var wantNeighs = []Neighbor{{
@@ -20,4 +24,8 @@ var wantNeighs = []Neighbor{{
 	Name: "hostname.two",
 	IP:   net.ParseIP("::ffff:ffff"),
 	MAC:  net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
+}, {
+	Name: "",
+	IP:   net.ParseIP("::1234"),
+	MAC:  net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
 }}
diff --git a/internal/aghnet/arpdb_linux_test.go b/internal/aghnet/arpdb_linux_test.go
index 0439c9b5..b9ed859f 100644
--- a/internal/aghnet/arpdb_linux_test.go
+++ b/internal/aghnet/arpdb_linux_test.go
@@ -16,14 +16,21 @@ import (
 
 const arpAOutputWrt = `
 IP address    HW type     Flags       HW address            Mask     Device
+1.2.3.4.5     0x1         0x2         aa:bb:cc:dd:ee:ff     *        wan
+1.2.3.4       0x1         0x2         12:34:56:78:910       *        wan
 192.168.1.2   0x1         0x2         ab:cd:ef:ab:cd:ef     *        wan
 ::ffff:ffff   0x1         0x2         ef:cd:ab:ef:cd:ab     *        wan`
 
 const arpAOutput = `
+invalid.mac (1.2.3.4) at 12:34:56:78:910 on el0 ifscope [ethernet]
+invalid.ip  (1.2.3.4.5) at ab:cd:ef:ab:cd:12 on ek0 ifscope [ethernet]
+invalid.fmt 1 at 12:cd:ef:ab:cd:ef on er0 ifscope [ethernet]
 ? (192.168.1.2) at ab:cd:ef:ab:cd:ef on en0 ifscope [ethernet]
 ? (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 100 seconds [ethernet]`
 
 const ipNeighOutput = `
+1.2.3.4.5 dev enp0s3 lladdr aa:bb:cc:dd:ee:ff DELAY
+1.2.3.4 dev enp0s3 lladdr 12:34:56:78:910 DELAY
 192.168.1.2 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef DELAY
 ::ffff:ffff dev enp0s3 lladdr ef:cd:ab:ef:cd:ab router STALE`
 
diff --git a/internal/aghnet/arpdb_openbsd_test.go b/internal/aghnet/arpdb_openbsd_test.go
index b1021513..915c17ff 100644
--- a/internal/aghnet/arpdb_openbsd_test.go
+++ b/internal/aghnet/arpdb_openbsd_test.go
@@ -9,6 +9,8 @@ import (
 
 const arpAOutput = `
 Host        Ethernet Address  Netif Expire    Flags
+1.2.3.4.5   aa:bb:cc:dd:ee:ff   em0 permanent
+1.2.3.4     12:34:56:78:910     em0 permanent
 192.168.1.2 ab:cd:ef:ab:cd:ef   em0 19m56s
 ::ffff:ffff ef:cd:ab:ef:cd:ab   em0 permanent l
 `
diff --git a/internal/aghnet/arpdb_test.go b/internal/aghnet/arpdb_test.go
index ce3da4fb..aa5c821a 100644
--- a/internal/aghnet/arpdb_test.go
+++ b/internal/aghnet/arpdb_test.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 	"sync"
 	"testing"
+	"testing/iotest"
 
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/testutil"
@@ -13,6 +14,15 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+func TestNewARPDB(t *testing.T) {
+	var a ARPDB
+	require.NotPanics(t, func() {
+		a = NewARPDB()
+	})
+
+	assert.NotNil(t, a)
+}
+
 // TestARPDB is the mock implementation of ARPDB to use in tests.
 type TestARPDB struct {
 	OnRefresh   func() (err error)
@@ -166,3 +176,46 @@ func TestCmdARPDB_arpa(t *testing.T) {
 		testutil.AssertErrorMsg(t, "cmd arpdb: running command: can't run", err)
 	})
 }
+
+func TestCmdARPDB_errors(t *testing.T) {
+	const errRead errors.Error = "can't read"
+
+	badReaderRunCmd := runCmdFunc(func() (r io.Reader, err error) {
+		return iotest.ErrReader(errRead), nil
+	})
+
+	a := &cmdARPDB{
+		runcmd: badReaderRunCmd,
+		parse:  parseArpA,
+		ns: &neighs{
+			mu: &sync.RWMutex{},
+			ns: make([]Neighbor, 0),
+		},
+	}
+
+	const wantErrMsg string = "cmd arpdb: scanning the output: " + string(errRead)
+
+	testutil.AssertErrorMsg(t, wantErrMsg, a.Refresh())
+}
+
+func TestEmptyARPDB(t *testing.T) {
+	a := EmptyARPDB{}
+
+	t.Run("refresh", func(t *testing.T) {
+		var err error
+		require.NotPanics(t, func() {
+			err = a.Refresh()
+		})
+
+		assert.NoError(t, err)
+	})
+
+	t.Run("neighbors", func(t *testing.T) {
+		var ns []Neighbor
+		require.NotPanics(t, func() {
+			ns = a.Neighbors()
+		})
+
+		assert.Empty(t, ns)
+	})
+}
diff --git a/internal/aghnet/hostscontainer.go b/internal/aghnet/hostscontainer.go
index 198d4c78..290dc1c0 100644
--- a/internal/aghnet/hostscontainer.go
+++ b/internal/aghnet/hostscontainer.go
@@ -368,8 +368,8 @@ func (hp *hostsParser) addPairs(ip net.IP, hosts []string) {
 	}
 }
 
-// writeRules writes the actual rule for the qtype and the PTR for the
-// host-ip pair into internal builders.
+// writeRules writes the actual rule for the qtype and the PTR for the host-ip
+// pair into internal builders.
 func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string) {
 	arpa, err := netutil.IPToReversedAddr(ip)
 	if err != nil {
diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go
index ecb70fa8..2d791c03 100644
--- a/internal/aghnet/net.go
+++ b/internal/aghnet/net.go
@@ -105,8 +105,7 @@ func GetValidNetInterfacesForWeb() ([]*NetInterface, error) {
 	ifaces, err := net.Interfaces()
 	if err != nil {
 		return nil, fmt.Errorf("couldn't get interfaces: %w", err)
-	}
-	if len(ifaces) == 0 {
+	} else if len(ifaces) == 0 {
 		return nil, errors.Error("couldn't find any legible interface")
 	}
 
diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go
index 34e99faa..b461cb44 100644
--- a/internal/aghnet/net_test.go
+++ b/internal/aghnet/net_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
+	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/stretchr/testify/assert"
@@ -130,3 +131,27 @@ func TestCheckPort(t *testing.T) {
 		assert.NoError(t, err)
 	})
 }
+
+func TestCollectAllIfacesAddrs(t *testing.T) {
+	addrs, err := CollectAllIfacesAddrs()
+	require.NoError(t, err)
+
+	assert.NotEmpty(t, addrs)
+}
+
+func TestIsAddrInUse(t *testing.T) {
+	t.Run("addr_in_use", func(t *testing.T) {
+		l, err := net.Listen("tcp", "0.0.0.0:0")
+		require.NoError(t, err)
+		testutil.CleanupAndRequireSuccess(t, l.Close)
+
+		_, err = net.Listen(l.Addr().Network(), l.Addr().String())
+		assert.True(t, IsAddrInUse(err))
+	})
+
+	t.Run("another", func(t *testing.T) {
+		const anotherErr errors.Error = "not addr in use"
+
+		assert.False(t, IsAddrInUse(anotherErr))
+	})
+}
diff --git a/internal/aghnet/systemresolvers.go b/internal/aghnet/systemresolvers.go
index 777127a3..13fbeb32 100644
--- a/internal/aghnet/systemresolvers.go
+++ b/internal/aghnet/systemresolvers.go
@@ -1,11 +1,5 @@
 package aghnet
 
-import (
-	"time"
-
-	"github.com/AdguardTeam/golibs/log"
-)
-
 // DefaultRefreshIvl is the default period of time between refreshing cached
 // addresses.
 // const DefaultRefreshIvl = 5 * time.Minute
@@ -16,39 +10,21 @@ type HostGenFunc func() (host string)
 
 // SystemResolvers helps to work with local resolvers' addresses provided by OS.
 type SystemResolvers interface {
-	// Get returns the slice of local resolvers' addresses.  It should be
-	// safe for concurrent use.
+	// Get returns the slice of local resolvers' addresses.  It must be safe for
+	// concurrent use.
 	Get() (rs []string)
-	// refresh refreshes the local resolvers' addresses cache.  It should be
-	// safe for concurrent use.
+	// refresh refreshes the local resolvers' addresses cache.  It must be safe
+	// for concurrent use.
 	refresh() (err error)
 }
 
-// refreshWithTicker refreshes the cache of sr after each tick form tickCh.
-func refreshWithTicker(sr SystemResolvers, tickCh <-chan time.Time) {
-	defer log.OnPanic("systemResolvers")
-
-	// TODO(e.burkov): Implement a functionality to stop ticker.
-	for range tickCh {
-		err := sr.refresh()
-		if err != nil {
-			log.Error("systemResolvers: error in refreshing goroutine: %s", err)
-
-			continue
-		}
-
-		log.Debug("systemResolvers: local addresses cache is refreshed")
-	}
-}
-
 // NewSystemResolvers returns a SystemResolvers with the cache refresh rate
 // defined by refreshIvl. It disables auto-resfreshing if refreshIvl is 0.  If
 // nil is passed for hostGenFunc, the default generator will be used.
 func NewSystemResolvers(
-	refreshIvl time.Duration,
 	hostGenFunc HostGenFunc,
 ) (sr SystemResolvers, err error) {
-	sr = newSystemResolvers(refreshIvl, hostGenFunc)
+	sr = newSystemResolvers(hostGenFunc)
 
 	// Fill cache.
 	err = sr.refresh()
@@ -56,11 +32,5 @@ func NewSystemResolvers(
 		return nil, err
 	}
 
-	if refreshIvl > 0 {
-		ticker := time.NewTicker(refreshIvl)
-
-		go refreshWithTicker(sr, ticker.C)
-	}
-
 	return sr, nil
 }
diff --git a/internal/aghnet/systemresolvers_others.go b/internal/aghnet/systemresolvers_others.go
index 8acdb6c7..f8afa286 100644
--- a/internal/aghnet/systemresolvers_others.go
+++ b/internal/aghnet/systemresolvers_others.go
@@ -24,12 +24,15 @@ func defaultHostGen() (host string) {
 
 // systemResolvers is a default implementation of SystemResolvers interface.
 type systemResolvers struct {
-	resolver    *net.Resolver
-	hostGenFunc HostGenFunc
-
-	// addrs is the set that contains cached local resolvers' addresses.
-	addrs     *stringutil.Set
+	// addrsLock protects addrs.
 	addrsLock sync.RWMutex
+	// addrs is the set that contains cached local resolvers' addresses.
+	addrs *stringutil.Set
+
+	// resolver is used to fetch the resolvers' addresses.
+	resolver *net.Resolver
+	// hostGenFunc generates hosts to resolve.
+	hostGenFunc HostGenFunc
 }
 
 const (
@@ -44,6 +47,7 @@ const (
 	errUnexpectedHostFormat errors.Error = "unexpected host format"
 )
 
+// refresh implements the SystemResolvers interface for *systemResolvers.
 func (sr *systemResolvers) refresh() (err error) {
 	defer func() { err = errors.Annotate(err, "systemResolvers: %w") }()
 
@@ -56,7 +60,7 @@ func (sr *systemResolvers) refresh() (err error) {
 	return err
 }
 
-func newSystemResolvers(refreshIvl time.Duration, hostGenFunc HostGenFunc) (sr SystemResolvers) {
+func newSystemResolvers(hostGenFunc HostGenFunc) (sr SystemResolvers) {
 	if hostGenFunc == nil {
 		hostGenFunc = defaultHostGen
 	}
@@ -76,19 +80,18 @@ func newSystemResolvers(refreshIvl time.Duration, hostGenFunc HostGenFunc) (sr S
 func validateDialedHost(host string) (err error) {
 	defer func() { err = errors.Annotate(err, "parsing %q: %w", host) }()
 
-	var ipStr string
 	parts := strings.Split(host, "%")
 	switch len(parts) {
 	case 1:
-		ipStr = host
+		// host
 	case 2:
 		// Remove the zone and check the IP address part.
-		ipStr = parts[0]
+		host = parts[0]
 	default:
 		return errUnexpectedHostFormat
 	}
 
-	if net.ParseIP(ipStr) == nil {
+	if _, err = netutil.ParseIP(host); err != nil {
 		return errBadAddrPassed
 	}
 
diff --git a/internal/aghnet/systemresolvers_others_test.go b/internal/aghnet/systemresolvers_others_test.go
index 79abeca2..f7cf9ef0 100644
--- a/internal/aghnet/systemresolvers_others_test.go
+++ b/internal/aghnet/systemresolvers_others_test.go
@@ -6,37 +6,32 @@ package aghnet
 import (
 	"context"
 	"testing"
-	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
-func createTestSystemResolversImp(
+func createTestSystemResolversImpl(
 	t *testing.T,
-	refreshDur time.Duration,
 	hostGenFunc HostGenFunc,
 ) (imp *systemResolvers) {
 	t.Helper()
 
-	sr := createTestSystemResolvers(t, refreshDur, hostGenFunc)
+	sr := createTestSystemResolvers(t, hostGenFunc)
+	require.IsType(t, (*systemResolvers)(nil), sr)
 
-	var ok bool
-	imp, ok = sr.(*systemResolvers)
-	require.True(t, ok)
-
-	return imp
+	return sr.(*systemResolvers)
 }
 
 func TestSystemResolvers_Refresh(t *testing.T) {
 	t.Run("expected_error", func(t *testing.T) {
-		sr := createTestSystemResolvers(t, 0, nil)
+		sr := createTestSystemResolvers(t, nil)
 
 		assert.NoError(t, sr.refresh())
 	})
 
 	t.Run("unexpected_error", func(t *testing.T) {
-		_, err := NewSystemResolvers(0, func() string {
+		_, err := NewSystemResolvers(func() string {
 			return "127.0.0.1::123"
 		})
 		assert.Error(t, err)
@@ -44,7 +39,7 @@ func TestSystemResolvers_Refresh(t *testing.T) {
 }
 
 func TestSystemResolvers_DialFunc(t *testing.T) {
-	imp := createTestSystemResolversImp(t, 0, nil)
+	imp := createTestSystemResolversImpl(t, nil)
 
 	testCases := []struct {
 		want    error
@@ -52,7 +47,7 @@ func TestSystemResolvers_DialFunc(t *testing.T) {
 		address string
 	}{{
 		want:    errFakeDial,
-		name:    "valid",
+		name:    "valid_ipv4",
 		address: "127.0.0.1",
 	}, {
 		want:    errFakeDial,
diff --git a/internal/aghnet/systemresolvers_test.go b/internal/aghnet/systemresolvers_test.go
index 13145817..0a19490d 100644
--- a/internal/aghnet/systemresolvers_test.go
+++ b/internal/aghnet/systemresolvers_test.go
@@ -2,7 +2,6 @@ package aghnet
 
 import (
 	"testing"
-	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -10,13 +9,12 @@ import (
 
 func createTestSystemResolvers(
 	t *testing.T,
-	refreshDur time.Duration,
 	hostGenFunc HostGenFunc,
 ) (sr SystemResolvers) {
 	t.Helper()
 
 	var err error
-	sr, err = NewSystemResolvers(refreshDur, hostGenFunc)
+	sr, err = NewSystemResolvers(hostGenFunc)
 	require.NoError(t, err)
 	require.NotNil(t, sr)
 
@@ -24,8 +22,14 @@ func createTestSystemResolvers(
 }
 
 func TestSystemResolvers_Get(t *testing.T) {
-	sr := createTestSystemResolvers(t, 0, nil)
-	assert.NotEmpty(t, sr.Get())
+	sr := createTestSystemResolvers(t, nil)
+
+	var rs []string
+	require.NotPanics(t, func() {
+		rs = sr.Get()
+	})
+
+	assert.NotEmpty(t, rs)
 }
 
 // TODO(e.burkov): Write tests for refreshWithTicker.
diff --git a/internal/aghnet/systemresolvers_windows.go b/internal/aghnet/systemresolvers_windows.go
index 5acdfa85..f82d6e7e 100644
--- a/internal/aghnet/systemresolvers_windows.go
+++ b/internal/aghnet/systemresolvers_windows.go
@@ -11,7 +11,6 @@ import (
 	"os/exec"
 	"strings"
 	"sync"
-	"time"
 
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
@@ -27,7 +26,7 @@ type systemResolvers struct {
 	addrsLock sync.RWMutex
 }
 
-func newSystemResolvers(refreshIvl time.Duration, _ HostGenFunc) (sr SystemResolvers) {
+func newSystemResolvers(_ HostGenFunc) (sr SystemResolvers) {
 	return &systemResolvers{}
 }
 
diff --git a/internal/aghnet/testdata/proc_net_arp b/internal/aghnet/testdata/proc_net_arp
index 07d214e1..8460c8bb 100644
--- a/internal/aghnet/testdata/proc_net_arp
+++ b/internal/aghnet/testdata/proc_net_arp
@@ -1,4 +1,6 @@
 IP address     HW type     Flags       HW address            Mask     Device
 192.168.1.2    0x1         0x2         ab:cd:ef:ab:cd:ef     *        wan
 ::ffff:ffff    0x1         0x0         ef:cd:ab:ef:cd:ab     *        br-lan
-0.0.0.0        0x0         0x0         00:00:00:00:00:00     *        unspec
\ No newline at end of file
+0.0.0.0        0x0         0x0         00:00:00:00:00:00     *        unspec
+1.2.3.4.5      0x1         0x2         aa:bb:cc:dd:ee:ff     *        wan
+1.2.3.4        0x1         0x2         12:34:56:78:910       *        wan
\ No newline at end of file
diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go
index a5b0098a..c0cd0e55 100644
--- a/internal/dnsforward/dnsforward.go
+++ b/internal/dnsforward/dnsforward.go
@@ -173,7 +173,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
 
 	// TODO(e.burkov): Enable the refresher after the actual implementation
 	// passes the public testing.
-	s.sysResolvers, err = aghnet.NewSystemResolvers(0, nil)
+	s.sysResolvers, err = aghnet.NewSystemResolvers(nil)
 	if err != nil {
 		return nil, fmt.Errorf("initializing system resolvers: %w", err)
 	}
diff --git a/internal/home/clients.go b/internal/home/clients.go
index 9230e565..fe15e514 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -257,6 +257,8 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
 }
 
 func (clients *clientsContainer) periodicUpdate() {
+	defer log.OnPanic("clients container")
+
 	for {
 		clients.Reload()
 		time.Sleep(clientsUpdatePeriod)
diff --git a/internal/home/home.go b/internal/home/home.go
index 4e4d3aee..5fae42c8 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -291,14 +291,7 @@ func setupConfig(args options) (err error) {
 		}
 	}
 
-	var arpdb aghnet.ARPDB
-	arpdb, err = aghnet.NewARPDB()
-	if err != nil {
-		log.Info("warning: creating arpdb: %s; using stub", err)
-
-		arpdb = aghnet.EmptyARPDB{}
-	}
-
+	arpdb := aghnet.NewARPDB()
 	Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts, arpdb)
 
 	if args.bindPort != 0 {

From 3603b1fcab3a39cbc6402ae0334133f58624e335 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 24 Mar 2022 17:12:41 +0300
Subject: [PATCH 024/143] Pull request: home: fix types

Updates #4424.

Squashed commit of the following:

commit 784b4940d46ce74edbfbbde6e5b24f95dcb4bc70
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Mar 24 17:07:41 2022 +0300

    home: fix types
---
 internal/home/config.go | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/internal/home/config.go b/internal/home/config.go
index c81de19e..aa8450be 100644
--- a/internal/home/config.go
+++ b/internal/home/config.go
@@ -333,8 +333,14 @@ type tcpPort int
 // ports should be either a udpPort or a tcpPort.
 func addPorts(uc aghalg.UniqChecker, ports ...interface{}) {
 	for _, p := range ports {
+		// Use separate cases for tcpPort and udpPort so that the untyped
+		// constant zero is converted to the appropriate type.
 		switch p := p.(type) {
-		case tcpPort, udpPort:
+		case tcpPort:
+			if p != 0 {
+				uc.Add(p)
+			}
+		case udpPort:
 			if p != 0 {
 				uc.Add(p)
 			}

From 0d562a7b1fb1ccc4706e844ae4d12d13f39baeaa Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 28 Mar 2022 17:05:19 +0300
Subject: [PATCH 025/143] Pull request: add go sumdb env

Merge in DNS/adguard-home from cn-sumdb to master

Squashed commit of the following:

commit 439973292f473efa72fb6a733a32be45e634274e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 28 16:51:28 2022 +0300

    Makefile: add go sumdb env
---
 Makefile | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Makefile b/Makefile
index 51269440..5d9e7a7c 100644
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@ DIST_DIR = dist
 # See https://unix.stackexchange.com/q/646255/105635.
 GO.MACRO = $${GO:-go}
 GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
+GOSUMDB = sum.golang.google.cn
 GPG_KEY = devteam@adguard.com
 GPG_KEY_PASSPHRASE = not-a-real-password
 NPM = npm
@@ -56,6 +57,7 @@ ENV = env\
 	DIST_DIR='$(DIST_DIR)'\
 	GO="$(GO.MACRO)"\
 	GOPROXY='$(GOPROXY)'\
+	GOSUMDB='$(GOSUMDB)'\
 	PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
 	RACE='$(RACE)'\
 	SIGN='$(SIGN)'\

From f31ffcc5d1bb1789c6b2e59c0ca5c33dc515cb8e Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 29 Mar 2022 16:21:22 +0300
Subject: [PATCH 026/143] Pull request: aghnet: fix catching timeout errors

Merge in DNS/adguard-home from fix-is-timeout to master

Squashed commit of the following:

commit b0fefd01f27a835a34e44beb2eb2c34027960a51
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 29 15:57:06 2022 +0300

    aghnet: fix catching timeout errors
---
 CHANGELOG.md                 |  4 ++++
 internal/aghnet/dhcp_unix.go | 39 +++++++++++++-----------------------
 2 files changed, 18 insertions(+), 25 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 65e8a64b..6d1e9260 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -81,6 +81,10 @@ In this release, the schema version has changed from 12 to 13.
 
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
 
+### Fixed
+
+- I/O timeout errors on checking another DHCP server.
+
 ### Removed
 
 - Go 1.16 support.
diff --git a/internal/aghnet/dhcp_unix.go b/internal/aghnet/dhcp_unix.go
index fd2b9b93..4791d0e5 100644
--- a/internal/aghnet/dhcp_unix.go
+++ b/internal/aghnet/dhcp_unix.go
@@ -156,7 +156,7 @@ func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, n
 	b := make([]byte, 1500)
 	n, _, err := c.ReadFrom(b)
 	if err != nil {
-		if isTimeout(err) {
+		if errors.Is(err, os.ErrDeadlineExceeded) {
 			log.Debug("dhcpv4: didn't receive dhcp response")
 
 			return false, false, nil
@@ -176,20 +176,21 @@ func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, n
 
 	log.Debug("dhcpv4: received message from server: %s", response.Summary())
 
-	if !(response.OpCode == dhcpv4.OpcodeBootReply &&
-		response.HWType == iana.HWTypeEthernet &&
-		bytes.Equal(response.ClientHWAddr, iface.HardwareAddr) &&
-		bytes.Equal(response.TransactionID[:], req.TransactionID[:]) &&
-		response.Options.Has(dhcpv4.OptionDHCPMessageType)) {
-
-		log.Debug("dhcpv4: received message from server doesn't match our request")
+	switch {
+	case
+		response.OpCode != dhcpv4.OpcodeBootReply,
+		response.HWType != iana.HWTypeEthernet,
+		!bytes.Equal(response.ClientHWAddr, iface.HardwareAddr),
+		response.TransactionID != req.TransactionID,
+		!response.Options.Has(dhcpv4.OptionDHCPMessageType):
+		log.Debug("dhcpv4: received response doesn't match the request")
 
 		return false, true, nil
+	default:
+		log.Tracef("dhcpv4: the packet is from an active dhcp server")
+
+		return true, false, nil
 	}
-
-	log.Tracef("dhcpv4: the packet is from an active dhcp server")
-
-	return true, false, nil
 }
 
 // checkOtherDHCPv6 sends a DHCP request to the specified network interface, and
@@ -275,7 +276,7 @@ func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error)
 
 	n, _, err := c.ReadFrom(b)
 	if err != nil {
-		if isTimeout(err) {
+		if errors.Is(err, os.ErrDeadlineExceeded) {
 			log.Debug("dhcpv6: didn't receive dhcp response")
 
 			return false, false, nil
@@ -318,15 +319,3 @@ func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error)
 
 	return true, false, nil
 }
-
-// isTimeout returns true if err is an operation timeout error from net package.
-//
-// TODO(e.burkov):  Consider moving into netutil.
-func isTimeout(err error) (ok bool) {
-	var operr *net.OpError
-	if errors.As(err, &operr) {
-		return operr.Timeout()
-	}
-
-	return false
-}

From 047970e5eef1f054441b3ca50becdbe137fb41e4 Mon Sep 17 00:00:00 2001
From: Peter Dave Hello <hsu@peterdavehello.org>
Date: Wed, 30 Mar 2022 18:02:50 +0800
Subject: [PATCH 027/143] Enable code block syntax hightlight in README.md

It'll make it just a little bit easier to read it
---
 README.md | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index 00664365..7044eece 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ It operates as a DNS server that re-routes tracking domains to a "black hole", t
 
 ### Automated install (Linux and Mac)
 Run the following command in your terminal:
-```
+```sh
 curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v
 ```
 
@@ -212,11 +212,11 @@ Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Ma
 In order to do this, specify `GOOS` and `GOARCH` env variables before running make.
 
 For example:
-```
+```sh
 env GOOS='linux' GOARCH='arm64' make
 ```
 Or:
-```
+```sh
 make GOOS='linux' GOARCH='arm64'
 ```
 
@@ -228,7 +228,7 @@ You'll need this to prepare a release build:
 
 Commands:
 
-```
+```sh
 make build-release CHANNEL='...' VERSION='...'
 ```
 
@@ -271,12 +271,12 @@ There are three options how you can install an unstable version:
 3. Standalone builds. Use the automated installation script or look for the available builds below.
 
 Beta:
-```
+```sh
 curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c beta
 ```
 
 Edge:
-```
+```sh
 curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c edge
 ```
 

From 5e71f5df6a8cd12a65fecd2f1e28b2aa69609193 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 30 Mar 2022 15:11:57 +0300
Subject: [PATCH 028/143] Pull request: 2846 cover aghnet vol.3

Merge in DNS/adguard-home from 2846-cover-aghnet-vol.3 to master

Updates #2846.

Squashed commit of the following:

commit cb22987c43c17bbc8d098e65639cc84e2284bc7b
Merge: cf995e9d f31ffcc5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 30 15:01:10 2022 +0300

    Merge branch 'master' into 2846-cover-aghnet-vol.3

commit cf995e9dce635f16e10406a61e2ab12f06407f1f
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 28 18:29:50 2022 +0300

    aghnet: imp tests

commit bc225fe8800633b29216840bc7d5b82d7c2d2bfb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 28 18:03:06 2022 +0300

    aghnet: imp tests

commit a82eb6045495b94a2e81ced9a3ef5bfe65788e56
Merge: f8081249 0d562a7b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 28 17:39:13 2022 +0300

    Merge branch 'master' into 2846-cover-aghnet-vol.3

commit f80812490c49f69655d409c6f015b069affa2f19
Merge: edccaa79 3603b1fc
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 28 17:29:20 2022 +0300

    Merge branch 'master' into 2846-cover-aghnet-vol.3

commit edccaa79fca061ffeea1985c293eed123b16a09c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 28 13:53:40 2022 +0300

    aghnet: imp tests

commit 7c5028c92f0a6680516bda67c73e794182c9b825
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 25 18:01:28 2022 +0300

    aghnet: imp code & docs

commit 7897c6b13e9be340ae8a71947a8a0bab82c682eb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 25 17:11:46 2022 +0300

    aghnet: imp coverage

commit 1eef110af3bf721a0275c695bf27c31815abff04
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 21:10:29 2022 +0300

    all: return byte slice
---
 internal/aghnet/arpdb.go               |  75 +++----
 internal/aghnet/arpdb_bsd.go           |   5 +-
 internal/aghnet/arpdb_linux.go         |  30 +--
 internal/aghnet/arpdb_linux_test.go    |  67 ++++---
 internal/aghnet/arpdb_openbsd.go       |   5 +-
 internal/aghnet/arpdb_test.go          |  41 ++--
 internal/aghnet/arpdb_unix.go          |  13 --
 internal/aghnet/arpdb_windows.go       |  11 +-
 internal/aghnet/hostscontainer_test.go |   4 +
 internal/aghnet/net.go                 |  43 ++--
 internal/aghnet/net_darwin.go          | 134 +++++++------
 internal/aghnet/net_darwin_test.go     | 261 +++++++++++++++++++++++++
 internal/aghnet/net_freebsd.go         |   2 +-
 internal/aghnet/net_freebsd_test.go    |  72 ++++---
 internal/aghnet/net_linux.go           |  29 ++-
 internal/aghnet/net_linux_test.go      | 202 ++++++++++---------
 internal/aghnet/net_openbsd.go         |   2 +-
 internal/aghnet/net_openbsd_test.go    |  66 ++++---
 internal/aghnet/net_test.go            |  99 +++++++++-
 internal/aghos/os.go                   |  14 +-
 internal/home/service_openbsd.go       |   5 +-
 21 files changed, 789 insertions(+), 391 deletions(-)
 delete mode 100644 internal/aghnet/arpdb_unix.go
 create mode 100644 internal/aghnet/net_darwin_test.go

diff --git a/internal/aghnet/arpdb.go b/internal/aghnet/arpdb.go
index afb880c3..4909af5f 100644
--- a/internal/aghnet/arpdb.go
+++ b/internal/aghnet/arpdb.go
@@ -2,13 +2,11 @@ package aghnet
 
 import (
 	"bufio"
+	"bytes"
 	"fmt"
-	"io"
 	"net"
-	"strings"
 	"sync"
 
-	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/netutil"
 )
@@ -116,50 +114,33 @@ func (ns *neighs) reset(with []Neighbor) {
 // of Neighbors.
 type parseNeighsFunc func(sc *bufio.Scanner, lenHint int) (ns []Neighbor)
 
-// runCmdFunc is the function that runs some command and returns its output
-// wrapped to be a io.Reader.
-type runCmdFunc func() (r io.Reader, err error)
-
 // cmdARPDB is the implementation of the ARPDB that uses command line to
 // retrieve data.
 type cmdARPDB struct {
-	parse  parseNeighsFunc
-	runcmd runCmdFunc
-	ns     *neighs
+	parse parseNeighsFunc
+	ns    *neighs
+	cmd   string
+	args  []string
 }
 
 // type check
 var _ ARPDB = (*cmdARPDB)(nil)
 
-// runCmd runs the cmd with it's args and returns the result wrapped to be an
-// io.Reader.  The error is returned either if the exit code retured by command
-// not equals 0 or the execution itself failed.
-func runCmd(cmd string, args ...string) (r io.Reader, err error) {
-	var code int
-	var out string
-	code, out, err = aghos.RunCommand(cmd, args...)
-	if err != nil {
-		return nil, err
-	} else if code != 0 {
-		return nil, fmt.Errorf("unexpected exit code %d", code)
-	}
-
-	return strings.NewReader(out), nil
-}
-
 // Refresh implements the ARPDB interface for *cmdARPDB.
 func (arp *cmdARPDB) Refresh() (err error) {
 	defer func() { err = errors.Annotate(err, "cmd arpdb: %w") }()
 
-	var r io.Reader
-	r, err = arp.runcmd()
+	code, out, err := aghosRunCommand(arp.cmd, arp.args...)
 	if err != nil {
 		return fmt.Errorf("running command: %w", err)
+	} else if code != 0 {
+		return fmt.Errorf("running command: unexpected exit code %d", code)
 	}
 
-	sc := bufio.NewScanner(r)
+	sc := bufio.NewScanner(bytes.NewReader(out))
 	ns := arp.parse(sc, arp.ns.len())
 	if err = sc.Err(); err != nil {
+		// TODO(e.burkov):  This error seems unreachable.  Investigate.
 		return fmt.Errorf("scanning the output: %w", err)
 	}
 
@@ -180,8 +161,7 @@ func (arp *cmdARPDB) Neighbors() (ns []Neighbor) {
 type arpdbs struct {
 	// arps is the set of ARPDB implementations to range through.
 	arps []ARPDB
-	// last is the last succeeded ARPDB index.
-	last int
+	neighs
 }
 
 // newARPDBs returns a properly initialized *arpdbs.  It begins refreshing from
@@ -189,7 +169,10 @@ type arpdbs struct {
 func newARPDBs(arps ...ARPDB) (arp *arpdbs) {
 	return &arpdbs{
 		arps: arps,
-		last: 0,
+		neighs: neighs{
+			mu: &sync.RWMutex{},
+			ns: make([]Neighbor, 0),
+		},
 	}
 }
 
@@ -199,20 +182,18 @@ var _ ARPDB = (*arpdbs)(nil)
 // Refresh implements the ARPDB interface for *arpdbs.
 func (arp *arpdbs) Refresh() (err error) {
 	var errs []error
-	l := len(arp.arps)
-	// Start from the last succeeded implementation.
-	for i := 0; i < l; i++ {
-		cur := (arp.last + i) % l
-		err = arp.arps[cur].Refresh()
-		if err == nil {
-			// The succeeded implementation found so update the last succeeded
-			// index.
-			arp.last = cur
 
-			return nil
+	for _, a := range arp.arps {
+		err = a.Refresh()
+		if err != nil {
+			errs = append(errs, err)
+
+			continue
 		}
 
-		errs = append(errs, err)
+		arp.reset(a.Neighbors())
+
+		return nil
 	}
 
 	if len(errs) > 0 {
@@ -223,10 +204,8 @@ func (arp *arpdbs) Refresh() (err error) {
 }
 
 // Neighbors implements the ARPDB interface for *arpdbs.
+//
+// TODO(e.burkov):  Think of a way to avoid cloning the slice twice.
 func (arp *arpdbs) Neighbors() (ns []Neighbor) {
-	if l := len(arp.arps); l > 0 && arp.last < l {
-		return arp.arps[arp.last].Neighbors()
-	}
-
-	return nil
+	return arp.clone()
 }
diff --git a/internal/aghnet/arpdb_bsd.go b/internal/aghnet/arpdb_bsd.go
index fe00418a..a82da76c 100644
--- a/internal/aghnet/arpdb_bsd.go
+++ b/internal/aghnet/arpdb_bsd.go
@@ -15,12 +15,13 @@ import (
 
 func newARPDB() *cmdARPDB {
 	return &cmdARPDB{
-		parse:  parseArpA,
-		runcmd: rcArpA,
+		parse: parseArpA,
 		ns: &neighs{
 			mu: &sync.RWMutex{},
 			ns: make([]Neighbor, 0),
 		},
+		cmd:  "arp",
+		args: []string{"-a"},
 	}
 }
 
diff --git a/internal/aghnet/arpdb_linux.go b/internal/aghnet/arpdb_linux.go
index 976d8b7a..3d391f29 100644
--- a/internal/aghnet/arpdb_linux.go
+++ b/internal/aghnet/arpdb_linux.go
@@ -6,7 +6,6 @@ package aghnet
 import (
 	"bufio"
 	"fmt"
-	"io"
 	"io/fs"
 	"net"
 	"strings"
@@ -34,11 +33,25 @@ func newARPDB() (arp *arpdbs) {
 
 	return newARPDBs(
 		// Try /proc/net/arp first.
-		&fsysARPDB{ns: ns, fsys: aghos.RootDirFS(), filename: "proc/net/arp"},
-		// Try "arp -a" then.
-		&cmdARPDB{parse: parseF, runcmd: rcArpA, ns: ns},
-		// Try "ip neigh" finally.
-		&cmdARPDB{parse: parseIPNeigh, runcmd: rcIPNeigh, ns: ns},
+		&fsysARPDB{
+			ns:       ns,
+			fsys:     rootDirFS,
+			filename: "proc/net/arp",
+		},
+		// Then, try "arp -a".
+		&cmdARPDB{
+			parse: parseF,
+			ns:    ns,
+			cmd:   "arp",
+			args:  []string{"-a"},
+		},
+		// Finally, try "ip neigh".
+		&cmdARPDB{
+			parse: parseIPNeigh,
+			ns:    ns,
+			cmd:   "ip",
+			args:  []string{"neigh"},
+		},
 	)
 }
 
@@ -187,11 +200,6 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
 	return ns
 }
 
-// rcIPNeigh runs "ip neigh".
-func rcIPNeigh() (r io.Reader, err error) {
-	return runCmd("ip", "neigh")
-}
-
 // parseIPNeigh parses the output of the "ip neigh" command on Linux.  The
 // expected input format:
 //
diff --git a/internal/aghnet/arpdb_linux_test.go b/internal/aghnet/arpdb_linux_test.go
index b9ed859f..46d87150 100644
--- a/internal/aghnet/arpdb_linux_test.go
+++ b/internal/aghnet/arpdb_linux_test.go
@@ -4,11 +4,10 @@
 package aghnet
 
 import (
-	"io"
 	"net"
-	"strings"
 	"sync"
 	"testing"
+	"testing/fstest"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -43,6 +42,8 @@ var wantNeighs = []Neighbor{{
 }}
 
 func TestFSysARPDB(t *testing.T) {
+	require.NoError(t, fstest.TestFS(testdata, "proc_net_arp"))
+
 	a := &fsysARPDB{
 		ns: &neighs{
 			mu: &sync.RWMutex{},
@@ -59,33 +60,43 @@ func TestFSysARPDB(t *testing.T) {
 	assert.Equal(t, wantNeighs, ns)
 }
 
-func TestCmdARPDB_arpawrt(t *testing.T) {
-	a := &cmdARPDB{
-		parse:  parseArpAWrt,
-		runcmd: func() (r io.Reader, err error) { return strings.NewReader(arpAOutputWrt), nil },
-		ns: &neighs{
-			mu: &sync.RWMutex{},
-			ns: make([]Neighbor, 0),
-		},
+func TestCmdARPDB_linux(t *testing.T) {
+	sh := mapShell{
+		"arp -a":   {err: nil, out: arpAOutputWrt, code: 0},
+		"ip neigh": {err: nil, out: ipNeighOutput, code: 0},
 	}
+	substShell(t, sh.RunCmd)
 
-	err := a.Refresh()
-	require.NoError(t, err)
+	t.Run("wrt", func(t *testing.T) {
+		a := &cmdARPDB{
+			parse: parseArpAWrt,
+			cmd:   "arp",
+			args:  []string{"-a"},
+			ns: &neighs{
+				mu: &sync.RWMutex{},
+				ns: make([]Neighbor, 0),
+			},
+		}
 
-	assert.Equal(t, wantNeighs, a.Neighbors())
-}
-
-func TestCmdARPDB_ipneigh(t *testing.T) {
-	a := &cmdARPDB{
-		parse:  parseIPNeigh,
-		runcmd: func() (r io.Reader, err error) { return strings.NewReader(ipNeighOutput), nil },
-		ns: &neighs{
-			mu: &sync.RWMutex{},
-			ns: make([]Neighbor, 0),
-		},
-	}
-	err := a.Refresh()
-	require.NoError(t, err)
-
-	assert.Equal(t, wantNeighs, a.Neighbors())
+		err := a.Refresh()
+		require.NoError(t, err)
+
+		assert.Equal(t, wantNeighs, a.Neighbors())
+	})
+
+	t.Run("ip_neigh", func(t *testing.T) {
+		a := &cmdARPDB{
+			parse: parseIPNeigh,
+			cmd:   "ip",
+			args:  []string{"neigh"},
+			ns: &neighs{
+				mu: &sync.RWMutex{},
+				ns: make([]Neighbor, 0),
+			},
+		}
+		err := a.Refresh()
+		require.NoError(t, err)
+
+		assert.Equal(t, wantNeighs, a.Neighbors())
+	})
 }
diff --git a/internal/aghnet/arpdb_openbsd.go b/internal/aghnet/arpdb_openbsd.go
index 805cb459..a00ffa85 100644
--- a/internal/aghnet/arpdb_openbsd.go
+++ b/internal/aghnet/arpdb_openbsd.go
@@ -14,12 +14,13 @@ import (
 
 func newARPDB() *cmdARPDB {
 	return &cmdARPDB{
-		runcmd: rcArpA,
-		parse:  parseArpA,
+		parse: parseArpA,
 		ns: &neighs{
 			mu: &sync.RWMutex{},
 			ns: make([]Neighbor, 0),
 		},
+		cmd:  "arp",
+		args: []string{"-a"},
 	}
 }
 
diff --git a/internal/aghnet/arpdb_test.go b/internal/aghnet/arpdb_test.go
index aa5c821a..75778b74 100644
--- a/internal/aghnet/arpdb_test.go
+++ b/internal/aghnet/arpdb_test.go
@@ -1,12 +1,9 @@
 package aghnet
 
 import (
-	"io"
 	"net"
-	"strings"
 	"sync"
 	"testing"
-	"testing/iotest"
 
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/testutil"
@@ -16,9 +13,7 @@ import (
 
 func TestNewARPDB(t *testing.T) {
 	var a ARPDB
-	require.NotPanics(t, func() {
-		a = NewARPDB()
-	})
+	require.NotPanics(t, func() { a = NewARPDB() })
 
 	assert.NotNil(t, a)
 }
@@ -135,11 +130,11 @@ func TestARPDBS(t *testing.T) {
 		assert.Equal(t, 1, succRefrCount)
 		assert.NotEmpty(t, a.Neighbors())
 
-		// Only the last succeeded ARPDB should be used.
+		// Unstable ARPDB should refresh successfully again.
 		err = a.Refresh()
 		require.NoError(t, err)
 
-		assert.Equal(t, 2, succRefrCount)
+		assert.Equal(t, 1, succRefrCount)
 		assert.NotEmpty(t, a.Neighbors())
 	})
 
@@ -153,6 +148,7 @@ func TestARPDBS(t *testing.T) {
 
 func TestCmdARPDB_arpa(t *testing.T) {
 	a := &cmdARPDB{
+		cmd:   "cmd",
 		parse: parseArpA,
 		ns: &neighs{
 			mu: &sync.RWMutex{},
@@ -161,7 +157,8 @@ func TestCmdARPDB_arpa(t *testing.T) {
 	}
 
 	t.Run("arp_a", func(t *testing.T) {
-		a.runcmd = func() (r io.Reader, err error) { return strings.NewReader(arpAOutput), nil }
+		sh := theOnlyCmd("cmd", 0, arpAOutput, nil)
+		substShell(t, sh.RunCmd)
 
 		err := a.Refresh()
 		require.NoError(t, err)
@@ -170,32 +167,20 @@ func TestCmdARPDB_arpa(t *testing.T) {
 	})
 
 	t.Run("runcmd_error", func(t *testing.T) {
-		a.runcmd = func() (r io.Reader, err error) { return nil, errors.Error("can't run") }
+		sh := theOnlyCmd("cmd", 0, "", errors.Error("can't run"))
+		substShell(t, sh.RunCmd)
 
 		err := a.Refresh()
 		testutil.AssertErrorMsg(t, "cmd arpdb: running command: can't run", err)
 	})
-}
 
-func TestCmdARPDB_errors(t *testing.T) {
-	const errRead errors.Error = "can't read"
+	t.Run("bad_code", func(t *testing.T) {
+		sh := theOnlyCmd("cmd", 1, "", nil)
+		substShell(t, sh.RunCmd)
 
-	badReaderRunCmd := runCmdFunc(func() (r io.Reader, err error) {
-		return iotest.ErrReader(errRead), nil
+		err := a.Refresh()
+		testutil.AssertErrorMsg(t, "cmd arpdb: running command: unexpected exit code 1", err)
 	})
-
-	a := &cmdARPDB{
-		runcmd: badReaderRunCmd,
-		parse:  parseArpA,
-		ns: &neighs{
-			mu: &sync.RWMutex{},
-			ns: make([]Neighbor, 0),
-		},
-	}
-
-	const wantErrMsg string = "cmd arpdb: scanning the output: " + string(errRead)
-
-	testutil.AssertErrorMsg(t, wantErrMsg, a.Refresh())
 }
 
 func TestEmptyARPDB(t *testing.T) {
diff --git a/internal/aghnet/arpdb_unix.go b/internal/aghnet/arpdb_unix.go
deleted file mode 100644
index 50346f92..00000000
--- a/internal/aghnet/arpdb_unix.go
+++ /dev/null
@@ -1,13 +0,0 @@
-//go:build !windows
-// +build !windows
-
-package aghnet
-
-import (
-	"io"
-)
-
-// rcArpA runs "arp -a".
-func rcArpA() (r io.Reader, err error) {
-	return runCmd("arp", "-a")
-}
diff --git a/internal/aghnet/arpdb_windows.go b/internal/aghnet/arpdb_windows.go
index 5156330b..2a70125f 100644
--- a/internal/aghnet/arpdb_windows.go
+++ b/internal/aghnet/arpdb_windows.go
@@ -5,7 +5,6 @@ package aghnet
 
 import (
 	"bufio"
-	"io"
 	"net"
 	"strings"
 	"sync"
@@ -13,20 +12,16 @@ import (
 
 func newARPDB() *cmdARPDB {
 	return &cmdARPDB{
-		runcmd: rcArpA,
+		parse: parseArpA,
 		ns: &neighs{
 			mu: &sync.RWMutex{},
 			ns: make([]Neighbor, 0),
 		},
-		parse: parseArpA,
+		cmd:  "arp",
+		args: []string{"/a"},
 	}
 }
 
-// rcArpA runs "arp /a".
-func rcArpA() (r io.Reader, err error) {
-	return runCmd("arp", "/a")
-}
-
 // parseArpA parses the output of the "arp /a" command on Windows.  The expected
 // input format (the first line is empty):
 //
diff --git a/internal/aghnet/hostscontainer_test.go b/internal/aghnet/hostscontainer_test.go
index 40bfb34c..807722a8 100644
--- a/internal/aghnet/hostscontainer_test.go
+++ b/internal/aghnet/hostscontainer_test.go
@@ -286,6 +286,8 @@ func TestHostsContainer_Translate(t *testing.T) {
 		OnClose:  func() (err error) { panic("not implemented") },
 	}
 
+	require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
+
 	hc, err := NewHostsContainer(0, testdata, &stubWatcher, "etc_hosts")
 	require.NoError(t, err)
 	testutil.CleanupAndRequireSuccess(t, hc.Close)
@@ -358,6 +360,8 @@ func TestHostsContainer_Translate(t *testing.T) {
 func TestHostsContainer(t *testing.T) {
 	const listID = 1234
 
+	require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
+
 	testCases := []struct {
 		want []*rules.DNSRewrite
 		name string
diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go
index 2d791c03..d17b9165 100644
--- a/internal/aghnet/net.go
+++ b/internal/aghnet/net.go
@@ -2,19 +2,27 @@
 package aghnet
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"io"
 	"net"
-	"os/exec"
-	"strings"
 	"syscall"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/golibs/netutil"
 )
 
+// aghosRunCommand is the function to run shell commands.  It's an unexported
+// variable instead of a direct call to make it substitutable in tests.
+var aghosRunCommand = aghos.RunCommand
+
+// rootDirFS is the filesystem pointing to the root directory.  It's an
+// unexported variable instead to make it substitutable in tests.
+var rootDirFS = aghos.RootDirFS()
+
 // ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about
 // the IP being static is available.
 const ErrNoStaticIPInfo errors.Error = "no information about static ip"
@@ -32,22 +40,29 @@ func IfaceSetStaticIP(ifaceName string) (err error) {
 }
 
 // GatewayIP returns IP address of interface's gateway.
-func GatewayIP(ifaceName string) net.IP {
-	cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
-	log.Tracef("executing %s %v", cmd.Path, cmd.Args)
-	d, err := cmd.Output()
-	if err != nil || cmd.ProcessState.ExitCode() != 0 {
+//
+// TODO(e.burkov):  Investigate if the gateway address may be fetched in another
+// way since not every machine has the software installed.
+func GatewayIP(ifaceName string) (ip net.IP) {
+	code, out, err := aghosRunCommand("ip", "route", "show", "dev", ifaceName)
+	if err != nil {
+		log.Debug("%s", err)
+
+		return nil
+	} else if code != 0 {
+		log.Debug("fetching gateway ip: unexpected exit code: %d", code)
+
 		return nil
 	}
 
-	fields := strings.Fields(string(d))
+	fields := bytes.Fields(out)
 	// The meaningful "ip route" command output should contain the word
 	// "default" at first field and default gateway IP address at third field.
-	if len(fields) < 3 || fields[0] != "default" {
+	if len(fields) < 3 || string(fields[0]) != "default" {
 		return nil
 	}
 
-	return net.ParseIP(fields[2])
+	return net.ParseIP(string(fields[2]))
 }
 
 // CanBindPort checks if we can bind to the given port.
@@ -101,7 +116,7 @@ func (iface NetInterface) MarshalJSON() ([]byte, error) {
 
 // GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and WEB only
 // we do not return link-local addresses here
-func GetValidNetInterfacesForWeb() ([]*NetInterface, error) {
+func GetValidNetInterfacesForWeb() (netInterfaces []*NetInterface, err error) {
 	ifaces, err := net.Interfaces()
 	if err != nil {
 		return nil, fmt.Errorf("couldn't get interfaces: %w", err)
@@ -109,8 +124,6 @@ func GetValidNetInterfacesForWeb() ([]*NetInterface, error) {
 		return nil, errors.Error("couldn't find any legible interface")
 	}
 
-	var netInterfaces []*NetInterface
-
 	for _, iface := range ifaces {
 		var addrs []net.Addr
 		addrs, err = iface.Addrs()
@@ -130,12 +143,14 @@ func GetValidNetInterfacesForWeb() ([]*NetInterface, error) {
 			ipNet, ok := addr.(*net.IPNet)
 			if !ok {
 				// Should be net.IPNet, this is weird.
-				return nil, fmt.Errorf("got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
+				return nil, fmt.Errorf("got %s that is not net.IPNet, it is %T", addr, addr)
 			}
+
 			// Ignore link-local.
 			if ipNet.IP.IsLinkLocalUnicast() {
 				continue
 			}
+
 			netIface.Addresses = append(netIface.Addresses, ipNet.IP)
 			netIface.Subnets = append(netIface.Subnets, ipNet)
 		}
diff --git a/internal/aghnet/net_darwin.go b/internal/aghnet/net_darwin.go
index 3c504988..63e57dc4 100644
--- a/internal/aghnet/net_darwin.go
+++ b/internal/aghnet/net_darwin.go
@@ -4,10 +4,11 @@
 package aghnet
 
 import (
+	"bufio"
+	"bytes"
 	"fmt"
-	"os"
+	"io"
 	"regexp"
-	"strings"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
 	"github.com/AdguardTeam/golibs/errors"
@@ -27,7 +28,7 @@ func canBindPrivilegedPorts() (can bool, err error) {
 	return aghos.HaveAdminRights()
 }
 
-func ifaceHasStaticIP(ifaceName string) (bool, error) {
+func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
 	portInfo, err := getCurrentHardwarePortInfo(ifaceName)
 	if err != nil {
 		return false, err
@@ -36,9 +37,10 @@ func ifaceHasStaticIP(ifaceName string) (bool, error) {
 	return portInfo.static, nil
 }
 
-// getCurrentHardwarePortInfo gets information for the specified network interface.
+// getCurrentHardwarePortInfo gets information for the specified network
+// interface.
 func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) {
-	// First of all we should find hardware port name
+	// First of all we should find hardware port name.
 	m := getNetworkSetupHardwareReports()
 	hardwarePort, ok := m[ifaceName]
 	if !ok {
@@ -48,6 +50,10 @@ func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) {
 	return getHardwarePortInfo(hardwarePort)
 }
 
+// hardwareReportsReg is the regular expression matching the lines of
+// networksetup command output lines containing the interface information.
+var hardwareReportsReg = regexp.MustCompile("Hardware Port: (.*?)\nDevice: (.*?)\n")
+
 // getNetworkSetupHardwareReports parses the output of the `networksetup
 // -listallhardwareports` command it returns a map where the key is the
 // interface name, and the value is the "hardware port" returns nil if it fails
@@ -56,54 +62,44 @@ func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) {
 // TODO(e.burkov):  There should be more proper approach than parsing the
 // command output.  For example, see
 // https://developer.apple.com/documentation/systemconfiguration.
-func getNetworkSetupHardwareReports() map[string]string {
-	_, out, err := aghos.RunCommand("networksetup", "-listallhardwareports")
+func getNetworkSetupHardwareReports() (reports map[string]string) {
+	_, out, err := aghosRunCommand("networksetup", "-listallhardwareports")
 	if err != nil {
 		return nil
 	}
 
-	re, err := regexp.Compile("Hardware Port: (.*?)\nDevice: (.*?)\n")
-	if err != nil {
-		return nil
+	reports = make(map[string]string)
+
+	matches := hardwareReportsReg.FindAllSubmatch(out, -1)
+	for _, m := range matches {
+		reports[string(m[2])] = string(m[1])
 	}
 
-	m := make(map[string]string)
-
-	matches := re.FindAllStringSubmatch(out, -1)
-	for i := range matches {
-		port := matches[i][1]
-		device := matches[i][2]
-		m[device] = port
-	}
-
-	return m
+	return reports
 }
 
-func getHardwarePortInfo(hardwarePort string) (hardwarePortInfo, error) {
-	h := hardwarePortInfo{}
+// hardwarePortReg is the regular expression matching the lines of networksetup
+// command output lines containing the port information.
+var hardwarePortReg = regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n")
 
-	_, out, err := aghos.RunCommand("networksetup", "-getinfo", hardwarePort)
+func getHardwarePortInfo(hardwarePort string) (h hardwarePortInfo, err error) {
+	_, out, err := aghosRunCommand("networksetup", "-getinfo", hardwarePort)
 	if err != nil {
 		return h, err
 	}
 
-	re := regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n")
-
-	match := re.FindStringSubmatch(out)
-	if len(match) == 0 {
+	match := hardwarePortReg.FindSubmatch(out)
+	if len(match) != 4 {
 		return h, errors.Error("could not find hardware port info")
 	}
 
-	h.name = hardwarePort
-	h.ip = match[1]
-	h.subnet = match[2]
-	h.gatewayIP = match[3]
-
-	if strings.Index(out, "Manual Configuration") == 0 {
-		h.static = true
-	}
-
-	return h, nil
+	return hardwarePortInfo{
+		name:      hardwarePort,
+		ip:        string(match[1]),
+		subnet:    string(match[2]),
+		gatewayIP: string(match[3]),
+		static:    bytes.Index(out, []byte("Manual Configuration")) == 0,
+	}, nil
 }
 
 func ifaceSetStaticIP(ifaceName string) (err error) {
@@ -113,7 +109,7 @@ func ifaceSetStaticIP(ifaceName string) (err error) {
 	}
 
 	if portInfo.static {
-		return errors.Error("IP address is already static")
+		return errors.Error("ip address is already static")
 	}
 
 	dnsAddrs, err := getEtcResolvConfServers()
@@ -121,50 +117,62 @@ func ifaceSetStaticIP(ifaceName string) (err error) {
 		return err
 	}
 
-	args := make([]string, 0)
-	args = append(args, "-setdnsservers", portInfo.name)
-	args = append(args, dnsAddrs...)
+	args := append([]string{"-setdnsservers", portInfo.name}, dnsAddrs...)
 
 	// Setting DNS servers is necessary when configuring a static IP
-	code, _, err := aghos.RunCommand("networksetup", args...)
+	code, _, err := aghosRunCommand("networksetup", args...)
 	if err != nil {
 		return err
-	}
-	if code != 0 {
+	} else if code != 0 {
 		return fmt.Errorf("failed to set DNS servers, code=%d", code)
 	}
 
 	// Actually configures hardware port to have static IP
-	code, _, err = aghos.RunCommand("networksetup", "-setmanual",
-		portInfo.name, portInfo.ip, portInfo.subnet, portInfo.gatewayIP)
+	code, _, err = aghosRunCommand(
+		"networksetup",
+		"-setmanual",
+		portInfo.name,
+		portInfo.ip,
+		portInfo.subnet,
+		portInfo.gatewayIP,
+	)
 	if err != nil {
 		return err
-	}
-	if code != 0 {
+	} else if code != 0 {
 		return fmt.Errorf("failed to set DNS servers, code=%d", code)
 	}
 
 	return nil
 }
 
+// etcResolvConfReg is the regular expression matching the lines of resolv.conf
+// file containing a name server information.
+var etcResolvConfReg = regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)")
+
 // getEtcResolvConfServers returns a list of nameservers configured in
 // /etc/resolv.conf.
-func getEtcResolvConfServers() ([]string, error) {
-	body, err := os.ReadFile("/etc/resolv.conf")
+func getEtcResolvConfServers() (addrs []string, err error) {
+	const filename = "etc/resolv.conf"
+
+	_, err = aghos.FileWalker(func(r io.Reader) (_ []string, _ bool, err error) {
+		sc := bufio.NewScanner(r)
+		for sc.Scan() {
+			matches := etcResolvConfReg.FindAllStringSubmatch(sc.Text(), -1)
+			if len(matches) == 0 {
+				continue
+			}
+
+			for _, m := range matches {
+				addrs = append(addrs, m[1])
+			}
+		}
+
+		return nil, false, sc.Err()
+	}).Walk(rootDirFS, filename)
 	if err != nil {
-		return nil, err
-	}
-
-	re := regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)")
-
-	matches := re.FindAllStringSubmatch(string(body), -1)
-	if len(matches) == 0 {
-		return nil, errors.Error("found no DNS servers in /etc/resolv.conf")
-	}
-
-	addrs := make([]string, 0)
-	for i := range matches {
-		addrs = append(addrs, matches[i][1])
+		return nil, fmt.Errorf("parsing etc/resolv.conf file: %w", err)
+	} else if len(addrs) == 0 {
+		return nil, fmt.Errorf("found no dns servers in %s", filename)
 	}
 
 	return addrs, nil
diff --git a/internal/aghnet/net_darwin_test.go b/internal/aghnet/net_darwin_test.go
new file mode 100644
index 00000000..905600d5
--- /dev/null
+++ b/internal/aghnet/net_darwin_test.go
@@ -0,0 +1,261 @@
+package aghnet
+
+import (
+	"io/fs"
+	"testing"
+	"testing/fstest"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
+	"github.com/AdguardTeam/golibs/errors"
+	"github.com/AdguardTeam/golibs/testutil"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIfaceHasStaticIP(t *testing.T) {
+	testCases := []struct {
+		name       string
+		shell      mapShell
+		ifaceName  string
+		wantHas    assert.BoolAssertionFunc
+		wantErrMsg string
+	}{{
+		name: "success",
+		shell: mapShell{
+			"networksetup -listallhardwareports": {
+				err:  nil,
+				out:  "Hardware Port: hwport\nDevice: en0\n",
+				code: 0,
+			},
+			"networksetup -getinfo hwport": {
+				err:  nil,
+				out:  "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n",
+				code: 0,
+			},
+		},
+		ifaceName:  "en0",
+		wantHas:    assert.False,
+		wantErrMsg: ``,
+	}, {
+		name: "success_static",
+		shell: mapShell{
+			"networksetup -listallhardwareports": {
+				err:  nil,
+				out:  "Hardware Port: hwport\nDevice: en0\n",
+				code: 0,
+			},
+			"networksetup -getinfo hwport": {
+				err: nil,
+				out: "Manual Configuration\nIP address: 1.2.3.4\n" +
+					"Subnet mask: 255.255.255.0\nRouter: 1.2.3.1\n",
+				code: 0,
+			},
+		},
+		ifaceName:  "en0",
+		wantHas:    assert.True,
+		wantErrMsg: ``,
+	}, {
+		name: "reports_error",
+		shell: theOnlyCmd(
+			"networksetup -listallhardwareports",
+			0,
+			"",
+			errors.Error("can't list"),
+		),
+		ifaceName:  "en0",
+		wantHas:    assert.False,
+		wantErrMsg: `could not find hardware port for en0`,
+	}, {
+		name: "port_error",
+		shell: mapShell{
+			"networksetup -listallhardwareports": {
+				err:  nil,
+				out:  "Hardware Port: hwport\nDevice: en0\n",
+				code: 0,
+			},
+			"networksetup -getinfo hwport": {
+				err:  errors.Error("can't get"),
+				out:  ``,
+				code: 0,
+			},
+		},
+		ifaceName:  "en0",
+		wantHas:    assert.False,
+		wantErrMsg: `can't get`,
+	}, {
+		name: "port_bad_output",
+		shell: mapShell{
+			"networksetup -listallhardwareports": {
+				err:  nil,
+				out:  "Hardware Port: hwport\nDevice: en0\n",
+				code: 0,
+			},
+			"networksetup -getinfo hwport": {
+				err:  nil,
+				out:  "nothing meaningful",
+				code: 0,
+			},
+		},
+		ifaceName:  "en0",
+		wantHas:    assert.False,
+		wantErrMsg: `could not find hardware port info`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			substShell(t, tc.shell.RunCmd)
+
+			has, err := IfaceHasStaticIP(tc.ifaceName)
+			testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+
+			tc.wantHas(t, has)
+		})
+	}
+}
+
+func TestIfaceSetStaticIP(t *testing.T) {
+	succFsys := fstest.MapFS{
+		"etc/resolv.conf": &fstest.MapFile{
+			Data: []byte(`nameserver 1.1.1.1`),
+		},
+	}
+	panicFsys := &aghtest.FS{
+		OnOpen: func(name string) (fs.File, error) { panic("not implemented") },
+	}
+
+	testCases := []struct {
+		name       string
+		shell      mapShell
+		fsys       fs.FS
+		wantErrMsg string
+	}{{
+		name: "success",
+		shell: mapShell{
+			"networksetup -listallhardwareports": {
+				err:  nil,
+				out:  "Hardware Port: hwport\nDevice: en0\n",
+				code: 0,
+			},
+			"networksetup -getinfo hwport": {
+				err:  nil,
+				out:  "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n",
+				code: 0,
+			},
+			"networksetup -setdnsservers hwport 1.1.1.1": {
+				err:  nil,
+				out:  "",
+				code: 0,
+			},
+			"networksetup -setmanual hwport 1.2.3.4 255.255.255.0 1.2.3.1": {
+				err:  nil,
+				out:  "",
+				code: 0,
+			},
+		},
+		fsys:       succFsys,
+		wantErrMsg: ``,
+	}, {
+		name: "static_already",
+		shell: mapShell{
+			"networksetup -listallhardwareports": {
+				err:  nil,
+				out:  "Hardware Port: hwport\nDevice: en0\n",
+				code: 0,
+			},
+			"networksetup -getinfo hwport": {
+				err: nil,
+				out: "Manual Configuration\nIP address: 1.2.3.4\n" +
+					"Subnet mask: 255.255.255.0\nRouter: 1.2.3.1\n",
+				code: 0,
+			},
+		},
+		fsys:       panicFsys,
+		wantErrMsg: `ip address is already static`,
+	}, {
+		name: "reports_error",
+		shell: theOnlyCmd(
+			"networksetup -listallhardwareports",
+			0,
+			"",
+			errors.Error("can't list"),
+		),
+		fsys:       panicFsys,
+		wantErrMsg: `could not find hardware port for en0`,
+	}, {
+		name: "resolv_conf_error",
+		shell: mapShell{
+			"networksetup -listallhardwareports": {
+				err:  nil,
+				out:  "Hardware Port: hwport\nDevice: en0\n",
+				code: 0,
+			},
+			"networksetup -getinfo hwport": {
+				err:  nil,
+				out:  "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n",
+				code: 0,
+			},
+		},
+		fsys: fstest.MapFS{
+			"etc/resolv.conf": &fstest.MapFile{
+				Data: []byte("this resolv.conf is invalid"),
+			},
+		},
+		wantErrMsg: `found no dns servers in etc/resolv.conf`,
+	}, {
+		name: "set_dns_error",
+		shell: mapShell{
+			"networksetup -listallhardwareports": {
+				err:  nil,
+				out:  "Hardware Port: hwport\nDevice: en0\n",
+				code: 0,
+			},
+			"networksetup -getinfo hwport": {
+				err:  nil,
+				out:  "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n",
+				code: 0,
+			},
+			"networksetup -setdnsservers hwport 1.1.1.1": {
+				err:  errors.Error("can't set"),
+				out:  "",
+				code: 0,
+			},
+		},
+		fsys:       succFsys,
+		wantErrMsg: `can't set`,
+	}, {
+		name: "set_manual_error",
+		shell: mapShell{
+			"networksetup -listallhardwareports": {
+				err:  nil,
+				out:  "Hardware Port: hwport\nDevice: en0\n",
+				code: 0,
+			},
+			"networksetup -getinfo hwport": {
+				err:  nil,
+				out:  "IP address: 1.2.3.4\nSubnet mask: 255.255.255.0\nRouter: 1.2.3.1\n",
+				code: 0,
+			},
+			"networksetup -setdnsservers hwport 1.1.1.1": {
+				err:  nil,
+				out:  "",
+				code: 0,
+			},
+			"networksetup -setmanual hwport 1.2.3.4 255.255.255.0 1.2.3.1": {
+				err:  errors.Error("can't set"),
+				out:  "",
+				code: 0,
+			},
+		},
+		fsys:       succFsys,
+		wantErrMsg: `can't set`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			substShell(t, tc.shell.RunCmd)
+			substRootDirFS(t, tc.fsys)
+
+			err := IfaceSetStaticIP("en0")
+			testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+		})
+	}
+}
diff --git a/internal/aghnet/net_freebsd.go b/internal/aghnet/net_freebsd.go
index a5200fb8..34d93303 100644
--- a/internal/aghnet/net_freebsd.go
+++ b/internal/aghnet/net_freebsd.go
@@ -22,7 +22,7 @@ func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
 
 	walker := aghos.FileWalker(interfaceName(ifaceName).rcConfStaticConfig)
 
-	return walker.Walk(aghos.RootDirFS(), rcConfFilename)
+	return walker.Walk(rootDirFS, rcConfFilename)
 }
 
 // rcConfStaticConfig checks if the interface is configured by /etc/rc.conf to
diff --git a/internal/aghnet/net_freebsd_test.go b/internal/aghnet/net_freebsd_test.go
index 3781b154..e00dafa7 100644
--- a/internal/aghnet/net_freebsd_test.go
+++ b/internal/aghnet/net_freebsd_test.go
@@ -4,56 +4,74 @@
 package aghnet
 
 import (
-	"strings"
+	"io/fs"
 	"testing"
+	"testing/fstest"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
 func TestRcConfStaticConfig(t *testing.T) {
-	const iface interfaceName = `em0`
-	const nl = "\n"
+	const (
+		ifaceName = `em0`
+		rcConf    = "etc/rc.conf"
+	)
 
 	testCases := []struct {
-		name       string
-		rcconfData string
-		wantCont   bool
+		name     string
+		rootFsys fs.FS
+		wantHas  assert.BoolAssertionFunc
 	}{{
-		name:       "simple",
-		rcconfData: `ifconfig_em0="inet 127.0.0.253 netmask 0xffffffff"` + nl,
-		wantCont:   false,
+		name: "simple",
+		rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{
+			Data: []byte(`ifconfig_` + ifaceName + `="inet 127.0.0.253 netmask 0xffffffff"` + nl),
+		}},
+		wantHas: assert.True,
 	}, {
-		name:       "case_insensitiveness",
-		rcconfData: `ifconfig_em0="InEt 127.0.0.253 NeTmAsK 0xffffffff"` + nl,
-		wantCont:   false,
+		name: "case_insensitiveness",
+		rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{
+			Data: []byte(`ifconfig_` + ifaceName + `="InEt 127.0.0.253 NeTmAsK 0xffffffff"` + nl),
+		}},
+		wantHas: assert.True,
 	}, {
 		name: "comments_and_trash",
-		rcconfData: `# comment 1` + nl +
-			`` + nl +
-			`# comment 2` + nl +
-			`ifconfig_em0="inet 127.0.0.253 netmask 0xffffffff"` + nl,
-		wantCont: false,
+		rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{
+			Data: []byte(`# comment 1` + nl +
+				`` + nl +
+				`# comment 2` + nl +
+				`ifconfig_` + ifaceName + `="inet 127.0.0.253 netmask 0xffffffff"` + nl,
+			),
+		}},
+		wantHas: assert.True,
 	}, {
 		name: "aliases",
-		rcconfData: `ifconfig_em0_alias="inet 127.0.0.1/24"` + nl +
-			`ifconfig_em0="inet 127.0.0.253 netmask 0xffffffff"` + nl,
-		wantCont: false,
+		rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{
+			Data: []byte(`ifconfig_` + ifaceName + `_alias="inet 127.0.0.1/24"` + nl +
+				`ifconfig_` + ifaceName + `="inet 127.0.0.253 netmask 0xffffffff"` + nl,
+			),
+		}},
+		wantHas: assert.True,
 	}, {
 		name: "incorrect_config",
-		rcconfData: `ifconfig_em0="inet6 127.0.0.253 netmask 0xffffffff"` + nl +
-			`ifconfig_em0="inet 256.256.256.256 netmask 0xffffffff"` + nl +
-			`ifconfig_em0=""` + nl,
-		wantCont: true,
+		rootFsys: fstest.MapFS{rcConf: &fstest.MapFile{
+			Data: []byte(
+				`ifconfig_` + ifaceName + `="inet6 127.0.0.253 netmask 0xffffffff"` + nl +
+					`ifconfig_` + ifaceName + `="inet 256.256.256.256 netmask 0xffffffff"` + nl +
+					`ifconfig_` + ifaceName + `=""` + nl,
+			),
+		}},
+		wantHas: assert.False,
 	}}
 
 	for _, tc := range testCases {
-		r := strings.NewReader(tc.rcconfData)
 		t.Run(tc.name, func(t *testing.T) {
-			_, cont, err := iface.rcConfStaticConfig(r)
+			substRootDirFS(t, tc.rootFsys)
+
+			has, err := IfaceHasStaticIP(ifaceName)
 			require.NoError(t, err)
 
-			assert.Equal(t, tc.wantCont, cont)
+			tc.wantHas(t, has)
 		})
 	}
 }
diff --git a/internal/aghnet/net_linux.go b/internal/aghnet/net_linux.go
index 93414165..c2526524 100644
--- a/internal/aghnet/net_linux.go
+++ b/internal/aghnet/net_linux.go
@@ -21,8 +21,7 @@ import (
 // have a static IP.
 func (n interfaceName) dhcpcdStaticConfig(r io.Reader) (subsources []string, cont bool, err error) {
 	s := bufio.NewScanner(r)
-	ifaceFound := findIfaceLine(s, string(n))
-	if !ifaceFound {
+	if !findIfaceLine(s, string(n)) {
 		return nil, true, s.Err()
 	}
 
@@ -61,9 +60,9 @@ func (n interfaceName) ifacesStaticConfig(r io.Reader) (sub []string, cont bool,
 		fields := strings.Fields(line)
 		fieldsNum := len(fields)
 
-		// Man page interfaces(5) declares that interface definition
-		// should consist of the key word "iface" followed by interface
-		// name, and method at fourth field.
+		// Man page interfaces(5) declares that interface definition should
+		// consist of the key word "iface" followed by interface name, and
+		// method at fourth field.
 		if fieldsNum >= 4 &&
 			fields[0] == "iface" && fields[1] == string(n) && fields[3] == "static" {
 			return nil, false, nil
@@ -78,10 +77,10 @@ func (n interfaceName) ifacesStaticConfig(r io.Reader) (sub []string, cont bool,
 }
 
 func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
-	// TODO(a.garipov): Currently, this function returns the first
-	// definitive result.  So if /etc/dhcpcd.conf has a static IP while
-	// /etc/network/interfaces doesn't, it will return true.  Perhaps this
-	// is not the most desirable behavior.
+	// TODO(a.garipov): Currently, this function returns the first definitive
+	// result.  So if /etc/dhcpcd.conf has and /etc/network/interfaces has no
+	// static IP configuration, it will return true.  Perhaps this is not the
+	// most desirable behavior.
 
 	iface := interfaceName(ifaceName)
 
@@ -95,12 +94,10 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
 		FileWalker: iface.ifacesStaticConfig,
 		filename:   "etc/network/interfaces",
 	}} {
-		has, err = pair.Walk(aghos.RootDirFS(), pair.filename)
+		has, err = pair.Walk(rootDirFS, pair.filename)
 		if err != nil {
 			return false, err
-		}
-
-		if has {
+		} else if has {
 			return true, nil
 		}
 	}
@@ -141,13 +138,15 @@ func ifaceSetStaticIP(ifaceName string) (err error) {
 	gatewayIP := GatewayIP(ifaceName)
 	add := dhcpcdConfIface(ifaceName, ipNet, gatewayIP, ipNet.IP)
 
-	body, err := os.ReadFile("/etc/dhcpcd.conf")
+	const filename = "/etc/dhcpcd.conf"
+
+	body, err := os.ReadFile(filename)
 	if err != nil && !errors.Is(err, os.ErrNotExist) {
 		return err
 	}
 
 	body = append(body, []byte(add)...)
-	err = maybe.WriteFile("/etc/dhcpcd.conf", body, 0o644)
+	err = maybe.WriteFile(filename, body, 0o644)
 	if err != nil {
 		return fmt.Errorf("writing conf: %w", err)
 	}
diff --git a/internal/aghnet/net_linux_test.go b/internal/aghnet/net_linux_test.go
index bf2cecfe..e46da4df 100644
--- a/internal/aghnet/net_linux_test.go
+++ b/internal/aghnet/net_linux_test.go
@@ -4,122 +4,130 @@
 package aghnet
 
 import (
-	"bytes"
+	"io/fs"
 	"net"
 	"testing"
+	"testing/fstest"
 
+	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 )
 
-func TestDHCPCDStaticConfig(t *testing.T) {
-	const iface interfaceName = `wlan0`
+func TestHasStaticIP(t *testing.T) {
+	const ifaceName = "wlan0"
+
+	const (
+		dhcpcd    = "etc/dhcpcd.conf"
+		netifaces = "etc/network/interfaces"
+	)
 
 	testCases := []struct {
-		name     string
-		data     []byte
-		wantCont bool
+		rootFsys   fs.FS
+		name       string
+		wantHas    assert.BoolAssertionFunc
+		wantErrMsg string
 	}{{
-		name: "has_not",
-		data: []byte(`#comment` + nl +
-			`# comment` + nl +
-			`interface eth0` + nl +
-			`static ip_address=192.168.0.1/24` + nl +
-			`# interface ` + iface + nl +
-			`static ip_address=192.168.1.1/24` + nl +
-			`# comment` + nl,
-		),
-		wantCont: true,
+		rootFsys: fstest.MapFS{
+			dhcpcd: &fstest.MapFile{
+				Data: []byte(`#comment` + nl +
+					`# comment` + nl +
+					`interface eth0` + nl +
+					`static ip_address=192.168.0.1/24` + nl +
+					`# interface ` + ifaceName + nl +
+					`static ip_address=192.168.1.1/24` + nl +
+					`# comment` + nl,
+				),
+			},
+		},
+		name:       "dhcpcd_has_not",
+		wantHas:    assert.False,
+		wantErrMsg: `no information about static ip`,
 	}, {
-		name: "has",
-		data: []byte(`#comment` + nl +
-			`# comment` + nl +
-			`interface eth0` + nl +
-			`static ip_address=192.168.0.1/24` + nl +
-			`# interface ` + iface + nl +
-			`static ip_address=192.168.1.1/24` + nl +
-			`# comment` + nl +
-			`interface ` + iface + nl +
-			`# comment` + nl +
-			`static ip_address=192.168.2.1/24` + nl,
-		),
-		wantCont: false,
+		rootFsys: fstest.MapFS{
+			dhcpcd: &fstest.MapFile{
+				Data: []byte(`#comment` + nl +
+					`# comment` + nl +
+					`interface ` + ifaceName + nl +
+					`static ip_address=192.168.0.1/24` + nl +
+					`# interface ` + ifaceName + nl +
+					`static ip_address=192.168.1.1/24` + nl +
+					`# comment` + nl,
+				),
+			},
+		},
+		name:       "dhcpcd_has",
+		wantHas:    assert.True,
+		wantErrMsg: ``,
+	}, {
+		rootFsys: fstest.MapFS{
+			netifaces: &fstest.MapFile{
+				Data: []byte(`allow-hotplug ` + ifaceName + nl +
+					`#iface enp0s3 inet static` + nl +
+					`#  address 192.168.0.200` + nl +
+					`#  netmask 255.255.255.0` + nl +
+					`#  gateway 192.168.0.1` + nl +
+					`iface ` + ifaceName + ` inet dhcp` + nl,
+				),
+			},
+		},
+		name:       "netifaces_has_not",
+		wantHas:    assert.False,
+		wantErrMsg: `no information about static ip`,
+	}, {
+		rootFsys: fstest.MapFS{
+			netifaces: &fstest.MapFile{
+				Data: []byte(`allow-hotplug ` + ifaceName + nl +
+					`iface ` + ifaceName + ` inet static` + nl +
+					`  address 192.168.0.200` + nl +
+					`  netmask 255.255.255.0` + nl +
+					`  gateway 192.168.0.1` + nl +
+					`#iface ` + ifaceName + ` inet dhcp` + nl,
+				),
+			},
+		},
+		name:       "netifaces_has",
+		wantHas:    assert.True,
+		wantErrMsg: ``,
+	}, {
+		rootFsys: fstest.MapFS{
+			netifaces: &fstest.MapFile{
+				Data: []byte(`source hello` + nl +
+					`#iface ` + ifaceName + ` inet static` + nl,
+				),
+			},
+			"hello": &fstest.MapFile{
+				Data: []byte(`iface ` + ifaceName + ` inet static` + nl),
+			},
+		},
+		name:       "netifaces_another_file",
+		wantHas:    assert.True,
+		wantErrMsg: ``,
+	}, {
+		rootFsys: fstest.MapFS{
+			netifaces: &fstest.MapFile{
+				Data: []byte(`source hello` + nl +
+					`iface ` + ifaceName + ` inet static` + nl,
+				),
+			},
+		},
+		name:       "netifaces_ignore_another",
+		wantHas:    assert.True,
+		wantErrMsg: ``,
 	}}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			r := bytes.NewReader(tc.data)
-			_, cont, err := iface.dhcpcdStaticConfig(r)
-			require.NoError(t, err)
+			substRootDirFS(t, tc.rootFsys)
 
-			assert.Equal(t, tc.wantCont, cont)
+			has, err := IfaceHasStaticIP(ifaceName)
+			testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+
+			tc.wantHas(t, has)
 		})
 	}
 }
 
-func TestIfacesStaticConfig(t *testing.T) {
-	const iface interfaceName = `enp0s3`
-
-	testCases := []struct {
-		name         string
-		data         []byte
-		wantCont     bool
-		wantPatterns []string
-	}{{
-		name: "has_not",
-		data: []byte(`allow-hotplug ` + iface + nl +
-			`#iface enp0s3 inet static` + nl +
-			`#  address 192.168.0.200` + nl +
-			`#  netmask 255.255.255.0` + nl +
-			`#  gateway 192.168.0.1` + nl +
-			`iface ` + iface + ` inet dhcp` + nl,
-		),
-		wantCont:     true,
-		wantPatterns: []string{},
-	}, {
-		name: "has",
-		data: []byte(`allow-hotplug ` + iface + nl +
-			`iface ` + iface + ` inet static` + nl +
-			`  address 192.168.0.200` + nl +
-			`  netmask 255.255.255.0` + nl +
-			`  gateway 192.168.0.1` + nl +
-			`#iface ` + iface + ` inet dhcp` + nl,
-		),
-		wantCont:     false,
-		wantPatterns: []string{},
-	}, {
-		name: "return_patterns",
-		data: []byte(`source hello` + nl +
-			`source world` + nl +
-			`#iface ` + iface + ` inet static` + nl,
-		),
-		wantCont:     true,
-		wantPatterns: []string{"hello", "world"},
-	}, {
-		// This one tests if the first found valid interface prevents
-		// checking files under the `source` directive.
-		name: "ignore_patterns",
-		data: []byte(`source hello` + nl +
-			`source world` + nl +
-			`iface ` + iface + ` inet static` + nl,
-		),
-		wantCont:     false,
-		wantPatterns: []string{},
-	}}
-
-	for _, tc := range testCases {
-		r := bytes.NewReader(tc.data)
-		t.Run(tc.name, func(t *testing.T) {
-			patterns, has, err := iface.ifacesStaticConfig(r)
-			require.NoError(t, err)
-
-			assert.Equal(t, tc.wantCont, has)
-			assert.ElementsMatch(t, tc.wantPatterns, patterns)
-		})
-	}
-}
-
-func TestSetStaticIPdhcpcdConf(t *testing.T) {
+func TestSetStaticIP_dhcpcdConfIface(t *testing.T) {
 	testCases := []struct {
 		name       string
 		dhcpcdConf string
diff --git a/internal/aghnet/net_openbsd.go b/internal/aghnet/net_openbsd.go
index 627db0ab..68ef90e0 100644
--- a/internal/aghnet/net_openbsd.go
+++ b/internal/aghnet/net_openbsd.go
@@ -20,7 +20,7 @@ func canBindPrivilegedPorts() (can bool, err error) {
 func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
 	filename := fmt.Sprintf("etc/hostname.%s", ifaceName)
 
-	return aghos.FileWalker(hostnameIfStaticConfig).Walk(aghos.RootDirFS(), filename)
+	return aghos.FileWalker(hostnameIfStaticConfig).Walk(rootDirFS, filename)
 }
 
 // hostnameIfStaticConfig checks if the interface is configured by
diff --git a/internal/aghnet/net_openbsd_test.go b/internal/aghnet/net_openbsd_test.go
index e157d93a..356799b7 100644
--- a/internal/aghnet/net_openbsd_test.go
+++ b/internal/aghnet/net_openbsd_test.go
@@ -4,49 +4,69 @@
 package aghnet
 
 import (
-	"strings"
+	"fmt"
+	"io/fs"
 	"testing"
+	"testing/fstest"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
-func TestHostnameIfStaticConfig(t *testing.T) {
-	const nl = "\n"
+func TestIfaceHasStaticIP(t *testing.T) {
+	const ifaceName = "em0"
+
+	confFile := fmt.Sprintf("etc/hostname.%s", ifaceName)
 
 	testCases := []struct {
-		name       string
-		rcconfData string
-		wantHas    bool
+		name     string
+		rootFsys fs.FS
+		wantHas  assert.BoolAssertionFunc
 	}{{
-		name:       "simple",
-		rcconfData: `inet 127.0.0.253` + nl,
-		wantHas:    true,
+		name: "simple",
+		rootFsys: fstest.MapFS{
+			confFile: &fstest.MapFile{
+				Data: []byte(`inet 127.0.0.253` + nl),
+			},
+		},
+		wantHas: assert.True,
 	}, {
-		name:       "case_sensitiveness",
-		rcconfData: `InEt 127.0.0.253` + nl,
-		wantHas:    false,
+		name: "case_sensitiveness",
+		rootFsys: fstest.MapFS{
+			confFile: &fstest.MapFile{
+				Data: []byte(`InEt 127.0.0.253` + nl),
+			},
+		},
+		wantHas: assert.False,
 	}, {
 		name: "comments_and_trash",
-		rcconfData: `# comment 1` + nl +
-			`` + nl +
-			`# inet 127.0.0.253` + nl +
-			`inet` + nl,
-		wantHas: false,
+		rootFsys: fstest.MapFS{
+			confFile: &fstest.MapFile{
+				Data: []byte(`# comment 1` + nl + nl +
+					`# inet 127.0.0.253` + nl +
+					`inet` + nl,
+				),
+			},
+		},
+		wantHas: assert.False,
 	}, {
 		name: "incorrect_config",
-		rcconfData: `inet6 127.0.0.253` + nl +
-			`inet 256.256.256.256` + nl,
-		wantHas: false,
+		rootFsys: fstest.MapFS{
+			confFile: &fstest.MapFile{
+				Data: []byte(`inet6 127.0.0.253` + nl + `inet 256.256.256.256` + nl),
+			},
+		},
+		wantHas: assert.False,
 	}}
 
 	for _, tc := range testCases {
-		r := strings.NewReader(tc.rcconfData)
 		t.Run(tc.name, func(t *testing.T) {
-			_, has, err := hostnameIfStaticConfig(r)
+			substRootDirFS(t, tc.rootFsys)
+
+			has, err := IfaceHasStaticIP(ifaceName)
 			require.NoError(t, err)
 
-			assert.Equal(t, tc.wantHas, has)
+			tc.wantHas(t, has)
 		})
 	}
 }
diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go
index b461cb44..29de869b 100644
--- a/internal/aghnet/net_test.go
+++ b/internal/aghnet/net_test.go
@@ -1,9 +1,11 @@
 package aghnet
 
 import (
+	"fmt"
 	"io/fs"
 	"net"
 	"os"
+	"strings"
 	"testing"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
@@ -14,11 +16,102 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+func TestMain(m *testing.M) {
+	aghtest.DiscardLogOutput(m)
+}
+
 // testdata is the filesystem containing data for testing the package.
 var testdata fs.FS = os.DirFS("./testdata")
 
-func TestMain(m *testing.M) {
-	aghtest.DiscardLogOutput(m)
+// substRootDirFS replaces the aghos.RootDirFS function used throughout the
+// package with fsys for tests ran under t.
+func substRootDirFS(t testing.TB, fsys fs.FS) {
+	t.Helper()
+
+	prev := rootDirFS
+	t.Cleanup(func() { rootDirFS = prev })
+	rootDirFS = fsys
+}
+
+// RunCmdFunc is the signature of aghos.RunCommand function.
+type RunCmdFunc func(cmd string, args ...string) (code int, out []byte, err error)
+
+// substShell replaces the the aghos.RunCommand function used throughout the
+// package with rc for tests ran under t.
+func substShell(t testing.TB, rc RunCmdFunc) {
+	t.Helper()
+
+	prev := aghosRunCommand
+	t.Cleanup(func() { aghosRunCommand = prev })
+	aghosRunCommand = rc
+}
+
+// mapShell is a substitution of aghos.RunCommand that maps the command to it's
+// execution result.  It's only needed to simplify testing.
+//
+// TODO(e.burkov):  Perhaps put all the shell interactions behind an interface.
+type mapShell map[string]struct {
+	err  error
+	out  string
+	code int
+}
+
+// theOnlyCmd returns s that only handles a single command and arguments
+// combination from cmd.
+func theOnlyCmd(cmd string, code int, out string, err error) (s mapShell) {
+	return mapShell{cmd: {code: code, out: out, err: err}}
+}
+
+// RunCmd is a RunCmdFunc handled by s.
+func (s mapShell) RunCmd(cmd string, args ...string) (code int, out []byte, err error) {
+	key := strings.Join(append([]string{cmd}, args...), " ")
+	ret, ok := s[key]
+	if !ok {
+		return 0, nil, fmt.Errorf("unexpected shell command %q", key)
+	}
+
+	return ret.code, []byte(ret.out), ret.err
+}
+
+func TestGatewayIP(t *testing.T) {
+	testCases := []struct {
+		name  string
+		shell mapShell
+		want  net.IP
+	}{{
+		name:  "success_v4",
+		shell: theOnlyCmd("ip route show dev ifaceName", 0, `default via 1.2.3.4 onlink`, nil),
+		want:  net.IP{1, 2, 3, 4}.To16(),
+	}, {
+		name:  "success_v6",
+		shell: theOnlyCmd("ip route show dev ifaceName", 0, `default via ::ffff onlink`, nil),
+		want: net.IP{
+			0x0, 0x0, 0x0, 0x0,
+			0x0, 0x0, 0x0, 0x0,
+			0x0, 0x0, 0x0, 0x0,
+			0x0, 0x0, 0xFF, 0xFF,
+		},
+	}, {
+		name:  "bad_output",
+		shell: theOnlyCmd("ip route show dev ifaceName", 0, `non-default via 1.2.3.4 onlink`, nil),
+		want:  nil,
+	}, {
+		name:  "err_runcmd",
+		shell: theOnlyCmd("ip route show dev ifaceName", 0, "", errors.Error("can't run command")),
+		want:  nil,
+	}, {
+		name:  "bad_code",
+		shell: theOnlyCmd("ip route show dev ifaceName", 1, "", nil),
+		want:  nil,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			substShell(t, tc.shell.RunCmd)
+
+			assert.Equal(t, tc.want, GatewayIP("ifaceName"))
+		})
+	}
 }
 
 func TestGetInterfaceByIP(t *testing.T) {
@@ -133,6 +226,8 @@ func TestCheckPort(t *testing.T) {
 }
 
 func TestCollectAllIfacesAddrs(t *testing.T) {
+	t.Skip("TODO(e.burkov):  Substitute the net.Interfaces.")
+
 	addrs, err := CollectAllIfacesAddrs()
 	require.NoError(t, err)
 
diff --git a/internal/aghos/os.go b/internal/aghos/os.go
index 8ac189a1..018a3e89 100644
--- a/internal/aghos/os.go
+++ b/internal/aghos/os.go
@@ -57,20 +57,22 @@ func HaveAdminRights() (bool, error) {
 const MaxCmdOutputSize = 64 * 1024
 
 // RunCommand runs shell command.
-func RunCommand(command string, arguments ...string) (code int, output string, err error) {
+func RunCommand(command string, arguments ...string) (code int, output []byte, err error) {
 	cmd := exec.Command(command, arguments...)
 	out, err := cmd.Output()
 	if len(out) > MaxCmdOutputSize {
 		out = out[:MaxCmdOutputSize]
 	}
 
-	if errors.As(err, new(*exec.ExitError)) {
-		return cmd.ProcessState.ExitCode(), string(out), nil
-	} else if err != nil {
-		return 1, "", fmt.Errorf("command %q failed: %w: %s", command, err, out)
+	if err != nil {
+		if eerr := new(exec.ExitError); errors.As(err, &eerr) {
+			return eerr.ExitCode(), eerr.Stderr, nil
+		}
+
+		return 1, nil, fmt.Errorf("command %q failed: %w: %s", command, err, out)
 	}
 
-	return cmd.ProcessState.ExitCode(), string(out), nil
+	return cmd.ProcessState.ExitCode(), out, nil
 }
 
 // PIDByCommand searches for process named command and returns its PID ignoring
diff --git a/internal/home/service_openbsd.go b/internal/home/service_openbsd.go
index 679a7437..8ad0d212 100644
--- a/internal/home/service_openbsd.go
+++ b/internal/home/service_openbsd.go
@@ -314,12 +314,13 @@ func (s *openbsdRunComService) runCom(cmd string) (out string, err error) {
 	// TODO(e.burkov):  It's possible that os.ErrNotExist is caused by
 	// something different than the service script's non-existence.  Keep it
 	// in mind, when replace the aghos.RunCommand.
-	_, out, err = aghos.RunCommand(scriptPath, cmd)
+	var outData []byte
+	_, outData, err = aghos.RunCommand(scriptPath, cmd)
 	if errors.Is(err, os.ErrNotExist) {
 		return "", service.ErrNotInstalled
 	}
 
-	return out, err
+	return string(outData), err
 }
 
 // Status implements service.Service interface for *openbsdRunComService.

From a79b61aac31db689a0d704bf356e67a6ea6f881e Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Thu, 31 Mar 2022 11:54:47 +0300
Subject: [PATCH 029/143] Pull request: fix down flag

Squashed commit of the following:

commit ea446e844a21e7e7e0271d4d133c581014facda1
Merge: bb8cabfa 5e71f5df
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Mar 31 10:49:20 2022 +0300

    Merge branch 'master' into client-down-flag

commit bb8cabfae8e2e3eaa09f48ffe7d2fb3b308d31fb
Author: Ildar Kamalov <ik@adguard.com>
Date:   Wed Mar 30 19:27:30 2022 +0300

    client: fix down flag
---
 client/src/install/Setup/Settings.js | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/client/src/install/Setup/Settings.js b/client/src/install/Setup/Settings.js
index ce128fb8..234d345a 100644
--- a/client/src/install/Setup/Settings.js
+++ b/client/src/install/Setup/Settings.js
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
 import { Field, reduxForm, formValueSelector } from 'redux-form';
 import { Trans, withTranslation } from 'react-i18next';
 import flow from 'lodash/flow';
+import i18n from 'i18next';
 
 import Controls from './Controls';
 import AddressList from './AddressList';
@@ -31,10 +32,10 @@ const renderInterfaces = (interfaces) => Object.values(interfaces)
 
         if (option && ip_addresses?.length > 0) {
             const ip = getInterfaceIp(option);
-            const isDown = flags?.includes('down');
+            const isUp = flags?.includes('up');
 
-            return <option value={ip} key={name} disabled={isDown}>
-                {name} - {ip} {isDown && `(${<Trans>down</Trans>})`}
+            return <option value={ip} key={name} disabled={!isUp}>
+                {name} - {ip} {!isUp && `(${i18n.t('down')})`}
             </option>;
         }
 

From c70f941bf87b92d819e6a7adf5255bc43404b026 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Thu, 31 Mar 2022 19:56:50 +0300
Subject: [PATCH 030/143] Pull request: 2846 cover aghnet vol.4

Merge in DNS/adguard-home from 2846-cover-aghnet-vol.4 to master

Updates #2846.

Squashed commit of the following:

commit 576ef857628a403ce1478c10a4aad23985c09613
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 31 19:38:57 2022 +0300

    aghnet: imp code

commit 5b4b17ff52867aaab2c9d30a0fc7fc2fe31ff4d5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 31 14:58:34 2022 +0300

    aghnet: imp coverage
---
 internal/aghnet/arpdb_test.go       |  10 +++
 internal/aghnet/hostgen.go          |   9 +--
 internal/aghnet/hostgen_test.go     |  88 +++++++++++++---------
 internal/aghnet/net.go              |  80 ++++++++------------
 internal/aghnet/net_darwin.go       |   4 -
 internal/aghnet/net_freebsd.go      |   4 -
 internal/aghnet/net_freebsd_test.go |   2 +-
 internal/aghnet/net_linux.go        |  70 +++++++++--------
 internal/aghnet/net_linux_test.go   |  36 ---------
 internal/aghnet/net_nolinux.go      |  10 +++
 internal/aghnet/net_openbsd.go      |   4 -
 internal/aghnet/net_test.go         | 112 +++++++++++++++++++++++++---
 internal/aghnet/net_windows.go      |   4 -
 internal/home/home.go               |  37 ++++-----
 scripts/make/go-lint.sh             |   3 +-
 15 files changed, 266 insertions(+), 207 deletions(-)
 create mode 100644 internal/aghnet/net_nolinux.go

diff --git a/internal/aghnet/arpdb_test.go b/internal/aghnet/arpdb_test.go
index 75778b74..d6971448 100644
--- a/internal/aghnet/arpdb_test.go
+++ b/internal/aghnet/arpdb_test.go
@@ -181,6 +181,16 @@ func TestCmdARPDB_arpa(t *testing.T) {
 		err := a.Refresh()
 		testutil.AssertErrorMsg(t, "cmd arpdb: running command: unexpected exit code 1", err)
 	})
+
+	t.Run("empty", func(t *testing.T) {
+		sh := theOnlyCmd("cmd", 0, "", nil)
+		substShell(t, sh.RunCmd)
+
+		err := a.Refresh()
+		require.NoError(t, err)
+
+		assert.Empty(t, a.Neighbors())
+	})
 }
 
 func TestEmptyARPDB(t *testing.T) {
diff --git a/internal/aghnet/hostgen.go b/internal/aghnet/hostgen.go
index d9278515..683c8d9f 100644
--- a/internal/aghnet/hostgen.go
+++ b/internal/aghnet/hostgen.go
@@ -11,7 +11,7 @@ const (
 	ipv6HostnameMaxLen = len("ff80-f076-0000-0000-0000-0000-0000-0010")
 )
 
-// generateIPv4Hostname generates the hostname for specific IP version.
+// generateIPv4Hostname generates the hostname by IP address version 4.
 func generateIPv4Hostname(ipv4 net.IP) (hostname string) {
 	hnData := make([]byte, 0, ipv4HostnameMaxLen)
 	for i, part := range ipv4 {
@@ -24,7 +24,7 @@ func generateIPv4Hostname(ipv4 net.IP) (hostname string) {
 	return string(hnData)
 }
 
-// generateIPv6Hostname generates the hostname for specific IP version.
+// generateIPv6Hostname generates the hostname by IP address version 6.
 func generateIPv6Hostname(ipv6 net.IP) (hostname string) {
 	hnData := make([]byte, 0, ipv6HostnameMaxLen)
 	for i, partsNum := 0, net.IPv6len/2; i < partsNum; i++ {
@@ -51,12 +51,11 @@ func generateIPv6Hostname(ipv6 net.IP) (hostname string) {
 //
 //   ff80-f076-0000-0000-0000-0000-0000-0010
 //
+// ip must be either an IPv4 or an IPv6.
 func GenerateHostname(ip net.IP) (hostname string) {
 	if ipv4 := ip.To4(); ipv4 != nil {
 		return generateIPv4Hostname(ipv4)
-	} else if ipv6 := ip.To16(); ipv6 != nil {
-		return generateIPv6Hostname(ipv6)
 	}
 
-	return ""
+	return generateIPv6Hostname(ip)
 }
diff --git a/internal/aghnet/hostgen_test.go b/internal/aghnet/hostgen_test.go
index 37121628..d37e556b 100644
--- a/internal/aghnet/hostgen_test.go
+++ b/internal/aghnet/hostgen_test.go
@@ -8,41 +8,57 @@ import (
 )
 
 func TestGenerateHostName(t *testing.T) {
-	testCases := []struct {
-		name string
-		want string
-		ip   net.IP
-	}{{
-		name: "good_ipv4",
-		want: "127-0-0-1",
-		ip:   net.IP{127, 0, 0, 1},
-	}, {
-		name: "bad_ipv4",
-		want: "",
-		ip:   net.IP{127, 0, 0, 1, 0},
-	}, {
-		name: "good_ipv6",
-		want: "fe00-0000-0000-0000-0000-0000-0000-0001",
-		ip:   net.ParseIP("fe00::1"),
-	}, {
-		name: "bad_ipv6",
-		want: "",
-		ip: net.IP{
-			0xff, 0xff, 0xff, 0xff,
-			0xff, 0xff, 0xff, 0xff,
-			0xff, 0xff, 0xff, 0xff,
-			0xff, 0xff, 0xff,
-		},
-	}, {
-		name: "nil",
-		want: "",
-		ip:   nil,
-	}}
+	t.Run("valid", func(t *testing.T) {
+		testCases := []struct {
+			name string
+			want string
+			ip   net.IP
+		}{{
+			name: "good_ipv4",
+			want: "127-0-0-1",
+			ip:   net.IP{127, 0, 0, 1},
+		}, {
+			name: "good_ipv6",
+			want: "fe00-0000-0000-0000-0000-0000-0000-0001",
+			ip:   net.ParseIP("fe00::1"),
+		}, {
+			name: "4to6",
+			want: "1-2-3-4",
+			ip:   net.ParseIP("::ffff:1.2.3.4"),
+		}}
 
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			hostname := GenerateHostname(tc.ip)
-			assert.Equal(t, tc.want, hostname)
-		})
-	}
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				hostname := GenerateHostname(tc.ip)
+				assert.Equal(t, tc.want, hostname)
+			})
+		}
+	})
+
+	t.Run("invalid", func(t *testing.T) {
+		testCases := []struct {
+			name string
+			ip   net.IP
+		}{{
+			name: "bad_ipv4",
+			ip:   net.IP{127, 0, 0, 1, 0},
+		}, {
+			name: "bad_ipv6",
+			ip: net.IP{
+				0xff, 0xff, 0xff, 0xff,
+				0xff, 0xff, 0xff, 0xff,
+				0xff, 0xff, 0xff, 0xff,
+				0xff, 0xff, 0xff,
+			},
+		}, {
+			name: "nil",
+			ip:   nil,
+		}}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				assert.Panics(t, func() { GenerateHostname(tc.ip) })
+			})
+		}
+	})
 }
diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go
index d17b9165..cd2edc72 100644
--- a/internal/aghnet/net.go
+++ b/internal/aghnet/net.go
@@ -15,13 +15,17 @@ import (
 	"github.com/AdguardTeam/golibs/netutil"
 )
 
-// aghosRunCommand is the function to run shell commands.  It's an unexported
-// variable instead of a direct call to make it substitutable in tests.
-var aghosRunCommand = aghos.RunCommand
+// Variables and functions to substitute in tests.
+var (
+	// aghosRunCommand is the function to run shell commands.
+	aghosRunCommand = aghos.RunCommand
 
-// rootDirFS is the filesystem pointing to the root directory.  It's an
-// unexported variable instead to make it substitutable in tests.
-var rootDirFS = aghos.RootDirFS()
+	// netInterfaces is the function to get the available network interfaces.
+	netInterfaceAddrs = net.InterfaceAddrs
+
+	// rootDirFS is the filesystem pointing to the root directory.
+	rootDirFS = aghos.RootDirFS()
+)
 
 // ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about
 // the IP being static is available.
@@ -65,23 +69,6 @@ func GatewayIP(ifaceName string) (ip net.IP) {
 	return net.ParseIP(string(fields[2]))
 }
 
-// CanBindPort checks if we can bind to the given port.
-func CanBindPort(port int) (can bool, err error) {
-	var addr *net.TCPAddr
-	addr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port))
-	if err != nil {
-		return false, err
-	}
-
-	var listener *net.TCPListener
-	listener, err = net.ListenTCP("tcp", addr)
-	if err != nil {
-		return false, err
-	}
-	_ = listener.Close()
-	return true, nil
-}
-
 // CanBindPrivilegedPorts checks if current process can bind to privileged
 // ports.
 func CanBindPrivilegedPorts() (can bool, err error) {
@@ -100,8 +87,8 @@ type NetInterface struct {
 	MTU          int              `json:"mtu"`
 }
 
-// MarshalJSON implements the json.Marshaler interface for NetInterface.
-func (iface NetInterface) MarshalJSON() ([]byte, error) {
+// MarshalText implements the json.Marshaler interface for NetInterface.
+func (iface NetInterface) MarshalText() ([]byte, error) {
 	type netInterface NetInterface
 	return json.Marshal(&struct {
 		HardwareAddr string `json:"hardware_address"`
@@ -114,9 +101,12 @@ func (iface NetInterface) MarshalJSON() ([]byte, error) {
 	})
 }
 
-// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and WEB only
-// we do not return link-local addresses here
-func GetValidNetInterfacesForWeb() (netInterfaces []*NetInterface, err error) {
+// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and
+// WEB only we do not return link-local addresses here.
+//
+// TODO(e.burkov):  Can't properly test the function since it's nontrivial to
+// substitute net.Interface.Addrs and the net.InterfaceAddrs can't be used.
+func GetValidNetInterfacesForWeb() (netIfaces []*NetInterface, err error) {
 	ifaces, err := net.Interfaces()
 	if err != nil {
 		return nil, fmt.Errorf("couldn't get interfaces: %w", err)
@@ -157,14 +147,16 @@ func GetValidNetInterfacesForWeb() (netInterfaces []*NetInterface, err error) {
 
 		// Discard interfaces with no addresses.
 		if len(netIface.Addresses) != 0 {
-			netInterfaces = append(netInterfaces, netIface)
+			netIfaces = append(netIfaces, netIface)
 		}
 	}
 
-	return netInterfaces, nil
+	return netIfaces, nil
 }
 
 // GetInterfaceByIP returns the name of interface containing provided ip.
+//
+// TODO(e.burkov):  See TODO on GetValidInterfacesForWeb.
 func GetInterfaceByIP(ip net.IP) string {
 	ifaces, err := GetValidNetInterfacesForWeb()
 	if err != nil {
@@ -184,6 +176,8 @@ func GetInterfaceByIP(ip net.IP) string {
 
 // GetSubnet returns pointer to net.IPNet for the specified interface or nil if
 // the search fails.
+//
+// TODO(e.burkov):  See TODO on GetValidInterfacesForWeb.
 func GetSubnet(ifaceName string) *net.IPNet {
 	netIfaces, err := GetValidNetInterfacesForWeb()
 	if err != nil {
@@ -234,29 +228,21 @@ func IsAddrInUse(err error) (ok bool) {
 // CollectAllIfacesAddrs returns the slice of all network interfaces IP
 // addresses without port number.
 func CollectAllIfacesAddrs() (addrs []string, err error) {
-	var ifaces []net.Interface
-	ifaces, err = net.Interfaces()
+	var ifaceAddrs []net.Addr
+	ifaceAddrs, err = netInterfaceAddrs()
 	if err != nil {
-		return nil, fmt.Errorf("getting network interfaces: %w", err)
+		return nil, fmt.Errorf("getting interfaces addresses: %w", err)
 	}
 
-	for _, iface := range ifaces {
-		var ifaceAddrs []net.Addr
-		ifaceAddrs, err = iface.Addrs()
+	for _, addr := range ifaceAddrs {
+		cidr := addr.String()
+		var ip net.IP
+		ip, _, err = net.ParseCIDR(cidr)
 		if err != nil {
-			return nil, fmt.Errorf("getting addresses for %q: %w", iface.Name, err)
+			return nil, fmt.Errorf("parsing cidr: %w", err)
 		}
 
-		for _, addr := range ifaceAddrs {
-			cidr := addr.String()
-			var ip net.IP
-			ip, _, err = net.ParseCIDR(cidr)
-			if err != nil {
-				return nil, fmt.Errorf("parsing cidr: %w", err)
-			}
-
-			addrs = append(addrs, ip.String())
-		}
+		addrs = append(addrs, ip.String())
 	}
 
 	return addrs, nil
diff --git a/internal/aghnet/net_darwin.go b/internal/aghnet/net_darwin.go
index 63e57dc4..296a18b0 100644
--- a/internal/aghnet/net_darwin.go
+++ b/internal/aghnet/net_darwin.go
@@ -24,10 +24,6 @@ type hardwarePortInfo struct {
 	static    bool
 }
 
-func canBindPrivilegedPorts() (can bool, err error) {
-	return aghos.HaveAdminRights()
-}
-
 func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
 	portInfo, err := getCurrentHardwarePortInfo(ifaceName)
 	if err != nil {
diff --git a/internal/aghnet/net_freebsd.go b/internal/aghnet/net_freebsd.go
index 34d93303..85d40184 100644
--- a/internal/aghnet/net_freebsd.go
+++ b/internal/aghnet/net_freebsd.go
@@ -13,10 +13,6 @@ import (
 	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
 )
 
-func canBindPrivilegedPorts() (can bool, err error) {
-	return aghos.HaveAdminRights()
-}
-
 func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
 	const rcConfFilename = "etc/rc.conf"
 
diff --git a/internal/aghnet/net_freebsd_test.go b/internal/aghnet/net_freebsd_test.go
index e00dafa7..2c758360 100644
--- a/internal/aghnet/net_freebsd_test.go
+++ b/internal/aghnet/net_freebsd_test.go
@@ -12,7 +12,7 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestRcConfStaticConfig(t *testing.T) {
+func TestIfaceHasStaticIP(t *testing.T) {
 	const (
 		ifaceName = `em0`
 		rcConf    = "etc/rc.conf"
diff --git a/internal/aghnet/net_linux.go b/internal/aghnet/net_linux.go
index c2526524..148abe1f 100644
--- a/internal/aghnet/net_linux.go
+++ b/internal/aghnet/net_linux.go
@@ -13,10 +13,28 @@ import (
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
 	"github.com/AdguardTeam/golibs/errors"
+	"github.com/AdguardTeam/golibs/stringutil"
 	"github.com/google/renameio/maybe"
 	"golang.org/x/sys/unix"
 )
 
+// dhcpсdConf is the name of /etc/dhcpcd.conf file in the root filesystem.
+const dhcpcdConf = "etc/dhcpcd.conf"
+
+func canBindPrivilegedPorts() (can bool, err error) {
+	cnbs, err := unix.PrctlRetInt(
+		unix.PR_CAP_AMBIENT,
+		unix.PR_CAP_AMBIENT_IS_SET,
+		unix.CAP_NET_BIND_SERVICE,
+		0,
+		0,
+	)
+	// Don't check the error because it's always nil on Linux.
+	adm, _ := aghos.HaveAdminRights()
+
+	return cnbs == 1 || adm, err
+}
+
 // dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to
 // have a static IP.
 func (n interfaceName) dhcpcdStaticConfig(r io.Reader) (subsources []string, cont bool, err error) {
@@ -89,7 +107,7 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
 		filename string
 	}{{
 		FileWalker: iface.dhcpcdStaticConfig,
-		filename:   "etc/dhcpcd.conf",
+		filename:   dhcpcdConf,
 	}, {
 		FileWalker: iface.ifacesStaticConfig,
 		filename:   "etc/network/interfaces",
@@ -105,14 +123,6 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
 	return false, ErrNoStaticIPInfo
 }
 
-func canBindPrivilegedPorts() (can bool, err error) {
-	cnbs, err := unix.PrctlRetInt(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, unix.CAP_NET_BIND_SERVICE, 0, 0)
-	// Don't check the error because it's always nil on Linux.
-	adm, _ := aghos.HaveAdminRights()
-
-	return cnbs == 1 || adm, err
-}
-
 // findIfaceLine scans s until it finds the line that declares an interface with
 // the given name.  If findIfaceLine can't find the line, it returns false.
 func findIfaceLine(s *bufio.Scanner, name string) (ok bool) {
@@ -128,25 +138,23 @@ func findIfaceLine(s *bufio.Scanner, name string) (ok bool) {
 }
 
 // ifaceSetStaticIP configures the system to retain its current IP on the
-// interface through dhcpdc.conf.
+// interface through dhcpcd.conf.
 func ifaceSetStaticIP(ifaceName string) (err error) {
 	ipNet := GetSubnet(ifaceName)
 	if ipNet.IP == nil {
 		return errors.Error("can't get IP address")
 	}
 
-	gatewayIP := GatewayIP(ifaceName)
-	add := dhcpcdConfIface(ifaceName, ipNet, gatewayIP, ipNet.IP)
-
-	const filename = "/etc/dhcpcd.conf"
-
-	body, err := os.ReadFile(filename)
+	body, err := os.ReadFile(dhcpcdConf)
 	if err != nil && !errors.Is(err, os.ErrNotExist) {
 		return err
 	}
 
+	gatewayIP := GatewayIP(ifaceName)
+	add := dhcpcdConfIface(ifaceName, ipNet, gatewayIP)
+
 	body = append(body, []byte(add)...)
-	err = maybe.WriteFile(filename, body, 0o644)
+	err = maybe.WriteFile(dhcpcdConf, body, 0o644)
 	if err != nil {
 		return fmt.Errorf("writing conf: %w", err)
 	}
@@ -156,22 +164,24 @@ func ifaceSetStaticIP(ifaceName string) (err error) {
 
 // dhcpcdConfIface returns configuration lines for the dhcpdc.conf files that
 // configure the interface to have a static IP.
-func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gatewayIP, dnsIP net.IP) (conf string) {
-	var body []byte
-
-	add := fmt.Sprintf(
-		"\n# %[1]s added by AdGuard Home.\ninterface %[1]s\nstatic ip_address=%s\n",
+func dhcpcdConfIface(ifaceName string, ipNet *net.IPNet, gwIP net.IP) (conf string) {
+	b := &strings.Builder{}
+	stringutil.WriteToBuilder(
+		b,
+		"\n# ",
 		ifaceName,
-		ipNet)
-	body = append(body, []byte(add)...)
+		" added by AdGuard Home.\ninterface ",
+		ifaceName,
+		"\nstatic ip_address=",
+		ipNet.String(),
+		"\n",
+	)
 
-	if gatewayIP != nil {
-		add = fmt.Sprintf("static routers=%s\n", gatewayIP)
-		body = append(body, []byte(add)...)
+	if gwIP != nil {
+		stringutil.WriteToBuilder(b, "static routers=", gwIP.String(), "\n")
 	}
 
-	add = fmt.Sprintf("static domain_name_servers=%s\n\n", dnsIP)
-	body = append(body, []byte(add)...)
+	stringutil.WriteToBuilder(b, "static domain_name_servers=", ipNet.IP.String(), "\n\n")
 
-	return string(body)
+	return b.String()
 }
diff --git a/internal/aghnet/net_linux_test.go b/internal/aghnet/net_linux_test.go
index e46da4df..838802ff 100644
--- a/internal/aghnet/net_linux_test.go
+++ b/internal/aghnet/net_linux_test.go
@@ -5,7 +5,6 @@ package aghnet
 
 import (
 	"io/fs"
-	"net"
 	"testing"
 	"testing/fstest"
 
@@ -126,38 +125,3 @@ func TestHasStaticIP(t *testing.T) {
 		})
 	}
 }
-
-func TestSetStaticIP_dhcpcdConfIface(t *testing.T) {
-	testCases := []struct {
-		name       string
-		dhcpcdConf string
-		routers    net.IP
-	}{{
-		name: "with_gateway",
-		dhcpcdConf: nl + `# wlan0 added by AdGuard Home.` + nl +
-			`interface wlan0` + nl +
-			`static ip_address=192.168.0.2/24` + nl +
-			`static routers=192.168.0.1` + nl +
-			`static domain_name_servers=192.168.0.2` + nl + nl,
-		routers: net.IP{192, 168, 0, 1},
-	}, {
-		name: "without_gateway",
-		dhcpcdConf: nl + `# wlan0 added by AdGuard Home.` + nl +
-			`interface wlan0` + nl +
-			`static ip_address=192.168.0.2/24` + nl +
-			`static domain_name_servers=192.168.0.2` + nl + nl,
-		routers: nil,
-	}}
-
-	ipNet := &net.IPNet{
-		IP:   net.IP{192, 168, 0, 2},
-		Mask: net.IPMask{255, 255, 255, 0},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			s := dhcpcdConfIface("wlan0", ipNet, tc.routers, net.IP{192, 168, 0, 2})
-			assert.Equal(t, tc.dhcpcdConf, s)
-		})
-	}
-}
diff --git a/internal/aghnet/net_nolinux.go b/internal/aghnet/net_nolinux.go
new file mode 100644
index 00000000..f429c6fa
--- /dev/null
+++ b/internal/aghnet/net_nolinux.go
@@ -0,0 +1,10 @@
+//go:build !linux
+// +build !linux
+
+package aghnet
+
+import "github.com/AdguardTeam/AdGuardHome/internal/aghos"
+
+func canBindPrivilegedPorts() (can bool, err error) {
+	return aghos.HaveAdminRights()
+}
diff --git a/internal/aghnet/net_openbsd.go b/internal/aghnet/net_openbsd.go
index 68ef90e0..cf911105 100644
--- a/internal/aghnet/net_openbsd.go
+++ b/internal/aghnet/net_openbsd.go
@@ -13,10 +13,6 @@ import (
 	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
 )
 
-func canBindPrivilegedPorts() (can bool, err error) {
-	return aghos.HaveAdminRights()
-}
-
 func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
 	filename := fmt.Sprintf("etc/hostname.%s", ifaceName)
 
diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go
index 29de869b..77eab3be 100644
--- a/internal/aghnet/net_test.go
+++ b/internal/aghnet/net_test.go
@@ -56,7 +56,7 @@ type mapShell map[string]struct {
 	code int
 }
 
-// theOnlyCmd returns s that only handles a single command and arguments
+// theOnlyCmd returns mapShell that only handles a single command and arguments
 // combination from cmd.
 func theOnlyCmd(cmd string, code int, out string, err error) (s mapShell) {
 	return mapShell{cmd: {code: code, out: out, err: err}}
@@ -73,18 +73,34 @@ func (s mapShell) RunCmd(cmd string, args ...string) (code int, out []byte, err
 	return ret.code, []byte(ret.out), ret.err
 }
 
+// ifaceAddrsFunc is the signature of net.InterfaceAddrs function.
+type ifaceAddrsFunc func() (ifaces []net.Addr, err error)
+
+// substNetInterfaceAddrs replaces the the net.InterfaceAddrs function used
+// throughout the package with f for tests ran under t.
+func substNetInterfaceAddrs(t *testing.T, f ifaceAddrsFunc) {
+	t.Helper()
+
+	prev := netInterfaceAddrs
+	t.Cleanup(func() { netInterfaceAddrs = prev })
+	netInterfaceAddrs = f
+}
+
 func TestGatewayIP(t *testing.T) {
+	const ifaceName = "ifaceName"
+	const cmd = "ip route show dev " + ifaceName
+
 	testCases := []struct {
 		name  string
 		shell mapShell
 		want  net.IP
 	}{{
 		name:  "success_v4",
-		shell: theOnlyCmd("ip route show dev ifaceName", 0, `default via 1.2.3.4 onlink`, nil),
+		shell: theOnlyCmd(cmd, 0, `default via 1.2.3.4 onlink`, nil),
 		want:  net.IP{1, 2, 3, 4}.To16(),
 	}, {
 		name:  "success_v6",
-		shell: theOnlyCmd("ip route show dev ifaceName", 0, `default via ::ffff onlink`, nil),
+		shell: theOnlyCmd(cmd, 0, `default via ::ffff onlink`, nil),
 		want: net.IP{
 			0x0, 0x0, 0x0, 0x0,
 			0x0, 0x0, 0x0, 0x0,
@@ -93,15 +109,15 @@ func TestGatewayIP(t *testing.T) {
 		},
 	}, {
 		name:  "bad_output",
-		shell: theOnlyCmd("ip route show dev ifaceName", 0, `non-default via 1.2.3.4 onlink`, nil),
+		shell: theOnlyCmd(cmd, 0, `non-default via 1.2.3.4 onlink`, nil),
 		want:  nil,
 	}, {
 		name:  "err_runcmd",
-		shell: theOnlyCmd("ip route show dev ifaceName", 0, "", errors.Error("can't run command")),
+		shell: theOnlyCmd(cmd, 0, "", errors.Error("can't run command")),
 		want:  nil,
 	}, {
 		name:  "bad_code",
-		shell: theOnlyCmd("ip route show dev ifaceName", 1, "", nil),
+		shell: theOnlyCmd(cmd, 1, "", nil),
 		want:  nil,
 	}}
 
@@ -109,7 +125,7 @@ func TestGatewayIP(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			substShell(t, tc.shell.RunCmd)
 
-			assert.Equal(t, tc.want, GatewayIP("ifaceName"))
+			assert.Equal(t, tc.want, GatewayIP(ifaceName))
 		})
 	}
 }
@@ -226,12 +242,56 @@ func TestCheckPort(t *testing.T) {
 }
 
 func TestCollectAllIfacesAddrs(t *testing.T) {
-	t.Skip("TODO(e.burkov):  Substitute the net.Interfaces.")
+	testCases := []struct {
+		name       string
+		wantErrMsg string
+		addrs      []net.Addr
+		wantAddrs  []string
+	}{{
+		name:       "success",
+		wantErrMsg: ``,
+		addrs: []net.Addr{&net.IPNet{
+			IP:   net.IP{1, 2, 3, 4},
+			Mask: net.CIDRMask(24, netutil.IPv4BitLen),
+		}, &net.IPNet{
+			IP:   net.IP{4, 3, 2, 1},
+			Mask: net.CIDRMask(16, netutil.IPv4BitLen),
+		}},
+		wantAddrs: []string{"1.2.3.4", "4.3.2.1"},
+	}, {
+		name:       "not_cidr",
+		wantErrMsg: `parsing cidr: invalid CIDR address: 1.2.3.4`,
+		addrs: []net.Addr{&net.IPAddr{
+			IP: net.IP{1, 2, 3, 4},
+		}},
+		wantAddrs: nil,
+	}, {
+		name:       "empty",
+		wantErrMsg: ``,
+		addrs:      []net.Addr{},
+		wantAddrs:  nil,
+	}}
 
-	addrs, err := CollectAllIfacesAddrs()
-	require.NoError(t, err)
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return tc.addrs, nil })
 
-	assert.NotEmpty(t, addrs)
+			addrs, err := CollectAllIfacesAddrs()
+			testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+
+			assert.Equal(t, tc.wantAddrs, addrs)
+		})
+	}
+
+	t.Run("internal_error", func(t *testing.T) {
+		const errAddrs errors.Error = "can't get addresses"
+		const wantErrMsg string = `getting interfaces addresses: ` + string(errAddrs)
+
+		substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return nil, errAddrs })
+
+		_, err := CollectAllIfacesAddrs()
+		testutil.AssertErrorMsg(t, wantErrMsg, err)
+	})
 }
 
 func TestIsAddrInUse(t *testing.T) {
@@ -250,3 +310,33 @@ func TestIsAddrInUse(t *testing.T) {
 		assert.False(t, IsAddrInUse(anotherErr))
 	})
 }
+
+func TestNetInterface_MarshalText(t *testing.T) {
+	const want = `{` +
+		`"hardware_address":"aa:bb:cc:dd:ee:ff",` +
+		`"flags":"up|multicast",` +
+		`"ip_addresses":["1.2.3.4","aaaa::1"],` +
+		`"name":"iface0",` +
+		`"mtu":1500` +
+		`}`
+
+	ip4, ip6 := net.IP{1, 2, 3, 4}, net.IP{0xAA, 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
+	mask4, mask6 := net.CIDRMask(24, netutil.IPv4BitLen), net.CIDRMask(8, netutil.IPv6BitLen)
+
+	iface := &NetInterface{
+		Addresses: []net.IP{ip4, ip6},
+		Subnets: []*net.IPNet{{
+			IP:   ip4.Mask(mask4),
+			Mask: mask4,
+		}, {
+			IP:   ip6.Mask(mask6),
+			Mask: mask6,
+		}},
+		Name:         "iface0",
+		HardwareAddr: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+		Flags:        net.FlagUp | net.FlagMulticast,
+		MTU:          1500,
+	}
+
+	testutil.AssertMarshalText(t, want, iface)
+}
diff --git a/internal/aghnet/net_windows.go b/internal/aghnet/net_windows.go
index 128f5716..0cea8fe7 100644
--- a/internal/aghnet/net_windows.go
+++ b/internal/aghnet/net_windows.go
@@ -13,10 +13,6 @@ import (
 	"golang.org/x/sys/windows"
 )
 
-func canBindPrivilegedPorts() (can bool, err error) {
-	return aghos.HaveAdminRights()
-}
-
 func ifaceHasStaticIP(string) (ok bool, err error) {
 	return false, aghos.Unsupported("checking static ip")
 }
diff --git a/internal/home/home.go b/internal/home/home.go
index 5fae42c8..df1dc95b 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -517,27 +517,15 @@ func StartMods() error {
 func checkPermissions() {
 	log.Info("Checking if AdGuard Home has necessary permissions")
 
-	if runtime.GOOS == "windows" {
-		// On Windows we need to have admin rights to run properly
-
-		admin, _ := aghos.HaveAdminRights()
-		if admin {
-			return
-		}
-
+	if ok, err := aghnet.CanBindPrivilegedPorts(); !ok || err != nil {
 		log.Fatal("This is the first launch of AdGuard Home. You must run it as Administrator.")
 	}
 
 	// We should check if AdGuard Home is able to bind to port 53
-	ok, err := aghnet.CanBindPort(53)
-
-	if ok {
-		log.Info("AdGuard Home can bind to port 53")
-		return
-	}
-
-	if errors.Is(err, os.ErrPermission) {
-		msg := `Permission check failed.
+	err := aghnet.CheckPort("tcp", net.IP{127, 0, 0, 1}, defaultPortDNS)
+	if err != nil {
+		if errors.Is(err, os.ErrPermission) {
+			log.Fatal(`Permission check failed.
 
 AdGuard Home is not allowed to bind to privileged ports (for instance, port 53).
 Please note, that this is crucial for a server to be able to use privileged ports.
@@ -545,16 +533,17 @@ Please note, that this is crucial for a server to be able to use privileged port
 You have two options:
 1. Run AdGuard Home with root privileges
 2. On Linux you can grant the CAP_NET_BIND_SERVICE capability:
-https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser`
+https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser`)
+		}
 
-		log.Fatal(msg)
+		log.Info(
+			"AdGuard failed to bind to port 53: %s\n\n"+
+				"Please note, that this is crucial for a DNS server to be able to use that port.",
+			err,
+		)
 	}
 
-	msg := fmt.Sprintf(`AdGuard failed to bind to port 53 due to %v
-
-Please note, that this is crucial for a DNS server to be able to use that port.`, err)
-
-	log.Info(msg)
+	log.Info("AdGuard Home can bind to port 53")
 }
 
 // Write PID to a file
diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh
index 8e646f0c..32d99480 100644
--- a/scripts/make/go-lint.sh
+++ b/scripts/make/go-lint.sh
@@ -134,9 +134,10 @@ underscores() {
 			-e '_bsd.go'\
 			-e '_darwin.go'\
 			-e '_freebsd.go'\
-			-e '_openbsd.go'\
 			-e '_linux.go'\
 			-e '_little.go'\
+			-e '_nolinux.go'\
+			-e '_openbsd.go'\
 			-e '_others.go'\
 			-e '_test.go'\
 			-e '_unix.go'\

From e9e0b7c4f907dd37e35511639caca0a88779bd0f Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Wed, 6 Apr 2022 19:27:24 +0300
Subject: [PATCH 031/143] Pull request: 700 validate only enabled encryption
 form

Merge in DNS/adguard-home from 700-validate to master

Updates #700.

Squashed commit of the following:

commit 9cd9ff2d23352e00c7782cf68195809111c832e5
Author: Ildar Kamalov <ik@adguard.com>
Date:   Wed Apr 6 18:50:11 2022 +0300

    client: validate only enabled encryption form
---
 client/src/components/Settings/Encryption/index.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/client/src/components/Settings/Encryption/index.js b/client/src/components/Settings/Encryption/index.js
index 13f5c47c..bcd2d323 100644
--- a/client/src/components/Settings/Encryption/index.js
+++ b/client/src/components/Settings/Encryption/index.js
@@ -25,7 +25,9 @@ class Encryption extends Component {
 
     handleFormChange = debounce((values) => {
         const submitValues = this.getSubmitValues(values);
-        this.props.validateTlsConfig(submitValues);
+        if (submitValues.enabled) {
+            this.props.validateTlsConfig(submitValues);
+        }
     }, DEBOUNCE_TIMEOUT);
 
     getInitialValues = (data) => {

From 8bb95469d92bac60811f3f76c59511e52d0ffadc Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 6 Apr 2022 19:36:13 +0300
Subject: [PATCH 032/143] Pull request: 4465 fix ifaces resp

Merge in DNS/adguard-home from 4465-bad-ifaces-resp to master

Closes #4465.

Squashed commit of the following:

commit cc44252b2f12ba4b15df315253417aba2a3f98a6
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 6 19:21:40 2022 +0300

    aghnet: fix get_addresses
---
 internal/aghnet/net.go      |  4 ++--
 internal/aghnet/net_test.go | 12 +++++++++---
 2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go
index cd2edc72..268380bd 100644
--- a/internal/aghnet/net.go
+++ b/internal/aghnet/net.go
@@ -87,8 +87,8 @@ type NetInterface struct {
 	MTU          int              `json:"mtu"`
 }
 
-// MarshalText implements the json.Marshaler interface for NetInterface.
-func (iface NetInterface) MarshalText() ([]byte, error) {
+// MarshalJSON implements the json.Marshaler interface for NetInterface.
+func (iface NetInterface) MarshalJSON() ([]byte, error) {
 	type netInterface NetInterface
 	return json.Marshal(&struct {
 		HardwareAddr string `json:"hardware_address"`
diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go
index 77eab3be..40d395ba 100644
--- a/internal/aghnet/net_test.go
+++ b/internal/aghnet/net_test.go
@@ -1,6 +1,8 @@
 package aghnet
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
 	"io/fs"
 	"net"
@@ -311,14 +313,14 @@ func TestIsAddrInUse(t *testing.T) {
 	})
 }
 
-func TestNetInterface_MarshalText(t *testing.T) {
+func TestNetInterface_MarshalJSON(t *testing.T) {
 	const want = `{` +
 		`"hardware_address":"aa:bb:cc:dd:ee:ff",` +
 		`"flags":"up|multicast",` +
 		`"ip_addresses":["1.2.3.4","aaaa::1"],` +
 		`"name":"iface0",` +
 		`"mtu":1500` +
-		`}`
+		`}` + "\n"
 
 	ip4, ip6 := net.IP{1, 2, 3, 4}, net.IP{0xAA, 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
 	mask4, mask6 := net.CIDRMask(24, netutil.IPv4BitLen), net.CIDRMask(8, netutil.IPv6BitLen)
@@ -338,5 +340,9 @@ func TestNetInterface_MarshalText(t *testing.T) {
 		MTU:          1500,
 	}
 
-	testutil.AssertMarshalText(t, want, iface)
+	b := &bytes.Buffer{}
+	err := json.NewEncoder(b).Encode(iface)
+	require.NoError(t, err)
+
+	assert.Equal(t, want, b.String())
 }

From 0e608fda1367c33e8da1b8fc28029f45b3d69fab Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 6 Apr 2022 19:56:39 +0300
Subject: [PATCH 033/143] Pull request: 4437 depr memory opt

Merge in DNS/adguard-home from 4437-rm-mem-opt to master

Updates #4437.
Updates #2044.

Squashed commit of the following:

commit d1e5520213f6b68570d18a8d831d4923112901ba
Merge: 73a6b494 8bb95469
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 6 19:37:09 2022 +0300

    Merge branch 'master' into 4437-rm-mem-opt

commit 73a6b4948cb32f1cb79a54b244018b29382fad76
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 6 18:33:23 2022 +0300

    all: imp log of changes

commit a62efcdcd44de300726c906c7f6198c0a02d4ccf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 6 18:27:42 2022 +0300

    home: depr memory opt
---
 CHANGELOG.md                  |  3 +++
 internal/home/home.go         |  3 ---
 internal/home/memory.go       | 40 -----------------------------------
 internal/home/options.go      | 13 ++++++------
 internal/home/options_test.go | 32 +++++++++++-----------------
 5 files changed, 22 insertions(+), 69 deletions(-)
 delete mode 100644 internal/home/memory.go

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d1e9260..9bc865a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -79,6 +79,8 @@ In this release, the schema version has changed from 12 to 13.
 
 ### Deprecated
 
+- Obsolete `--no-mem-optimization` option ([#4437]).  v0.109.0 will remove the
+  flag completely.
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
 
 ### Fixed
@@ -111,6 +113,7 @@ In this release, the schema version has changed from 12 to 13.
 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
+[#4437]: https://github.com/AdguardTeam/AdGuardHome/issues/4437
 
 [repr]:         https://reproducible-builds.org/docs/source-date-epoch/
 [doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
diff --git a/internal/home/home.go b/internal/home/home.go
index df1dc95b..420e67d8 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -389,9 +389,6 @@ func run(args options, clientBuildFS fs.FS) {
 	// configure log level and output
 	configureLogger(args)
 
-	// Go memory hacks
-	memoryUsage(args)
-
 	// Print the first message after logger is configured.
 	log.Println(version.Full())
 	log.Debug("current working directory is %s", Context.workDir)
diff --git a/internal/home/memory.go b/internal/home/memory.go
deleted file mode 100644
index bcb6e70f..00000000
--- a/internal/home/memory.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package home
-
-import (
-	"os"
-	"runtime/debug"
-	"time"
-
-	"github.com/AdguardTeam/golibs/log"
-)
-
-// memoryUsage implements a couple of not really beautiful hacks which purpose is to
-// make OS reclaim the memory freed by AdGuard Home as soon as possible.
-// See this for the details on the performance hits & gains:
-// https://github.com/AdguardTeam/AdGuardHome/internal/issues/2044#issuecomment-687042211
-func memoryUsage(args options) {
-	if args.disableMemoryOptimization {
-		log.Info("Memory optimization is disabled")
-		return
-	}
-
-	// Makes Go allocate heap at a slower pace
-	// By default we keep it at 50%
-	debug.SetGCPercent(50)
-
-	// madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED
-	// instead of MADV_FREE on Linux when returning memory to the
-	// kernel. This is less efficient, but causes RSS numbers to drop
-	// more quickly.
-	_ = os.Setenv("GODEBUG", "madvdontneed=1")
-
-	// periodically call "debug.FreeOSMemory" so
-	// that the OS could reclaim the free memory
-	go func() {
-		ticker := time.NewTicker(5 * time.Minute)
-		for range ticker.C {
-			log.Debug("free os memory")
-			debug.FreeOSMemory()
-		}
-	}()
-}
diff --git a/internal/home/options.go b/internal/home/options.go
index 6d9a1f99..731c8620 100644
--- a/internal/home/options.go
+++ b/internal/home/options.go
@@ -7,6 +7,7 @@ import (
 	"strconv"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/version"
+	"github.com/AdguardTeam/golibs/log"
 )
 
 // options passed from command-line arguments
@@ -27,10 +28,6 @@ type options struct {
 	// runningAsService flag is set to true when options are passed from the service runner
 	runningAsService bool
 
-	// disableMemoryOptimization - disables memory optimization hacks
-	// see memoryUsage() function for the details
-	disableMemoryOptimization bool
-
 	glinetMode bool // Activate GL-Inet compatibility mode
 
 	// noEtcHosts flag should be provided when /etc/hosts file shouldn't be
@@ -180,8 +177,12 @@ var noCheckUpdateArg = arg{
 var disableMemoryOptimizationArg = arg{
 	"Disable memory optimization.",
 	"no-mem-optimization", "",
-	nil, func(o options) (options, error) { o.disableMemoryOptimization = true; return o, nil }, nil,
-	func(o options) []string { return boolSliceOrNil(o.disableMemoryOptimization) },
+	nil, nil, func(_ options, _ string) (f effect, err error) {
+		log.Info("warning: using --no-mem-optimization flag has no effect and is deprecated")
+
+		return nil, nil
+	},
+	func(o options) []string { return nil },
 }
 
 var verboseArg = arg{
diff --git a/internal/home/options_test.go b/internal/home/options_test.go
index b189f045..21972b0a 100644
--- a/internal/home/options_test.go
+++ b/internal/home/options_test.go
@@ -101,9 +101,13 @@ func TestParseDisableUpdate(t *testing.T) {
 	assert.True(t, testParseOK(t, "--no-check-update").disableUpdate, "--no-check-update is disable update")
 }
 
+// TODO(e.burkov):  Remove after v0.108.0.
 func TestParseDisableMemoryOptimization(t *testing.T) {
-	assert.False(t, testParseOK(t).disableMemoryOptimization, "empty is not disable update")
-	assert.True(t, testParseOK(t, "--no-mem-optimization").disableMemoryOptimization, "--no-mem-optimization is disable update")
+	o, eff, err := parse("", []string{"--no-mem-optimization"})
+	require.NoError(t, err)
+
+	assert.Nil(t, eff)
+	assert.Zero(t, o)
 }
 
 func TestParseService(t *testing.T) {
@@ -127,8 +131,6 @@ func TestParseUnknown(t *testing.T) {
 }
 
 func TestSerialize(t *testing.T) {
-	const reportFmt = "expected %s but got %s"
-
 	testCases := []struct {
 		name string
 		opts options
@@ -173,19 +175,14 @@ func TestSerialize(t *testing.T) {
 		name: "glinet_mode",
 		opts: options{glinetMode: true},
 		ss:   []string{"--glinet"},
-	}, {
-		name: "disable_mem_opt",
-		opts: options{disableMemoryOptimization: true},
-		ss:   []string{"--no-mem-optimization"},
 	}, {
 		name: "multiple",
 		opts: options{
-			serviceControlAction:      "run",
-			configFilename:            "config",
-			workDir:                   "work",
-			pidFile:                   "pid",
-			disableUpdate:             true,
-			disableMemoryOptimization: true,
+			serviceControlAction: "run",
+			configFilename:       "config",
+			workDir:              "work",
+			pidFile:              "pid",
+			disableUpdate:        true,
 		},
 		ss: []string{
 			"-c", "config",
@@ -193,18 +190,13 @@ func TestSerialize(t *testing.T) {
 			"-s", "run",
 			"--pidfile", "pid",
 			"--no-check-update",
-			"--no-mem-optimization",
 		},
 	}}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 			result := serialize(tc.opts)
-			require.Lenf(t, result, len(tc.ss), reportFmt, tc.ss, result)
-
-			for i, r := range result {
-				assert.Equalf(t, tc.ss[i], r, reportFmt, tc.ss, result)
-			}
+			assert.ElementsMatch(t, tc.ss, result)
 		})
 	}
 }

From 4c5b38a4474b573086b1afd97c4e24522829ebe6 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Thu, 7 Apr 2022 14:07:27 +0300
Subject: [PATCH 034/143] Pull request: 4437 imp help output

Merge in DNS/adguard-home from imp-help to master

Updates #4437.

Squashed commit of the following:

commit 941338b93e19021c5b211e9e644387e4326533ce
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 7 13:59:55 2022 +0300

    home: imp help output
---
 internal/home/options.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/internal/home/options.go b/internal/home/options.go
index 731c8620..dc11ca35 100644
--- a/internal/home/options.go
+++ b/internal/home/options.go
@@ -175,7 +175,7 @@ var noCheckUpdateArg = arg{
 }
 
 var disableMemoryOptimizationArg = arg{
-	"Disable memory optimization.",
+	"Deprecated.  Disable memory optimization.",
 	"no-mem-optimization", "",
 	nil, nil, func(_ options, _ string) (f effect, err error) {
 		log.Info("warning: using --no-mem-optimization flag has no effect and is deprecated")

From 96594a34333a49a8ce3a818b3b7e48ceb547d7c2 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 7 Apr 2022 18:08:39 +0300
Subject: [PATCH 035/143] Pull request: dnsforward: upd svcp param ech name

Merge in DNS/adguard-home from upd-ech-dnsrewrite to master

Squashed commit of the following:

commit b5d9e8643fcb0d7fe7bc44c6d8fc8a9d3f2c9595
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 7 18:01:18 2022 +0300

    all: imp chlog

commit 447c5ea6bc2031d4af46578bdb8d724bff001ca0
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 7 15:40:18 2022 +0300

    dnsforward: upd svcp param ech name
---
 CHANGELOG.md                        | 41 ++++++++++++++++++-----------
 go.mod                              | 10 +++----
 go.sum                              | 18 ++++++++-----
 internal/dnsforward/svcbmsg.go      | 30 ++++++++++++++++++---
 internal/dnsforward/svcbmsg_test.go |  8 ++++--
 5 files changed, 75 insertions(+), 32 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bc865a3..ca7d27ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,8 +30,11 @@ and this project adheres to
 
 ### Changed
 
+- Filtering rules with the `dnsrewrite` modifier that create SVCB or HTTPS
+  responses should use `ech` instead of `echconfig` to conform with the [latest
+  drafts][svcb-draft-08].
 - The default DNS-over-QUIC port number is now `853` instead of `754` in
-  accoradance with the latest [RFC draft][doq-draft-10] ([#4276]).
+  accordance with the latest [RFC draft][doq-draft-10] ([#4276]).
 - Reverse DNS now has a greater priority as the source of runtime clients'
   information than ARP neighborhood.
 - Improved detection of runtime clients through more resilient ARP processing
@@ -79,6 +82,9 @@ In this release, the schema version has changed from 12 to 13.
 
 ### Deprecated
 
+- SVCB/HTTPS parameter name `echconfig` in filtering rules with the `dnsrewrite`
+  modifier.  Use `ech` instead.  v0.109.0 will remove support for the outdated
+  name `echconfig`.
 - Obsolete `--no-mem-optimization` option ([#4437]).  v0.109.0 will remove the
   flag completely.
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
@@ -115,8 +121,9 @@ In this release, the schema version has changed from 12 to 13.
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
 [#4437]: https://github.com/AdguardTeam/AdGuardHome/issues/4437
 
-[repr]:         https://reproducible-builds.org/docs/source-date-epoch/
-[doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
+[repr]:          https://reproducible-builds.org/docs/source-date-epoch/
+[doq-draft-10]:  https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
+[svcb-draft-08]: https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-08.html
 
 
 
@@ -176,7 +183,7 @@ See also the [v0.107.3 GitHub milestone][ms-v0.107.3].
 
 ### Added
 
-- Support for a `$dnsrewrite` modifier with an empty `NOERROR` response
+- Support for a `dnsrewrite` modifier with an empty `NOERROR` response
   ([#4133]).
 
 ### Fixed
@@ -292,7 +299,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
 - Better error message for ED25519 private keys, which are not widely supported
   ([#3737]).
 - Cache now follows RFC more closely for negative answers ([#3707]).
-- `$dnsrewrite` rules and other DNS rewrites will now be applied even when the
+- `dnsrewrite` rules and other DNS rewrites will now be applied even when the
   protection is disabled ([#1558]).
 - DHCP gateway address, subnet mask, IP address range, and leases validations
   ([#3529]).
@@ -365,7 +372,7 @@ In this release, the schema version has changed from 10 to 12.
 ### Fixed
 
 - EDNS0 TCP keepalive option handling ([#3778]).
-- Rules with the `$denyallow` modifier applying to IP addresses when they
+- Rules with the `denyallow` modifier applying to IP addresses when they
   shouldn't ([#3175]).
 - The length of the EDNS0 client subnet option appearing too long for some
   upstream servers ([#3887]).
@@ -373,8 +380,8 @@ In this release, the schema version has changed from 10 to 12.
   settings ([#3558]).
 - Incomplete propagation of the client's IP anonymization setting to the
   statistics ([#3890]).
-- Incorrect `$dnsrewrite` results for entries from the operating system's hosts
-  file ([#3815]).
+- Incorrect results with the `dnsrewrite` modifier for entries from the
+  operating system's hosts file ([#3815]).
 - Matching against rules with `|` at the end of the domain name ([#3371]).
 - Incorrect assignment of explicitly configured DHCP options ([#3744]).
 - Occasional panic during shutdown ([#3655]).
@@ -401,8 +408,8 @@ In this release, the schema version has changed from 10 to 12.
 - Letter case mismatches in `CNAME` filtering ([#3335]).
 - Occasional breakages on network errors with DNS-over-HTTP upstreams ([#3217]).
 - Errors when setting static IP on Linux ([#3257]).
-- Treatment of domain names and FQDNs in custom rules with `$dnsrewrite` that
-  use the `PTR` type ([#3256]).
+- Treatment of domain names and FQDNs in custom rules with the `dnsrewrite`
+  modifier that use the `PTR` type ([#3256]).
 - Redundant hostname generating while loading static leases with empty hostname
   ([#3166]).
 - Domain name case in responses ([#3194]).
@@ -566,7 +573,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
 
 - The ability to block user for login after configurable number of unsuccessful
   attempts for configurable time ([#2826]).
-- `$denyallow` modifier for filters ([#2923]).
+- `denyallow` modifier for filters ([#2923]).
 - Hostname uniqueness validation in the DHCP server ([#2952]).
 - Hostname generating for DHCP clients which don't provide their own ([#2723]).
 - New flag `--no-etc-hosts` to disable client domain name lookups in the
@@ -581,7 +588,8 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
   network ([#2393], [#2961]).
 - The ability to serve DNS queries on multiple hosts and interfaces ([#1401]).
 - `ips` and `text` DHCP server options ([#2385]).
-- `SRV` records support in `$dnsrewrite` filters ([#2533]).
+- `SRV` records support in filtering rules with the `dnsrewrite` modifier
+  ([#2533]).
 
 ### Changed
 
@@ -595,7 +603,8 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
   ([#2704]).
 - Stricter validation of the IP addresses of static leases in the DHCP server
   with regards to the netmask ([#2838]).
-- Stricter validation of `$dnsrewrite` filter modifier parameters ([#2498]).
+- Stricter validation of `dnsrewrite` filtering rule modifier parameters
+  ([#2498]).
 - New, more correct versioning scheme ([#2412]).
 
 ### Deprecated
@@ -604,7 +613,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
 
 ### Fixed
 
-- Multiple answers for `$dnsrewrite` rule matching requests with repeating
+- Multiple answers for a `dnsrewrite` rule matching requests with repeating
   patterns in it ([#2981]).
 - Root server resolving when custom upstreams for hosts are specified ([#2994]).
 - Inconsistent resolving of DHCP clients when the DHCP server is disabled
@@ -743,7 +752,7 @@ See also the [v0.105.0 GitHub milestone][ms-v0.105.0].
 - `ipset` subdomain matching, just like `dnsmasq` does ([#2179]).
 - ClientID support for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS
   ([#1383]).
-- `$dnsrewrite` modifier for filters ([#2102]).
+- The new `dnsrewrite` modifier for filters ([#2102]).
 - The host checking API and the query logs API can now return multiple matched
   rules ([#2102]).
 - Detecting of network interface configured to have static IP address via
@@ -751,7 +760,7 @@ See also the [v0.105.0 GitHub milestone][ms-v0.105.0].
 - DNSCrypt protocol support ([#1361]).
 - A 5 second wait period until a DHCP server's network interface gets an IP
   address ([#2304]).
-- `$dnstype` modifier for filters ([#2337]).
+- `dnstype` modifier for filters ([#2337]).
 - HTTP API request body size limit ([#2305]).
 
 ### Changed
diff --git a/go.mod b/go.mod
index 8cfd5475..1886d519 100644
--- a/go.mod
+++ b/go.mod
@@ -20,14 +20,14 @@ require (
 	github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
 	github.com/mdlayher/netlink v1.5.0
 	github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b
-	github.com/miekg/dns v1.1.45
+	github.com/miekg/dns v1.1.48
 	github.com/satori/go.uuid v1.2.0
 	github.com/stretchr/testify v1.7.0
 	github.com/ti-mo/netfilter v0.4.0
 	go.etcd.io/bbolt v1.3.6
 	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
-	golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
-	golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
+	golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
+	golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v2 v2.4.0
 	howett.net/plist v1.0.0
@@ -55,10 +55,10 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/stretchr/objx v0.1.1 // indirect
 	github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 // indirect
-	golang.org/x/mod v0.5.1 // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/tools v0.1.8 // indirect
+	golang.org/x/tools v0.1.10 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
diff --git a/go.sum b/go.sum
index 5e5ef775..1e8c5022 100644
--- a/go.sum
+++ b/go.sum
@@ -197,8 +197,8 @@ github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00v
 github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
 github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
 github.com/miekg/dns v1.1.44/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
-github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk=
-github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
+github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
+github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
@@ -301,6 +301,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -311,8 +312,9 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
 golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -353,8 +355,9 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
+golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -424,9 +427,11 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
+golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -451,8 +456,9 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
-golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
 golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
+golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/dnsforward/svcbmsg.go b/internal/dnsforward/svcbmsg.go
index 99413ed3..3d25f05b 100644
--- a/internal/dnsforward/svcbmsg.go
+++ b/internal/dnsforward/svcbmsg.go
@@ -32,12 +32,16 @@ func (s *Server) genAnswerHTTPS(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns.HTT
 // github.com/miekg/dns module.
 var strToSVCBKey = map[string]dns.SVCBKey{
 	"alpn":            dns.SVCB_ALPN,
-	"echconfig":       dns.SVCB_ECHCONFIG,
+	"ech":             dns.SVCB_ECHCONFIG,
 	"ipv4hint":        dns.SVCB_IPV4HINT,
 	"ipv6hint":        dns.SVCB_IPV6HINT,
 	"mandatory":       dns.SVCB_MANDATORY,
 	"no-default-alpn": dns.SVCB_NO_DEFAULT_ALPN,
 	"port":            dns.SVCB_PORT,
+
+	// TODO(a.garipov): This is the previous name for the parameter that has
+	// since been changed.  Remove this in v0.109.0.
+	"echconfig": dns.SVCB_ECHCONFIG,
 }
 
 // svcbKeyHandler is a handler for one SVCB parameter key.
@@ -51,10 +55,10 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
 		}
 	},
 
-	"echconfig": func(valStr string) (val dns.SVCBKeyValue) {
+	"ech": func(valStr string) (val dns.SVCBKeyValue) {
 		ech, err := base64.StdEncoding.DecodeString(valStr)
 		if err != nil {
-			log.Debug("can't parse svcb/https echconfig: %s; ignoring", err)
+			log.Debug("can't parse svcb/https ech: %s; ignoring", err)
 
 			return nil
 		}
@@ -119,6 +123,26 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
 			Port: uint16(port64),
 		}
 	},
+
+	// TODO(a.garipov): This is the previous name for the parameter that has
+	// since been changed.  Remove this in v0.109.0.
+	"echconfig": func(valStr string) (val dns.SVCBKeyValue) {
+		log.Info(
+			`warning: svcb/https record parameter name "echconfig" is deprecated; ` +
+				`use "ech" instead`,
+		)
+
+		ech, err := base64.StdEncoding.DecodeString(valStr)
+		if err != nil {
+			log.Debug("can't parse svcb/https ech: %s; ignoring", err)
+
+			return nil
+		}
+
+		return &dns.SVCBECHConfig{
+			ECH: ech,
+		}
+	},
 }
 
 // genAnswerSVCB returns a properly initialized SVCB resource record.
diff --git a/internal/dnsforward/svcbmsg_test.go b/internal/dnsforward/svcbmsg_test.go
index db6994c3..28794531 100644
--- a/internal/dnsforward/svcbmsg_test.go
+++ b/internal/dnsforward/svcbmsg_test.go
@@ -87,14 +87,18 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) {
 		svcb: dnssvcb("alpn", "h3"),
 		want: wantsvcb(&dns.SVCBAlpn{Alpn: []string{"h3"}}),
 		name: "alpn",
+	}, {
+		svcb: dnssvcb("ech", "AAAA"),
+		want: wantsvcb(&dns.SVCBECHConfig{ECH: []byte{0, 0, 0}}),
+		name: "ech",
 	}, {
 		svcb: dnssvcb("echconfig", "AAAA"),
 		want: wantsvcb(&dns.SVCBECHConfig{ECH: []byte{0, 0, 0}}),
-		name: "echconfig",
+		name: "ech_deprecated",
 	}, {
 		svcb: dnssvcb("echconfig", "%BAD%"),
 		want: wantsvcb(nil),
-		name: "echconfig_invalid",
+		name: "ech_invalid",
 	}, {
 		svcb: dnssvcb("ipv4hint", "127.0.0.1"),
 		want: wantsvcb(&dns.SVCBIPv4Hint{Hint: []net.IP{ip4}}),

From 9f0fdc5e78331a9486c1be2de2d062abff63deb7 Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Fri, 8 Apr 2022 16:43:49 +0300
Subject: [PATCH 036/143] Pull request: upd bamboo-specs snapcraft

Merge in DNS/adguard-home from upd-bamboo-spec to master

Squashed commit of the following:

commit c26c70f97cbce98afd5c7d4241188d6949869c2a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Apr 8 13:51:23 2022 +0200

    upd bamboo-specs snapcraft

commit afe40c03b70d2b2dff9c7c25044d7924bdd3c765
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Apr 8 13:10:38 2022 +0200

    upd bamboo-specs snapcraft
---
 bamboo-specs/release.yaml | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index 50bf1362..62abf5ce 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -183,8 +183,27 @@
 
         cd ./dist/
 
+        channel="${bamboo.channel}"
+        readonly channel
+        case "$channel"
+        in
+        ('release')
+              snapchannel='candidate'
+              ;;
+        ('beta')
+              snapchannel='beta'
+              ;;
+        ('edge')
+              snapchannel='edge'
+              ;;
+        (*)
+              echo "invalid channel '$channel'"
+              exit 1
+              ;;
+        esac
+
         env\
-                SNAPCRAFT_CHANNEL=edge\
+                SNAPCRAFT_CHANNEL="$snapchannel"\
                 SNAPCRAFT_EMAIL="${bamboo.snapcraftEmail}"\
                 SNAPCRAFT_MACAROON="${bamboo.snapcraftMacaroonPassword}"\
                 SNAPCRAFT_UBUNTU_DISCHARGE="${bamboo.snapcraftUbuntuDischargePassword}"\

From 2c2c0d445b338a1411208339816c75898f9fad5e Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Tue, 12 Apr 2022 15:45:18 +0300
Subject: [PATCH 037/143] Pull request #1473: svcb dohpath support

Merge in DNS/adguard-home from 4463-ddr-support to master

Squashed commit of the following:

commit 99a149e9024354ad0341739c3c9b08cefbd74468
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 12 14:13:17 2022 +0200

    imp docs

commit 26150be8df8b35e47c108f6e3319c57b39fb8e38
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 11 20:36:18 2022 +0200

    imp code docs

commit 5a4607f71abba83a9ac8753abd74c9fb97e4a545
Merge: 00f0abf5 9f0fdc5e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 11 16:14:49 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4463-ddr-support

    # Conflicts:
    #	internal/dnsforward/svcbmsg.go

commit 00f0abf5eea07aeeebc2a856a958215021a51ab7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 11 16:06:42 2022 +0200

    svcb dohpath support

commit ace81ce1ea2fb96c4434c6c1fded4a79427cf17e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Apr 7 14:31:32 2022 +0200

    svcb dohpath support

commit a1b5df4fb2e87dab265d6ca55928610a6acc1c00
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Apr 6 16:53:17 2022 +0200

    svcb dohpath support
---
 CHANGELOG.md                           |  4 ++++
 go.mod                                 |  3 +++
 go.sum                                 |  2 ++
 internal/dnsforward/dnsrewrite_test.go | 32 +++++++++++++++++++++-----
 internal/dnsforward/svcbmsg.go         |  6 +++++
 internal/dnsforward/svcbmsg_test.go    |  4 ++++
 6 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca7d27ad..aa305b2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@ and this project adheres to
 
 ### Added
 
+- Support for SVCB/HTTPS parameter `dohpath` in filtering rules with 
+  `dnsrewrite` modifier according to the [RFC draft][dns-draft-02] ([#4463]).
 - The ability to customize the set of networks that are considered private
   through the new `dns.private_networks` property in the configuration file
   ([#3142]).
@@ -120,10 +122,12 @@ In this release, the schema version has changed from 12 to 13.
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
 [#4437]: https://github.com/AdguardTeam/AdGuardHome/issues/4437
+[#4463]: https://github.com/AdguardTeam/AdGuardHome/issues/4463
 
 [repr]:          https://reproducible-builds.org/docs/source-date-epoch/
 [doq-draft-10]:  https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
 [svcb-draft-08]: https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-08.html
+[dns-draft-02]:  https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02#section-5.1
 
 
 
diff --git a/go.mod b/go.mod
index 1886d519..110f2bbb 100644
--- a/go.mod
+++ b/go.mod
@@ -64,3 +64,6 @@ require (
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 	honnef.co/go/tools v0.2.2 // indirect
 )
+
+// TODO(a.garipov): Return to the main repo once miekg/dns#1359 is merged.
+replace github.com/miekg/dns => github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8
diff --git a/go.sum b/go.sum
index 1e8c5022..e405e27c 100644
--- a/go.sum
+++ b/go.sum
@@ -29,6 +29,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
 github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
 github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
 github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
+github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8 h1:Hp2waLwK989ui3bDkFpedlIHfyWdZ77gynvd+GPEqXY=
+github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 github.com/ameshkov/dnscrypt/v2 v2.2.3 h1:X9UP5AHtwp46Ji+sGFfF/1Is6OPI/SjxLqhKpx0P5UI=
 github.com/ameshkov/dnscrypt/v2 v2.2.3/go.mod h1:xJB9cE1/GF+NB6EEQqRlkoa4bjcV2w7VYn1G+zVq7Bs=
 github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
diff --git a/internal/dnsforward/dnsrewrite_test.go b/internal/dnsforward/dnsrewrite_test.go
index 347b8f29..5ba10582 100644
--- a/internal/dnsforward/dnsrewrite_test.go
+++ b/internal/dnsforward/dnsrewrite_test.go
@@ -22,7 +22,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
 		Preference: 32,
 	}
 	svcbVal := &rules.DNSSVCB{
-		Params:   map[string]string{"alpn": "h3"},
+		Params:   map[string]string{"alpn": "h3", "dohpath": "/dns-query"},
 		Target:   dns.Fqdn(domain),
 		Priority: 32,
 	}
@@ -164,10 +164,20 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
 
 		require.Len(t, d.Res.Answer, 1)
 		ans, ok := d.Res.Answer[0].(*dns.SVCB)
-		require.True(t, ok)
 
-		assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key())
-		assert.Equal(t, svcbVal.Params["alpn"], ans.Value[0].String())
+		require.True(t, ok)
+		require.Len(t, ans.Value, 2)
+
+		assert.ElementsMatch(
+			t,
+			[]dns.SVCBKey{dns.SVCB_ALPN, dns.SVCB_DOHPATH},
+			[]dns.SVCBKey{ans.Value[0].Key(), ans.Value[1].Key()},
+		)
+		assert.ElementsMatch(
+			t,
+			[]string{svcbVal.Params["alpn"], svcbVal.Params["dohpath"]},
+			[]string{ans.Value[0].String(), ans.Value[1].String()},
+		)
 		assert.Equal(t, svcbVal.Target, ans.Target)
 		assert.Equal(t, svcbVal.Priority, ans.Priority)
 	})
@@ -186,8 +196,18 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
 		ans, ok := d.Res.Answer[0].(*dns.HTTPS)
 
 		require.True(t, ok)
-		assert.Equal(t, dns.SVCB_ALPN, ans.Value[0].Key())
-		assert.Equal(t, svcbVal.Params["alpn"], ans.Value[0].String())
+		require.Len(t, ans.Value, 2)
+
+		assert.ElementsMatch(
+			t,
+			[]dns.SVCBKey{dns.SVCB_ALPN, dns.SVCB_DOHPATH},
+			[]dns.SVCBKey{ans.Value[0].Key(), ans.Value[1].Key()},
+		)
+		assert.ElementsMatch(
+			t,
+			[]string{svcbVal.Params["alpn"], svcbVal.Params["dohpath"]},
+			[]string{ans.Value[0].String(), ans.Value[1].String()},
+		)
 		assert.Equal(t, svcbVal.Target, ans.Target)
 		assert.Equal(t, svcbVal.Priority, ans.Priority)
 	})
diff --git a/internal/dnsforward/svcbmsg.go b/internal/dnsforward/svcbmsg.go
index 3d25f05b..d1e3e3d8 100644
--- a/internal/dnsforward/svcbmsg.go
+++ b/internal/dnsforward/svcbmsg.go
@@ -143,6 +143,12 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
 			ECH: ech,
 		}
 	},
+
+	"dohpath": func(valStr string) (val dns.SVCBKeyValue) {
+		return &dns.SVCBDoHPath{
+			Template: valStr,
+		}
+	},
 }
 
 // genAnswerSVCB returns a properly initialized SVCB resource record.
diff --git a/internal/dnsforward/svcbmsg_test.go b/internal/dnsforward/svcbmsg_test.go
index 28794531..8de53988 100644
--- a/internal/dnsforward/svcbmsg_test.go
+++ b/internal/dnsforward/svcbmsg_test.go
@@ -127,6 +127,10 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) {
 		svcb: dnssvcb("no-default-alpn", ""),
 		want: wantsvcb(&dns.SVCBNoDefaultAlpn{}),
 		name: "no_default_alpn",
+	}, {
+		svcb: dnssvcb("dohpath", "/dns-query"),
+		want: wantsvcb(&dns.SVCBDoHPath{Template: "/dns-query"}),
+		name: "dohpath",
 	}, {
 		svcb: dnssvcb("port", "8080"),
 		want: wantsvcb(&dns.SVCBPort{Port: 8080}),

From 21a1187ed2d18e316d8af33c5d8bdd19034fea9f Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 13 Apr 2022 18:16:33 +0300
Subject: [PATCH 038/143] Pull request: all: upd go, tools

Merge in DNS/adguard-home from upd-go to master

Squashed commit of the following:

commit 26cd13146df705ead5e1c39c27e73252c71fa64d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 13 17:46:24 2022 +0300

    all: upd go, tools
---
 CHANGELOG.md                           | 134 +++++++++++++---------
 bamboo-specs/release.yaml              |   6 +-
 bamboo-specs/test.yaml                 |   2 +-
 go.mod                                 |  45 ++++----
 go.sum                                 | 149 +++++++++----------------
 internal/aghnet/hostscontainer.go      |   2 +-
 internal/aghnet/hostscontainer_test.go |  30 ++---
 internal/filtering/filtering.go        |   4 +-
 internal/tools/go.mod                  |  24 ++--
 internal/tools/go.sum                  |  74 +++++++-----
 10 files changed, 236 insertions(+), 234 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa305b2a..a80b4ea1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,13 +12,17 @@ and this project adheres to
 ## [Unreleased]
 
 <!--
-## [v0.108.0] - 2022-06-01 (APPROX.)
+## [v0.108.0] - 2022-07-01 (APPROX.)
 -->
 
+### Security
+
+- Enforced password strength policy ([#3503]).
+- Weaker cipher suites that use the CBC (cipher block chaining) mode of
+  operation have been disabled ([#2993]).
+
 ### Added
 
-- Support for SVCB/HTTPS parameter `dohpath` in filtering rules with 
-  `dnsrewrite` modifier according to the [RFC draft][dns-draft-02] ([#4463]).
 - The ability to customize the set of networks that are considered private
   through the new `dns.private_networks` property in the configuration file
   ([#3142]).
@@ -32,9 +36,6 @@ and this project adheres to
 
 ### Changed
 
-- Filtering rules with the `dnsrewrite` modifier that create SVCB or HTTPS
-  responses should use `ech` instead of `echconfig` to conform with the [latest
-  drafts][svcb-draft-08].
 - The default DNS-over-QUIC port number is now `853` instead of `754` in
   accordance with the latest [RFC draft][doq-draft-10] ([#4276]).
 - Reverse DNS now has a greater priority as the source of runtime clients'
@@ -84,28 +85,8 @@ In this release, the schema version has changed from 12 to 13.
 
 ### Deprecated
 
-- SVCB/HTTPS parameter name `echconfig` in filtering rules with the `dnsrewrite`
-  modifier.  Use `ech` instead.  v0.109.0 will remove support for the outdated
-  name `echconfig`.
-- Obsolete `--no-mem-optimization` option ([#4437]).  v0.109.0 will remove the
-  flag completely.
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
 
-### Fixed
-
-- I/O timeout errors on checking another DHCP server.
-
-### Removed
-
-- Go 1.16 support.
-
-### Security
-
-- `User-Agent` HTTP header removed from outgoing DNS-over-HTTPS requests.
-- Enforced password strength policy ([#3503]).
-- Weaker cipher suites that use the CBC (cipher block chaining) mode of
-  operation have been disabled ([#2993]).
-
 [#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
@@ -117,27 +98,76 @@ In this release, the schema version has changed from 12 to 13.
 [#3978]: https://github.com/AdguardTeam/AdGuardHome/issues/3978
 [#4166]: https://github.com/AdguardTeam/AdGuardHome/issues/4166
 [#4213]: https://github.com/AdguardTeam/AdGuardHome/issues/4213
-[#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
-[#4437]: https://github.com/AdguardTeam/AdGuardHome/issues/4437
-[#4463]: https://github.com/AdguardTeam/AdGuardHome/issues/4463
 
-[repr]:          https://reproducible-builds.org/docs/source-date-epoch/
-[doq-draft-10]:  https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
-[svcb-draft-08]: https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-08.html
-[dns-draft-02]:  https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02#section-5.1
+[repr]:         https://reproducible-builds.org/docs/source-date-epoch/
+[doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
 
 
 
 <!--
-## [v0.107.6] - 2022-04-04 (APPROX.)
+## [v0.107.7] - 2022-05-18 (APPROX.)
+
+See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
+
+[ms-v0.107.7]: https://github.com/AdguardTeam/AdGuardHome/milestone/43?closed=1
+-->
+
+
+
+## [v0.107.6] - 2022-04-13
 
 See also the [v0.107.6 GitHub milestone][ms-v0.107.6].
 
-[ms-v0.107.6]: https://github.com/AdguardTeam/AdGuardHome/milestone/42?closed=1
--->
+### Security
+
+- `User-Agent` HTTP header removed from outgoing DNS-over-HTTPS requests.
+- Go version was updated to prevent the possibility of exploiting the
+  [CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] vulnerabilities.
+
+### Added
+
+- Support for SVCB/HTTPS parameter `dohpath` in filtering rules with
+  the `dnsrewrite` modifier according to the [RFC draft][dns-draft-02]
+  ([#4463]).
+
+### Changed
+
+- Filtering rules with the `dnsrewrite` modifier that create SVCB or HTTPS
+  responses should use `ech` instead of `echconfig` to conform with the [latest
+  drafts][svcb-draft-08].
+
+### Deprecated
+
+- SVCB/HTTPS parameter name `echconfig` in filtering rules with the `dnsrewrite`
+  modifier.  Use `ech` instead.  v0.109.0 will remove support for the outdated
+  name `echconfig`.
+- Obsolete `--no-mem-optimization` option ([#4437]).  v0.109.0 will remove the
+  flag completely.
+
+### Fixed
+
+- I/O timeout errors when checking for the presence of another DHCP server.
+- Network interfaces being incorrectly labeled as down during installation.
+- Rules for blocking the QQ service ([#3717]).
+
+### Removed
+
+- Go 1.16 support, since that branch of the Go compiler has reached end of life
+  and doesn't receive security updates anymore.
+
+[#3717]: https://github.com/AdguardTeam/AdGuardHome/issues/3717
+[#4437]: https://github.com/AdguardTeam/AdGuardHome/issues/4437
+[#4463]: https://github.com/AdguardTeam/AdGuardHome/issues/4463
+
+[CVE-2022-24675]: https://www.cvedetails.com/cve/CVE-2022-24675
+[CVE-2022-27536]: https://www.cvedetails.com/cve/CVE-2022-27536
+[CVE-2022-28327]: https://www.cvedetails.com/cve/CVE-2022-28327
+[dns-draft-02]:   https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02#section-5.1
+[ms-v0.107.6]:    https://github.com/AdguardTeam/AdGuardHome/milestone/42?closed=1
+[svcb-draft-08]:  https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-08.html
 
 
 
@@ -159,6 +189,11 @@ were resolved.
 
 See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
 
+### Security
+
+- Go version was updated to prevent the possibility of exploiting the
+  [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] vulnerabilities.
+
 ### Fixed
 
 - Optimistic cache now responds with expired items even if those can't be
@@ -166,11 +201,6 @@ See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
 - Unnecessarily complex hosts-related logic leading to infinite recursion in
   some cases ([#4216]).
 
-### Security
-
-- Go version was updated to prevent the possibility of exploiting the
-  [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] vulnerabilities.
-
 [#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
 [#4254]: https://github.com/AdguardTeam/AdGuardHome/issues/4254
 
@@ -276,7 +306,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
   ([#2141]).
 - The ability to completely purge DHCP leases ([#1691]).
 - Settable timeouts for querying the upstream servers ([#2280]).
-- Configuration file parameters to change group and user ID on startup on Unix
+- Configuration file properties to change group and user ID on startup on Unix
   ([#2763]).
 - Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439], [#3225],
   [#3226]).
@@ -325,8 +355,8 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
   original encoded version shown in request details ([#3013]).
 - When /etc/hosts-type rules have several IPs for one host, all IPs are now
   returned instead of only the first one ([#1381]).
-- Property `rlimit_nofile` is now in the `os` object of the configuration file,
-  together with the new `group` and `user` properties ([#2763]).
+- Property `rlimit_nofile` is now in the `os` object of the configuration
+  file, together with the new `group` and `user` properties ([#2763]).
 - Permissions on filter files are now `0o644` instead of `0o600` ([#3198]).
 
 #### Configuration Changes
@@ -669,6 +699,10 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
 
 ## [v0.105.2] - 2021-03-10
 
+### Security
+
+- Session token doesn't contain user's information anymore ([#2470]).
+
 See also the [v0.105.2 GitHub milestone][ms-v0.105.2].
 
 ### Fixed
@@ -682,10 +716,6 @@ See also the [v0.105.2 GitHub milestone][ms-v0.105.2].
 - Incomplete DNS upstreams validation ([#2674]).
 - Wrong parsing of DHCP options of the `ip` type ([#2688]).
 
-### Security
-
-- Session token doesn't contain user's information anymore ([#2470]).
-
 [#2470]: https://github.com/AdguardTeam/AdGuardHome/issues/2470
 [#2582]: https://github.com/AdguardTeam/AdGuardHome/issues/2582
 [#2600]: https://github.com/AdguardTeam/AdGuardHome/issues/2600
@@ -897,13 +927,13 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
 
 
 
-
 <!--
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...HEAD
-[v0.107.6]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...v0.107.6
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...HEAD
+[v0.107.7]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...v0.107.7
 -->
 
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...HEAD
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...HEAD
+[v0.107.6]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...v0.107.6
 [v0.107.5]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5
 [v0.107.4]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.3...v0.107.4
 [v0.107.3]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.2...v0.107.3
diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index 62abf5ce..d0513f0f 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -7,7 +7,7 @@
 # Make sure to sync any changes with the branch overrides below.
 'variables':
   'channel': 'edge'
-  'dockerGo': 'adguard/golang-ubuntu:4.2'
+  'dockerGo': 'adguard/golang-ubuntu:4.3'
 
 'stages':
 - 'Make release':
@@ -285,7 +285,7 @@
     # need to build a few of these.
     'variables':
       'channel': 'beta'
-      'dockerGo': 'adguard/golang-ubuntu:4.2'
+      'dockerGo': 'adguard/golang-ubuntu:4.3'
 # release-vX.Y.Z branches are the branches from which the actual final release
 # is built.
 - '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -300,4 +300,4 @@
     # are the ones that actually get released.
     'variables':
       'channel': 'release'
-      'dockerGo': 'adguard/golang-ubuntu:4.2'
+      'dockerGo': 'adguard/golang-ubuntu:4.3'
diff --git a/bamboo-specs/test.yaml b/bamboo-specs/test.yaml
index fa410195..e973b77c 100644
--- a/bamboo-specs/test.yaml
+++ b/bamboo-specs/test.yaml
@@ -5,7 +5,7 @@
   'key': 'AHBRTSPECS'
   'name': 'AdGuard Home - Build and run tests'
 'variables':
-  'dockerGo': 'adguard/golang-ubuntu:4.2'
+  'dockerGo': 'adguard/golang-ubuntu:4.3'
 
 'stages':
 - 'Tests':
diff --git a/go.mod b/go.mod
index 110f2bbb..30d8b377 100644
--- a/go.mod
+++ b/go.mod
@@ -3,66 +3,67 @@ module github.com/AdguardTeam/AdGuardHome
 go 1.17
 
 require (
-	github.com/AdguardTeam/dnsproxy v0.41.4
+	github.com/AdguardTeam/dnsproxy v0.42.1
 	github.com/AdguardTeam/golibs v0.10.8
-	github.com/AdguardTeam/urlfilter v0.15.2
+	github.com/AdguardTeam/urlfilter v0.16.0
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/ameshkov/dnscrypt/v2 v2.2.3
 	github.com/digineo/go-ipset/v2 v2.2.1
 	github.com/fsnotify/fsnotify v1.5.1
 	github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
-	github.com/google/go-cmp v0.5.6
+	github.com/google/go-cmp v0.5.7
 	github.com/google/gopacket v1.1.19
 	github.com/google/renameio v1.0.1
-	github.com/insomniacslk/dhcp v0.0.0-20211214070828-5297eed8f489
-	github.com/kardianos/service v1.2.0
-	github.com/lucas-clemente/quic-go v0.25.0
-	github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
-	github.com/mdlayher/netlink v1.5.0
+	github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41
+	github.com/kardianos/service v1.2.1
+	github.com/lucas-clemente/quic-go v0.26.0
+	github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
+	github.com/mdlayher/netlink v1.6.0
+	// TODO(a.garipov): This package is deprecated; find a new one or use
+	// our own code for that.
 	github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b
 	github.com/miekg/dns v1.1.48
 	github.com/satori/go.uuid v1.2.0
 	github.com/stretchr/testify v1.7.0
 	github.com/ti-mo/netfilter v0.4.0
 	go.etcd.io/bbolt v1.3.6
-	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
-	golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
-	golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12
+	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
+	golang.org/x/net v0.0.0-20220412020605-290c469a71a5
+	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v2 v2.4.0
 	howett.net/plist v1.0.0
 )
 
 require (
-	github.com/BurntSushi/toml v0.4.1 // indirect
+	github.com/BurntSushi/toml v1.1.0 // indirect
 	github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
 	github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
 	github.com/ameshkov/dnsstamps v1.0.3 // indirect
-	github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect
+	github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
 	github.com/cheekybits/genny v1.0.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/google/uuid v1.3.0 // indirect
-	github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
-	github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
-	github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
-	github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 // indirect
-	github.com/mdlayher/socket v0.1.1 // indirect
+	github.com/josharian/native v1.0.0 // indirect
+	github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
+	github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
+	github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
+	github.com/mdlayher/socket v0.2.3 // indirect
 	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/onsi/ginkgo v1.16.5 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/stretchr/objx v0.1.1 // indirect
-	github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 // indirect
+	github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/tools v0.1.10 // indirect
-	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a // indirect
+	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
-	honnef.co/go/tools v0.2.2 // indirect
 )
 
 // TODO(a.garipov): Return to the main repo once miekg/dns#1359 is merged.
diff --git a/go.sum b/go.sum
index e405e27c..e75df993 100644
--- a/go.sum
+++ b/go.sum
@@ -7,20 +7,19 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/AdguardTeam/dnsproxy v0.41.4 h1:zA8BJmWBkSL5kp4b8CblQRgIrLGzJ4IUGQ7tA1255Cw=
-github.com/AdguardTeam/dnsproxy v0.41.4/go.mod h1:GCdEbTw683vBqksJIccPSYzBg2yIFbRiDnXltyIinug=
+github.com/AdguardTeam/dnsproxy v0.42.1 h1:RZAtW75cvMX1d9Mibg0CA343V7VWV5PLrXsLhBZfdYc=
+github.com/AdguardTeam/dnsproxy v0.42.1/go.mod h1:thHuk3599mgmucsv5J9HR9lBVQHnf4YleE08EbxNrN0=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
-github.com/AdguardTeam/golibs v0.10.6/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
 github.com/AdguardTeam/golibs v0.10.8 h1:diU9gP9qG1qeLbAkzIwfUerpHSqzR6zaBgzvRMR/m6Q=
 github.com/AdguardTeam/golibs v0.10.8/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
 github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
-github.com/AdguardTeam/urlfilter v0.15.2 h1:LZGgrm4l4Ys9eAqB+UUmZfiC6vHlDlYFhx0WXqo6LtQ=
-github.com/AdguardTeam/urlfilter v0.15.2/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
+github.com/AdguardTeam/urlfilter v0.16.0 h1:IO29m+ZyQuuOnPLTzHuXj35V1DZOp1Dcryl576P2syg=
+github.com/AdguardTeam/urlfilter v0.16.0/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
-github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
 github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
@@ -37,15 +36,14 @@ github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaE
 github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
 github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
-github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 h1:M57m0xQqZIhx7CEJgeLSvRFKEK1RjzRuIXiA3HfYU7g=
 github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
+github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
+github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
 github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
 github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
-github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
-github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -58,7 +56,6 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
 github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
-github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
@@ -97,10 +94,10 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
@@ -120,87 +117,68 @@ github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpg
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
 github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
-github.com/insomniacslk/dhcp v0.0.0-20211214070828-5297eed8f489 h1:jhdHqd7DxBrzfuFSoPxjD6nUVaV/1RIn9aHA0WCf/as=
-github.com/insomniacslk/dhcp v0.0.0-20211214070828-5297eed8f489/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
+github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 h1:Yg3n3AI7GoHnWt7dyjsLPU+TEuZfPAg0OdiA3MJUV6I=
+github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
 github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
-github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
-github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
+github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
+github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
 github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
 github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
 github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
-github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
-github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
-github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
-github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
-github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
-github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa793TP5z5GNAn/VLPzlc0ewzWdeP/25gDfgQ=
-github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
-github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
+github.com/kardianos/service v1.2.1 h1:AYndMsehS+ywIS6RB9KOlcXzteWUzxgMgBymJD7+BYk=
+github.com/kardianos/service v1.2.1/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lucas-clemente/quic-go v0.24.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
-github.com/lucas-clemente/quic-go v0.25.0 h1:K+X9Gvd7JXsOHtU0N2icZ2Nw3rx82uBej3mP4CLgibc=
 github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
+github.com/lucas-clemente/quic-go v0.26.0 h1:ALBQXr9UJ8A1LyzvceX4jd9QFsHvlI0RR6BkV16o00A=
+github.com/lucas-clemente/quic-go v0.26.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
 github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
 github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
 github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
-github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco=
 github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
-github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk=
+github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
+github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
 github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
-github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM=
+github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
+github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
 github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
+github.com/marten-seemann/qtls-go1-18 v0.1.0/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
+github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y=
+github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
 github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
-github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
-github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60 h1:tHdB+hQRHU10CfcK0furo6rSNgZ38JT8uPh70c/pFD8=
-github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE=
-github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
-github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
+github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
+github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og=
 github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
 github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
 github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
 github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
 github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
 github.com/mdlayher/netlink v1.1.2-0.20201013204415-ded538f7f4be/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
-github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
-github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
-github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
-github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
-github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
-github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
-github.com/mdlayher/netlink v1.5.0 h1:r4fa439+SsMarM0rMONU3iSshSV3ArVqJl6H/zjrhh4=
-github.com/mdlayher/netlink v1.5.0/go.mod h1:1Kr8BBFxGyUyNmztC9WLOayqYVAd2wsgOZm18nqGuzQ=
+github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
+github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
+github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
 github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
 github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
 github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b h1:MHcTarUMC4sFA7eiyR8IEJ6j2PgmgXR+B9X2IIMjh7A=
 github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
-github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
-github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
-github.com/mdlayher/socket v0.1.0/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
-github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI=
 github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
+github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
+github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
+github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
 github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
-github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
-github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
-github.com/miekg/dns v1.1.44/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
-github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
-github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
@@ -283,13 +261,12 @@ github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ
 github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
 github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
 github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
-github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 h1:XMAtQHwKjWHIRwg+8Nj/rzUomQY1q6cM3ncA0wP8GU4=
-github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
+github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME=
+github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
 github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
 github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
 go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
@@ -304,8 +281,9 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
+golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -314,7 +292,6 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -328,10 +305,10 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -341,25 +318,19 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
-golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
-golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -387,7 +358,6 @@ golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -402,17 +372,8 @@ golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -421,17 +382,16 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
-golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -451,21 +411,19 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
-golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
-golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
 golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II=
+golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -516,9 +474,6 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
-honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
-honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
 howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
 howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
 sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
diff --git a/internal/aghnet/hostscontainer.go b/internal/aghnet/hostscontainer.go
index 290dc1c0..65c9d3c4 100644
--- a/internal/aghnet/hostscontainer.go
+++ b/internal/aghnet/hostscontainer.go
@@ -51,7 +51,7 @@ type requestMatcher struct {
 //
 // It's safe for concurrent use.
 func (rm *requestMatcher) MatchRequest(
-	req urlfilter.DNSRequest,
+	req *urlfilter.DNSRequest,
 ) (res *urlfilter.DNSResult, ok bool) {
 	switch req.DNSType {
 	case dns.TypeA, dns.TypeAAAA, dns.TypePTR:
diff --git a/internal/aghnet/hostscontainer_test.go b/internal/aghnet/hostscontainer_test.go
index 807722a8..42202f43 100644
--- a/internal/aghnet/hostscontainer_test.go
+++ b/internal/aghnet/hostscontainer_test.go
@@ -193,7 +193,7 @@ func TestHostsContainer_refresh(t *testing.T) {
 
 		// Require the changes are written.
 		require.Eventually(t, func() bool {
-			res, ok := hc.MatchRequest(urlfilter.DNSRequest{
+			res, ok := hc.MatchRequest(&urlfilter.DNSRequest{
 				Hostname: "hostname",
 				DNSType:  dns.TypeA,
 			})
@@ -207,7 +207,7 @@ func TestHostsContainer_refresh(t *testing.T) {
 
 		// Require the changes are written.
 		require.Eventually(t, func() bool {
-			res, ok := hc.MatchRequest(urlfilter.DNSRequest{
+			res, ok := hc.MatchRequest(&urlfilter.DNSRequest{
 				Hostname: "hostname",
 				DNSType:  dns.TypeA,
 			})
@@ -365,7 +365,7 @@ func TestHostsContainer(t *testing.T) {
 	testCases := []struct {
 		want []*rules.DNSRewrite
 		name string
-		req  urlfilter.DNSRequest
+		req  *urlfilter.DNSRequest
 	}{{
 		want: []*rules.DNSRewrite{{
 			RCode:  dns.RcodeSuccess,
@@ -377,7 +377,7 @@ func TestHostsContainer(t *testing.T) {
 			RRType: dns.TypeAAAA,
 		}},
 		name: "simple",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "simplehost",
 			DNSType:  dns.TypeA,
 		},
@@ -392,7 +392,7 @@ func TestHostsContainer(t *testing.T) {
 			RRType: dns.TypeAAAA,
 		}},
 		name: "hello_alias",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "hello.world",
 			DNSType:  dns.TypeA,
 		},
@@ -407,21 +407,21 @@ func TestHostsContainer(t *testing.T) {
 			RRType: dns.TypeAAAA,
 		}},
 		name: "other_line_alias",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "hello.world.again",
 			DNSType:  dns.TypeA,
 		},
 	}, {
 		want: []*rules.DNSRewrite{},
 		name: "hello_subdomain",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "say.hello",
 			DNSType:  dns.TypeA,
 		},
 	}, {
 		want: []*rules.DNSRewrite{},
 		name: "hello_alias_subdomain",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "say.hello.world",
 			DNSType:  dns.TypeA,
 		},
@@ -436,7 +436,7 @@ func TestHostsContainer(t *testing.T) {
 			Value:  net.ParseIP("::2"),
 		}},
 		name: "lots_of_aliases",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "for.testing",
 			DNSType:  dns.TypeA,
 		},
@@ -447,21 +447,21 @@ func TestHostsContainer(t *testing.T) {
 			Value:  "simplehost.",
 		}},
 		name: "reverse",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "1.0.0.1.in-addr.arpa",
 			DNSType:  dns.TypePTR,
 		},
 	}, {
 		want: []*rules.DNSRewrite{},
 		name: "non-existing",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "nonexisting",
 			DNSType:  dns.TypeA,
 		},
 	}, {
 		want: nil,
 		name: "bad_type",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "1.0.0.1.in-addr.arpa",
 			DNSType:  dns.TypeSRV,
 		},
@@ -476,7 +476,7 @@ func TestHostsContainer(t *testing.T) {
 			Value:  net.ParseIP("::42"),
 		}},
 		name: "issue_4216_4_6",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "domain",
 			DNSType:  dns.TypeA,
 		},
@@ -491,7 +491,7 @@ func TestHostsContainer(t *testing.T) {
 			Value:  net.IPv4(1, 3, 5, 7),
 		}},
 		name: "issue_4216_4",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "domain4",
 			DNSType:  dns.TypeA,
 		},
@@ -506,7 +506,7 @@ func TestHostsContainer(t *testing.T) {
 			Value:  net.ParseIP("::31"),
 		}},
 		name: "issue_4216_6",
-		req: urlfilter.DNSRequest{
+		req: &urlfilter.DNSRequest{
 			Hostname: "domain6",
 			DNSType:  dns.TypeAAAA,
 		},
diff --git a/internal/filtering/filtering.go b/internal/filtering/filtering.go
index 9bedeef7..8af49b0c 100644
--- a/internal/filtering/filtering.go
+++ b/internal/filtering/filtering.go
@@ -471,7 +471,7 @@ func (d *DNSFilter) matchSysHosts(
 		return res, nil
 	}
 
-	dnsres, _ := d.EtcHosts.MatchRequest(urlfilter.DNSRequest{
+	dnsres, _ := d.EtcHosts.MatchRequest(&urlfilter.DNSRequest{
 		Hostname:         host,
 		SortedClientTags: setts.ClientTags,
 		// TODO(e.burkov):  Wait for urlfilter update to pass net.IP.
@@ -802,7 +802,7 @@ func (d *DNSFilter) matchHost(
 		return Result{}, nil
 	}
 
-	ureq := urlfilter.DNSRequest{
+	ureq := &urlfilter.DNSRequest{
 		Hostname:         host,
 		SortedClientTags: setts.ClientTags,
 		// TODO(e.burkov): Wait for urlfilter update to pass net.IP.
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index 98ef459a..2ad6ce16 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -3,30 +3,32 @@ module github.com/AdguardTeam/AdGuardHome/internal/tools
 go 1.17
 
 require (
-	github.com/fzipp/gocyclo v0.4.0
+	github.com/fzipp/gocyclo v0.5.1
 	github.com/golangci/misspell v0.3.5
 	github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8
 	github.com/kisielk/errcheck v1.6.0
 	github.com/kyoh86/looppointer v0.1.7
-	github.com/securego/gosec/v2 v2.9.5
+	github.com/securego/gosec/v2 v2.11.0
 	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
-	golang.org/x/tools v0.1.8
-	honnef.co/go/tools v0.2.2
-	mvdan.cc/gofumpt v0.2.1
-	mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5
+	golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a
+	honnef.co/go/tools v0.3.0
+	mvdan.cc/gofumpt v0.3.1
+	mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b
 )
 
 require (
-	github.com/BurntSushi/toml v0.4.1 // indirect
+	github.com/BurntSushi/toml v1.1.0 // indirect
 	github.com/client9/misspell v0.3.4 // indirect
-	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/go-cmp v0.5.7 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/gookit/color v1.5.0 // indirect
 	github.com/kyoh86/nolint v0.0.1 // indirect
 	github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
 	github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
-	golang.org/x/mod v0.5.1 // indirect
-	golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
-	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	golang.org/x/exp/typeparams v0.0.0-20220407100705-7b9b53b0aca4 // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
+	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
+	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
+	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 )
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index e1b2e226..7dbf5491 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -34,8 +34,9 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
 github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
@@ -92,14 +93,13 @@ github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
-github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
-github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
+github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns=
+github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
-github.com/fzipp/gocyclo v0.4.0 h1:IykTnjwh2YLyYkGa0y92iTTEQcnyAz0r9zOo15EbJ7k=
-github.com/fzipp/gocyclo v0.4.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
+github.com/fzipp/gocyclo v0.5.1 h1:L66amyuYogbxl0j2U+vGqJXusPF2IkduvXLnYD5TFgw=
+github.com/fzipp/gocyclo v0.5.1/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -157,8 +157,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -168,6 +168,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw=
 github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -200,6 +201,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
 github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -276,7 +278,6 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6Fx
 github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
 github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
@@ -284,13 +285,16 @@ github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2f
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
-github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
-github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -325,8 +329,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
 github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/securego/gosec/v2 v2.9.5 h1:Wiyf78NNedu8RClwW0vPRgPKCY7LJX4WujjJcPV2Nwg=
-github.com/securego/gosec/v2 v2.9.5/go.mod h1:lG831xFHrZofatyJb9Y5yMUE8Ws6z5U5CMHe9vYn1kM=
+github.com/securego/gosec/v2 v2.11.0 h1:+PDkpzR41OI2jrw1q6AdXZCbsNGNGT7pQjal0H0cArI=
+github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -401,7 +405,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -412,7 +417,11 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo=
 golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
+golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
+golang.org/x/exp/typeparams v0.0.0-20220407100705-7b9b53b0aca4 h1:P5yukcpQfG1ZDKR0pGdaZCVwaNPntMxLFKYg81li58M=
+golang.org/x/exp/typeparams v0.0.0-20220407100705-7b9b53b0aca4/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -436,8 +445,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
 golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -473,6 +483,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -493,6 +504,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -539,15 +551,16 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -613,15 +626,17 @@ golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
-golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II=
+golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -714,7 +729,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
@@ -737,12 +751,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
-honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
-mvdan.cc/gofumpt v0.2.1 h1:7jakRGkQcLAJdT+C8Bwc9d0BANkVPSkHZkzNv07pJAs=
-mvdan.cc/gofumpt v0.2.1/go.mod h1:a/rvZPhsNaedOJBzqRD9omnwVwHZsBdJirXHa9Gh9Ig=
-mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5 h1:Jh3LAeMt1eGpxomyu3jVkmVZWW2MxZ1qIIV2TZ/nRio=
-mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5/go.mod h1:b8RRCBm0eeiWR8cfN88xeq2G5SG3VKGO+5UPWi5FSOY=
+honnef.co/go/tools v0.3.0 h1:2LdYUZ7CIxnYgskbUZfY7FPggmqnh6shBqfWa8Tn3XU=
+honnef.co/go/tools v0.3.0/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70=
+mvdan.cc/gofumpt v0.3.1 h1:avhhrOmv0IuvQVK7fvwV91oFSGAk5/6Po8GXTzICeu8=
+mvdan.cc/gofumpt v0.3.1/go.mod h1:w3ymliuxvzVx8DAutBnVyDqYb1Niy/yCJt/lk821YCE=
+mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b h1:C8Pi6noat8BcrL9WnSRYeQ63fpkJk3hKVHtF5731kIw=
+mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b/go.mod h1:WqFWCt8MGPoFSYGsQSiIORRlYVhkJsIk+n2MY6rhNbA=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

From 57171f0a61574bf9cc4819e2f8444638c4b5693c Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 13 Apr 2022 20:30:36 +0300
Subject: [PATCH 039/143] Pull request: client: upd i18n

Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 181c13667eb79e5f0c8ec6502e8bd79f7403bf8d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 13 20:24:32 2022 +0300

    client: upd i18n
---
 client/src/__locales/bg.json    | 38 ++++++++++++++-
 client/src/__locales/de.json    |  4 +-
 client/src/__locales/fa.json    | 84 +++++++++++++++++++++++++++++++--
 client/src/__locales/nl.json    | 20 ++++----
 client/src/__locales/no.json    | 45 ++++++++++++++++--
 client/src/__locales/ru.json    |  4 +-
 client/src/__locales/sv.json    | 10 ++--
 client/src/__locales/tr.json    |  6 +--
 client/src/__locales/zh-tw.json |  4 +-
 9 files changed, 183 insertions(+), 32 deletions(-)

diff --git a/client/src/__locales/bg.json b/client/src/__locales/bg.json
index 1b6a1db5..a64b7620 100644
--- a/client/src/__locales/bg.json
+++ b/client/src/__locales/bg.json
@@ -1,16 +1,22 @@
 {
+    "client_settings": "Kлиентски настройки",
+    "example_upstream_comment": "Можете да поставите коментар",
+    "load_balancing": "Балансиране на натоварването",
     "check_dhcp_servers": "Проверка за активен DHCP сървър",
+    "save_config": "Запиши настройките",
     "enabled_dhcp": "DHCP е разрешен",
     "disabled_dhcp": "DHCP е забранен",
     "dhcp_title": "DHCP сървър (тестови!)",
     "dhcp_description": "Ако рутера ви не раздава DHCP адреси, може да използвате вградения в AdGuard DHCP сървър.",
     "dhcp_enable": "Рзреши DHCP сървъра",
     "dhcp_disable": "Забрани DHCP сървъра",
+    "dhcp_not_found": "Вашата мрежа няма активен DHCP сървър. Безопасно е ползването на вградения DHCP сървър.",
     "dhcp_found": "Вашата мрежа вече има активен DHCP сървър. Не е безопасно ползването на втори!",
     "dhcp_leases": "DHCP раздадени адреси",
     "dhcp_leases_not_found": "Няма намерени активни DHCP адреси",
+    "dhcp_config_saved": "Запиши конфигурацията на DHCP сървъра",
     "form_error_required": "Задължително поле",
-    "form_error_ip_format": "Невалиден IP адрес",
+    "form_error_ip_format": "Невалиден IPv4 адрес",
     "form_error_positive": "Проверете дали е положително число",
     "dhcp_form_gateway_input": "IP шлюз",
     "dhcp_form_subnet_input": "Мрежова маска",
@@ -24,15 +30,21 @@
     "dhcp_ip_addresses": "IP адреси",
     "dhcp_table_hostname": "Име на устройство",
     "dhcp_table_expires": "История",
+    "dhcp_warning": "Ако искате да използвате вградения DHCP сървър, трябва да няма друг активен DHCP в мрежата Ви!",
+    "country": "Държава",
     "city": "Град",
+    "delete_confirm": "Наистина ли искате да изтриете \"{{key}}\"?",
+    "error_details": "Подробности за грешка",
     "response_details": "Подробности за отговора",
     "request_details": "Поискайте подробности",
+    "details": "Детайли",
     "back": "Назад",
     "dashboard": "Табло",
     "settings": "Настройки",
     "filters": "Филтри",
     "filter": "Филтър",
     "query_log": "История на заявките",
+    "compact": "Compact",
     "faq": "ЧЗВ",
     "version": "версия",
     "address": "Адрес",
@@ -60,7 +72,12 @@
     "top_clients": "Най-активни IP адреси",
     "no_clients_found": "Нямa намерени адреси",
     "general_statistics": "Обща статисика",
+    "number_of_dns_query_24_hours": "Сума на DNS заявки за последните 24 часа",
+    "number_of_dns_query_blocked_24_hours": "Сума на блокирани DNS заявки от филтрите за реклама и местни",
+    "number_of_dns_query_blocked_24_hours_by_sec": "Сума на блокирани DNS заявки от AdGuard свързани със сигурността",
+    "number_of_dns_query_blocked_24_hours_adult": "Сума на блокирани сайтове за възрастни",
     "enforced_save_search": "Активирано Безопасно Търсене",
+    "number_of_dns_query_to_safe_search": "Сума на DNS заявки при който е приложено Безопасно Търсене",
     "average_processing_time": "Средно време за обработка",
     "average_processing_time_hint": "Средно време за обработка на DNS заявки в милисекунди",
     "block_domain_use_filters_and_hosts": "Блокирани домейни - общи и местни филтри",
@@ -70,6 +87,7 @@
     "use_adguard_parental": "Включи AdGuard Родителски Надзор",
     "use_adguard_parental_hint": "Модул XXX в AdGuard Home ще провери дали страницата има материали за възвъстни. Използва се същия API за анонимност като при модула за Сигурност.",
     "enforce_safe_search": "Включи Безопасно Търсене",
+    "enforce_save_search_hint": "AdGuard Home прилага Безопасно Търсене в следните търсачки и сайтове: Google, Youtube, Bing, и Yandex.",
     "no_servers_specified": "Няма избрани услуги",
     "general_settings": "Общи настройки",
     "custom_filtering_rules": "Местни правила за филтриране",
@@ -106,10 +124,13 @@
     "example_comment_hash": "# Това е също коментар",
     "example_regex_meaning": "блокирай достъп до домейни който съвпадат със следното",
     "example_upstream_regular": "класически DNS (UDP протокол)",
+    "example_upstream_udp": "обикновен DNS (върху UDP, име на хост);",
     "example_upstream_dot": "криптиран <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-върху-TLS</a>",
     "example_upstream_doh": "криптиран <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-върху-HTTPS</a>",
     "example_upstream_sdns": "може да ползвате <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Подписване</a> за <a href='https://dnscrypt.info/' target='_blank'>DNSCrypt</a> или <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-върху-HTTPS</a> сървъри",
     "example_upstream_tcp": "класически DNS (TCP протокол)",
+    "example_upstream_tcp_hostname": "обикновен DNS (върху TCP, име на хост);",
+    "updated_upstream_dns_toast": "Глобалните DNS сървъри са обновени",
     "dns_test_ok_toast": "Въведените DNS сървъри работят коректно",
     "dns_test_not_ok_toast": "Сървър \"{{key}}\": не работи, моля проверете дали е въведен коректно",
     "unblock": "Отблокирай",
@@ -135,11 +156,13 @@
     "rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране: {{rule}}",
     "default": "По подразбиране",
     "custom_ip": "Персонализиран IP",
+    "dns_over_https": "DNS-пред-HTTPS",
     "dns_over_quic": "DNS-over-QUIC",
     "plain_dns": "Обикновен DNS",
     "source_label": "Източник",
     "found_in_known_domain_db": "Намерен в списъците с домейни.",
     "category_label": "Категория",
+    "rule_label": "Правило",
     "unknown_filter": "Непознат филтър {{filterId}}",
     "install_welcome_title": "Добре дошли в AdGuard Home!",
     "install_welcome_desc": "AdGuard Home e мрежово решение за блокиране на реклами и тракери на DNS ниво. Създадено е за да ви даде пълен контрол над мрежата и всичките ви устройства, без да е необходимо допълнително инсталиране на друг софтуер.",
@@ -147,6 +170,7 @@
     "install_settings_listen": "Активни интерфейси",
     "install_settings_port": "Порт",
     "install_settings_interface_link": "Вашата AdGuard Home страница за администрация ще е достъпна на този адрес:",
+    "form_error_port": "Моля въведете валиден порт",
     "install_settings_dns": "DNS сървър",
     "install_settings_dns_desc": "За да работи, ще трябва да настроите вашият рутер или устройства да ползват DNS сървър с адрес:",
     "install_settings_all_interfaces": "Всички интерфейси",
@@ -165,6 +189,7 @@
     "install_devices_router": "Рутер",
     "install_devices_router_desc": "Ако настроите вашият рутер няма нужда ръчно да настройвате всяко едно от устрйствата в мрежата.",
     "install_devices_address": "AdGuard Home DNS сървърът е на следния адрес",
+    "install_devices_router_list_1": "Отворете страницата за настройки на вашия рутер. Обикновено тя се намира на URL (тук http://192.168.0.1/ или тук http://192.168.1.1/). За достъп може да ви трябва парола. Ако сте забравили паролата може да я ресетнете като натиснета скрития ресет бутон - внимание това ще ресетне всички настройки на рутера до фабрични! Някой рутери могат да бъдате администрирани от софтуер или приложение, който би трябвало да е вече инсталиран на компютъра/телефона ви.",
     "install_devices_router_list_2": "Намерета DHCP/DNS настройки. В под раздел DHCP рзгледайте и намерете къде е полето за DNS настройка в което може да въведете персонализирани настройки за DNS сървъри.",
     "install_devices_router_list_3": "Въведете адресът на AdGuard Home сървъра.",
     "install_devices_windows_list_1": "Отворете Контролния Панел през Старт меню или чрез функция търсене на Windows.",
@@ -192,8 +217,10 @@
     "install_saved": "Успешно записано",
     "encryption_title": "Криптиране",
     "encryption_desc": "Подържа се сигурна връзка (HTTPS/TLS) включително за DNS и страницата за администрация",
+    "encryption_config_saved": "Конфигурацията е успешно записана",
     "encryption_server": "Име на сървъра",
     "encryption_server_enter": "Въведете име на домейна",
+    "encryption_server_desc": "За да използвате HTTPS, трябва името на сървъра да съвпада с това на SSL сертификата.",
     "encryption_redirect": "Автоматично пренасочване към HTTPS",
     "encryption_redirect_desc": "Служи за автоматично пренасочване от HTTP към HTTPS на страницата за Администрация в AdGuard Home.",
     "encryption_https": "HTTPS порт",
@@ -219,7 +246,9 @@
     "encryption_reset": "Сигурни ли сте че искате да изтриете настройките за криптиране?",
     "topline_expiring_certificate": "Вашият SSL сертификат изтича. Обнови <0>Настройки за криптиране</0>.",
     "topline_expired_certificate": "Вашият SSL сертификат е изтекъл. Обнови <0>Настройки за криптиране</0>.",
+    "form_error_port_range": "Въведете порт в диапазона 80-65535",
     "form_error_port_unsafe": "Не е безопасно да използвате този порт",
+    "form_error_equal": "Не трябва да съвпада",
     "form_error_password": "Паролата не съвпада",
     "reset_settings": "Изтрий всички настройки",
     "update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация.",
@@ -230,7 +259,11 @@
     "name": "Име",
     "clients_not_found": "Нямa намерени адреси",
     "check_updates_now": "Провери за актуализации",
+    "interval_6_hour": "6 часа",
+    "interval_24_hour": "24 часа",
     "domain": "Домейн",
+    "ecs": "ECS",
+    "statistics_clear": "Нулирай статистиката",
     "disabled": "Деактивиран",
     "username_label": "Потребител",
     "username_placeholder": "Въведете потребител",
@@ -238,11 +271,14 @@
     "password_placeholder": "Въведете парола",
     "network": "Мрежа",
     "descr": "Описание",
+    "try_again": "Опитай пак",
+    "disable_ipv6": "Изключете IPv6 протокола",
     "show_blocked_responses": "Блокирано",
     "show_whitelisted_responses": "В белия списък",
     "show_processed_responses": "Обработен",
     "allowed": "В белия списък",
     "safe_search": "Безопасно търсене",
+    "blocklist": "Черен списък",
     "filter_category_general": "General",
     "filter_category_security": "Сигурност",
     "port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това.",
diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json
index 6f8ff803..be5666a8 100644
--- a/client/src/__locales/de.json
+++ b/client/src/__locales/de.json
@@ -274,8 +274,8 @@
     "blocking_ipv4": "IPv4-Sperren",
     "blocking_ipv6": "IPv6-Sperren",
     "dnscrypt": "DNSCrypt",
-    "dns_over_https": "DNS-over-HTTPS (DNS-Abfrage über HTTPS)",
-    "dns_over_tls": "DNS-over-TLS (DNS-Abfrage über TLS)",
+    "dns_over_https": "DNS-over-HTTPS",
+    "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
     "client_id": "Client-ID",
     "client_id_placeholder": "Client-ID eingeben",
diff --git a/client/src/__locales/fa.json b/client/src/__locales/fa.json
index d465b2bf..20cc372d 100644
--- a/client/src/__locales/fa.json
+++ b/client/src/__locales/fa.json
@@ -1,26 +1,50 @@
 {
     "client_settings": "تنظیمات کلاینت",
+    "example_upstream_reserved": "میتوانید جریان ارسالی DNS <0> را برای یک دامنه مشخص تعیین کنید </0>",
+    "upstream_parallel": "استفاده از جستار موازی برای سرعت دادن به تفکیک با جستار همزمان همه جریان های ارسالی",
     "parallel_requests": "درخواست های موازی",
     "bootstrap_dns": "خودراه انداز سرورهای DNS",
     "bootstrap_dns_desc": "خودراه انداز سرورهای DNS برای تفکیک آدرس آی پی  تفکیک کننده های DoH/DoT که شما بعنوان جریان ارسالی تعیین کردید استفاده میشود.",
+    "local_ptr_title": "سرورهای خصوصی DNS",
+    "local_ptr_desc": "سرور یا سرور های DNS ای که AdGuard Home برای درخواست های منابع محلی ارائه شده مورد استفاده قرار خواهد داد. برای مثال، این سرور برای تعیین نام های سرویس دهنده برای سرویس گیرنده با آدرس های آی پی خصوصی مورد استفاده قرار خواهد گرفت. اگر تعیین نشود،AdGuard Home به طور خودکار از تعیین کننده ی DNS پیش فرض شما استفاده خواهد کرد.",
+    "local_ptr_default_resolver": "به طور پیش فرض، AdGuard Home از تعیین کننده های DNS معکوس زیر استفاده می کند: {{ip}}.",
+    "local_ptr_no_default_resolver": "AdGuard Home نتوانست برای این دستگاه تعیین کننده های DNS معکوس محرمانه مناسب را معین کند.",
+    "local_ptr_placeholder": "در هر خط یک آدرس سرور را وارد کنید",
+    "resolve_clients_title": "فعال سازی تعیین نام های سرویس دهنده ی سرویس گیرندگان",
+    "resolve_clients_desc": "در صورت فعال بودن،AdGuard Home به طور خودکار اقدام به تعیین نام های سرویس دهنده ی سرویس گیرندگان از آدرس های آی پی با ارسال یک درخواست PTR به یک تعیین کننده ی همتا خواهد کرد (سرور خصوصی DNS برای سرویس گیرندگان محلی،سرور مادر برای سرویس گیرندگان با آی پی عمومی).",
+    "use_private_ptr_resolvers_title": "از تعیین کننده های rDNS محرمانه استفاده کنید",
+    "use_private_ptr_resolvers_desc": "داده گیری های دی ان اس معکوس را برای آدرس های اینترنتی خدمات منظقه ای با استفاده از سرور های مادر اجرا کنید. چنانچه غیر فعال باشد،AdGuard Home با NXDOMAIN به همه چنین درخواست های PTR پاسخ می دهد به استثناء خدمات گیرنده های شناخته شده از طرف DHCP،/و غیره/هوست ها، و سایر.",
     "check_dhcp_servers": "بررسی برای سرورهای DHCP",
+    "save_config": "ذخیره پیکربندی",
     "enabled_dhcp": "سرور DHCP  فعال شده است",
     "disabled_dhcp": "سرور DHCP  غیرفعال شده است",
     "dhcp_title": "سرور DHCP",
     "dhcp_description": "اگر روتر شما تنظیمات DHCP ارائه نمی کند،میتوانید از سرور DHCP تو-کار خود AdGuard استفاده کنید.",
     "dhcp_enable": "فعالسازی سرور DHCP",
     "dhcp_disable": "غیرفعالسازی سرور DHCP",
+    "dhcp_not_found": "سرورهای فعال DHCP در شبکه یافت نشد.فعالسازی سرور DHCP تو-کار اَمن است.",
     "dhcp_found": "تعدادی سرور DHCP فعال در شبکه یافت شد.فعالسازی سرور DHCP توکار اَمن نیست.",
     "dhcp_leases": "اجاره DHCP",
     "dhcp_static_leases": "اجاره DHCP ایستا",
     "dhcp_leases_not_found": "اجاره DHCP یافت نشد",
+    "dhcp_config_saved": "پیکربندی سرور DHCP ذخیره شده است",
     "form_error_required": "فیلد مورد نیاز",
     "form_error_ip4_format": "فرمت نامعتبر IPv4",
+    "form_error_ip4_range_start_format": "قالب IPv4 شروع دامنه نامعتبر است",
+    "form_error_ip4_range_end_format": "قالب IPv4 پایان دامنه نامعتبر است",
+    "form_error_ip4_gateway_format": "قالب IPv4 درگاه نامعتبر است",
     "form_error_ip6_format": "فرمت نامعتبر IPv6",
-    "form_error_ip_format": "آدرس آی پی نامعتبر است",
+    "form_error_ip_format": "فرمت IPv4 نامعتبر است",
     "form_error_mac_format": "فرمت مَک نامعتبر است",
     "form_error_client_id_format": "فرمت شناسه کلاینت نامعتبر است",
+    "form_error_subnet": "زیرشبکه\"{{cidr}}\"آدرس آی پی {{ip}} را در بر ندارد",
     "form_error_positive": "باید بزرگتر از 0 باشد",
+    "out_of_range_error": "باید خارج از دامنه باشد\"{{start}}\"-\"{{end}}\"",
+    "lower_range_start_error": "باید کمتر از شروع دامنه باشد",
+    "greater_range_start_error": "باید بیشتر از شروع دامنه باشد",
+    "greater_range_end_error": "باید بیشتر از پایان دامنه باشد",
+    "subnet_error": "آدرس ها باید در یک زیرشبکه باشند",
+    "gateway_or_subnet_invalid": "پوشش زیرشبکه نامعتبر است",
     "dhcp_form_gateway_input": "آی پی دروازه",
     "dhcp_form_subnet_input": "ماسک زیر شبکه",
     "dhcp_form_range_title": "دامنه آدرس های آی پی",
@@ -34,11 +58,19 @@
     "ip": "IP",
     "dhcp_table_hostname": "نام میزبان",
     "dhcp_table_expires": "انقضاء",
+    "dhcp_warning": "اگر میخواهید DHCP سرور توکار را فعال کنید،مطمئن شوید DHCP سرور دیگری فعال نباشد.در غیر اینصورت،آن دسترسی به اینترنت را برای دستگاه های وصل شده قطع می کند!",
+    "dhcp_error": "ما نمیتوانیم تشخیص دهیم آیا یک سرور DHCP دیگر در شبکه موجود است یا نه.",
+    "dhcp_static_ip_error": "به منظور استفاده از سرور DHCP یک آدرس آی پی ثابت باید تنظیم شود.ما موفق به تشخیص اینکه  آیا رابط این شبکه برای استفاده از آی پی ثابت تنظیم شده است یا نه موفق نشدیم.لطفا آدرس آی پی ثابت را دستی تنظیم کنید.",
+    "dhcp_dynamic_ip_found": "سیستم شما آدرس آی پی متغییر برای این رابط استفاده می کند <0>{{interfaceName}}</0>. به منظور استفاده از سرور DHCP آدرس ثابت باید تعیین شود. آدرس آی پی فعلی شما هست <0>{{ipAddress}}</0>. اگر شما دکمه DHCP را فشار دهید ما این آدرس آی پی را بعنوان ثابت تنظیم می کنیم.",
     "dhcp_lease_added": "اجاره ایستا \"{{key}}\" با موفقیت اضافه شد",
     "dhcp_lease_deleted": "اجاره ایستا \"{{key}}\" با موفقیت حذف شد",
     "dhcp_new_static_lease": "اجاره ایستا جدید",
     "dhcp_static_leases_not_found": "هیچ اجاره DHCP ایستا یافت نشد",
     "dhcp_add_static_lease": "افزودن اجاره ایستا",
+    "dhcp_reset_leases": "بازگردانی همه مجوزهای منابع",
+    "dhcp_reset_leases_confirm": "آیا می خواهید تمام مجوزهای منابع را بازگردانی کنید؟",
+    "dhcp_reset_leases_success": "مجوزهای منابع DHCP با موفقیت بازگردانی شد",
+    "dhcp_reset": "آیا میخواهید پیکربندی DHCP را ریست کنید؟",
     "country": "کشور",
     "city": "شهر",
     "delete_confirm": "آیا میخواهید \"{{key}}\" را حذف کنید؟",
@@ -54,6 +86,7 @@
     "filters": "فيلترها",
     "filter": "فیلتر",
     "query_log": "جستار وقایع",
+    "compact": "فشرده",
     "nothing_found": "هیچ چیز یافت نشد",
     "faq": "پرسش و پاسخ",
     "version": "نسخه",
@@ -78,13 +111,22 @@
     "for_last_24_hours": "برای 24 ساعت گذشته",
     "for_last_days": "برای {{count}} روز آخر",
     "for_last_days_plural": "برای {{count}} روز گذشته",
+    "stats_disabled": "آمار غیرفعال شده است. شما می توانید از قسمت <0>صفحه تنظیمات</0> آن را روشن نمایید.",
+    "stats_disabled_short": "آمار غیرفعال شده است",
     "no_domains_found": "دامنه یافت نشد",
     "requests_count": "تعداد درخواست ها",
     "top_blocked_domains": "دامنه های بیشتر مسدود شده",
     "top_clients": "بالاترین کلاینت ها",
     "no_clients_found": "کلاینتی یافت نشد",
     "general_statistics": "آمار عمومی",
+    "number_of_dns_query_days": "تعداد جستار DNS پردازش شده در {{count}} روز آخر",
+    "number_of_dns_query_days_plural": "تعداد جستار DNS پردازش شده در {{count}} روز گذشته",
+    "number_of_dns_query_24_hours": "تعداد جستار DNS پردازش شده در 24 ساعت گذشته",
+    "number_of_dns_query_blocked_24_hours": "تعداد درخواست DNS مسدود شده با فیلترهای مسدودساز تبلیغ و لیست سیاه میزبان",
+    "number_of_dns_query_blocked_24_hours_by_sec": "تعداد درخواست DNS مسدود شده با مدل امنیت وب گردی AdGuard",
+    "number_of_dns_query_blocked_24_hours_adult": "تعداد وبسایت های غیر اخلاقی مسدود شده",
     "enforced_save_search": "جستجوی اَمن اجبار شده",
+    "number_of_dns_query_to_safe_search": "تعداد درخواست های DNS برای موتور جستجو که جستجوی اَمن اجبار شده",
     "average_processing_time": "میانگین زمان پردازش",
     "average_processing_time_hint": "زمان میانگین بر هزارم ثانیه در پردازش درخواست DNS",
     "block_domain_use_filters_and_hosts": "مسدودسازی دامنه ها توسط فیلترها و فایل های میزبان",
@@ -94,6 +136,7 @@
     "use_adguard_parental": "از سرویس وب نظارت والدین AdGuard استفاده کن",
     "use_adguard_parental_hint": "AdGuard Home بررسی می کند اگر دامنه حاوی موارد غیر اخلاقی است.آن از همان اِی پی آی دارای حریم خصوصی سرویس وب امنیت وب گردی استفاده می کند.",
     "enforce_safe_search": "اجبار جستجوی اَمن",
+    "enforce_save_search_hint": "AdGuard Home میتواند جستجوی اَمن را در موتور جستجوهای زیر اعمال کند:گوگل،یوتوب،بینگ و یاندکس",
     "no_servers_specified": "سروری تعیین نشده است",
     "general_settings": "تنظیمات عمومی",
     "dns_settings": "تنظیمات DNS",
@@ -145,6 +188,7 @@
     "form_error_url_or_path_format": "آدرس نامعتبر یا یک مسیر کامل لیست",
     "custom_filter_rules": "دستورات فیلترینگ دستی",
     "custom_filter_rules_hint": "یک دستور در خط وارد کنید.میتوانید از دستورات مسدودساز تبلیغ یا نحو فایل های میزبان استفاده کنید.",
+    "system_host_files": "فایل های هوست سیستم",
     "examples_title": "مثال ها",
     "example_meaning_filter_block": "مسدودسازی دسترسی به دامنه example.org و همه زیر دامنه ها آن",
     "example_meaning_filter_whitelist": "بازکردن دسترسی به دامنه example.org و همه زیر دامنه ها آن",
@@ -159,6 +203,7 @@
     "example_upstream_sdns": "شما میتوانید از <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Stamps</a> برای <a href='https://dnscrypt.info/' target='_blank'>DNSCrypt</a> یا <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a> resolvers استفاده کنید",
     "example_upstream_tcp": "DNS عادی (بر TCP)",
     "all_lists_up_to_date_toast": "همه لیست ها از قبل بروز اند",
+    "updated_upstream_dns_toast": "سرورهای DNS جریان ارسالی بروز رسانی شده است",
     "dns_test_ok_toast": "سرورهای DNS تعیین شده بدرستی کار می کنند",
     "dns_test_not_ok_toast": "سرور \"{{key}}\": نمیتواند مورد استفاده قرار گیرد،لطفا بررسی کنید آن را بدرستی نوشته اید",
     "unblock": "رفع انسداد",
@@ -169,6 +214,7 @@
     "domain_or_client": "دامنه یا کلاینت",
     "type_table_header": "نوع",
     "response_table_header": "پاسخ",
+    "response_code": "کد پاسخ",
     "client_table_header": "کلاینت",
     "empty_response_status": "خالی",
     "show_all_filter_type": "نمایش همه",
@@ -187,6 +233,7 @@
     "query_log_filtered": "فیلتر شده با {{filter}}",
     "query_log_confirm_clear": "آیا واقعا میخواهید کل وقایع جستار را پاک کنید؟",
     "query_log_cleared": "وقایع جستار با موفقیت پاک شد",
+    "query_log_updated": "فیلتر با موفقیت بروز رسانی شد",
     "query_log_clear": "پاکسازی وقایع جستار",
     "query_log_retention": "حفظ وقایع جستار برای",
     "query_log_enable": "فعالسازی وقایع",
@@ -212,15 +259,19 @@
     "rate_limit": "میزان محدودیت",
     "edns_enable": "فعالسازی زیرشبکه کلاینت EDNS",
     "edns_cs_desc": "اگر فعال باشد،AdGuard Home زیرشبکه های کلاینت ها را به سرورهای DNS می فرستد.",
+    "rate_limit_desc": "تعداد درخواست های بر ثانیه مجازی که یک کلاینت میتواند بسازد (0: نامحدود)",
     "blocking_ipv4_desc": "آدرس آی پی برگشت داده شده برای درخواست مسدود شده A",
     "blocking_ipv6_desc": "آدرس آی پی برگشت داده شده برای درخواست مسدود شده AAAA",
+    "blocking_mode_default": "پیش فرض: وقتی مسدود شود با دستور سبک-مسدودساز تبلیغ REFUSED پاسخ میدهد،پاسخ با آدرس آی پی تعیین شده در دستور وقتی با دستور /etc/hosts-style rule مسدود شود",
     "blocking_mode_nxdomain": "NXDOMAIN: پاسخ با کُد NXDOMAIN",
     "blocking_mode_null_ip": "Null IP: پاسخ با آدرس آی پی صفر(0.0.0.0 برای A; :: برای AAAA)",
     "blocking_mode_custom_ip": "آی پی دستی: پاسخ با آدرس آی پی دستی تنظیم شده",
     "upstream_dns_client_desc": "اگر این فیلد را خالی نگه دارید، AdGuard Home از سرور پیکربندی شده در <0> تنظیماتDNS </0> استفاده می کند.",
+    "tracker_source": "منبع ردیاب",
     "source_label": "منبع",
     "found_in_known_domain_db": "در پایگاه داده دامنه های شناخته شده پیدا شد",
     "category_label": "دسته بندی",
+    "rule_label": "دستور",
     "list_label": "لیست",
     "unknown_filter": "فیلتر ناشناخته {{filterId}}",
     "known_tracker": "ردیاب های شناخته شده",
@@ -230,6 +281,7 @@
     "install_settings_listen": "رابط گوش دادن",
     "install_settings_port": "پورت",
     "install_settings_interface_link": "رابط صفحه وب آدمین AdGuard Home شما در این آدرس قابل دسترسی خواهد بود:",
+    "form_error_port": "مقدار پورت معتبر وارد کنید",
     "install_settings_dns": "سرور DNS",
     "install_settings_dns_desc": "نیاز است شما دستگاه یا روتر خود را برای استفاده از سرور DNS روی آدرس های زیر پیکربندی کنید:",
     "install_settings_all_interfaces": "همه رابط ها",
@@ -248,8 +300,10 @@
     "install_devices_router": "روتر",
     "install_devices_router_desc": "این راه انداز خودکار همه دستگاه های متصل شده به روتر خانه را پوشش میدهد و نیازی نیست شما هر یک از آنها را دستی پیکربندی کنید.",
     "install_devices_address": "DNS سرور AdGuard Home به آدرس های زیر گوش میدهد",
+    "install_devices_router_list_1": "اولویت ها را برای روتر خود باز کنید.معمولا میتوانید آن را ز طریق مرورگر از طریق آدرسی مانند ( http://192.168.0.1/ یا http://192.168.1.1/) دسترسی داشته باشید.ممکن است رمزعبور پرسیده شود،اگر آن را بخاطر ندارید،غالبا میتوان رمزعبور را با فشردن دکمه پشت روتر ریست کرد.برخی روترها برنامه خاصی نیاز دارد که باید در رایانه/گوشی نصب شده باشد.",
     "install_devices_router_list_2": "تنظیمات DHCP/DNS را بیابید.دنبال حروف DNS بگردید در فیلدی که اجازه دو یا سه گروه عدد را میدهد و هر کدام در چهار گروه سه عددی شکسته شده است",
     "install_devices_router_list_3": "آدرس سرور AdGuard Home  خود را آنجا وارد کنید",
+    "install_devices_router_list_4": "شما نمیتوانید DNS سرور سفارشی در برخی از روترها تنظیم کنید. در این مورد اگر شما AdGuard Home را بعنوان DHCP سرور راه اندازی کنید میتواند کمک کند. در غیر اینصورت باید راهنمای سفارشی سازی DNS سرورها برای مدل خاص روتر خود را انتخاب کنید.",
     "install_devices_windows_list_1": "کنترل پنل را از طریق استارت منو یا جستجوی ویندوز باز کنید.",
     "install_devices_windows_list_2": "بروید به شبکه و دسته اینترنت و سپس به شبکه و مرکز اشتراک گذاری",
     "install_devices_windows_list_3": "در سمت چپ صفحه تنظیمات آداپتور را تغییر داده و روی آن کلیک کنید",
@@ -275,8 +329,10 @@
     "install_saved": "با موفقیت ذخیره نشد",
     "encryption_title": "رمزگُذاری",
     "encryption_desc": "پشتیبانی رمزگُذاری (HTTPS/TLS) برای DNS و رابط آدمین وب",
+    "encryption_config_saved": "پیکربندی رمزگذاری ذخیره شد",
     "encryption_server": "نام سرور",
     "encryption_server_enter": "نام دامنه خود را وارد کنید",
+    "encryption_server_desc": "به منظور استفاده از HTTPS،شما باید نام سرور مطابق با گواهینامه اِس اِس اِل را وارد کنید.",
     "encryption_redirect": "تغییر مسیر خودکار به HTTPS",
     "encryption_redirect_desc": "اگر انتخاب شده باشد،AdGuard Home  خودکار شما را از آدرس HTTP به HTTPS منتقل می کند",
     "encryption_https": "پورت HTTPS",
@@ -302,10 +358,13 @@
     "encryption_reset": "آیا میخواهید تنظیمات رمزگُذاری به پیش فرض بازگردد؟",
     "topline_expiring_certificate": "گواهینامه اِس اِس اِل شما در صدد انقضاء است.  <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.",
     "topline_expired_certificate": "گواهینامه اِس اِس اِل شما منقضی شده است. <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.",
+    "form_error_port_range": "مقدار پورت را در محدوده 80-65535 وارد کنید",
     "form_error_port_unsafe": "این پورت غیر ایمن است",
+    "form_error_equal": "نباید برابر باشد",
     "form_error_password": "رمزعبور تطبیق ندارد",
     "reset_settings": "ریست تنظیمات",
     "update_announcement": "AdGuard Home {{version}} در دسترس است! <0>اینجا کلیک</0> کنید برای اطلاعات بیشتر.",
+    "setup_guide": "راهنمای راه اندازی",
     "dns_addresses": "آدرس های DNS",
     "dns_start": "سرور DNS در حال شروع است",
     "dns_status_error": "خطا در دریافت وضعیت DNS",
@@ -314,6 +373,7 @@
     "dns_providers": "در اینجا یک <0>لیست از سرویس های ارائه دهنده DNS</0> برای انتخاب هست.",
     "update_now": "حالا بروز رسانی",
     "update_failed": "بروز رسانی خودکار موفق نشد. لطفا <a>مراحل را دنبال کرده</a> تا بطور دستی بروز رسانی کنید.",
+    "manual_update": "لطفا<a>این مراحل را دنبال کنید</a>تا به طور دستی بروزرسانی نمایید.",
     "processing_update": "منتظر بمانید،AdGuard Home در حال بروز رسانی است",
     "clients_title": "کلاینت ها",
     "clients_desc": "پیکربندی دستگاه های متصل شده به AdGuard Home",
@@ -327,7 +387,9 @@
     "client_edit": "ویرایش کلاینت",
     "client_identifier": "احراز با",
     "ip_address": "آدرس آی پی",
+    "client_identifier_desc": "کلاینت میتواند با آدرس آی پی یا آدرس مَک احراز شود. لطفا توجه کنید،که استفاده از مَک بعنوان عامل احراز زمانی امکان دارد که  AdGuard Home نیز <0>سرور DHCP </0> باشد",
     "form_enter_ip": "آی پی را وارد کنید",
+    "form_enter_subnet_ip": "یک آدرس آی پی در زیر شبکه \"{{cidr}}\" وارد کنید",
     "form_enter_mac": "مَک را وارد کنید",
     "form_enter_id": "خطای احرازکننده",
     "form_add_id": "افزودن احرازکننده",
@@ -349,6 +411,7 @@
     "access_disallowed_title": "کلاینت های غیرمجاز",
     "access_disallowed_desc": "یک لیست از CIDR یا آدرس های IP.اگر پیکربندی شود،AdGuard Home درخواست ها را از این آدرس های IP نمی پذیرد.",
     "access_blocked_title": "دامنه های مسدود شده",
+    "access_blocked_desc": "این را با فیلتر ها به اشتباه نگیرید.AdGuard Home  جستار DNS را با این دامنه ها در جستار سوال ها نمی پذیرد.",
     "access_settings_saved": "تنظیمات دسترسی با موفقیت ذخیره شد",
     "updates_checked": "بروز رسانی با موفقیت بررسی شد",
     "updates_version_equal": "AdGuard Home بروز است",
@@ -356,6 +419,7 @@
     "dns_privacy": "حریم خصوصی DNS",
     "setup_dns_privacy_1": "<0>DNS-over-TLS:</0> استفاده از<1>{{address}}</1> .",
     "setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> استفاده از <1>{{address}}</1> .",
+    "setup_dns_privacy_3": "<0>لطفا توجه کنید که پروتکل های رمزگذاری شده DNS فقط در آندروئید 9 پشتیبانی می شود. پس برای سیستم عامل های دیگر نیاز است که برنامه دیگری نصب کنید.</0><0>در اینجا میتوانید لیست نرم افزارهای قابل استفاده را ببینید.</0>",
     "setup_dns_privacy_android_1": "آندروئید 9 بطور پیش فرض از  DNS-over-TLS پشتیبانی می کند. برای پیکربندی آن، بروید به تنظیمات → شبکه & اینترنت → پیشرفته → DNS خصوصی و نام دامنه را آنجا وارد کنید.",
     "setup_dns_privacy_android_2": "<0>AdGuard for Android</0> پشتیبانی از <1>DNS-over-HTTPS</1> و <1>DNS-over-TLS</1>.",
     "setup_dns_privacy_android_3": "<0>Intra</0> قابلیت <1>DNS-over-HTTPS</1> را به آندروئید اضافه می کند.",
@@ -410,10 +474,11 @@
     "statistics_configuration": "پیکربندی آمارها",
     "statistics_retention": "مدت حفظ آمارها",
     "statistics_retention_desc": "اگر مقدار فاصله را کاهش دهید،برخی داده ها از بین خواهد رفت",
-    "statistics_clear": "بازنشانی آمار",
+    "statistics_clear": " پاکسازی آمار",
     "statistics_clear_confirm": "آیا واقعا میخواهید آمار را پاک کنید؟",
     "statistics_retention_confirm": "آیا واقعا میخواهید مدت حفظ آمار را تغییر دهید؟ اگر فاصله را کاهش دهید، برخی داده ها حذف میشود",
     "statistics_cleared": "آمارها با موفقیت حذف شد",
+    "statistics_enable": "فعالسازی داده های آماری",
     "interval_hours": "{{count}} ساعت",
     "interval_hours_plural": "{{count}} ساعت",
     "filters_configuration": "پیکربندی فیلترها",
@@ -440,7 +505,10 @@
     "domain_desc": "نامه دامنه یا علامت تطبیقی را برای بازنویسی وارد کنید.",
     "example_rewrite_domain": "فقط بازنویسی پاسخ برای این دامنه.",
     "example_rewrite_wildcard": "بازنویسی پاسخ ها برای همه زیردامنه های <0>example.org</0>.",
+    "disable_ipv6": "غیرفعالسازی IPv6",
+    "disable_ipv6_desc": "اگر این ویژگی فعال شده، همه جستارهای DNS برای آدرس های IPv6 (نوع AAAA) رها میشود.",
     "fastest_addr": "سریعترین آدرس آی پی",
+    "fastest_addr_desc": "جستار همه سرورهای DNS و بازگرداندن سریعترین آدرس IP از میان همه پاسخ ها",
     "autofix_warning_text": "اگر روی \"تعمیر\" کلیک کنید، AdGuardHome سیستم شما را برای استفاده از DNS سرور AdGuardHome پیکربندی می کند.",
     "autofix_warning_list": "این وظایف را اجرا میکند: <0>غیرفعالسازی DNSStubListener سیستم</0> <0>تنظیم آدرس DNS 127.0.0.1</0> سرور به <0>جایگزینی لینک نمادی هدف /etc/resolv.conf به/run/systemd/resolve/resolv.conf</0> <0>توقف DNSStubListener (بارگیری مجدد سرویس systemd-resolved)</0>",
     "autofix_warning_result": "در نتیجه همه درخواست های DNS از سیستم شما بطور پیش فرض با AdGuardHome پردازش خواهد شد.",
@@ -467,6 +535,7 @@
     "set_static_ip": "تنظیم یک آدرس آی پی ثابت",
     "install_static_ok": "خبر خوب! آدرس آی پی ثابت از قبل پیکربندی شده است",
     "install_static_error": "AdGuard Home نمیتواند رابط این شبکه را خودکار پیکربندی کند. لطفا دستورالعمل چگونگی انجام دستی آن را مطالعه کنید.",
+    "install_static_configure": "ما تشخیص دادیم از آدرس آی پی پویا استفاده شده است — <0>{{ip}}</0>. آیا میخواهید از آن بعنوان آدرس ثابت استفاده کنید؟",
     "confirm_static_ip": "AdGuard Home {{ip}} بعنوان آدرس آی پی ثابت شما پیکربندی می کند. ادامه میدهید؟",
     "list_updated": "{{count}} لیست بروز رسانی شد",
     "list_updated_plural": "{{count}} لیست بروز رسانی شد",
@@ -477,16 +546,21 @@
     "show_whitelisted_responses": "لیست سفید",
     "show_processed_responses": "پردازش شده",
     "blocked_safebrowsing": "بستن وب گردی اَمن",
-    "blocked_adult_websites": "مسدود شده با نظارت والدین",
+    "blocked_adult_websites": "وبسایت غیراخلاقی مسدود شده",
     "blocked_threats": "تهدیدات مسدود شده",
     "allowed": "اجازه داده شده",
     "filtered": "فیلتر شده",
     "rewritten": "بازنویسی شده",
-    "safe_search": "فعالسازی جستجوی اَمن",
+    "safe_search": "جستجوی اَمن",
     "blocklist": "لیست سیاه",
     "milliseconds_abbreviation": "هـ ثـ",
+    "cache_optimistic": "حالت ویژه پردازش",
+    "cache_optimistic_desc": "AdGuard Home را وادار می کند که از سمت حافظه پنهان پاسخ دهد حتی وقتی که موارد وارد شده منقضی شده باشد و همچنین سعی بر تازه کردن آنها می کند.",
     "filter_category_general": "General",
     "filter_category_security": "مسدودسازی بدافزار و فیشینگ",
     "filter_category_other": "ساير",
-    "parental_control": "نظارت والدین"
+    "use_saved_key": "از کلید ذخیره شده قبلی استفاده کنید",
+    "parental_control": "نظارت والدین",
+    "safe_browsing": "وب گردی اَمن",
+    "form_error_password_length": "رمزعبور باید حداقل {{value}} کاراکتر باشد."
 }
diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json
index 990f4b09..7fc19d73 100644
--- a/client/src/__locales/nl.json
+++ b/client/src/__locales/nl.json
@@ -126,7 +126,7 @@
     "top_blocked_domains": "Top geblokkeerde domeinen",
     "top_clients": "Top gebruikers",
     "no_clients_found": "Geen gebruikers gevonden",
-    "general_statistics": "Generieke statistieken",
+    "general_statistics": "Algemene statistieken",
     "number_of_dns_query_days": "Aantal verwerkte DNS aanvragen van de laatste {{count}} dag",
     "number_of_dns_query_days_plural": "Aantal verwerkte DNS aanvragen van de laatste {{count}} dagen",
     "number_of_dns_query_24_hours": "Aantal verwerkte DNS aanvragen van de laatste 24 uur",
@@ -137,7 +137,7 @@
     "number_of_dns_query_to_safe_search": "Aantal DNS aanvragen in zoekmachines dmv geforceerd veilig zoeken",
     "average_processing_time": "Gemiddelde procestijd",
     "average_processing_time_hint": "Gemiddelde verwerkingstijd in milliseconden van een DNS aanvraag",
-    "block_domain_use_filters_and_hosts": "Blokkeerd domeinen dmv filters en host bestanden",
+    "block_domain_use_filters_and_hosts": "Domeinen blokkeren d.m.v. filters en host-bestanden",
     "filters_block_toggle_hint": "Je kan blokkeringsregels toevoegen in de <a>Filters</a> instellingen.",
     "use_adguard_browsing_sec": "Gebruik AdGuardBrowsing Security web service",
     "use_adguard_browsing_sec_hint": "AdGuard Home controleert of het domein in de blokkeerlijst voorkomt dmv Browsing Security web service. Dit gebeurt dmv een privacy vriendelijk API verzoek:een korte prefix van de domein naam met SHA256 hash wordt verzonden naar de server.",
@@ -146,15 +146,15 @@
     "enforce_safe_search": "Veilig zoeken gebruiken",
     "enforce_save_search_hint": "AdGuard Home kan veilig zoeken forceren voor de volgende zoekmachines: Google, Youtube, Bing, en DuckDuckGo, Yandex, Pixabay.",
     "no_servers_specified": "Geen servers gespecificeerd",
-    "general_settings": "Generieke instellingen",
-    "dns_settings": "DNS Instellingen",
+    "general_settings": "Algemene instellingen",
+    "dns_settings": "DNS instellingen",
     "dns_blocklists": "DNS blokkeerlijsten",
     "dns_allowlists": "DNS toestemmingslijsten",
     "dns_blocklists_desc": "AdGuard Home zal domeinen blokkeren die voorkomen in de blokkeerlijsten.",
     "dns_allowlists_desc": "Domeinen in de DNS toestemmingslijsten worden toegestaan zelfs al komen ze voor in de blokkeerlijsten.",
     "custom_filtering_rules": "Aangepaste filter regels",
-    "encryption_settings": "Encryptie Instellingen",
-    "dhcp_settings": "DHCP Instellingen",
+    "encryption_settings": "Encryptie instellingen",
+    "dhcp_settings": "DHCP instellingen",
     "upstream_dns": "Upstream DNS-servers",
     "upstream_dns_help": "Een server-adres per regel invoeren. <a>Meer weten</a> over het configureren van upstream DNS-servers.",
     "upstream_dns_configured_in_file": "Geconfigureerd in {{path}}",
@@ -225,8 +225,8 @@
     "block": "Blokkeren",
     "disallow_this_client": "Toepassing/systeem niet toelaten",
     "allow_this_client": "Toepassing/systeem toelaten",
-    "block_for_this_client_only": "Alleen voor deze toepassing/dit systeem blokkeren",
-    "unblock_for_this_client_only": "Alleen voor deze toepassing/dit systeem deblokkeren",
+    "block_for_this_client_only": "Alleen voor deze cliënt blokkeren",
+    "unblock_for_this_client_only": "Alleen voor deze cliënt deblokkeren",
     "time_table_header": "Tijd",
     "date": "Datum",
     "domain_name_table_header": "Domein naam",
@@ -403,7 +403,7 @@
     "down": "Uitgeschakeld",
     "fix": "Los op",
     "dns_providers": "hier is een <0>lijst of gekende DNS providers</0> waarvan je kan kiezen.",
-    "update_now": "Update nu",
+    "update_now": "Nu bijwerken",
     "update_failed": "Automatisch bijwerken is mislukt. <a>Volg deze stappen</a> om handmatig bij te werken.",
     "manual_update": "<a>Volg deze stappen</a> om handmatig bij te werken.",
     "processing_update": "Even geduld, AdGuard Home wordt bijgewerkt",
@@ -624,7 +624,7 @@
     "original_response": "Oorspronkelijke reactie",
     "click_to_view_queries": "Klik om queries te bekijken",
     "port_53_faq_link": "Poort 53 wordt vaak gebruikt door services als DNSStubListener- of de systeem DNS-resolver. Lees a.u.b. <0>deze instructie</0> hoe dit is op te lossen.",
-    "adg_will_drop_dns_queries": "AdGuard Home zal alle DNS verzoeken van deze toepassing/dit systeem negeren.",
+    "adg_will_drop_dns_queries": "AdGuard Home zal alle DNS-verzoeken van deze cliënt laten vervallen.",
     "filter_allowlist": "WAARSCHUWING: Deze actie zal ook de regel \"{{disallowed_rule}}\" uitsluiten van de lijst met toegestane clients.",
     "last_rule_in_allowlist": "Kan deze client niet weigeren omdat het uitsluiten van de regel \"{{disallowed_rule}}\" de lijst \"Toegestane clients\" zal UITSCHAKELEN.",
     "use_saved_key": "De eerder opgeslagen sleutel gebruiken",
diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json
index b8f8bc63..0259d267 100644
--- a/client/src/__locales/no.json
+++ b/client/src/__locales/no.json
@@ -10,6 +10,10 @@
     "bootstrap_dns_desc": "Bootstrap-DNS-tjenere brukes til å oppklare IP-adressene til DoH/DoT-oppklarerene som du har valgt som oppstrømstjenere.",
     "local_ptr_title": "Private DNS-tjenere",
     "local_ptr_desc": "DNS-tjenerne som AdGuard Home bruker for lokale PTR-spørringer. Disse tjenerne brukes til å løse vertsnavnene til klienter med private IP-adresser, for eksempel \"192.168.12.34\", ved bruk av omvendt DNS. Hvis det ikke er angitt, bruker AdGuard Home adressene til standard-DNS-løserne til operativsystemet ditt, bortsett fra adressene til selve AdGuard Home.",
+    "local_ptr_default_resolver": "Som standard, bruker AdGuard Home følgende revers-DNS-oppletere: {{ip}}.",
+    "local_ptr_no_default_resolver": "AdGuard Home klarte ikke å finne egnede private revers-DNS-oppletere for dette systemet.",
+    "local_ptr_placeholder": "Skriv inn én tjeneradresse per linje",
+    "resolve_clients_title": "Skru på revers-oppleting av klienters IP-adresser",
     "use_private_ptr_resolvers_title": "Bruk private omvendte DNS-løsere",
     "check_dhcp_servers": "Se etter DHCP-tjenere",
     "save_config": "Lagre oppsettet",
@@ -21,20 +25,24 @@
     "dhcp_description": "Dersom ruteren din ikke har DHCP-innstillinger, kan du bruke AdGuard Home sin egen innebygde DHCP-tjener.",
     "dhcp_enable": "Skru på DHCP-tjeneren",
     "dhcp_disable": "Skru av DHCP-tjeneren",
+    "dhcp_not_found": "Det er trygt å skru på den innebygde DHCP-tjeneren - vi kunne ikke finne noen aktive DHCP-tjenere i nettverket. Men vi oppfordrer deg til å dobbeltsjekke manuelt, siden vår automatiske test ikke gir 100% sikre svar ennå.",
     "dhcp_found": "En aktiv DHCP-tjener ble oppdaget i nettverket. Det er ikke trygt å bruke den innebygde DHCP-tjeneren.",
     "dhcp_leases": "DHCP-leieavtaler",
     "dhcp_static_leases": "Statiske DHCP-leieavtaler",
     "dhcp_leases_not_found": "Ingen DHCP-leieavtaler ble funnet",
+    "dhcp_config_saved": "Lagret DHCP-tjeneroppsettet",
     "dhcp_ipv4_settings": "DHCP IPv4-innstillinger",
     "dhcp_ipv6_settings": "DHCP IPv6-innstillinger",
     "form_error_required": "Påkrevd felt",
     "form_error_ip4_format": "Ugyldig IPv4-format",
     "form_error_ip6_format": "Ugyldig IPv6-format",
-    "form_error_ip_format": "Ugyldig IP-adresse",
+    "form_error_ip_format": "Ugyldig IPv4-format",
     "form_error_mac_format": "Ugyldig MAC-format",
     "form_error_client_id_format": "Ugyldig ID-klientformat",
     "form_error_server_name": "Ugyldig tjenernavn",
+    "form_error_subnet": "Undernettet «{{cidr}}» inneholder ikke IP-adressen «{{ip}}»",
     "form_error_positive": "Må være høyere enn 0",
+    "greater_range_start_error": "Må være høyere enn rekkeviddens start",
     "dhcp_form_gateway_input": "Gateway-IP",
     "dhcp_form_subnet_input": "Nettverksmaske",
     "dhcp_form_range_title": "Spennvidden til IP-adressene",
@@ -48,11 +56,16 @@
     "ip": "IP-adresse",
     "dhcp_table_hostname": "Vertsnavn",
     "dhcp_table_expires": "Utløper",
+    "dhcp_warning": "Hvis du vil aktivere DHCP-tjeneren likevel, så sørg for at det ikke er noen andre aktive DHCP-tjenere i nettverket ditt. Ellers kan det knekke internettilgangen til tilkoblede enheter!",
+    "dhcp_error": "Vi klarte ikke å fastslå om det er en annen DHCP-tjener i nettverket ditt eller ikke.",
+    "dhcp_static_ip_error": "For å kunne bruke DHCP-tjeneren, må det være satt en statisk IP-adresse. Vi klarte ikke å finne ut om dette nettverksgrensesnittet har blitt satt opp med en statisk IP-adresse. Vennligst sett opp en statisk IP-adresse manuelt.",
+    "dhcp_dynamic_ip_found": "Systemet ditt bruker et oppsett med dynamisk IP-adresse for grensesnittet <0>{{interfaceName}}</0>. For å kunne bruke DHCP-tjeneren, må en statisk IP-adresse ha blitt satt opp. Din nåværende IP-adresse er <0>{{ipAddress}}</0>. Vi vil automatisk gjøre denne IP-adressen statisk hvis du trykker på «Skru på DHCP»-knappen.",
     "dhcp_lease_added": "Den statiske leieavtalen «{{key}}» ble vellykket lagt til",
     "dhcp_lease_deleted": "Den statiske leieavtalen «{{key}}» ble vellykket lagt slettet",
     "dhcp_new_static_lease": "Ny statisk leieavtale",
     "dhcp_static_leases_not_found": "Ingen statiske DHCP-leieavtaler ble funnet",
     "dhcp_add_static_lease": "Legg til statisk leieavtale",
+    "dhcp_reset": "Er du sikker på at du vil tilbakestille DHCP-oppsettet?",
     "country": "Land",
     "city": "By",
     "delete_confirm": "Er du sikker på at du vil slette «{{key}}»?",
@@ -93,13 +106,22 @@
     "for_last_24_hours": "de siste 24 timene",
     "for_last_days": "det siste døgnet",
     "for_last_days_plural": "de siste {{count}} dagene",
+    "stats_disabled": "Statistikkene har blitt skrudd av. Du kan skru den på fra <0>innstillingssiden</0>.",
+    "stats_disabled_short": "Statistikkene har blitt skrudd av",
     "no_domains_found": "Ingen domener ble funnet",
     "requests_count": "Antall forespørsler",
     "top_blocked_domains": "Mest blokkerte domener",
     "top_clients": "Vanligste klienter",
     "no_clients_found": "Ingen klienter ble funnet",
     "general_statistics": "Generelle statistikker",
+    "number_of_dns_query_days": "Antall DNS-forespørsler som ble behandlet det siste døgnet",
+    "number_of_dns_query_days_plural": "Antall DNS-forespørsler som ble behandlet de siste {{count}} dagene",
+    "number_of_dns_query_24_hours": "Antall DNS-forespørsler som ble behandlet de siste 24 timene",
+    "number_of_dns_query_blocked_24_hours": "Antall DNS-forespørsler som ble blokkert av adblock-filtre, hosts-lister, og domene-lister",
+    "number_of_dns_query_blocked_24_hours_by_sec": "Antall DNS-forespørsler som ble blokkert av AdGuard sin nettlesersikkerhetsmodul",
+    "number_of_dns_query_blocked_24_hours_adult": "Antall voksennettsteder som ble blokkert",
     "enforced_save_search": "Påtvungede barnevennlige søk",
+    "number_of_dns_query_to_safe_search": "Antall DNS-forespørsler til søkemotorer der \"Safe Search\" ble fremtvunget",
     "average_processing_time": "Gjennomsnittlig behandlingstid",
     "average_processing_time_hint": "Gjennomsnittstid for behandling av DNS-forespørsler i millisekunder",
     "block_domain_use_filters_and_hosts": "Blokker domener ved hjelp av filtre, «hosts»-filer, og rå domener",
@@ -181,6 +203,7 @@
     "example_upstream_sdns": "du kan bruke <0>DNS-stempler</0> med <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-behandlere",
     "example_upstream_tcp": "vanlig DNS (over TCP)",
     "all_lists_up_to_date_toast": "Alle listene er allerede oppdatert",
+    "updated_upstream_dns_toast": "Oppdaterte oppstrøms-DNS-tjenerne",
     "dns_test_ok_toast": "De spesifiserte DNS-tjenerne fungerer riktig",
     "dns_test_not_ok_toast": "Tjeneren «{{key}}» kunne ikke brukes, vennligst dobbeltsjekk at du har skrevet den riktig",
     "unblock": "Tillat",
@@ -241,13 +264,16 @@
     "dns_over_quic": "DNS-over-QUIC",
     "client_id": "Klient-ID",
     "client_id_placeholder": "Skriv inn klient-ID",
+    "client_id_desc": "Forskjellige klienter kan identifiserer med en spesiell klient-ID. <a>Her</a> kan du lære mer om hvordan man identifiserer klienter.",
     "download_mobileconfig_doh": "Last ned .mobileconfig for DNS-over-HTTPS",
     "download_mobileconfig_dot": "Last ned .mobileconfig for DNS-over-TLS",
+    "download_mobileconfig": "Last ned oppsettsfil",
     "plain_dns": "Ordinær DNS",
     "form_enter_rate_limit": "Skriv inn forespørselsfrekvensgrense",
     "rate_limit": "Forespørselsfrekvensgrense",
     "edns_enable": "Aktiver EDNS-klientundernett",
     "edns_cs_desc": "Hvis det er skrudd på, vil AdGuard Home sende klientenes undernett til DNS-tjenerne.",
+    "rate_limit_desc": "Antallet forespørsler per sekund som én enkelt klient har lov til å be om (0: ubegrenset)",
     "blocking_ipv4_desc": "IP-adressen som det skal svares med for blokkerte A-forespørsler",
     "blocking_ipv6_desc": "IP-adressen som det skal svares med for blokkerte AAAA-forespørsler",
     "blocking_mode_default": "Standard: Svar med null-IP-adresse (0.0.0.0 for A; :: for AAAA) når den blokkeres av adblock-aktige oppføringer; svar med IP-adressen som er spesifisert i oppføringen når den blokkeres av /etc/hosts-typeoppføringer",
@@ -289,8 +315,10 @@
     "install_devices_router": "Ruter",
     "install_devices_router_desc": "Dette oppsettet vil automatisk dekke alle enhetene som er koblet til hjemmeruteren din, og du vil ikke måtte sette opp hver av dem manuelt.",
     "install_devices_address": "AdGuard Home-DNS-tjeneren lytter til de følgende adressene",
+    "install_devices_router_list_1": "Åpne innstillingene til ruteren din. Vanligvis kan du få tilgang til den på nettleseren din gjennom en URL (f.eks. http://192.168.0.1/ eller http://192.168.1.1/). Du kan bli spurt om å skrive inn passordet ditt. Hvis du ikke husker det, kan du som oftest tilbakestille passordet ditt ved å trykke på knapp på selve ruteren. Noen rutere krever et spesifikt program, som i så fall er ment å allerede ha blitt installert på din PC/mobil.",
     "install_devices_router_list_2": "Finn DHCP-/DNS-innstillingene. Se etter DNS-bokstavene ved siden av et felt som tillater to eller tre sett med sifre, som hver er delt opp i fire grupper på 1-3 sifre.",
     "install_devices_router_list_3": "Skriv inn din AdGuard Home-tjeners adresser her.",
+    "install_devices_router_list_4": "På noen rutertyper, f.eks. Altibox sine hjemmesentraler, kan man ikke velge en selvvalgt DNS-tjener. I så fall kan det hjelpe på saken om du setter opp AdGuard Home som en <0>DHCP-tjener</0>. Alternativt, burde du se i bruksanvisningen til din spesifikke rutermodell om hvordan man tilpasser DNS-tjenerne.",
     "install_devices_windows_list_1": "Åpne «Kontrollpanel» gjennom Start-menyen eller et Windows-søk.",
     "install_devices_windows_list_2": "Gå til «Nettverk og internett»-kategorien, og så til «Nettverks- og delingssenter».",
     "install_devices_windows_list_3": "På den venstre siden av skjermen, finn «Endre innstillinger for nettverkskort» og klikk på den.",
@@ -316,6 +344,7 @@
     "install_saved": "Lagringen var vellykket",
     "encryption_title": "Kryptering",
     "encryption_desc": "Krypteringsstøtte (HTTPS/TLS) for både DNS og admin-nettgrensesnittet",
+    "encryption_config_saved": "Krypteringsoppsettet ble lagret",
     "encryption_server": "Tjenerens navn",
     "encryption_server_enter": "Skriv inn domenenavnet ditt",
     "encryption_server_desc": "For å kunne bruke HTTPS, må du skrive inn tjenernavnet som samsvarer med ditt SSL-sertifikat eller jokertegnsertifikat. Hvis feltet er tomt, vil den akseptere TLS-tilkoblinger til ethvert domene.",
@@ -346,7 +375,9 @@
     "encryption_reset": "Er du sikker på at du vil tilbakestille krypteringsinnstillingene?",
     "topline_expiring_certificate": "Ditt SSL-sertifikat er i ferd med å utløpe. Oppdater <0>Krypteringsinnstillinger</0>.",
     "topline_expired_certificate": "SSL-sertifikatet har utløpt. Oppdater <0>Krypteringsinnstillinger</0>.",
+    "form_error_port_range": "Skriv inn et portnummer i området 80-65535",
     "form_error_port_unsafe": "Denne porten er ikke trygg",
+    "form_error_equal": "Burde ikke være de samme",
     "form_error_password": "Passordet samsvarer ikke",
     "reset_settings": "Tilbakestill innstillinger",
     "update_announcement": "AdGuard Home {{version}} er nå tilgjengelig! <0>Klikk her</0> for mere informasjon.",
@@ -374,6 +405,7 @@
     "ip_address": "IP-adresse",
     "client_identifier_desc": "Klienter kan bli identifisert gjennom IP-adressen, CIDR, MAC-adressen, eller en spesiell klient-ID (kan også brukes for DoT/DoH/DoQ). <0>Her</0> kan du lære mer om å identifisere klienter.",
     "form_enter_ip": "Skriv inn IP",
+    "form_enter_subnet_ip": "Skriv inn en IP-adresse i undernettet «{{cidr}}»",
     "form_enter_mac": "Skriv inn MAC",
     "form_enter_id": "Skriv inn identifikator",
     "form_add_id": "Legg til identifikator",
@@ -416,6 +448,7 @@
     "setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> støtter <1>DNS-over-HTTPS</1>.",
     "setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> støtter <1>DNS-over-HTTPS</1>.",
     "setup_dns_privacy_other_5": "Du finner flere implementeringer <0>her</0> og <1>her</1>.",
+    "setup_dns_privacy_ioc_mac": "iOS- og macOS-oppsett",
     "setup_dns_notice": "For å benytte <1>DNS-over-HTTPS</1> eller <1>DNS-over-TLS</1>, må du <0>sette opp Kryptering</0> i AdGuard Home-innstillingene.",
     "rewrite_added": "DNS-omdirigeringen for «{{key}}» ble vellykket lagt til",
     "rewrite_deleted": "DNS-omdirigeringen for «{{key}}» ble vellykket slettet",
@@ -453,6 +486,7 @@
     "interval_days": "{{count}} dag",
     "interval_days_plural": "{{count}} dager",
     "domain": "Domene",
+    "punycode": "Punycode",
     "answer": "Svar",
     "filter_added_successfully": "Filteret har blitt vellykket lagt til",
     "filter_removed_successfully": "Listen ble vellykket fjernet",
@@ -499,6 +533,7 @@
     "disable_ipv6": "Skru av IPv6",
     "disable_ipv6_desc": "Hvis dette er skrudd på, vil alle DNS-forespørslene til IPv6-adresser (AAAA-type) bli droppet.",
     "fastest_addr": "Raskeste IP-adresse",
+    "fastest_addr_desc": "Forespør alle DNS-tjenerne og hent den raskeste IP-adressen blant alle svarene. Dette vil gjøre DNS-forespørslene tregere, siden vi må vente på svar fra alle DNS-tjenerne, men det vil forbedre tilkoblingen generelt.",
     "autofix_warning_text": "Hvis du klikker på «Fiks», vil AdGuard Home sette opp systemet ditt til å bruke 'AdGuard Home'-DNS-tjeneren.",
     "autofix_warning_list": "Den vil utføre disse handlingene: <0>Skru av systemets DNSStubListener</0> <0>Sette DNS-tjeneradressen til 127.0.0.1</0> <0>Bytte ut det symbolske lenkemålet til /etc/resolv.conf med /run/systemd/resolve/resolv.conf</0> <0>Stoppe DNSStubListener (gjeninnlast 'systemd-resolved'-tjenesten)</0>",
     "autofix_warning_result": "Som følge av det vil alle DNS-forespørsler fra systemet ditt bli behandlet av AdGuard Home som standard.",
@@ -528,6 +563,7 @@
     "set_static_ip": "Velg en statisk IP-adresse",
     "install_static_ok": "Gode nyheter! Den statiske IP-adressen er allerede satt opp",
     "install_static_error": "AdGuard Home kan ikke sette opp automatisk i dette nettverksgrensesnitt. Vennligst let opp anvisningen for hvordan man gjør det manuellt.",
+    "install_static_configure": "Vi har oppdaget at det brukes en dynamisk IP-adresse — <0>{{ip}}</0>. Vil du bruke det som din statiske adresse?",
     "confirm_static_ip": "AdGuard Home vil sette opp {{ip}} til å bli din statiske IP-adresse. Vil du fortsette?",
     "list_updated": "{{count}} liste oppdatert",
     "list_updated_plural": "{{count}} lister oppdatert",
@@ -539,12 +575,12 @@
     "show_whitelisted_responses": "Hvitelistet",
     "show_processed_responses": "Bearbeidet",
     "blocked_safebrowsing": "Blokkert av barnevennlig nettlesing",
-    "blocked_adult_websites": "Blokkert av foreldrekontroll",
+    "blocked_adult_websites": "Blokkerte voksennettsteder",
     "blocked_threats": "Blokkerte trusler",
     "allowed": "Unntak",
     "filtered": "Filtrert",
     "rewritten": "Omskrevet",
-    "safe_search": "Aktivert sikkert søk",
+    "safe_search": "Trygge søk",
     "blocklist": "Blokkeringsliste",
     "milliseconds_abbreviation": "ms",
     "cache_size": "Mellomlagerstørrelse",
@@ -564,13 +600,16 @@
     "filter_category_regional": "Regional",
     "filter_category_other": "Andre",
     "filter_category_general_desc": "Lister som blokkerer sporing og reklamer på de fleste enheter",
+    "filter_category_security_desc": "Lister som spesialiserer seg på å blokkere skadevare-, phishing- eller svindeldomener",
     "filter_category_regional_desc": "Lister som fokuserer på regionale reklamer og sporingstjenere",
     "filter_category_other_desc": "Andre blokkeringslister",
+    "setup_config_to_enable_dhcp_server": "Oppsett for å skru på DHCP-tjeneren",
     "original_response": "Opprinnelig svar",
     "click_to_view_queries": "Klikk for å vise forespørsler",
     "port_53_faq_link": "Port 53 er ofte opptatt av «DNSStubListener»- eller «systemd-resolved»-tjenestene. Vennligst les <0>denne instruksjonen</0> om hvordan man løser dette.",
     "adg_will_drop_dns_queries": "AdGuard Home vil droppe alle DNS-forespørsler fra denne klienten.",
     "use_saved_key": "Bruk den tidligere lagrede nøkkelen",
     "parental_control": "Foreldrekontroll",
+    "safe_browsing": "Sikker surfing",
     "served_from_cache": "{{value}} <i>(formidlet fra mellomlageret)</i>"
 }
diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json
index 214169fd..d439d0ff 100644
--- a/client/src/__locales/ru.json
+++ b/client/src/__locales/ru.json
@@ -198,7 +198,7 @@
     "enter_valid_allowlist": "Добавьте действующий URL-адрес в белый список.",
     "form_error_url_format": "Неверный формат URL",
     "form_error_url_or_path_format": "Неверный URL или абсолютный путь к списку",
-    "custom_filter_rules": "Пользовательское правило фильтрации",
+    "custom_filter_rules": "Пользовательские правила фильтрации",
     "custom_filter_rules_hint": "Вводите по одному правилу на строчку. Вы можете использовать правила блокировки или синтаксис файлов hosts.",
     "system_host_files": "Системные hosts-файлы",
     "examples_title": "Примеры",
@@ -475,7 +475,7 @@
     "rewrite_applied": "Применено правило перезаписи",
     "rewrite_hosts_applied": "Переписано по правилу файла hosts",
     "dns_rewrites": "Перезапись DNS-запросов",
-    "form_domain": "Введите домен",
+    "form_domain": "Введите имя или маску домена",
     "form_answer": "Введите IP адрес или домен",
     "form_error_domain_format": "Некорректный домен",
     "form_error_answer_format": "Некорректный ответ",
diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json
index 90ddd608..a107e8c7 100644
--- a/client/src/__locales/sv.json
+++ b/client/src/__locales/sv.json
@@ -1,7 +1,7 @@
 {
     "client_settings": "Klientinställningar",
-    "example_upstream_reserved": "Du kan specificera DNS-uppström <0>för en specifik domän</0>",
-    "example_upstream_comment": "Du kan ange en kommentar",
+    "example_upstream_reserved": "uppström <0>för en specifik domän</0>;",
+    "example_upstream_comment": "du kan ange en kommentar.",
     "upstream_parallel": "Använd parallella förfrågningar för att snabba upp dessa genom att fråga alla uppströmsservrar samtidigt.",
     "parallel_requests": "Parallella förfrågningar",
     "load_balancing": "Lastbalansering",
@@ -196,7 +196,7 @@
     "choose_allowlist": "Välj frilistor",
     "enter_valid_blocklist": "Ange en giltig URL till blockeringslistan.",
     "enter_valid_allowlist": "Ange en giltig URL till frilistan.",
-    "form_error_url_format": "Ogiltigt URL format",
+    "form_error_url_format": "Ogiltigt URL-format",
     "form_error_url_or_path_format": "Ogiltig URL eller absolut sökväg till listan",
     "custom_filter_rules": "Egna filterregler",
     "custom_filter_rules_hint": "Skriv en regel per rad. Du kan använda antingen annonsblockeringsregler eller värdfilssyntax.",
@@ -210,11 +210,13 @@
     "example_comment_hash": "# Också en kommentar",
     "example_regex_meaning": "blockera åtkomst till domäner som matchar det angivna uttrycket",
     "example_upstream_regular": "vanlig DNS (över UDP)",
+    "example_upstream_udp": "vanlig DNS (över UDP, värdnamn);",
     "example_upstream_dot": "krypterat <0>DNS-over-TLS</0>",
     "example_upstream_doh": "krypterat <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "krypterat <0>DNS-over-QUIC</0>",
+    "example_upstream_doq": "krypterat <0>DNS-over-QUIC</0> (experimentell);",
     "example_upstream_sdns": "Du kan använda <0>DNS-stamps</0> för <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-resolvers",
     "example_upstream_tcp": "vanlig DNS (över UDP)",
+    "example_upstream_tcp_hostname": "vanlig DNS (över TCP, värdnamn);",
     "all_lists_up_to_date_toast": "Alla listor är redan uppdaterade",
     "updated_upstream_dns_toast": "Sparade uppströms dns-servrar",
     "dns_test_ok_toast": "Angivna DNS servrar fungerar korrekt",
diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json
index 5aa53ec1..74dedd8b 100644
--- a/client/src/__locales/tr.json
+++ b/client/src/__locales/tr.json
@@ -388,7 +388,7 @@
     "encryption_issuer": "Sağlayan",
     "encryption_hostnames": "Ana makine adları",
     "encryption_reset": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?",
-    "topline_expiring_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını</0> güncelleyin.",
+    "topline_expiring_certificate": "SSL sertifikanızın süresi sona üzere. <0>Şifreleme ayarlarını</0> güncelleyin.",
     "topline_expired_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını</0> güncelleyin.",
     "form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin",
     "form_error_port_unsafe": "Güvenli olmayan bağlantı noktası",
@@ -489,9 +489,9 @@
     "blocked_service": "Engellenen hizmet",
     "block_all": "Tümünü engelle",
     "unblock_all": "Tüm engellemeyi kaldır",
-    "encryption_certificate_path": "Sertifika yolu",
+    "encryption_certificate_path": "Sertifika dosya yolu",
     "encryption_private_key_path": "Özel anahtar yolu",
-    "encryption_certificates_source_path": "Sertifika dosyalarının yolunu belirleyin",
+    "encryption_certificates_source_path": "Bir sertifika dosyası yolu ayarlayın",
     "encryption_certificates_source_content": "Sertifika içeriğini yapıştır",
     "encryption_key_source_path": "Özel bir anahtar dosyası belirleyin",
     "encryption_key_source_content": "Özel anahtar içeriğini yapıştır",
diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json
index 8b1fab72..666ddcc3 100644
--- a/client/src/__locales/zh-tw.json
+++ b/client/src/__locales/zh-tw.json
@@ -625,8 +625,8 @@
     "click_to_view_queries": "點擊以檢視查詢",
     "port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。",
     "adg_will_drop_dns_queries": "AdGuard Home 將持續排除來自此用戶端之所有的 DNS 查詢。",
-    "filter_allowlist": "警告:此操作將把 \"{{disallowed_rule}}\" 規則排除在已允許用戶端的清單之外。",
-    "last_rule_in_allowlist": "無法禁止此用戶端,因為排除 “{{disallowed_rule}}” 規則將禁用“已允許用戶端”的清單。",
+    "filter_allowlist": "警告:此動作也將把 \"{{disallowed_rule}}\" 規則排除在已允許的用戶端的清單之外。",
+    "last_rule_in_allowlist": "因為排除 \"{{disallowed_rule}}\" 規則將禁用\"已允許的用戶端\"清單,無法不允許此用戶端。",
     "use_saved_key": "使用該先前已儲存的金鑰",
     "parental_control": "家長控制",
     "safe_browsing": "安全瀏覽",

From 12ee287d0b25cc1fb498e54c4484c995613e9bcb Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 19 Apr 2022 15:01:49 +0300
Subject: [PATCH 040/143] Pull request: 3157 excessive ptrs

Merge in DNS/adguard-home from 3157-excessive-ptrs to master

Updates #3157.

Squashed commit of the following:

commit 6803988240dca2f147bb80a5b3f78d7749d2fa14
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 14:50:01 2022 +0300

    aghnet: and again

commit 1a7f4d1dbc8fd4d3ae620349917526a75fa71b47
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 14:49:20 2022 +0300

    aghnet: docs again

commit d88da1fc7135f3cd03aff10b02d9957c8ffdfd30
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 14:47:36 2022 +0300

    aghnet: imp docs

commit c45dbc7800e882c6c4110aab640c32b03046f89a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 14:41:19 2022 +0300

    aghnet: keep alphabetical order

commit b61781785d096ef43f60fb4f1905a4ed3cdf7c68
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 13:50:56 2022 +0300

    aghnet: imp code quality

commit 578dbd71ed2f2089c69343d7d4bf8bbc29150ace
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 12 17:02:38 2022 +0300

    aghnet: imp arp container
---
 CHANGELOG.md                                  |  5 +++++
 internal/aghnet/arpdb_bsd.go                  | 13 +++++++++----
 internal/aghnet/arpdb_linux.go                | 19 ++++++++++++-------
 internal/aghnet/arpdb_openbsd.go              | 15 ++++++++++-----
 internal/aghnet/arpdb_windows.go              |  2 +-
 internal/dnsforward/dns.go                    |  1 -
 internal/dnsforward/recursiondetector_test.go | 14 ++++----------
 internal/home/rdns.go                         | 11 +++++------
 8 files changed, 46 insertions(+), 34 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a80b4ea1..9d82fb57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -87,10 +87,15 @@ In this release, the schema version has changed from 12 to 13.
 
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
 
+### Fixed
+
+- ARP tables refreshing process causing excessive PTR requests ([#3157]).
+
 [#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
 [#3142]: https://github.com/AdguardTeam/AdGuardHome/issues/3142
+[#3157]: https://github.com/AdguardTeam/AdGuardHome/issues/3157
 [#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
 [#3381]: https://github.com/AdguardTeam/AdGuardHome/issues/3381
 [#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
diff --git a/internal/aghnet/arpdb_bsd.go b/internal/aghnet/arpdb_bsd.go
index a82da76c..26ac7758 100644
--- a/internal/aghnet/arpdb_bsd.go
+++ b/internal/aghnet/arpdb_bsd.go
@@ -13,19 +13,24 @@ import (
 	"github.com/AdguardTeam/golibs/netutil"
 )
 
-func newARPDB() *cmdARPDB {
+func newARPDB() (arp *cmdARPDB) {
 	return &cmdARPDB{
 		parse: parseArpA,
 		ns: &neighs{
 			mu: &sync.RWMutex{},
 			ns: make([]Neighbor, 0),
 		},
-		cmd:  "arp",
-		args: []string{"-a"},
+		cmd: "arp",
+		// Use -n flag to avoid resolving the hostnames of the neighbors.  By
+		// default ARP attempts to resolve the hostnames via DNS.  See man 8
+		// arp.
+		//
+		// See also https://github.com/AdguardTeam/AdGuardHome/issues/3157.
+		args: []string{"-a", "-n"},
 	}
 }
 
-// parseArpA parses the output of the "arp -a" command on macOS and FreeBSD.
+// parseArpA parses the output of the "arp -a -n" command on macOS and FreeBSD.
 // The expected input format:
 //
 //   host.name (192.168.0.1) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet]
diff --git a/internal/aghnet/arpdb_linux.go b/internal/aghnet/arpdb_linux.go
index 3d391f29..9cc38906 100644
--- a/internal/aghnet/arpdb_linux.go
+++ b/internal/aghnet/arpdb_linux.go
@@ -38,12 +38,17 @@ func newARPDB() (arp *arpdbs) {
 			fsys:     rootDirFS,
 			filename: "proc/net/arp",
 		},
-		// Then, try "arp -a".
+		// Then, try "arp -a -n".
 		&cmdARPDB{
 			parse: parseF,
 			ns:    ns,
 			cmd:   "arp",
-			args:  []string{"-a"},
+			// Use -n flag to avoid resolving the hostnames of the neighbors.
+			// By default ARP attempts to resolve the hostnames via DNS.  See
+			// man 8 arp.
+			//
+			// See also https://github.com/AdguardTeam/AdGuardHome/issues/3157.
+			args: []string{"-a", "-n"},
 		},
 		// Finally, try "ip neigh".
 		&cmdARPDB{
@@ -109,11 +114,11 @@ func (arp *fsysARPDB) Neighbors() (ns []Neighbor) {
 	return arp.ns.clone()
 }
 
-// parseArpAWrt parses the output of the "arp -a" command on OpenWrt.  The
+// parseArpAWrt parses the output of the "arp -a -n" command on OpenWrt.  The
 // expected input format:
 //
-//   IP address       HW type     Flags       HW address            Mask     Device
-//   192.168.11.98    0x1         0x2         5a:92:df:a9:7e:28     *        wan
+//   IP address     HW type  Flags  HW address         Mask  Device
+//   192.168.11.98  0x1      0x2    5a:92:df:a9:7e:28  *     wan
 //
 func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
 	if !sc.Scan() {
@@ -153,8 +158,8 @@ func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
 	return ns
 }
 
-// parseArpA parses the output of the "arp -a" command on Linux.  The expected
-// input format:
+// parseArpA parses the output of the "arp -a -n" command on Linux.  The
+// expected input format:
 //
 //   hostname (192.168.1.1) at ab:cd:ef:ab:cd:ef [ether] on enp0s3
 //
diff --git a/internal/aghnet/arpdb_openbsd.go b/internal/aghnet/arpdb_openbsd.go
index a00ffa85..d5ec5fea 100644
--- a/internal/aghnet/arpdb_openbsd.go
+++ b/internal/aghnet/arpdb_openbsd.go
@@ -12,20 +12,25 @@ import (
 	"github.com/AdguardTeam/golibs/log"
 )
 
-func newARPDB() *cmdARPDB {
+func newARPDB() (arp *cmdARPDB) {
 	return &cmdARPDB{
 		parse: parseArpA,
 		ns: &neighs{
 			mu: &sync.RWMutex{},
 			ns: make([]Neighbor, 0),
 		},
-		cmd:  "arp",
-		args: []string{"-a"},
+		cmd: "arp",
+		// Use -n flag to avoid resolving the hostnames of the neighbors.  By
+		// default ARP attempts to resolve the hostnames via DNS.  See man 8
+		// arp.
+		//
+		// See also https://github.com/AdguardTeam/AdGuardHome/issues/3157.
+		args: []string{"-a", "-n"},
 	}
 }
 
-// parseArpA parses the output of the "arp -a" command on OpenBSD.  The expected
-// input format:
+// parseArpA parses the output of the "arp -a -n" command on OpenBSD.  The
+// expected input format:
 //
 //   Host        Ethernet Address  Netif Expire    Flags
 //   192.168.1.1 ab:cd:ef:ab:cd:ef   em0 19m59s
diff --git a/internal/aghnet/arpdb_windows.go b/internal/aghnet/arpdb_windows.go
index 2a70125f..8d5431eb 100644
--- a/internal/aghnet/arpdb_windows.go
+++ b/internal/aghnet/arpdb_windows.go
@@ -10,7 +10,7 @@ import (
 	"sync"
 )
 
-func newARPDB() *cmdARPDB {
+func newARPDB() (arp *cmdARPDB) {
 	return &cmdARPDB{
 		parse: parseArpA,
 		ns: &neighs{
diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go
index 63f694bd..d423482a 100644
--- a/internal/dnsforward/dns.go
+++ b/internal/dnsforward/dns.go
@@ -135,7 +135,6 @@ func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
 		pctx.Res = s.genNXDomain(pctx.Req)
 
 		return resultCodeFinish
-
 	}
 
 	return resultCodeSuccess
diff --git a/internal/dnsforward/recursiondetector_test.go b/internal/dnsforward/recursiondetector_test.go
index 7573b668..4edb3a37 100644
--- a/internal/dnsforward/recursiondetector_test.go
+++ b/internal/dnsforward/recursiondetector_test.go
@@ -83,7 +83,7 @@ func TestRecursionDetector_Suspect(t *testing.T) {
 	testCases := []struct {
 		name string
 		msg  dns.Msg
-		want bool
+		want int
 	}{{
 		name: "simple",
 		msg: dns.Msg{
@@ -95,24 +95,18 @@ func TestRecursionDetector_Suspect(t *testing.T) {
 				Qtype: dns.TypeA,
 			}},
 		},
-		want: true,
+		want: 1,
 	}, {
 		name: "unencumbered",
 		msg:  dns.Msg{},
-		want: false,
+		want: 0,
 	}}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Cleanup(rd.clear)
-
 			rd.add(tc.msg)
-
-			if tc.want {
-				assert.Equal(t, 1, rd.recentRequests.Stats().Count)
-			} else {
-				assert.Zero(t, rd.recentRequests.Stats().Count)
-			}
+			assert.Equal(t, tc.want, rd.recentRequests.Stats().Count)
 		})
 	}
 }
diff --git a/internal/home/rdns.go b/internal/home/rdns.go
index 7a2d1bcd..924aff37 100644
--- a/internal/home/rdns.go
+++ b/internal/home/rdns.go
@@ -16,18 +16,17 @@ type RDNS struct {
 	exchanger dnsforward.RDNSExchanger
 	clients   *clientsContainer
 
-	// usePrivate is used to store the state of current private RDNS
-	// resolving settings and to react to it's changes.
+	// usePrivate is used to store the state of current private RDNS resolving
+	// settings and to react to it's changes.
 	usePrivate uint32
 
 	// ipCh used to pass client's IP to rDNS workerLoop.
 	ipCh chan net.IP
 
 	// ipCache caches the IP addresses to be resolved by rDNS.  The resolved
-	// address stays here while it's inside clients.  After leaving clients
-	// the address will be resolved once again.  If the address couldn't be
-	// resolved, cache prevents further attempts to resolve it for some
-	// time.
+	// address stays here while it's inside clients.  After leaving clients the
+	// address will be resolved once again.  If the address couldn't be
+	// resolved, cache prevents further attempts to resolve it for some time.
 	ipCache cache.Cache
 }
 

From 82af43039c78b49908151c09175a5161dd911601 Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Tue, 19 Apr 2022 16:07:57 +0300
Subject: [PATCH 041/143] Pull request: whotracksme tracker links

Merge in DNS/adguard-home from 4416-ui-tracker-href to master

Squashed commit of the following:

commit 979ea82a3b4d2c2a895b81aacd613fb7e5bec586
Merge: 4fe6328b 12ee287d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 19 15:03:13 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4416-ui-tracker-href

commit 4fe6328b276e697a2aa351c6543d2efe6d2dc2e1
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 19 14:08:10 2022 +0200

    whotracksme tracker links
---
 client/src/helpers/trackers/trackers.js       | 19 ++++++++++++++++++-
 .../src/helpers/trackers/whotracksme_web.json |  6 ++++++
 2 files changed, 24 insertions(+), 1 deletion(-)
 create mode 100644 client/src/helpers/trackers/whotracksme_web.json

diff --git a/client/src/helpers/trackers/trackers.js b/client/src/helpers/trackers/trackers.js
index 7af6dcb4..5327d18e 100644
--- a/client/src/helpers/trackers/trackers.js
+++ b/client/src/helpers/trackers/trackers.js
@@ -1,4 +1,5 @@
 import whotracksmeDb from './whotracksme.json';
+import whotracksmeWebsites from './whotracksme_web.json';
 import adguardDb from './adguard.json';
 import { REPOSITORY } from '../constants';
 
@@ -20,6 +21,22 @@ export const sources = {
     ADGUARD: 2,
 };
 
+/**
+ * Gets link to tracker page on whotracks.me.
+ *
+ * @param trackerId
+ * @return {string}
+ */
+const getWhotracksmeUrl = (trackerId) => {
+    const websiteId = whotracksmeWebsites.websites[trackerId];
+    if (websiteId) {
+        // Overrides links to websites.
+        return `https://whotracks.me/websites/${websiteId}.html`;
+    }
+
+    return `https://whotracks.me/trackers/${trackerId}.html`;
+};
+
 /**
  * Gets the source metadata for the specified tracker
  * @param {TrackerData} trackerData tracker data
@@ -33,7 +50,7 @@ export const getSourceData = (trackerData) => {
     if (trackerData.source === sources.WHOTRACKSME) {
         return {
             name: 'Whotracks.me',
-            url: `https://whotracks.me/trackers/${trackerData.id}.html`,
+            url: getWhotracksmeUrl(trackerData.id),
         };
     }
     if (trackerData.source === sources.ADGUARD) {
diff --git a/client/src/helpers/trackers/whotracksme_web.json b/client/src/helpers/trackers/whotracksme_web.json
new file mode 100644
index 00000000..8123fe1b
--- /dev/null
+++ b/client/src/helpers/trackers/whotracksme_web.json
@@ -0,0 +1,6 @@
+{
+  "timeUpdated": "2021-12-19T13:50:00.512Z",
+  "websites": {
+    "netflix": "netflix.com"
+  }
+}

From 0f2a9f262e5cf021ef5e58e65e92da1288e9da13 Mon Sep 17 00:00:00 2001
From: RoboMagus <68224306+RoboMagus@users.noreply.github.com>
Date: Mon, 25 Apr 2022 09:41:31 +0200
Subject: [PATCH 042/143] Add '/blocked_services/services' API

---
 AGHTechDoc.md                           | 13 +++++++++++++
 client/src/actions/services.js          | 15 +++++++++++++++
 client/src/api/Api.js                   |  7 +++++++
 client2/src/lib/apis/blockedServices.ts | 12 ++++++++++++
 internal/filtering/blocked.go           | 16 ++++++++++++++++
 openapi/openapi.yaml                    | 13 +++++++++++++
 6 files changed, 76 insertions(+)

diff --git a/AGHTechDoc.md b/AGHTechDoc.md
index 4758a0da..8f69e5bd 100644
--- a/AGHTechDoc.md
+++ b/AGHTechDoc.md
@@ -1355,6 +1355,19 @@ Internally, all supported services are stored as a map:
 	service name -> list of rules
 
 
+### API: Get blocked services list of available services
+
+Request:
+
+	GET /control/blocked_services/services
+
+Response:
+
+	200 OK
+
+	[ "name1", ... ]
+
+
 ### API: Get blocked services list
 
 Request:
diff --git a/client/src/actions/services.js b/client/src/actions/services.js
index 1c95e3a1..650aa330 100644
--- a/client/src/actions/services.js
+++ b/client/src/actions/services.js
@@ -2,6 +2,21 @@ import { createAction } from 'redux-actions';
 import apiClient from '../api/Api';
 import { addErrorToast, addSuccessToast } from './toasts';
 
+export const getBlockedServicesAvailableServicesRequest = createAction('GET_BLOCKED_SERVICES_AVAILABLE_SERVICES_REQUEST');
+export const getBlockedServicesAvailableServicesFailure = createAction('GET_BLOCKED_SERVICES_AVAILABLE_SERVICES_FAILURE');
+export const getBlockedServicesAvailableServicesSuccess = createAction('GET_BLOCKED_SERVICES_AVAILABLE_SERVICES_SUCCESS');
+
+export const getBlockedServicesAvailableServices = () => async (dispatch) => {
+    dispatch(getBlockedServicesAvailableServicesRequest());
+    try {
+        const data = await apiClient.getBlockedServicesAvailableServices();
+        dispatch(getBlockedServicesAvailableServicesSuccess(data));
+    } catch (error) {
+        dispatch(addErrorToast({ error }));
+        dispatch(getBlockedServicesAvailableServicesFailure());
+    }
+};
+
 export const getBlockedServicesRequest = createAction('GET_BLOCKED_SERVICES_REQUEST');
 export const getBlockedServicesFailure = createAction('GET_BLOCKED_SERVICES_FAILURE');
 export const getBlockedServicesSuccess = createAction('GET_BLOCKED_SERVICES_SUCCESS');
diff --git a/client/src/api/Api.js b/client/src/api/Api.js
index 1f6b2832..625da9e0 100644
--- a/client/src/api/Api.js
+++ b/client/src/api/Api.js
@@ -481,10 +481,17 @@ class Api {
     }
 
     // Blocked services
+    BLOCKED_SERVICES_SERVICES = { path: 'blocked_services/services', method: 'GET' };
+
     BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' };
 
     BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
 
+    getBlockedServicesAvailableServices() {
+        const { path, method } = this.BLOCKED_SERVICES_SERVICES;
+        return this.makeRequest(path, method);
+    }
+    
     getBlockedServices() {
         const { path, method } = this.BLOCKED_SERVICES_LIST;
         return this.makeRequest(path, method);
diff --git a/client2/src/lib/apis/blockedServices.ts b/client2/src/lib/apis/blockedServices.ts
index 381a236d..7daa3344 100644
--- a/client2/src/lib/apis/blockedServices.ts
+++ b/client2/src/lib/apis/blockedServices.ts
@@ -1,6 +1,18 @@
 // This file was autogenerated. Please do not change.
 // All changes will be overwrited on commit.
 export default class BlockedServicesApi {
+    static async blockedServicesAvailableServices(): Promise<string[] | Error> {
+        return await fetch(`/control/blocked_services/services`, {
+            method: 'GET',
+        }).then(async (res) => {
+            if (res.status === 200) {
+                return res.json();
+            } else {
+                return new Error(String(res.status));
+            }
+        })
+    }
+
     static async blockedServicesList(): Promise<string[] | Error> {
         return await fetch(`/control/blocked_services/list`, {
             method: 'GET',
diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index 1d165cf4..bf990de9 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -331,6 +331,21 @@ func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string, global
 	}
 }
 
+func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) {	
+	var list []string
+	for _, v := range serviceRulesArray {
+		list = append(list, s.name)
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	err := json.NewEncoder(w).Encode(list)
+	if err != nil {
+		aghhttp.Error(r, w, http.StatusInternalServerError, "json.Encode: %s", err)
+
+		return
+	}
+}
+
 func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
 	d.confLock.RLock()
 	list := d.Config.BlockedServices
@@ -365,6 +380,7 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
 
 // registerBlockedServicesHandlers - register HTTP handlers
 func (d *DNSFilter) registerBlockedServicesHandlers() {
+	d.Config.HTTPRegister(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesAvailableServices)
 	d.Config.HTTPRegister(http.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList)
 	d.Config.HTTPRegister(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet)
 }
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 8b21a01f..19193151 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -874,6 +874,19 @@
       'summary': 'Set (dis)allowed clients, blocked hosts, etc.'
       'tags':
       - 'clients'
+  '/blocked_services/services':
+    'get':
+      'tags':
+      - 'blocked_services'
+      'operationId': 'blockedServicesAvailableServices'
+      'summary': 'Get available services to use for blocking'
+      'responses':
+        '200':
+          'description': 'OK.'
+          'content':
+            'application/json':
+              'schema':
+                '$ref': '#/components/schemas/BlockedServicesArray'
   '/blocked_services/list':
     'get':
       'tags':

From 9b7fe740862c590f3311ad427672a0b06c6b4ff6 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 25 Apr 2022 13:59:34 +0300
Subject: [PATCH 043/143] Pull request: all: do not mark help-wanted issues as
 stale

Merge in DNS/adguard-home from help-wanted-stale to master

Squashed commit of the following:

commit 1c5ffcdd0153dd7d9d9bcc1e35dee4a0b3113f59
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Apr 22 20:04:01 2022 +0300

    all: do not mark help-wanted issues as stale
---
 .github/stale.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/stale.yml b/.github/stale.yml
index d242832f..6ed9a7df 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -8,6 +8,7 @@
 - 'documentation'
 - 'enhancement'
 - 'feature request'
+- 'help wanted'
 - 'localization'
 - 'needs investigation'
 - 'recurrent'

From 9d144ecb0a4433e594365ea89c73881da561938d Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 25 Apr 2022 17:09:49 +0300
Subject: [PATCH 044/143] Pull request: client: imp rdns desc

Squashed commit of the following:

commit 5631f5f7155d7e5ad58dc5088cc3c93cb40a94a4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 25 17:03:13 2022 +0300

    client: imp rdns desc
---
 client/src/__locales/en.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 706ddf55..f39d2f51 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Bootstrap DNS servers",
     "bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams.",
     "local_ptr_title": "Private reverse DNS servers",
-    "local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve the hostnames of clients with private IP addresses, for example \"192.168.12.34\", using reverse DNS. If not set, AdGuard Home uses the addresses of the default DNS resolvers of your OS except for the addresses of AdGuard Home itself.",
+    "local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve PTR requests for addresses in private IP ranges, for example \"192.168.12.34\", using reverse DNS. If not set, AdGuard Home uses the addresses of the default DNS resolvers of your OS except for the addresses of AdGuard Home itself.",
     "local_ptr_default_resolver": "By default, AdGuard Home uses the following reverse DNS resolvers: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home could not determine suitable private reverse DNS resolvers for this system.",
     "local_ptr_placeholder": "Enter one server address per line",

From 2a1ad532f454ed2ab25d19eaf168ba9f6d534f2f Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 25 Apr 2022 18:41:39 +0300
Subject: [PATCH 045/143] Pull request: home: rm unnecessary locking in update;
 refactor

Merge in DNS/adguard-home from 4499-rm-unnecessary-locking to master

Squashed commit of the following:

commit 6d70472506dd0fd69225454c73d9f7f6a208b76b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Apr 25 17:26:54 2022 +0300

    home: rm unnecessary locking in update; refactor
---
 CHANGELOG.md                                  |   2 +
 .../aghnet/{net_nolinux.go => net_bsd.go}     |   4 +-
 internal/aghnet/net_windows.go                |   8 +-
 internal/home/controlupdate.go                | 122 ++++++++++--------
 internal/updater/check.go                     |   2 +-
 scripts/make/go-lint.sh                       |   1 -
 6 files changed, 79 insertions(+), 60 deletions(-)
 rename internal/aghnet/{net_nolinux.go => net_bsd.go} (69%)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d82fb57..b43f0524 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -89,6 +89,7 @@ In this release, the schema version has changed from 12 to 13.
 
 ### Fixed
 
+- Slow version update queries making other HTTP APIs unresponsible ([#4499]).
 - ARP tables refreshing process causing excessive PTR requests ([#3157]).
 
 [#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730
@@ -106,6 +107,7 @@ In this release, the schema version has changed from 12 to 13.
 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
+[#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499
 
 [repr]:         https://reproducible-builds.org/docs/source-date-epoch/
 [doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
diff --git a/internal/aghnet/net_nolinux.go b/internal/aghnet/net_bsd.go
similarity index 69%
rename from internal/aghnet/net_nolinux.go
rename to internal/aghnet/net_bsd.go
index f429c6fa..bd705e92 100644
--- a/internal/aghnet/net_nolinux.go
+++ b/internal/aghnet/net_bsd.go
@@ -1,5 +1,5 @@
-//go:build !linux
-// +build !linux
+//go:build darwin || freebsd || openbsd
+// +build darwin freebsd openbsd
 
 package aghnet
 
diff --git a/internal/aghnet/net_windows.go b/internal/aghnet/net_windows.go
index 0cea8fe7..17499cce 100644
--- a/internal/aghnet/net_windows.go
+++ b/internal/aghnet/net_windows.go
@@ -1,5 +1,5 @@
-//go:build !(linux || darwin || freebsd || openbsd)
-// +build !linux,!darwin,!freebsd,!openbsd
+//go:build windows
+// +build windows
 
 package aghnet
 
@@ -13,6 +13,10 @@ import (
 	"golang.org/x/sys/windows"
 )
 
+func canBindPrivilegedPorts() (can bool, err error) {
+	return true, nil
+}
+
 func ifaceHasStaticIP(string) (ok bool, err error) {
 	return false, aghos.Unsupported("checking static ip")
 }
diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go
index 79b9f37e..ae469598 100644
--- a/internal/home/controlupdate.go
+++ b/internal/home/controlupdate.go
@@ -3,6 +3,7 @@ package home
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"os"
 	"os/exec"
@@ -27,12 +28,16 @@ type temporaryError interface {
 
 // Get the latest available version from the Internet
 func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+
 	resp := &versionResponse{}
 	if Context.disableUpdate {
-		// w.Header().Set("Content-Type", "application/json")
 		resp.Disabled = true
-		_ = json.NewEncoder(w).Encode(resp)
-		// TODO(e.burkov): Add error handling and deal with headers.
+		err := json.NewEncoder(w).Encode(resp)
+		if err != nil {
+			aghhttp.Error(r, w, http.StatusInternalServerError, "writing body: %s", err)
+		}
+
 		return
 	}
 
@@ -44,30 +49,48 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
 	if r.ContentLength != 0 {
 		err = json.NewDecoder(r.Body).Decode(req)
 		if err != nil {
-			aghhttp.Error(r, w, http.StatusBadRequest, "JSON parse: %s", err)
+			aghhttp.Error(r, w, http.StatusBadRequest, "parsing request: %s", err)
 
 			return
 		}
 	}
 
+	err = requestVersionInfo(resp, req.Recheck)
+	if err != nil {
+		// Don't wrap the error, because it's informative enough as is.
+		aghhttp.Error(r, w, http.StatusBadGateway, "%s", err)
+
+		return
+	}
+
+	err = resp.setAllowedToAutoUpdate()
+	if err != nil {
+		// Don't wrap the error, because it's informative enough as is.
+		aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
+
+		return
+	}
+
+	err = json.NewEncoder(w).Encode(resp)
+	if err != nil {
+		aghhttp.Error(r, w, http.StatusInternalServerError, "writing body: %s", err)
+	}
+}
+
+// requestVersionInfo sets the VersionInfo field of resp if it can reach the
+// update server.
+func requestVersionInfo(resp *versionResponse, recheck bool) (err error) {
 	for i := 0; i != 3; i++ {
-		func() {
-			Context.controlLock.Lock()
-			defer Context.controlLock.Unlock()
-
-			resp.VersionInfo, err = Context.updater.VersionInfo(req.Recheck)
-		}()
-
+		resp.VersionInfo, err = Context.updater.VersionInfo(recheck)
 		if err != nil {
 			var terr temporaryError
 			if errors.As(err, &terr) && terr.Temporary() {
-				// Temporary network error.  This case may happen while
-				// we're restarting our DNS server.  Log and sleep for
-				// some time.
+				// Temporary network error.  This case may happen while we're
+				// restarting our DNS server.  Log and sleep for some time.
 				//
 				// See https://github.com/AdguardTeam/AdGuardHome/issues/934.
 				d := time.Duration(i) * time.Second
-				log.Info("temp net error: %q; sleeping for %s and retrying", err, d)
+				log.Info("update: temp net error: %q; sleeping for %s and retrying", err, d)
 				time.Sleep(d)
 
 				continue
@@ -76,29 +99,14 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
 
 		break
 	}
+
 	if err != nil {
 		vcu := Context.updater.VersionCheckURL()
-		// TODO(a.garipov): Figure out the purpose of %T verb.
-		aghhttp.Error(
-			r,
-			w,
-			http.StatusBadGateway,
-			"Couldn't get version check json from %s: %T %s\n",
-			vcu,
-			err,
-			err,
-		)
 
-		return
+		return fmt.Errorf("getting version info from %s: %s", vcu, err)
 	}
 
-	resp.confirmAutoUpdate()
-
-	w.Header().Set("Content-Type", "application/json")
-	err = json.NewEncoder(w).Encode(resp)
-	if err != nil {
-		aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
-	}
+	return nil
 }
 
 // handleUpdate performs an update to the latest available version procedure.
@@ -132,31 +140,37 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) {
 
 // versionResponse is the response for /control/version.json endpoint.
 type versionResponse struct {
-	Disabled bool `json:"disabled"`
 	updater.VersionInfo
+	Disabled bool `json:"disabled"`
 }
 
-// confirmAutoUpdate checks the real possibility of auto update.
-func (vr *versionResponse) confirmAutoUpdate() {
-	if vr.CanAutoUpdate != nil && *vr.CanAutoUpdate {
-		canUpdate := true
-
-		var tlsConf *tlsConfigSettings
-		if runtime.GOOS != "windows" {
-			tlsConf = &tlsConfigSettings{}
-			Context.tls.WriteDiskConfig(tlsConf)
-		}
-
-		if tlsConf != nil &&
-			((tlsConf.Enabled && (tlsConf.PortHTTPS < 1024 ||
-				tlsConf.PortDNSOverTLS < 1024 ||
-				tlsConf.PortDNSOverQUIC < 1024)) ||
-				config.BindPort < 1024 ||
-				config.DNS.Port < 1024) {
-			canUpdate, _ = aghnet.CanBindPrivilegedPorts()
-		}
-		vr.CanAutoUpdate = &canUpdate
+// setAllowedToAutoUpdate sets CanAutoUpdate to true if AdGuard Home is actually
+// allowed to perform an automatic update by the OS.
+func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
+	if vr.CanAutoUpdate == nil || !*vr.CanAutoUpdate {
+		return
 	}
+
+	tlsConf := &tlsConfigSettings{}
+	Context.tls.WriteDiskConfig(tlsConf)
+
+	canUpdate := true
+	if tlsConfUsesPrivilegedPorts(tlsConf) || config.BindPort < 1024 || config.DNS.Port < 1024 {
+		canUpdate, err = aghnet.CanBindPrivilegedPorts()
+		if err != nil {
+			return fmt.Errorf("checking ability to bind privileged ports: %w", err)
+		}
+	}
+
+	vr.CanAutoUpdate = &canUpdate
+
+	return nil
+}
+
+// tlsConfUsesPrivilegedPorts returns true if the provided TLS configuration
+// indicates that privileged ports are used.
+func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) {
+	return c.Enabled && (c.PortHTTPS < 1024 || c.PortDNSOverTLS < 1024 || c.PortDNSOverQUIC < 1024)
 }
 
 // finishUpdate completes an update procedure.
diff --git a/internal/updater/check.go b/internal/updater/check.go
index edf046af..ec7176b2 100644
--- a/internal/updater/check.go
+++ b/internal/updater/check.go
@@ -17,11 +17,11 @@ const versionCheckPeriod = 8 * time.Hour
 
 // VersionInfo contains information about a new version.
 type VersionInfo struct {
+	CanAutoUpdate        *bool  `json:"can_autoupdate,omitempty"`
 	NewVersion           string `json:"new_version,omitempty"`
 	Announcement         string `json:"announcement,omitempty"`
 	AnnouncementURL      string `json:"announcement_url,omitempty"`
 	SelfUpdateMinVersion string `json:"-"`
-	CanAutoUpdate        *bool  `json:"can_autoupdate,omitempty"`
 }
 
 // MaxResponseSize is responses on server's requests maximum length in bytes.
diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh
index 32d99480..c363e513 100644
--- a/scripts/make/go-lint.sh
+++ b/scripts/make/go-lint.sh
@@ -136,7 +136,6 @@ underscores() {
 			-e '_freebsd.go'\
 			-e '_linux.go'\
 			-e '_little.go'\
-			-e '_nolinux.go'\
 			-e '_openbsd.go'\
 			-e '_others.go'\
 			-e '_test.go'\

From 0a1ff65b4a3155ce65661d051fb6e8600c265702 Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Mon, 25 Apr 2022 19:10:52 +0300
Subject: [PATCH 046/143] Pull request: client: fix constant loading for
 blocked requests

Updates #4420

Squashed commit of the following:

commit 461a59e1541626020bf0bcfaf34ba7d2f4509dc7
Merge: 5c5e7b5d 2a1ad532
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Apr 25 18:46:02 2022 +0300

    Merge branch 'master' into 4420-loading-log

commit 5c5e7b5d1a69d30e40e71f49f46dea89fa8c40a2
Author: Ildar Kamalov <ik@adguard.com>
Date:   Sun Apr 24 22:18:22 2022 +0300

    client: fix constant loading for blocked requests
---
 client/src/components/Logs/InfiniteTable.js | 29 ++++++++++++++-------
 client/src/helpers/helpers.js               |  4 +--
 2 files changed, 21 insertions(+), 12 deletions(-)

diff --git a/client/src/components/Logs/InfiniteTable.js b/client/src/components/Logs/InfiniteTable.js
index d419ac3d..36f411c8 100644
--- a/client/src/components/Logs/InfiniteTable.js
+++ b/client/src/components/Logs/InfiniteTable.js
@@ -43,7 +43,7 @@ const InfiniteTable = ({
 
     useEffect(() => {
         listener();
-    }, [items.length < QUERY_LOGS_PAGE_LIMIT]);
+    }, [items.length < QUERY_LOGS_PAGE_LIMIT, isEntireLog]);
 
     useEffect(() => {
         const THROTTLE_TIME = 100;
@@ -66,15 +66,24 @@ const InfiniteTable = ({
 
     const isNothingFound = items.length === 0 && !processingGetLogs;
 
-    return <div className='logs__table' role='grid'>
-        {loading && <Loading />}
-        <Header />
-        {isNothingFound
-            ? <label className="logs__no-data">{t('nothing_found')}</label>
-            : <>{items.map(renderRow)}
-                    {!isEntireLog && <div ref={loader} className="logs__loading text-center">{t('loading_table_status')}</div>}
-            </>}
-    </div>;
+    return (
+        <div className="logs__table" role="grid">
+            {loading && <Loading />}
+            <Header />
+            {isNothingFound ? (
+                <label className="logs__no-data">{t('nothing_found')}</label>
+            ) : (
+                <>
+                    {items.map(renderRow)}
+                    {!isEntireLog && (
+                        <div ref={loader} className="logs__loading text-center">
+                            {t('loading_table_status')}
+                        </div>
+                    )}
+                </>
+            )}
+        </div>
+    );
 };
 
 InfiniteTable.propTypes = {
diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js
index 5fb42c05..a7bf9485 100644
--- a/client/src/helpers/helpers.js
+++ b/client/src/helpers/helpers.js
@@ -693,8 +693,8 @@ export const replaceZeroWithEmptyString = (value) => (parseInt(value, 10) === 0
  * @returns {string}
  */
 export const getLogsUrlParams = (search, response_status) => `?${queryString.stringify({
-    search,
-    response_status,
+    search: search || undefined,
+    response_status: response_status || undefined,
 })}`;
 
 export const processContent = (

From 235316e0508ee015bf4ce663d2b7929079541c59 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 26 Apr 2022 13:04:16 +0300
Subject: [PATCH 047/143] Pull request: 3020 runtime clients sources control

Merge in DNS/adguard-home from 3020-client-sources to master

Closes #3020.

Squashed commit of the following:

commit f8e6b6d63373f99b52f7b8c32f4242c453daf1a4
Merge: 41fb071d 0a1ff65b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Apr 25 19:19:15 2022 +0300

    Merge branch 'master' into 3020-client-sources

commit 41fb071deb2a87e0a69d09c8f418a016b4dd7e93
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 25 13:38:28 2022 +0300

    home: fix nil, imp docs

commit aaa7765914a8a4645eba357cd088a9470611ffdc
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 25 12:25:47 2022 +0300

    home: imp code

commit 3f71b999564c604583b46313d29f5b70cf51ee14
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Apr 22 19:12:27 2022 +0300

    home: runtime clients sources control
---
 CHANGELOG.md                  |  36 ++++++++-
 internal/home/clients.go      |  26 ++++--
 internal/home/config.go       |  28 ++++---
 internal/home/dns.go          |  17 ++--
 internal/home/home.go         |  15 ++--
 internal/home/options.go      |  12 ++-
 internal/home/upgrade.go      |  69 +++++++++++++++-
 internal/home/upgrade_test.go | 145 ++++++++++++++++++++++++++--------
 8 files changed, 283 insertions(+), 65 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b43f0524..047726ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,8 @@ and this project adheres to
 
 ### Added
 
+- The ability to control each source of runtime clients separately via
+  `clients.runtime_sources` configuration object ([#3020]).
 - The ability to customize the set of networks that are considered private
   through the new `dns.private_networks` property in the configuration file
   ([#3142]).
@@ -63,8 +65,36 @@ and this project adheres to
 
 #### Configuration Changes
 
-In this release, the schema version has changed from 12 to 13.
+In this release, the schema version has changed from 12 to 14.
 
+- Object `clients`, which in schema versions 13 and earlier was an array of
+  actual persistent clients, is now consist of `persistent` and
+  `runtime_sources` properties:
+
+  ```yaml
+  # BEFORE:
+  'clients':
+  - name: client-name
+    # …
+
+  # AFTER:
+  'clients':
+    'persistent':
+      - name: client-name
+        # …
+    'runtime_sources':
+      whois: true
+      arp: true
+      rdns: true
+      dhcp: true
+      hosts: true
+  ```
+
+  The value for `clients.runtime_sources.rdns` field is taken from
+  `dns.resolve_clients` property.  To rollback this change, remove the
+  `runtime_sources` property, move the contents of `persistent` into the
+  `clients` itself, the value of `clients.runtime_sources.rdns` into the
+  `dns.resolve_clietns`, and change the `schema_version` back to `13`.
 - Property `local_domain_name`, which in schema versions 12 and earlier used to
   be a part of the `dns` object, is now a part of the `dhcp` object:
 
@@ -85,6 +115,9 @@ In this release, the schema version has changed from 12 to 13.
 
 ### Deprecated
 
+- The `--no-etc-hosts` option.  Its' functionality is now controlled by
+  `clients.runtime_sources.hosts` configuration property.  v0.109.0 will remove
+  the flag completely.
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
 
 ### Fixed
@@ -94,6 +127,7 @@ In this release, the schema version has changed from 12 to 13.
 
 [#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+[#3020]: https://github.com/AdguardTeam/AdGuardHome/issues/3020
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
 [#3142]: https://github.com/AdguardTeam/AdGuardHome/issues/3142
 [#3157]: https://github.com/AdguardTeam/AdGuardHome/issues/3157
diff --git a/internal/home/clients.go b/internal/home/clients.go
index fe15e514..d4d6b959 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -59,6 +59,16 @@ const (
 	ClientSourceHostsFile
 )
 
+// clientSourceConf is used to configure where the runtime clients will be
+// obtained from.
+type clientSourcesConf struct {
+	WHOIS     bool `yaml:"whois"`
+	ARP       bool `yaml:"arp"`
+	RDNS      bool `yaml:"rdns"`
+	DHCP      bool `yaml:"dhcp"`
+	HostsFile bool `yaml:"hosts"`
+}
+
 // RuntimeClient information
 type RuntimeClient struct {
 	WHOISInfo *RuntimeClientWHOISInfo
@@ -134,14 +144,14 @@ func (clients *clientsContainer) Init(
 		clients.dhcpServer.SetOnLeaseChanged(clients.onDHCPLeaseChanged)
 	}
 
-	go clients.handleHostsUpdates()
+	if clients.etcHosts != nil {
+		go clients.handleHostsUpdates()
+	}
 }
 
 func (clients *clientsContainer) handleHostsUpdates() {
-	if clients.etcHosts != nil {
-		for upd := range clients.etcHosts.Upd() {
-			clients.addFromHostsFile(upd)
-		}
+	for upd := range clients.etcHosts.Upd() {
+		clients.addFromHostsFile(upd)
 	}
 }
 
@@ -158,7 +168,9 @@ func (clients *clientsContainer) Start() {
 
 // Reload reloads runtime clients.
 func (clients *clientsContainer) Reload() {
-	clients.addFromSystemARP()
+	if clients.arpdb != nil {
+		clients.addFromSystemARP()
+	}
 }
 
 type clientObject struct {
@@ -843,7 +855,7 @@ func (clients *clientsContainer) addFromSystemARP() {
 // updateFromDHCP adds the clients that have a non-empty hostname from the DHCP
 // server.
 func (clients *clientsContainer) updateFromDHCP(add bool) {
-	if clients.dhcpServer == nil {
+	if clients.dhcpServer == nil || !config.Clients.Sources.DHCP {
 		return
 	}
 
diff --git a/internal/home/config.go b/internal/home/config.go
index aa8450be..720683a1 100644
--- a/internal/home/config.go
+++ b/internal/home/config.go
@@ -51,6 +51,13 @@ type osConfig struct {
 	RlimitNoFile uint64 `yaml:"rlimit_nofile"`
 }
 
+type clientsConfig struct {
+	// Sources defines the set of sources to fetch the runtime clients from.
+	Sources *clientSourcesConf `yaml:"runtime_sources"`
+	// Persistent are the configured clients.
+	Persistent []*clientObject `yaml:"persistent"`
+}
+
 // configuration is loaded from YAML
 // field ordering is important -- yaml fields will mirror ordering from here
 type configuration struct {
@@ -88,7 +95,7 @@ type configuration struct {
 	// Clients contains the YAML representations of the persistent clients.
 	// This field is only used for reading and writing persistent client data.
 	// Keep this field sorted to ensure consistent ordering.
-	Clients []*clientObject `yaml:"clients"`
+	Clients *clientsConfig `yaml:"clients"`
 
 	logSettings `yaml:",inline"`
 
@@ -123,9 +130,6 @@ type dnsConfig struct {
 	// UpstreamTimeout is the timeout for querying upstream servers.
 	UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"`
 
-	// ResolveClients enables and disables resolving clients with RDNS.
-	ResolveClients bool `yaml:"resolve_clients"`
-
 	// PrivateNets is the set of IP networks for which the private reverse DNS
 	// resolver should be used.
 	PrivateNets []string `yaml:"private_networks"`
@@ -198,7 +202,6 @@ var config = &configuration{
 		FilteringEnabled:           true, // whether or not use filter lists
 		FiltersUpdateIntervalHours: 24,
 		UpstreamTimeout:            timeutil.Duration{Duration: dnsforward.DefaultTimeout},
-		ResolveClients:             true,
 		UsePrivateRDNS:             true,
 	},
 	TLS: tlsConfigSettings{
@@ -209,6 +212,15 @@ var config = &configuration{
 	DHCP: &dhcpd.ServerConfig{
 		LocalDomainName: "lan",
 	},
+	Clients: &clientsConfig{
+		Sources: &clientSourcesConf{
+			WHOIS:     true,
+			ARP:       true,
+			RDNS:      true,
+			DHCP:      true,
+			HostsFile: true,
+		},
+	},
 	logSettings: logSettings{
 		LogCompress:   false,
 		LogLocalTime:  false,
@@ -404,9 +416,7 @@ func (c *configuration) write() error {
 		s.WriteDiskConfig(&c)
 		dns := &config.DNS
 		dns.FilteringConfig = c
-		dns.LocalPTRResolvers,
-			dns.ResolveClients,
-			dns.UsePrivateRDNS = s.RDNSSettings()
+		dns.LocalPTRResolvers, config.Clients.Sources.RDNS, dns.UsePrivateRDNS = s.RDNSSettings()
 	}
 
 	if Context.dhcpServer != nil {
@@ -415,7 +425,7 @@ func (c *configuration) write() error {
 		config.DHCP = c
 	}
 
-	config.Clients = Context.clients.forConfig()
+	config.Clients.Persistent = Context.clients.forConfig()
 
 	configFile := config.getConfigFilename()
 	log.Debug("Writing YAML file: %s", configFile)
diff --git a/internal/home/dns.go b/internal/home/dns.go
index c30e12ec..e0fc1aab 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -136,7 +136,10 @@ func initDNSServer() (err error) {
 	}
 
 	Context.rdns = NewRDNS(Context.dnsServer, &Context.clients, config.DNS.UsePrivateRDNS)
-	Context.whois = initWHOIS(&Context.clients)
+
+	if !config.Clients.Sources.WHOIS {
+		Context.whois = initWHOIS(&Context.clients)
+	}
 
 	Context.filters.Init()
 	return nil
@@ -153,10 +156,11 @@ func onDNSRequest(pctx *proxy.DNSContext) {
 		return
 	}
 
-	if config.DNS.ResolveClients && !ip.IsLoopback() {
+	srcs := config.Clients.Sources
+	if srcs.RDNS && !ip.IsLoopback() {
 		Context.rdns.Begin(ip)
 	}
-	if !netutil.IsSpecialPurpose(ip) {
+	if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) {
 		Context.whois.Begin(ip)
 	}
 }
@@ -239,7 +243,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
 	newConf.FilterHandler = applyAdditionalFiltering
 	newConf.GetCustomUpstreamByClient = Context.clients.findUpstreams
 
-	newConf.ResolveClients = dnsConf.ResolveClients
+	newConf.ResolveClients = config.Clients.Sources.RDNS
 	newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS
 	newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
 	newConf.UpstreamTimeout = dnsConf.UpstreamTimeout.Duration
@@ -387,10 +391,11 @@ func startDNSServer() error {
 			continue
 		}
 
-		if config.DNS.ResolveClients && !ip.IsLoopback() {
+		srcs := config.Clients.Sources
+		if srcs.RDNS && !ip.IsLoopback() {
 			Context.rdns.Begin(ip)
 		}
-		if !netutil.IsSpecialPurpose(ip) {
+		if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) {
 			Context.whois.Begin(ip)
 		}
 	}
diff --git a/internal/home/home.go b/internal/home/home.go
index 420e67d8..539552d1 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -173,6 +173,11 @@ func setupContext(args options) {
 
 			os.Exit(0)
 		}
+
+		if !args.noEtcHosts && config.Clients.Sources.HostsFile {
+			err = setupHostsContainer()
+			fatalOnError(err)
+		}
 	}
 
 	Context.mux = http.NewServeMux()
@@ -285,14 +290,12 @@ func setupConfig(args options) (err error) {
 		ConfName: config.getConfigFilename(),
 	})
 
-	if !args.noEtcHosts {
-		if err = setupHostsContainer(); err != nil {
-			return err
-		}
+	var arpdb aghnet.ARPDB
+	if config.Clients.Sources.ARP {
+		arpdb = aghnet.NewARPDB()
 	}
 
-	arpdb := aghnet.NewARPDB()
-	Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts, arpdb)
+	Context.clients.Init(config.Clients.Persistent, Context.dhcpServer, Context.etcHosts, arpdb)
 
 	if args.bindPort != 0 {
 		uc := aghalg.UniqChecker{}
diff --git a/internal/home/options.go b/internal/home/options.go
index dc11ca35..6f5a4d8d 100644
--- a/internal/home/options.go
+++ b/internal/home/options.go
@@ -230,13 +230,19 @@ var helpArg = arg{
 }
 
 var noEtcHostsArg = arg{
-	description:     "Do not use the OS-provided hosts.",
+	description:     "Deprecated.  Do not use the OS-provided hosts.",
 	longName:        "no-etc-hosts",
 	shortName:       "",
 	updateWithValue: nil,
 	updateNoValue:   func(o options) (options, error) { o.noEtcHosts = true; return o, nil },
-	effect:          nil,
-	serialize:       func(o options) []string { return boolSliceOrNil(o.noEtcHosts) },
+	effect: func(_ options, _ string) (f effect, err error) {
+		log.Info(
+			"warning: --no-etc-hosts flag is deprecated and will be removed in the future versions",
+		)
+
+		return nil, nil
+	},
+	serialize: func(o options) []string { return boolSliceOrNil(o.noEtcHosts) },
 }
 
 var localFrontendArg = arg{
diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go
index 34297470..d9611dc9 100644
--- a/internal/home/upgrade.go
+++ b/internal/home/upgrade.go
@@ -21,9 +21,11 @@ import (
 )
 
 // currentSchemaVersion is the current schema version.
-const currentSchemaVersion = 13
+const currentSchemaVersion = 14
 
 // These aliases are provided for convenience.
+//
+// TODO(e.burkov):  Remove any after updating to Go 1.18.
 type (
 	any  = interface{}
 	yarr = []any
@@ -86,6 +88,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
 		upgradeSchema10to11,
 		upgradeSchema11to12,
 		upgradeSchema12to13,
+		upgradeSchema13to14,
 	}
 
 	n := 0
@@ -726,7 +729,7 @@ func upgradeSchema12to13(diskConf yobj) (err error) {
 	var dhcp yobj
 	dhcp, ok = dhcpVal.(yobj)
 	if !ok {
-		return fmt.Errorf("unexpected type of dhcp: %T", dnsVal)
+		return fmt.Errorf("unexpected type of dhcp: %T", dhcpVal)
 	}
 
 	const field = "local_domain_name"
@@ -737,6 +740,68 @@ func upgradeSchema12to13(diskConf yobj) (err error) {
 	return nil
 }
 
+// upgradeSchema13to14 performs the following changes:
+//
+//   # BEFORE:
+//   'clients':
+//   - 'name': 'client-name'
+//     # …
+//
+//   # AFTER:
+//   'clients':
+//     'persistent':
+//     - 'name': 'client-name'
+//       # …
+//     'runtime_sources':
+//       'whois': true
+//       'arp': true
+//       'rdns': true
+//       'dhcp': true
+//       'hosts': true
+//
+func upgradeSchema13to14(diskConf yobj) (err error) {
+	log.Printf("Upgrade yaml: 13 to 14")
+	diskConf["schema_version"] = 14
+
+	clientsVal, ok := diskConf["clients"]
+	if !ok {
+		clientsVal = yarr{}
+	}
+
+	var rdnsSrc bool
+	if dnsVal, dok := diskConf["dns"]; dok {
+		var dnsSettings yobj
+		dnsSettings, ok = dnsVal.(yobj)
+		if !ok {
+			return fmt.Errorf("unexpected type of dns: %T", dnsVal)
+		}
+
+		var rdnsSrcVal any
+		rdnsSrcVal, ok = dnsSettings["resolve_clients"]
+		if ok {
+			rdnsSrc, ok = rdnsSrcVal.(bool)
+			if !ok {
+				return fmt.Errorf("unexpected type of resolve_clients: %T", rdnsSrcVal)
+			}
+
+			delete(dnsSettings, "resolve_clients")
+		}
+	}
+
+	diskConf["clients"] = yobj{
+		"persistent": clientsVal,
+		"runtime_sources": &clientSourcesConf{
+			WHOIS:     true,
+			ARP:       true,
+			RDNS:      rdnsSrc,
+			DHCP:      true,
+			HostsFile: true,
+		},
+	}
+
+	return nil
+}
+
 // TODO(a.garipov): Replace with log.Output when we port it to our logging
 // package.
 func funcName() string {
diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go
index c63bc443..4c25cba3 100644
--- a/internal/home/upgrade_test.go
+++ b/internal/home/upgrade_test.go
@@ -513,46 +513,129 @@ func TestUpgradeSchema11to12(t *testing.T) {
 }
 
 func TestUpgradeSchema12to13(t *testing.T) {
-	t.Run("no_dns", func(t *testing.T) {
-		conf := yobj{}
+	const newSchemaVer = 13
 
-		err := upgradeSchema12to13(conf)
-		require.NoError(t, err)
-
-		assert.Equal(t, conf["schema_version"], 13)
-	})
-
-	t.Run("no_dhcp", func(t *testing.T) {
-		conf := yobj{
-			"dns": yobj{},
-		}
-
-		err := upgradeSchema12to13(conf)
-		require.NoError(t, err)
-
-		assert.Equal(t, conf["schema_version"], 13)
-	})
-
-	t.Run("good", func(t *testing.T) {
-		conf := yobj{
+	testCases := []struct {
+		in   yobj
+		want yobj
+		name string
+	}{{
+		in:   yobj{},
+		want: yobj{"schema_version": newSchemaVer},
+		name: "no_dns",
+	}, {
+		in: yobj{"dns": yobj{}},
+		want: yobj{
+			"dns":            yobj{},
+			"schema_version": newSchemaVer,
+		},
+		name: "no_dhcp",
+	}, {
+		in: yobj{
 			"dns": yobj{
 				"local_domain_name": "lan",
 			},
 			"dhcp":           yobj{},
-			"schema_version": 12,
-		}
-
-		wantConf := yobj{
+			"schema_version": newSchemaVer - 1,
+		},
+		want: yobj{
 			"dns": yobj{},
 			"dhcp": yobj{
 				"local_domain_name": "lan",
 			},
-			"schema_version": 13,
-		}
+			"schema_version": newSchemaVer,
+		},
+		name: "good",
+	}}
 
-		err := upgradeSchema12to13(conf)
-		require.NoError(t, err)
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := upgradeSchema12to13(tc.in)
+			require.NoError(t, err)
 
-		assert.Equal(t, wantConf, conf)
-	})
+			assert.Equal(t, tc.want, tc.in)
+		})
+	}
+}
+
+func TestUpgradeSchema13to14(t *testing.T) {
+	const newSchemaVer = 14
+
+	testClient := &clientObject{
+		Name:              "agh-client",
+		IDs:               []string{"id1"},
+		UseGlobalSettings: true,
+	}
+
+	testCases := []struct {
+		in   yobj
+		want yobj
+		name string
+	}{{
+		in: yobj{},
+		want: yobj{
+			"schema_version": newSchemaVer,
+			// The clients field will be added anyway.
+			"clients": yobj{
+				"persistent": yarr{},
+				"runtime_sources": &clientSourcesConf{
+					WHOIS:     true,
+					ARP:       true,
+					RDNS:      false,
+					DHCP:      true,
+					HostsFile: true,
+				},
+			},
+		},
+		name: "no_clients",
+	}, {
+		in: yobj{
+			"clients": []*clientObject{testClient},
+		},
+		want: yobj{
+			"schema_version": newSchemaVer,
+			"clients": yobj{
+				"persistent": []*clientObject{testClient},
+				"runtime_sources": &clientSourcesConf{
+					WHOIS:     true,
+					ARP:       true,
+					RDNS:      false,
+					DHCP:      true,
+					HostsFile: true,
+				},
+			},
+		},
+		name: "no_dns",
+	}, {
+		in: yobj{
+			"clients": []*clientObject{testClient},
+			"dns": yobj{
+				"resolve_clients": true,
+			},
+		},
+		want: yobj{
+			"schema_version": newSchemaVer,
+			"clients": yobj{
+				"persistent": []*clientObject{testClient},
+				"runtime_sources": &clientSourcesConf{
+					WHOIS:     true,
+					ARP:       true,
+					RDNS:      true,
+					DHCP:      true,
+					HostsFile: true,
+				},
+			},
+			"dns": yobj{},
+		},
+		name: "good",
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := upgradeSchema13to14(tc.in)
+			require.NoError(t, err)
+
+			assert.Equal(t, tc.want, tc.in)
+		})
+	}
 }

From 1c89394aef6abb1e4810c84db01d44cec770dee7 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 26 Apr 2022 15:21:45 +0300
Subject: [PATCH 048/143] Pull request: 4525 fix panic

Merge in DNS/adguard-home from 3020-fix-panic to master

Closes #4525.

Squashed commit of the following:

commit f8d9e25eccb485269aa2f0275d4e08da767f9d05
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 26 15:09:11 2022 +0300

    home: imp code

commit 8fe02c8f057c05b9e8ce1de056a92e7cd69ae4c6
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 26 14:44:33 2022 +0300

    home: fix panic
---
 internal/home/dns.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/internal/home/dns.go b/internal/home/dns.go
index e0fc1aab..e28193ee 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -135,9 +135,11 @@ func initDNSServer() (err error) {
 		return fmt.Errorf("dnsServer.Prepare: %w", err)
 	}
 
-	Context.rdns = NewRDNS(Context.dnsServer, &Context.clients, config.DNS.UsePrivateRDNS)
+	if config.Clients.Sources.RDNS {
+		Context.rdns = NewRDNS(Context.dnsServer, &Context.clients, config.DNS.UsePrivateRDNS)
+	}
 
-	if !config.Clients.Sources.WHOIS {
+	if config.Clients.Sources.WHOIS {
 		Context.whois = initWHOIS(&Context.clients)
 	}
 

From ed449c618628c5e3ee76072cb010ece32eea565e Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 26 Apr 2022 20:50:09 +0300
Subject: [PATCH 049/143] Pull request: all: add stub binary for new api

Merge in DNS/adguard-home from new-api to master

Squashed commit of the following:

commit 83f4418c253b9abc5131d9e2acc2a4a96e4122c4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 26 19:09:34 2022 +0300

    all: fix build

commit 1fbb53fdf779bde79fab72f9c8eb929e08bb044c
Merge: 73a55197 1c89394a
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 26 18:37:27 2022 +0300

    Merge branch 'master' into new-api

commit 73a5519723f662979bdeb5192bc15835e7f03512
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 26 18:36:50 2022 +0300

    v1: imp names, docs

commit d3fbc2f2082612b8ba438c8216c6c74421cc2df5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Apr 22 17:55:42 2022 +0300

    cmd: imp docs

commit c2a73aa364a848e8066d1132d4b53bbc3e22db2d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Apr 22 16:19:14 2022 +0300

    all: add stub binary for new api
---
 Makefile                          |    3 +
 internal/aghos/os.go              |   10 +
 internal/aghos/os_unix.go         |   27 +
 internal/aghos/os_windows.go      |   21 +
 internal/v1/agh/agh.go            |   33 +
 internal/v1/cmd/cmd.go            |   69 +
 internal/v1/cmd/signal.go         |   70 +
 internal/v1/websvc/websvc.go      |  185 ++
 internal/v1/websvc/websvc_test.go |   69 +
 main.go                           |    3 +
 main_v1.go                        |   21 +
 openapi/v1.yaml                   | 4913 +++++++++++++++++++++++++++++
 scripts/make/go-build.sh          |   12 +-
 scripts/make/go-lint.sh           |    3 +-
 14 files changed, 5437 insertions(+), 2 deletions(-)
 create mode 100644 internal/aghos/os_unix.go
 create mode 100644 internal/v1/agh/agh.go
 create mode 100644 internal/v1/cmd/cmd.go
 create mode 100644 internal/v1/cmd/signal.go
 create mode 100644 internal/v1/websvc/websvc.go
 create mode 100644 internal/v1/websvc/websvc_test.go
 create mode 100644 main_v1.go
 create mode 100644 openapi/v1.yaml

diff --git a/Makefile b/Makefile
index 5d9e7a7c..7c8f205f 100644
--- a/Makefile
+++ b/Makefile
@@ -34,6 +34,8 @@ YARN_INSTALL_FLAGS = $(YARN_FLAGS) --network-timeout 120000 --silent\
 	--ignore-engines --ignore-optional --ignore-platform\
 	--ignore-scripts
 
+V1API = 0
+
 # Macros for the build-release target.  If FRONTEND_PREBUILT is 0, the
 # default, the macro $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT)) expands
 # into BUILD_RELEASE_DEPS_0, and so both frontend and backend
@@ -61,6 +63,7 @@ ENV = env\
 	PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
 	RACE='$(RACE)'\
 	SIGN='$(SIGN)'\
+	V1API='$(V1API)'\
 	VERBOSE='$(VERBOSE)'\
 	VERSION='$(VERSION)'\
 
diff --git a/internal/aghos/os.go b/internal/aghos/os.go
index 018a3e89..3b688749 100644
--- a/internal/aghos/os.go
+++ b/internal/aghos/os.go
@@ -175,3 +175,13 @@ func RootDirFS() (fsys fs.FS) {
 	// behavior is undocumented but it currently works.
 	return os.DirFS("")
 }
+
+// NotifyShutdownSignal notifies c on receiving shutdown signals.
+func NotifyShutdownSignal(c chan<- os.Signal) {
+	notifyShutdownSignal(c)
+}
+
+// IsShutdownSignal returns true if sig is a shutdown signal.
+func IsShutdownSignal(sig os.Signal) (ok bool) {
+	return isShutdownSignal(sig)
+}
diff --git a/internal/aghos/os_unix.go b/internal/aghos/os_unix.go
new file mode 100644
index 00000000..9a3cc308
--- /dev/null
+++ b/internal/aghos/os_unix.go
@@ -0,0 +1,27 @@
+//go:build darwin || freebsd || linux || openbsd
+// +build darwin freebsd linux openbsd
+
+package aghos
+
+import (
+	"os"
+	"os/signal"
+
+	"golang.org/x/sys/unix"
+)
+
+func notifyShutdownSignal(c chan<- os.Signal) {
+	signal.Notify(c, unix.SIGINT, unix.SIGQUIT, unix.SIGTERM)
+}
+
+func isShutdownSignal(sig os.Signal) (ok bool) {
+	switch sig {
+	case
+		unix.SIGINT,
+		unix.SIGQUIT,
+		unix.SIGTERM:
+		return true
+	default:
+		return false
+	}
+}
diff --git a/internal/aghos/os_windows.go b/internal/aghos/os_windows.go
index bff5a3f0..31fca3ef 100644
--- a/internal/aghos/os_windows.go
+++ b/internal/aghos/os_windows.go
@@ -4,6 +4,10 @@
 package aghos
 
 import (
+	"os"
+	"os/signal"
+	"syscall"
+
 	"golang.org/x/sys/windows"
 )
 
@@ -35,3 +39,20 @@ func haveAdminRights() (bool, error) {
 func isOpenWrt() (ok bool) {
 	return false
 }
+
+func notifyShutdownSignal(c chan<- os.Signal) {
+	// syscall.SIGTERM is processed automatically.  See go doc os/signal,
+	// section Windows.
+	signal.Notify(c, os.Interrupt)
+}
+
+func isShutdownSignal(sig os.Signal) (ok bool) {
+	switch sig {
+	case
+		os.Interrupt,
+		syscall.SIGTERM:
+		return true
+	default:
+		return false
+	}
+}
diff --git a/internal/v1/agh/agh.go b/internal/v1/agh/agh.go
new file mode 100644
index 00000000..212da4d6
--- /dev/null
+++ b/internal/v1/agh/agh.go
@@ -0,0 +1,33 @@
+// Package agh contains common entities and interfaces of AdGuard Home.
+//
+// TODO(a.garipov): Move to the upper-level internal/.
+package agh
+
+import "context"
+
+// Service is the interface for API servers.
+//
+// TODO(a.garipov): Consider adding a context to Start.
+//
+// TODO(a.garipov): Consider adding a Wait method or making an extension
+// interface for that.
+type Service interface {
+	// Start starts the service.  It does not block.
+	Start() (err error)
+
+	// Shutdown gracefully stops the service.  ctx is used to determine
+	// a timeout before trying to stop the service less gracefully.
+	Shutdown(ctx context.Context) (err error)
+}
+
+// type check
+var _ Service = EmptyService{}
+
+// EmptyService is a Service that does nothing.
+type EmptyService struct{}
+
+// Start implements the Service interface for EmptyService.
+func (EmptyService) Start() (err error) { return nil }
+
+// Shutdown implements the Service interface for EmptyService.
+func (EmptyService) Shutdown(_ context.Context) (err error) { return nil }
diff --git a/internal/v1/cmd/cmd.go b/internal/v1/cmd/cmd.go
new file mode 100644
index 00000000..4c4e252f
--- /dev/null
+++ b/internal/v1/cmd/cmd.go
@@ -0,0 +1,69 @@
+// Package cmd is the AdGuard Home entry point.  It contains the on-disk
+// configuration file utilities, signal processing logic, and so on.
+//
+// TODO(a.garipov): Move to the upper-level internal/.
+package cmd
+
+import (
+	"context"
+	"io/fs"
+	"math/rand"
+	"net"
+	"time"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/v1/websvc"
+	"github.com/AdguardTeam/golibs/log"
+	"github.com/AdguardTeam/golibs/netutil"
+)
+
+// Main is the entry point of application.
+func Main(clientBuildFS fs.FS) {
+	// # Initial Configuration
+
+	rand.Seed(time.Now().UnixNano())
+
+	// TODO(a.garipov): Set up logging.
+
+	// # Web Service
+
+	// TODO(a.garipov): Use in the Web service.
+	_ = clientBuildFS
+
+	// TODO(a.garipov): Make configurable.
+	web := websvc.New(&websvc.Config{
+		Addresses: []*netutil.IPPort{{
+			IP:   net.IP{127, 0, 0, 1},
+			Port: 3001,
+		}},
+		Timeout: 60 * time.Second,
+	})
+
+	err := web.Start()
+	fatalOnError(err)
+
+	sigHdlr := newSignalHandler(
+		web,
+	)
+
+	go sigHdlr.handle()
+
+	select {}
+}
+
+// defaultTimeout is the timeout used for some operations where another timeout
+// hasn't been defined yet.
+const defaultTimeout = 15 * time.Second
+
+// ctxWithDefaultTimeout is a helper function that returns a context with
+// timeout set to defaultTimeout.
+func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
+	return context.WithTimeout(context.Background(), defaultTimeout)
+}
+
+// fatalOnError is a helper that exits the program with an error code if err is
+// not nil.  It must only be used within Main.
+func fatalOnError(err error) {
+	if err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/internal/v1/cmd/signal.go b/internal/v1/cmd/signal.go
new file mode 100644
index 00000000..b9f09673
--- /dev/null
+++ b/internal/v1/cmd/signal.go
@@ -0,0 +1,70 @@
+package cmd
+
+import (
+	"os"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
+	"github.com/AdguardTeam/AdGuardHome/internal/v1/agh"
+	"github.com/AdguardTeam/golibs/log"
+)
+
+// signalHandler processes incoming signals and shuts services down.
+type signalHandler struct {
+	signal chan os.Signal
+
+	// services are the services that are shut down before application
+	// exiting.
+	services []agh.Service
+}
+
+// handle processes OS signals.
+func (h *signalHandler) handle() {
+	defer log.OnPanic("signalProcessor.handle")
+
+	for sig := range h.signal {
+		log.Info("sigproc: received signal %q", sig)
+
+		if aghos.IsShutdownSignal(sig) {
+			h.shutdown()
+		}
+	}
+}
+
+// Exit status constants.
+const (
+	statusSuccess = 0
+	statusError   = 1
+)
+
+// shutdown gracefully shuts down all services.
+func (h *signalHandler) shutdown() {
+	ctx, cancel := ctxWithDefaultTimeout()
+	defer cancel()
+
+	status := statusSuccess
+
+	log.Info("sigproc: shutting down services")
+	for i, service := range h.services {
+		err := service.Shutdown(ctx)
+		if err != nil {
+			log.Error("sigproc: shutting down service at index %d: %s", i, err)
+			status = statusError
+		}
+	}
+
+	log.Info("sigproc: shutting down adguard home")
+
+	os.Exit(status)
+}
+
+// newSignalHandler returns a new signalHandler that shuts down svcs.
+func newSignalHandler(svcs ...agh.Service) (h *signalHandler) {
+	h = &signalHandler{
+		signal:   make(chan os.Signal, 1),
+		services: svcs,
+	}
+
+	aghos.NotifyShutdownSignal(h.signal)
+
+	return h
+}
diff --git a/internal/v1/websvc/websvc.go b/internal/v1/websvc/websvc.go
new file mode 100644
index 00000000..e741ff3d
--- /dev/null
+++ b/internal/v1/websvc/websvc.go
@@ -0,0 +1,185 @@
+// Package websvc contains the AdGuard Home web service.
+//
+// TODO(a.garipov): Add tests.
+package websvc
+
+import (
+	"context"
+	"crypto/tls"
+	"fmt"
+	"io"
+	"net"
+	"net/http"
+	"sync"
+	"time"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/v1/agh"
+	"github.com/AdguardTeam/golibs/errors"
+	"github.com/AdguardTeam/golibs/log"
+	"github.com/AdguardTeam/golibs/netutil"
+)
+
+// Config is the AdGuard Home web service configuration structure.
+type Config struct {
+	// TLS is the optional TLS configuration.  If TLS is not nil,
+	// SecureAddresses must not be empty.
+	TLS *tls.Config
+
+	// Addresses are the addresses on which to serve the plain HTTP API.
+	Addresses []*netutil.IPPort
+
+	// SecureAddresses are the addresses on which to serve the HTTPS API.  If
+	// SecureAddresses is not empty, TLS must not be nil.
+	SecureAddresses []*netutil.IPPort
+
+	// Timeout is the timeout for all server operations.
+	Timeout time.Duration
+}
+
+// Service is the AdGuard Home web service.  A nil *Service is a valid service
+// that does nothing.
+type Service struct {
+	tls     *tls.Config
+	servers []*http.Server
+	timeout time.Duration
+}
+
+// New returns a new properly initialized *Service.  If c is nil, svc is a nil
+// *Service that does nothing.
+func New(c *Config) (svc *Service) {
+	if c == nil {
+		return nil
+	}
+
+	svc = &Service{
+		tls:     c.TLS,
+		timeout: c.Timeout,
+	}
+
+	mux := http.NewServeMux()
+	mux.HandleFunc("/health-check", svc.handleGetHealthCheck)
+
+	for _, a := range c.Addresses {
+		addr := a.String()
+		errLog := log.StdLog("websvc: http: "+addr, log.ERROR)
+		svc.servers = append(svc.servers, &http.Server{
+			Addr:              addr,
+			Handler:           mux,
+			ErrorLog:          errLog,
+			ReadTimeout:       c.Timeout,
+			WriteTimeout:      c.Timeout,
+			IdleTimeout:       c.Timeout,
+			ReadHeaderTimeout: c.Timeout,
+		})
+	}
+
+	for _, a := range c.SecureAddresses {
+		addr := a.String()
+		errLog := log.StdLog("websvc: https: "+addr, log.ERROR)
+		svc.servers = append(svc.servers, &http.Server{
+			Addr:              addr,
+			Handler:           mux,
+			TLSConfig:         c.TLS,
+			ErrorLog:          errLog,
+			ReadTimeout:       c.Timeout,
+			WriteTimeout:      c.Timeout,
+			IdleTimeout:       c.Timeout,
+			ReadHeaderTimeout: c.Timeout,
+		})
+	}
+
+	return svc
+}
+
+// Addrs returns all addresses on which this server serves the HTTP API.  Addrs
+// must not be called until Start returns.
+func (svc *Service) Addrs() (addrs []string) {
+	addrs = make([]string, 0, len(svc.servers))
+	for _, srv := range svc.servers {
+		addrs = append(addrs, srv.Addr)
+	}
+
+	return addrs
+}
+
+// handleGetHealthCheck is the handler for the GET /health-check HTTP API.
+func (svc *Service) handleGetHealthCheck(w http.ResponseWriter, _ *http.Request) {
+	_, _ = io.WriteString(w, "OK")
+}
+
+// unit is a convenient alias for struct{}.
+type unit = struct{}
+
+// type check
+var _ agh.Service = (*Service)(nil)
+
+// Start implements the agh.Service interface for *Service.  svc may be nil.
+// After Start exits, all HTTP servers have tried to start, possibly failing and
+// writing error messages to the log.
+func (svc *Service) Start() (err error) {
+	if svc == nil {
+		return nil
+	}
+
+	srvs := svc.servers
+
+	wg := &sync.WaitGroup{}
+	wg.Add(len(srvs))
+	for _, srv := range srvs {
+		go serve(srv, wg)
+	}
+
+	wg.Wait()
+
+	return nil
+}
+
+// serve starts and runs srv and writes all errors into its log.
+func serve(srv *http.Server, wg *sync.WaitGroup) {
+	addr := srv.Addr
+	defer log.OnPanic(addr)
+
+	var l net.Listener
+	var err error
+	if srv.TLSConfig == nil {
+		l, err = net.Listen("tcp", addr)
+	} else {
+		l, err = tls.Listen("tcp", addr, srv.TLSConfig)
+	}
+	if err != nil {
+		srv.ErrorLog.Printf("starting srv %s: binding: %s", addr, err)
+	}
+
+	// Update the server's address in case the address had the port zero, which
+	// would mean that a random available port was automatically chosen.
+	srv.Addr = l.Addr().String()
+
+	log.Info("websvc: starting srv http://%s", srv.Addr)
+	wg.Done()
+
+	err = srv.Serve(l)
+	if err != nil && !errors.Is(err, http.ErrServerClosed) {
+		srv.ErrorLog.Printf("starting srv %s: %s", addr, err)
+	}
+}
+
+// Shutdown implements the agh.Service interface for *Service.  svc may be nil.
+func (svc *Service) Shutdown(ctx context.Context) (err error) {
+	if svc == nil {
+		return nil
+	}
+
+	var errs []error
+	for _, srv := range svc.servers {
+		serr := srv.Shutdown(ctx)
+		if serr != nil {
+			errs = append(errs, fmt.Errorf("shutting down srv %s: %w", srv.Addr, serr))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.List("shutting down")
+	}
+
+	return nil
+}
diff --git a/internal/v1/websvc/websvc_test.go b/internal/v1/websvc/websvc_test.go
new file mode 100644
index 00000000..01b892cd
--- /dev/null
+++ b/internal/v1/websvc/websvc_test.go
@@ -0,0 +1,69 @@
+package websvc_test
+
+import (
+	"context"
+	"io"
+	"net"
+	"net/http"
+	"net/url"
+	"testing"
+	"time"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/v1/websvc"
+	"github.com/AdguardTeam/golibs/netutil"
+	"github.com/AdguardTeam/golibs/testutil"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+const testTimeout = 1 * time.Second
+
+func TestService_Start_getHealthCheck(t *testing.T) {
+	c := &websvc.Config{
+		TLS: nil,
+		Addresses: []*netutil.IPPort{{
+			IP:   net.IP{127, 0, 0, 1},
+			Port: 0,
+		}},
+		SecureAddresses: nil,
+		Timeout:         testTimeout,
+	}
+
+	svc := websvc.New(c)
+
+	err := svc.Start()
+	require.NoError(t, err)
+	t.Cleanup(func() {
+		ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+		t.Cleanup(cancel)
+
+		err = svc.Shutdown(ctx)
+		require.NoError(t, err)
+	})
+
+	addrs := svc.Addrs()
+	require.Len(t, addrs, 1)
+
+	u := &url.URL{
+		Scheme: "http",
+		Host:   addrs[0],
+		Path:   "/health-check",
+	}
+	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
+	require.NoError(t, err)
+
+	httpCli := &http.Client{
+		Timeout: testTimeout,
+	}
+	resp, err := httpCli.Do(req)
+	require.NoError(t, err)
+
+	testutil.CleanupAndRequireSuccess(t, resp.Body.Close)
+
+	assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+	body, err := io.ReadAll(resp.Body)
+	require.NoError(t, err)
+
+	assert.Equal(t, []byte("OK"), body)
+}
diff --git a/main.go b/main.go
index 505eb3e5..03ad2f03 100644
--- a/main.go
+++ b/main.go
@@ -1,3 +1,6 @@
+//go:build !v1
+// +build !v1
+
 package main
 
 import (
diff --git a/main_v1.go b/main_v1.go
new file mode 100644
index 00000000..6b5f3dea
--- /dev/null
+++ b/main_v1.go
@@ -0,0 +1,21 @@
+//go:build v1
+// +build v1
+
+package main
+
+import (
+	"embed"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/v1/cmd"
+)
+
+// Embed the prebuilt client here since we strive to keep .go files inside the
+// internal directory and the embed package is unable to embed files located
+// outside of the same or underlying directory.
+
+//go:embed build2
+var clientBuildFS embed.FS
+
+func main() {
+	cmd.Main(clientBuildFS)
+}
diff --git a/openapi/v1.yaml b/openapi/v1.yaml
new file mode 100644
index 00000000..30c318bc
--- /dev/null
+++ b/openapi/v1.yaml
@@ -0,0 +1,4913 @@
+'openapi': '3.0.3'
+'info':
+  'contact':
+    'email': 'devteam@adguard.com'
+    'name': 'AdGuard Home'
+    'url': 'https://github.com/AdguardTeam/AdGuardHome'
+  'description': |
+    **!! WARNING!  API IS AT THE DRAFT STAGE! THINGS WILL BREAK! !!**
+
+    AdGuard Home REST API, V1 **DRAFT**.  Our administration web interface is
+    built on top of this REST API.
+
+    This API is currently a **DRAFT** and is not covered by any stability
+    guarantees.  Once this API reaches maturity, the old `/control/` API will
+    mostly be removed.
+
+    ##  Information For API Users
+
+     *  Empty arrays are always sent by the backend, unless documented
+        otherwise.  If the backend doesn't, it's a backend error.
+
+     *  `PATCH` requests with JSON bodies use RFC 7396 JSON Merge Patch unless
+        documented otherwise.
+
+     *  The property `x-error-class` on plain text error responses suggests,
+        which class of error should be used if the API user wants to wrap it
+        into an object.  The property `x-error-code` suggest the error code for
+        the error object.  The code usually goes into the `code` property, and
+        the content, into `msg`.
+
+     *  The property `x-skip-web-api` on operations suggests API clients for
+        web, like our frontend, to skip this operation in their generated code.
+
+     *  The header `Server` will be set to `AdGuardHome/<<full-version>>`.  For
+        example: `AdGuardHome/v0.107.0-a.42+abcd1234`.
+
+    ##  Conventions For API Authors
+
+       ###  Naming
+
+     *  `CapitalCamelCase` for entities.
+
+     *  Initialisms are spelled like `DhcpSettings` and not `DHCPSettings`.
+
+     *  `lower_snake_case` for path and query parameters.
+
+     *  No unit suffices.
+
+     *  Path parameters's names start with `Path`; query, with `Query`.
+
+     *  Requests end with `Req`; responses, with `Resp`.
+
+       ###  Structure
+
+     *  Add `400` and `422` responses to requests that accept data.
+
+     *  Add `401` responses, unless the method can work without authorization.
+
+     *  Add `500` responses.
+
+     *  Descriptions are always on their own lines.
+
+     *  Don't add a description if there is already one a level above.
+
+     *  Five levels of indentation max, except for descriptions and array
+        items.
+
+     *  Keep things in alphabetical order.
+
+     *  Mark required things as such.  Document possibly-absent fields **both**
+        in `required` **and** in `description`, if there is one.
+
+     *  Prefer flat objects.  Example: `resp.top_user`, not `resp.top.user.val`.
+
+     *  Prefer to make it easier for the frontend where possible.
+
+     *  Provide examples for requests and responses.  If examples are provided
+        elsewhere, document that.
+
+     *  Summaries and descriptions with dots.
+
+     *  Top-level value in a JSON request or response must be an object.
+
+       ###  Types
+
+     *  Add `'maximum': 65535` for 16-bit unsigned integers (for example, port
+        numbers).
+
+     *  Add `'minimum': 0` for unsigned integers.
+
+     *  Duration in milliseconds.  Time in milliseconds in the Unix epoch.  Both
+        of type `double`, because that is easier for the JS frontend.
+
+     *  Integers are always `int64`, numbers are always `double.
+
+  'license':
+    'name': 'GNU General Public License v3.0'
+    'url': 'https://www.gnu.org/licenses/gpl-3.0.txt'
+  'title': 'AdGuard Home V1 DRAFT API'
+  'version': '0.108'
+
+'servers':
+- 'description': >
+    The V1 HTTP API namespace.
+  'url': '/api/v1'
+
+'security':
+- 'basicAuth': []
+
+'tags':
+- 'description': >
+    Authorization and account management.
+  'name': 'accounts'
+- 'description': >
+    Configuration and settings for Apple products.
+  'name': 'apple'
+- 'description': >
+    Runtime and persistent client information.
+  'name': 'clients'
+- 'description': >
+    DHCP server methods.
+  'name': 'dhcp'
+- 'description': >
+    First-time install configuration handlers.  Will not be available once the
+    installation is done.
+  'name': 'install'
+- 'description': >
+    Query logs.
+  'name': 'log'
+- 'description': >
+    Filter lists, blocked services, and custom filtering rules.
+  'name': 'protection'
+- 'description': >
+    Settings management.
+  'name': 'settings'
+- 'description': >
+    Query, filtering, system, and other statistics.
+  'name': 'stats'
+- 'description': >
+    Information about the AdGuard Home server and the host system.
+  'name': 'system'
+
+'paths':
+  '/health-check':
+    'get':
+      'operationId': 'HealthCheck'
+      'servers':
+        - 'url': '/'
+      'summary': 'Check if the server is up.'
+      'tags':
+      - 'system'
+
+  '/accounts/profile':
+    'get':
+      'operationId': 'GetV1AccountsProfile'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1AccountsProfileResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get the profile of the current user.'
+      'tags':
+      - 'accounts'
+    'patch':
+      'operationId': 'PatchV1AccountsProfile'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1AccountsProfileReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1AccountsProfileResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update the profile of the current user.'
+      'tags':
+      - 'accounts'
+
+  '/accounts/session':
+    'delete':
+      'operationId': 'DeleteV1AccountsSession'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Log out of the system.'
+      'tags':
+      - 'accounts'
+    'post':
+      'operationId': 'PostV1AccountsSession'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1AccountsSessionReq'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Log into the system.'
+      'tags':
+      - 'accounts'
+
+  '/apple/doh.mobileconfig':
+    'get':
+      'operationId': 'GetV1AppleDohMobileconfig'
+      'parameters':
+      - '$ref': '#/components/parameters/QueryClientId'
+      - '$ref': '#/components/parameters/QueryHost'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1AppleDohMobileconfigResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get a DNS-over-HTTPS .mobileconfig.'
+      'tags':
+      - 'apple'
+      'x-skip-web-api': true
+
+  '/apple/dot.mobileconfig':
+    'get':
+      'operationId': 'GetV1AppleDotMobileconfig'
+      'parameters':
+      - '$ref': '#/components/parameters/QueryHost'
+      - '$ref': '#/components/parameters/QueryClientId'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1AppleDotMobileconfigResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get a DNS-over-HTTPS .mobileconfig.'
+      'tags':
+      - 'apple'
+      'x-skip-web-api': true
+
+  '/clients/persistent':
+    'get':
+      'operationId': 'GetV1ClientsPersistent'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1ClientsPersistentResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get all persistent clients.'
+      'tags':
+      - 'clients'
+    'post':
+      'operationId': 'PostV1ClientsPersistent'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1ClientsPersistentReq'
+      'responses':
+        '201':
+          '$ref': '#/components/responses/PostV1ClientsPersistentResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Create a new persistent client.'
+      'tags':
+      - 'clients'
+
+  '/clients/persistent/{client_uid}':
+    'delete':
+      'operationId': 'DeleteV1ClientPersistent'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '404':
+          '$ref': '#/components/responses/NotFoundResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Delete a persistent client.'
+      'tags':
+      - 'clients'
+    'parameters':
+    - '$ref': '#/components/parameters/PathClientUid'
+    'patch':
+      'operationId': 'PatchV1ClientPersistent'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1ClientPersistentReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1ClientPersistentResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '404':
+          '$ref': '#/components/responses/NotFoundResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update a persistent client.'
+      'tags':
+      - 'clients'
+
+  '/clients/runtime':
+    'get':
+      'operationId': 'GetV1ClientsRuntime'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1ClientsRuntimeResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get all runtime clients.'
+      'tags':
+      - 'clients'
+
+  '/dhcp/leases':
+    'get':
+      'operationId': 'GetV1DhcpLeases'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1DhcpLeasesResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get all dynamic and static DHCP leases.'
+      'tags':
+      - 'dhcp'
+    'post':
+      'operationId': 'PostV1DhcpLeases'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1DhcpLeasesReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PostV1DhcpLeasesResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Create a new static DHCP lease.'
+      'tags':
+      - 'dhcp'
+
+  '/dhcp/leases/{lease_uid}':
+    'delete':
+      'operationId': 'DeleteV1DhcpLease'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '404':
+          '$ref': '#/components/responses/NotFoundResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Delete a static DHCP lease.'
+      'tags':
+      - 'dhcp'
+    'parameters':
+    - '$ref': '#/components/parameters/PathLeaseUid'
+    'patch':
+      'operationId': 'PatchV1DhcpLease'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1DhcpLeaseReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1DhcpLeaseResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '404':
+          '$ref': '#/components/responses/NotFoundResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update a static DHCP lease.'
+      'tags':
+      - 'dhcp'
+
+  '/dhcp/status':
+    'get':
+      'operationId': 'GetV1DhcpStatus'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1DhcpStatusResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get DHCP server status.'
+      'tags':
+      - 'dhcp'
+
+  '/install/check':
+    'post':
+      'operationId': 'PostV1InstallCheck'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1InstallCheckReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PostV1InstallCheckResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Check initial configuration.'
+      'tags':
+      - 'install'
+
+  '/install/configure':
+    'post':
+      'operationId': 'PostV1InstallConfigure'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1InstallConfigureReq'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Apply initial configuration.'
+      'tags':
+      - 'install'
+
+  '/install/info':
+    'get':
+      'operationId': 'GetV1InstallInfo'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1InstallInfoResp'
+        '404':
+          '$ref': '#/components/responses/NotFoundResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get initial configuration information.'
+      'tags':
+      - 'install'
+
+  '/log/clear':
+    'post':
+      'operationId': 'PostV1LogClear'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1LogClearReq'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Clear the whole query log.'
+      'tags':
+      - 'log'
+
+  '/log/search':
+    'get':
+      'operationId': 'GetV1LogSearch'
+      'parameters':
+      - '$ref': '#/components/parameters/QueryBefore'
+      - '$ref': '#/components/parameters/QueryLimit'
+      - '$ref': '#/components/parameters/QueryReason'
+      - '$ref': '#/components/parameters/QueryTerm'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1LogSearchResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Search query logs.'
+      'tags':
+      - 'log'
+
+  '/protection/blocked_services':
+    'get':
+      'operationId': 'GetV1ProtectionBlockedServices'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1ProtectionBlockedServicesResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get blocked services.'
+      'tags':
+      - 'protection'
+    'put':
+      'operationId': 'PutV1ProtectionBlockedServices'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PutV1ProtectionBlockedServicesReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1ProtectionBlockedServicesResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Replace blocked services.'
+      'tags':
+      - 'protection'
+
+  '/protection/check_custom_rules':
+    'post':
+      'operationId': 'PostV1ProtectionCheckCustomRules'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1ProtectionCheckCustomRulesReq'
+      'responses':
+        '201':
+          '$ref': '#/components/responses/PostV1ProtectionCheckCustomRulesResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Check custom filtering rules.'
+      'tags':
+      - 'protection'
+
+  '/protection/custom_rules':
+    'get':
+      'operationId': 'GetV1ProtectionCustomRules'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1ProtectionCustomRulesResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get custom rules.'
+      'tags':
+      - 'protection'
+    'put':
+      'operationId': 'PutV1ProtectionCustomRules'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PutV1ProtectionCustomRulesReq'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Replace custom rules.'
+      'tags':
+      - 'protection'
+
+  '/protection/dns_rewrites':
+    'get':
+      'operationId': 'GetV1ProtectionDnsRewrites'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1ProtectionDnsRewritesResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get all classic DNS rewrites.'
+      'tags':
+      - 'protection'
+    'post':
+      'operationId': 'PostV1ProtectionDnsRewrites'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1ProtectionDnsRewritesReq'
+      'responses':
+        '201':
+          '$ref': '#/components/responses/PostV1ProtectionDnsRewritesResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Add a new classic DNS rewrite.'
+      'tags':
+      - 'protection'
+
+  '/protection/dns_rewrites/{dns_rewrite_uid}':
+    'delete':
+      'operationId': 'DeleteV1ProtectionDnsRewrite'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '404':
+          '$ref': '#/components/responses/NotFoundResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Delete a classic DNS rewrite.'
+      'tags':
+      - 'protection'
+    'parameters':
+    - '$ref': '#/components/parameters/PathDnsRewriteUid'
+
+  '/protection/filters':
+    'get':
+      'operationId': 'GetV1ProtectionFilters'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1ProtectionFiltersResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get all filters.'
+      'tags':
+      - 'protection'
+    'post':
+      'operationId': 'PostV1ProtectionFilters'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1ProtectionFiltersReq'
+      'responses':
+        '201':
+          '$ref': '#/components/responses/PostV1ProtectionFiltersResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Add a new filter.'
+      'tags':
+      - 'protection'
+
+  '/protection/filters/{filter_uid}':
+    'delete':
+      'operationId': 'DeleteV1ProtectionFilter'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '404':
+          '$ref': '#/components/responses/NotFoundResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Delete a filter.'
+      'tags':
+      - 'protection'
+    'parameters':
+    - '$ref': '#/components/parameters/PathFilterUid'
+    'patch':
+      'operationId': 'PatchV1ProtectionFilter'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1ProtectionFilterReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1ProtectionFilterResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '404':
+          '$ref': '#/components/responses/NotFoundResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': >
+        Update a filter's settings.
+      'tags':
+      - 'protection'
+
+  '/protection/refresh_filters':
+    'post':
+      'operationId': 'PostV1ProtectionRefreshFilters'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1ProtectionRefreshFiltersReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PostV1ProtectionRefreshFiltersResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': >
+        Refresh all filters.
+      'tags':
+      - 'protection'
+
+  '/protection/refresh_filters/{filter_uid}':
+    'parameters':
+    - '$ref': '#/components/parameters/PathFilterUid'
+    'post':
+      'operationId': 'PostV1ProtectionRefreshFilter'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1ProtectionRefreshFilterReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PostV1ProtectionRefreshFilterResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': >
+        Refresh a filter.
+      'tags':
+      - 'protection'
+
+  '/settings/all':
+    'get':
+      'operationId': 'GetV1SettingsAll'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1SettingsAllResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get all settings.'
+      'tags':
+      - 'settings'
+
+  '/settings/dhcp':
+    'patch':
+      'operationId': 'PatchV1SettingsDhcp'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1SettingsDhcpReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1SettingsDhcpResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update DHCP server settings.'
+      'tags':
+      - 'settings'
+
+  '/settings/dns':
+    'patch':
+      'operationId': 'PatchV1SettingsDns'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1SettingsDnsReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1SettingsDnsResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update DNS server settings.'
+      'tags':
+      - 'settings'
+
+  '/settings/dns/access':
+    'get':
+      'description': >
+        Get DNS access settings.  This is a separate API, because these lists
+        can become quite big.
+      'operationId': 'GetV1SettingsDnsAccess'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1SettingsDnsAccessResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get DNS access settings.'
+      'tags':
+      - 'settings'
+    'put':
+      'description': >
+        Update DNS access settings.  This is a separate API, because these lists
+        can become quite big.
+      'operationId': 'PutV1SettingsDnsAccess'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PutV1SettingsDnsAccessReq'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update DNS access settings.'
+      'tags':
+      - 'settings'
+
+  '/settings/dns/check':
+    'post':
+      'operationId': 'PostV1SettingsDnsCheck'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1SettingsDnsCheckReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PostV1SettingsDnsCheckResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Check DNS upstream settings.'
+      'tags':
+      - 'settings'
+
+  '/settings/log':
+    'patch':
+      'operationId': 'PatchV1SettingsLog'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1SettingsLogReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1SettingsLogResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update query logging settings.'
+      'tags':
+      - 'settings'
+
+  '/settings/protection':
+    'patch':
+      'operationId': 'PatchV1SettingsProtection'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1SettingsProtectionReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1SettingsProtectionResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update protection settings.'
+      'tags':
+      - 'settings'
+
+  '/settings/stats':
+    'patch':
+      'operationId': 'PatchV1SettingsStats'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1SettingsStatsReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1SettingsStatsResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update statistics settings.'
+      'tags':
+      - 'settings'
+
+  '/settings/tls':
+    'patch':
+      'operationId': 'PatchV1SettingsTls'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1SettingsTlsReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1SettingsTlsResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update TLS and encryption settings.'
+      'tags':
+      - 'settings'
+
+  '/settings/tls/check':
+    'post':
+      'operationId': 'PostV1SettingsTlsCheck'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1SettingsTlsCheckReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PostV1SettingsTlsCheckResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Check TLS and encryption settings.'
+      'tags':
+      - 'settings'
+
+  '/stats/all':
+    'get':
+      'operationId': 'GetV1StatsAll'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1StatsAllResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get all statistics.'
+      'tags':
+      - 'stats'
+
+  '/stats/clear':
+    'post':
+      'operationId': 'PostV1StatsClear'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1StatsClearReq'
+      'responses':
+        '204':
+          '$ref': '#/components/responses/NoContentResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Clear all statistics.'
+      'tags':
+      - 'stats'
+
+  '/system/info':
+    'get':
+      'operationId': 'GetV1SystemInfo'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/GetV1SystemInfoResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Get server information.'
+      'tags':
+      - 'system'
+
+  '/system/reset':
+    'post':
+      'operationId': 'PostV1SystemReset'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1SystemResetReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PostV1SystemResetResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Reset all settings to defaults.'
+      'tags':
+      - 'system'
+
+  '/system/update':
+    'post':
+      'operationId': 'PostV1SystemUpdate'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PostV1SystemUpdateReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PostV1SystemUpdateResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update AdGuard Home.'
+      'tags':
+      - 'system'
+
+'components':
+  'parameters':
+    'PathDnsRewriteUid':
+      'description': >
+        DNS rewrite ID.
+      'example': 'abcd1234'
+      'in': 'path'
+      'name': 'dns_rewrite_uid'
+      'required': true
+      'schema':
+        '$ref': '#/components/schemas/Uid'
+
+    'PathClientUid':
+      'description': >
+        The unique ID of a client.
+      'example': 'abcd1234'
+      'in': 'path'
+      'name': 'client_uid'
+      'required': true
+      'schema':
+        '$ref': '#/components/schemas/Uid'
+
+    'PathFilterUid':
+      'description': >
+        The ID of a filter.
+      'example': 'abcd1234'
+      'in': 'path'
+      'name': 'filter_uid'
+      'required': true
+      'schema':
+        '$ref': '#/components/schemas/Uid'
+
+    'PathLeaseUid':
+      'description': >
+        The ID of a static lease.
+      'example': 'abcd1234'
+      'in': 'path'
+      'name': 'lease_uid'
+      'required': true
+      'schema':
+        '$ref': '#/components/schemas/Uid'
+
+    'QueryBefore':
+      'description': >
+        Unix time, before which to show the search results, in milliseconds.
+      'example': 1614345496000
+      'in': 'query'
+      'name': 'before'
+      'required': false
+      'schema':
+        'format': 'double'
+        'type': 'number'
+
+    'QueryClientId':
+      'description': >
+        ClientID, **not** its UID.
+      'example': 'client-1'
+      'in': 'query'
+      'name': 'client_id'
+      'required': false
+      'schema':
+        '$ref': '#/components/schemas/ClientId'
+
+    'QueryHost':
+      'description': >
+        The host for which the Configuration is generated.
+      'example': 'example.org'
+      'in': 'query'
+      'name': 'host'
+      'required': true
+      'schema':
+        'type': 'string'
+
+    'QueryLimit':
+      'description': >
+        Maximum amount of records to return.
+      'example': 100
+      'in': 'query'
+      'name': 'limit'
+      'required': false
+      'schema':
+        'format': 'int64'
+        'type': 'integer'
+
+    'QueryReason':
+      'description': >
+        Filter query log results by filtering reason.
+      'example': 'not_filtered_notfound'
+      'in': 'query'
+      'name': 'reason'
+      'required': false
+      'schema':
+        '$ref': '#/components/schemas/FilteringReason'
+
+    'QueryTerm':
+      'description': >
+        Search term.
+      'example': '127.0.0.1'
+      'in': 'query'
+      'name': 'term'
+      'required': false
+      'schema':
+        'type': 'string'
+
+  'requestBodies':
+    'PatchV1AccountsProfileReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1AccountsProfileReq'
+      'required': true
+
+    'PatchV1ClientPersistentReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1ClientPersistentReq'
+      'required': true
+
+    'PatchV1DhcpLeaseReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1DhcpLeaseReq'
+      'required': true
+
+    'PatchV1ProtectionFilterReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1ProtectionFilterReq'
+      'required': true
+
+    'PatchV1SettingsDhcpReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsDhcpReq'
+      'required': true
+
+    'PatchV1SettingsDnsReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsDnsReq'
+      'required': true
+
+    'PatchV1SettingsLogReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsLogReq'
+      'required': true
+
+    'PatchV1SettingsProtectionReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsProtectionReq'
+      'required': true
+
+    'PatchV1SettingsStatsReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsStatsReq'
+      'required': true
+
+    'PatchV1SettingsTlsReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsTlsReq'
+      'required': true
+
+    'PostV1AccountsSessionReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1AccountsSessionReq'
+      'required': true
+
+    'PostV1ClientsPersistentReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ClientsPersistentReq'
+      'required': true
+
+    'PostV1DhcpLeasesReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1DhcpLeasesReq'
+      'required': true
+
+    'PostV1InstallCheckReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1InstallCheckReq'
+      'required': true
+
+    'PostV1InstallConfigureReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1InstallConfigureReq'
+      'required': true
+
+    'PostV1LogClearReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1LogClearReq'
+      'required': true
+
+    'PostV1ProtectionCheckCustomRulesReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionCheckCustomRulesReq'
+      'required': true
+
+    'PostV1ProtectionDnsRewritesReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionDnsRewritesReq'
+      'required': true
+
+    'PostV1ProtectionFiltersReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionFiltersReq'
+      'required': true
+
+    'PostV1ProtectionRefreshFilterReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionRefreshFilterReq'
+      'required': true
+
+    'PostV1ProtectionRefreshFiltersReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionRefreshFiltersReq'
+      'required': true
+
+    'PostV1SettingsDnsCheckReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1SettingsDnsCheckReq'
+      'required': true
+
+    'PostV1SettingsTlsCheckReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1SettingsTlsCheckReq'
+      'required': true
+
+    'PostV1StatsClearReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1StatsClearReq'
+      'required': true
+
+    'PostV1SystemResetReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1SystemResetReq'
+      'required': true
+
+    'PostV1SystemUpdateReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1SystemUpdateReq'
+      'required': true
+
+    'PutV1ProtectionBlockedServicesReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PutV1ProtectionBlockedServicesReq'
+      'required': true
+
+    'PutV1ProtectionCustomRulesReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PutV1ProtectionCustomRulesReq'
+      'required': true
+
+    'PutV1SettingsDnsAccessReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PutV1SettingsDnsAccessReq'
+      'required': true
+
+  'responses':
+    'BadRequestResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/BadRequestResp'
+        'text/plain':
+          'example': >-
+            invalid character '{' looking for beginning of object key string
+          'x-error-class': '#/components/schemas/BadRequestResp'
+          'x-error-code': 'TXT400'
+      'description': >
+        Generic bad request response.  Sent when the request data is malformed
+        (for example, invalid JSON).
+
+    'GetV1AccountsProfileResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1AccountsProfileResp'
+      'description': >
+        A successful response to a `GET /api/v1/accounts/profile` request.
+
+    'GetV1AppleDohMobileconfigResp':
+      'content':
+        'application/xml':
+          'schema':
+            '$ref': '#/components/schemas/GetV1AppleDohMobileconfigResp'
+      'description': >
+        A successful response to a `GET /api/v1/apple/doh.mobileconfig` request.
+
+    'GetV1AppleDotMobileconfigResp':
+      'content':
+        'application/xml':
+          'schema':
+            '$ref': '#/components/schemas/GetV1AppleDotMobileconfigResp'
+      'description': >
+        A successful response to a `GET /api/v1/apple/dot.mobileconfig` request.
+
+    'GetV1ClientsPersistentResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1ClientsPersistentResp'
+      'description': >
+        A successful response to a `GET /api/v1/clients/persistent` request.
+
+    'GetV1ClientsRuntimeResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1ClientsRuntimeResp'
+      'description': >
+        A successful response to a `GET /api/v1/clients/runtime` request.
+
+    'GetV1DhcpLeasesResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1DhcpLeasesResp'
+      'description': >
+        A successful response to a `GET /api/v1/dhcp/leases` request.
+
+    'GetV1DhcpStatusResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1DhcpStatusResp'
+      'description': >
+        A successful response to a `GET /api/v1/dhcp/status` request.
+
+    'GetV1InstallInfoResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1InstallInfoResp'
+      'description': >
+        A successful response to a `GET /api/v1/install/info` request.
+
+    'GetV1LogSearchResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1LogSearchResp'
+      'description': >
+        A successful response to a `GET /api/v1/log/search` request.
+
+    'GetV1ProtectionBlockedServicesResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1ProtectionBlockedServicesResp'
+      'description': >
+        A successful response to a `GET /api/v1/protection/blocked_services`
+        or a `PUT /api/v1/protection/blocked_services` request.
+
+    'GetV1ProtectionCustomRulesResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1ProtectionCustomRulesResp'
+      'description': >
+        A successful response to a `GET /api/v1/protection/custom_rules`
+        request.
+
+    'GetV1ProtectionDnsRewritesResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1ProtectionDnsRewritesResp'
+      'description': >
+        A successful response to a `GET /api/v1/protection/dns_rewrites`
+        request.
+
+    'GetV1ProtectionFiltersResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1ProtectionFiltersResp'
+      'description': >
+        A successful response to a `GET /api/v1/protection/filters` request.
+
+    'GetV1SettingsAllResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1SettingsAllResp'
+      'description': >
+        A successful response to a `GET /api/v1/settings/all` request.
+
+    'GetV1SettingsDnsAccessResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1SettingsDnsAccessResp'
+      'description': >
+        A successful response to a `GET /api/v1/settings/dns/access` request.
+
+    'GetV1StatsAllResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1StatsAllResp'
+      'description': >
+        A successful response to a `GET /api/v1/stats/all` request.
+
+    'GetV1SystemInfoResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/GetV1SystemInfoResp'
+      'description': >
+        A successful response to a `GET /api/v1/server/info` request.
+
+    'InternalServerErrorResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/InternalServerErrorResp'
+        'text/plain':
+          'example': >-
+            runtime error: invalid memory address or nil pointer dereference
+          'x-error-class': '#/components/schemas/InternalServerErrorResp'
+          'x-error-code': 'TXT500'
+      'description': >
+        Generic internal server error.
+
+    'NoContentResp':
+      'description': >
+        Generic no-error no-content response.
+
+    'NotFoundResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/NotFoundResp'
+        'text/plain':
+          'example': >-
+            Not found.
+          'x-error-class': '#/components/schemas/NotFoundResp'
+          'x-error-code': 'TXT404'
+      'description': >
+        Generic not found response.
+
+    'PatchV1AccountsProfileResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1AccountsProfileResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/accounts/profile` request.
+
+    'PatchV1ClientPersistentResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1ClientPersistentResp'
+      'description': >
+        A successful response to
+        a `PATCH /api/v1/clients/persistent/{client_uid}` request.
+
+    'PatchV1DhcpLeaseResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1DhcpLeaseResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/dhcp/leases/{lease_uid}`
+        request.
+
+    'PatchV1ProtectionFilterResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1ProtectionFilterResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/filters/{filter_uid}` request.
+
+    'PatchV1SettingsDhcpResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsDhcpResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/settings/dhcp` request.
+
+    'PatchV1SettingsDnsResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsDnsResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/settings/dns` request.
+
+    'PatchV1SettingsLogResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsLogResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/settings/log` request.
+
+    'PatchV1SettingsProtectionResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsProtectionResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/settings/protection` request.
+
+    'PatchV1SettingsStatsResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsStatsResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/settings/stats` request.
+
+    'PatchV1SettingsTlsResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsTlsResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/settings/tls` request.
+
+    'PostV1ClientsPersistentResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ClientsPersistentResp'
+      'description': >
+        A successful response to a `POST /api/v1/clients/persistent` request.
+
+    'PostV1DhcpLeasesResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1DhcpLeasesResp'
+      'description': >
+        A successful response to a `POST /api/v1/dhcp/leases` request.
+
+    'PostV1InstallCheckResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1InstallCheckResp'
+      'description': >
+        A successful response to a `POST /api/v1/install/check` request.
+
+    'PostV1ProtectionCheckCustomRulesResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionCheckCustomRulesResp'
+      'description': >
+        A successful response to a `POST /api/v1/protection/check_custom_rules`
+        request.
+
+    'PostV1ProtectionDnsRewritesResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionDnsRewritesResp'
+      'description': >
+        A successful response to a `POST /api/v1/protection/dns_rewrites`
+        request.
+
+    'PostV1ProtectionFiltersResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionFiltersResp'
+      'description': >
+        A successful response to a `POST /api/v1/protection/filters` request.
+
+    'PostV1ProtectionRefreshFilterResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionRefreshFilterResp'
+      'description': >
+        A successful response to
+        a `POST /api/v1/protection/refresh_filters/{filter_uid}` request.
+
+    'PostV1ProtectionRefreshFiltersResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1ProtectionRefreshFiltersResp'
+      'description': >
+        A successful response to a `POST /api/v1/protection/refresh_filters`
+        request.
+
+    'PostV1SettingsDnsCheckResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1SettingsDnsCheckResp'
+      'description': >
+        A successful response to a `POST /api/v1/settings/dns/check` request.
+
+    'PostV1SettingsTlsCheckResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1SettingsTlsCheckResp'
+      'description': >
+        A successful response to a `POST /api/v1/settings/tls/check` request.
+
+    'PostV1SystemResetResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1SystemResetResp'
+      'description': >
+        A successful response to a `POST /api/v1/system/reset` request.
+
+    'PostV1SystemUpdateResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PostV1SystemUpdateResp'
+      'description': >
+        A successful response to a `POST /api/v1/system/update` request.
+
+    'UnauthorizedResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/UnauthorizedResp'
+        'text/plain':
+          'example': 'no or bad authorization provided'
+          'x-error-class': '#/components/schemas/UnauthorizedResp'
+          'x-error-code': 'TXT401'
+      'description': >
+        This API requires authorization.
+      'headers':
+        'WWW-Authenticate':
+          'description': >
+            The required WWW-Authenticate header.
+          'example': 'Basic realm="AdGuard Home", charset="UTF-8"'
+          'required': true
+          'schema':
+            'type': 'string'
+
+    'UnprocessableEntityResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/UnprocessableEntityResp'
+      'description': >
+        Generic bad request data response.  Sent when the request data is
+        well-formed but is invalid for this request.
+
+  'schemas':
+    'BadRequestResp':
+      'example':
+        'code': 'JSN000'
+        'msg': >-
+          invalid character '{' looking for beginning of object key string
+      'properties':
+        'code':
+          '$ref': '#/components/schemas/ErrorCode'
+        'msg':
+          'description': >
+            Error message string.
+          'type': 'string'
+      'required':
+      - 'code'
+      - 'msg'
+      'type': 'object'
+
+    'BlockedServiceId':
+      'description': >
+        ID of a blocked service.
+      'enum':
+      - '9gag'
+      - 'amazon'
+      - 'cloudflare'
+      - 'dailymotion'
+      - 'discord'
+      - 'disneyplus'
+      - 'ebay'
+      - 'epic_games'
+      - 'facebook'
+      - 'hulu'
+      - 'imgur'
+      - 'instagram'
+      - 'mail_ru'
+      - 'netflix'
+      - 'ok'
+      - 'origin'
+      - 'pinterest'
+      - 'qq'
+      - 'reddit'
+      - 'skype'
+      - 'snapchat'
+      - 'spotify'
+      - 'steam'
+      - 'telegram'
+      - 'tiktok'
+      - 'tinder'
+      - 'twitch'
+      - 'twitter'
+      - 'viber'
+      - 'vimeo'
+      - 'vk'
+      - 'wechat'
+      - 'weibo'
+      - 'whatsapp'
+      - 'youtube'
+      'type': 'string'
+
+    'BlockedServices':
+      'description': >
+        Blocked services.
+      'example':
+        'services':
+        - '9gag'
+        - 'dailymotion'
+      'properties':
+        'services':
+          'description': >
+            All blocked services.
+          'items':
+            '$ref': '#/components/schemas/BlockedServiceId'
+          'type': 'array'
+      'required':
+      - 'services'
+      'type': 'object'
+
+    'Channel':
+      'description': >
+        AdGuard Home release channel.
+      'enum':
+      - 'beta'
+      - 'development'
+      - 'edge'
+      - 'release'
+      'type': 'string'
+
+    'ClientId':
+      'pattern': '[0-9a-z-]{1,64}'
+      'type': 'string'
+
+    'ClientInfo':
+      'description': >
+        A shorter information about a client.  If the `uid` field is present,
+        this is a persistent client.  Otherwise, this is a runtime client.
+      'properties':
+        'blocked':
+          'description': >
+            If `true`, client is blocked.
+          'type': 'boolean'
+        'ids':
+          'description': |
+            Client identifiers.  That includes ClientIDs set by users as well as
+            IP addresses.  There must be at least one identifier.
+
+            Not to be confused with the `uid` field.
+          'example':
+          - '1.2.3.4'
+          - 'user-1'
+          'items':
+            'type': 'string'
+          'minItems': 1
+          'type': 'array'
+        'name':
+          'description': >
+            The name of the client, if any.  If there are none, this field is
+            absent.
+          'example': 'User 1'
+          'type': 'string'
+        'num':
+          'description': >
+            Total number of requests for this client.
+          'example': 1000
+          'format': 'int64'
+          'type': 'integer'
+        'num_blocked':
+          'description': >
+            Total number of blocked requests for this client.
+          'example': 1000
+          'format': 'int64'
+          'type': 'integer'
+        'uid':
+          '$ref': '#/components/schemas/Uid'
+        'whois':
+          '$ref': '#/components/schemas/Whois'
+      'required':
+      - 'blocked'
+      - 'ids'
+      - 'num'
+      - 'num_blocked'
+      'type': 'object'
+
+    'CustomRules':
+      'description': >
+        Custom filtering rules.
+      'example':
+        'rules':
+        - '||example.com'
+        - '# Some comment'
+      'properties':
+        'rules':
+          'description': >
+            All custom filtering rules
+          'items':
+            'type': 'string'
+          'type': 'array'
+      'required':
+      - 'rules'
+      'type': 'object'
+
+    'DhcpLease':
+      'allOf':
+      - '$ref': '#/components/schemas/DhcpLeasePost'
+      - 'description': >
+          A dynamic or static DHCP lease.  If the `uid` field is present, this is
+          a static lease.  Otherwise, this is a dynamic lease.
+        'example':
+          'expires': 1614345496000
+          'hostname': 'my-mobile'
+          'ip': '192.168.1.2'
+          'mac': '01:23:45:67:89:ab'
+          'uid': 'abcd1234'
+        'properties':
+          'uid':
+            '$ref': '#/components/schemas/Uid'
+
+    'DhcpLeasePatch':
+      'description': >
+        A static DHCP lease update object.
+      'example':
+        'expires': 1614345496000
+      'properties':
+        'expires':
+          'description': >
+            The Unix time of the lease's expiry time, in milliseconds.
+          'format': 'double'
+          'type': 'number'
+        'hostname':
+          'description': >
+            Client's hostname.
+          'type': 'string'
+        'ip':
+          'description': >
+            IP address leased to the client.
+          'type': 'string'
+        'mac':
+          'description': >
+            Hardware address of the lease client.
+          'type': 'string'
+      'type': 'object'
+
+    'DhcpLeasePost':
+      'allOf':
+      - '$ref': '#/components/schemas/DhcpLeasePatch'
+      - 'description': >
+          A static DHCP lease create object.
+        'example':
+          'expires': 1614345496000
+          'hostname': 'my-mobile'
+          'ip': '192.168.1.2'
+          'mac': '01:23:45:67:89:ab'
+        'required':
+        - 'expires'
+        - 'hostname'
+        - 'ip'
+        - 'mac'
+
+    'DhcpSettings':
+      'allOf':
+      - '$ref': '#/components/schemas/DhcpSettingsPatch'
+      - 'description': >
+          DHCP server settings.
+        'example':
+          'enabled': true
+          'interface_name': 'wlan0'
+          'ipv4_gateway_ip': '192.168.1.1'
+          'ipv4_lease_duration': 86400000
+          'ipv4_range_end': '192.168.1.101'
+          'ipv4_range_start': '192.168.1.2'
+          'ipv4_subnet_mask': '255.255.255.0'
+          'ipv6_range_start': '2001:db8::1'
+          'ipv6_lease_duration': 86400000
+        'required':
+        - 'enabled'
+
+    'DhcpSettingsPatch':
+      'description': >
+        DHCP server settings update object.
+      'example':
+        'enabled': true
+        'interface_name': 'wlan0'
+        'ipv4_gateway_ip': '192.168.1.1'
+        'ipv4_lease_duration': 86400000
+        'ipv4_range_end': '192.168.1.101'
+        'ipv4_range_start': '192.168.1.2'
+        'ipv4_subnet_mask': '255.255.255.0'
+      'properties':
+        'enabled':
+          'description': >
+            If `true`, the DHCP server is enabled.
+          'type': 'boolean'
+        'interface_name':
+          'description': >
+            The name of network interface to serve on.
+          'type': 'string'
+        'ipv4_gateway_ip':
+          'description': >
+            The IP address of the gateway.
+          'type': 'string'
+        'ipv4_lease_duration':
+          'description': >
+            The duration of the IPv4 lease, in milliseconds.
+          'type': 'number'
+        'ipv4_range_end':
+          'description': >
+            The end of the IPv4 addresses to serve to clients.
+          'type': 'string'
+        'ipv4_range_start':
+          'description': >
+            The start of the IPv4 addresses to serve to clients.
+          'type': 'string'
+        'ipv4_subnet_mask':
+          'description': >
+            The IP subnet mask.
+          'type': 'string'
+        'ipv6_lease_duration':
+          'description': >
+            The duration of the IPv6 lease, in milliseconds.
+          'type': 'number'
+        'ipv6_range_start':
+          'description': >
+            The start of the IPv6 addresses to serve to clients.
+          'type': 'string'
+      'type': 'object'
+
+    'DnsAccessSettings':
+      'description': >
+        DNS server access settings.
+      'example':
+        'allowed_clients': []
+        'blocked_clients':
+        - '1.2.3.4'
+        - '5.6.7.8/16'
+        'blocked_domain_rules':
+        - 'id.server'
+        - '*.example.org'
+        - '||example.com^'
+      'properties':
+        'allowed_clients':
+          'description': >
+            CIDR or IP addresses of clients in the allowlist.  If non-empty,
+            AdGuard Home will accept requests from these IP addresses only.
+          'items':
+            'type': 'string'
+          'type': 'array'
+        'blocked_clients':
+          'description': >
+            CIDR or IP addresses of clients in the blocklist.  If non-empty,
+            AdGuard Home will drop requests from these IP addresses.
+          'items':
+            'type': 'string'
+          'type': 'array'
+        'blocked_domain_rules':
+          'description': >
+            AdGuard Home will drop DNS queries, if the domains in their queries
+            match these rules.  Here you can specify the exact domain
+            names, wildcards, and `urlfilter` rules.  Examples:
+
+             *  `example.org`
+
+             *  `*.example.org`
+
+             *  `||example.org^`
+          'items':
+            'type': 'string'
+          'type': 'array'
+      'required':
+      - 'allowed_clients'
+      - 'blocked_clients'
+      - 'blocked_domain_rules'
+      'type': 'object'
+
+    'DnsBlockingMode':
+      'description': |
+        DNS blocking mode.
+
+         *  `custom_ip`: Respond with a custom IP address.  If this mode is
+             selected, both `blocking_ipv4` and `blocking_ipv6` parameters must
+             be set.
+
+         *  `default`: Same as `null_ip` for Adblock-style rules, but respond
+             with the IP address specified in the rule when blocked by an
+            `/etc/hosts`-style rule.
+
+         *  `null_ip`: Respond with a zero IP address: `0.0.0.0` for `A`
+             requests and `::` for `AAAA` ones.
+
+         *  `nxdomain`: Respond with the `NXDOMAIN` code.
+
+         *  `refused`: Respond with the `REFUSET` code.
+
+      'enum':
+      - 'custom_ip'
+      - 'default'
+      - 'null_ip'
+      - 'nxdomain'
+      - 'refused'
+      'type': 'string'
+
+    'DnsClass':
+      'description': >
+        DNS resource record class, aka `CLASS`.
+      'enum':
+      - 'any'
+      - 'ch'
+      - 'cs'
+      - 'hs'
+      - 'in'
+      'type': 'string'
+
+    'DnsProto':
+      'description': >
+        DNS protocol.
+      'enum':
+      - 'dot'
+      - 'doh'
+      - 'doq'
+      - 'dnscrypt'
+      - 'udp'
+      'type': 'string'
+
+    'DnsResponseCode':
+      'description': >
+        DNS response code, aka `RCODE`.
+      'enum':
+      - 'badalg'
+      - 'badcookie'
+      - 'badkey'
+      - 'badmode'
+      - 'badname'
+      - 'badsig'
+      - 'badtime'
+      - 'badtrunc'
+      - 'badvers'
+      - 'formerr'
+      - 'noerror'
+      - 'notauth'
+      - 'notimp'
+      - 'notzone'
+      - 'nxdomain'
+      - 'nxrrset'
+      - 'refused'
+      - 'servfail'
+      - 'yxdomain'
+      - 'yxrrset'
+      'type': 'string'
+
+    'DnsRewrite':
+      'allOf':
+      - '$ref': '#/components/schemas/DnsRewritePost'
+      - 'description': >
+          A classic DNS rewrite.
+        'example':
+          'answer': 'A'
+          'domain': 'example.com'
+          'id': 'abcd1234'
+        'properties':
+          'id':
+            '$ref': '#/components/schemas/Uid'
+        'required':
+        - 'answer'
+        - 'domain'
+        - 'id'
+        'type': 'object'
+
+    'DnsRewritePost':
+      'description': >
+        A classic DNS rewrite create object.
+      'example':
+        'answer': 'A'
+        'domain': 'example.com'
+      'properties':
+        'answer':
+          'description': >
+            The value of an `A`, `AAAA`, or `CNAME` DNS record in the response.
+            Acceptable formats:
+
+             *   Domain name: add a `CNAME` record with this domain name.
+
+             *   IPv4 address: use this IP in an `A` response.
+
+             *   IPv6 address: use this IP in an `AAAA` response.
+
+             *   The literal `A`: keep only `A` records from the upstream
+                 response.
+
+             *   The literal `AAAA`: keep only `AAAA` records from the upstream
+                 response.
+          'type': 'string'
+        'domain':
+          'description': >
+            Domain name or wildcard.
+          'type': 'string'
+      'required':
+      - 'answer'
+      - 'domain'
+      'type': 'object'
+
+    'DnsSettings':
+      'allOf':
+      - '$ref': '#/components/schemas/DnsSettingsPatch'
+      - 'description': >
+          DNS server settings.
+        'example':
+          'blocking_mode': 'default'
+          'bootstrap_servers':
+          - '9.9.9.10'
+          - '149.112.112.10'
+          'cache_size': 4194304
+          'cache_ttl_max': 0
+          'cache_ttl_min': 0
+          'dnssec': false
+          'edns_client_subnet': false
+          'ipv6': true
+          'rate_limit': 20
+          'upstream_mode': 'load_balancing'
+          'upstream_servers':
+          - '1.1.1.1'
+          - '8.8.8.8'
+        'required':
+        - 'blocking_mode'
+        - 'bootstrap_servers'
+        - 'cache_size'
+        - 'cache_ttl_max'
+        - 'cache_ttl_min'
+        - 'dnssec'
+        - 'edns_client_subnet'
+        - 'ipv6'
+        - 'rate_limit'
+        - 'upstream_mode'
+        - 'upstream_servers'
+
+    'DnsSettingsPatch':
+      'description': >
+        DNS server settings update object.
+      'example':
+        'cache_size': 4194304
+        'upstream_servers':
+        - '1.1.1.1'
+      'properties':
+        'blocking_ipv4':
+          'description': >
+            IPv4 address to respond with when `blocking_mode` is `custom_ip`.
+            See the documentation for the `DnsBlockingMode` schema.  If
+            `blocking_mode` is different from `custom_ip`, this property is not
+            included.
+          'type': 'string'
+        'blocking_ipv6':
+          'description': >
+            IPv6 address to respond with when `blocking_mode` is `custom_ip`.
+            See the documentation for the `DnsBlockingMode` schema.  If
+            `blocking_mode` is different from `custom_ip`, this property is not
+            included.
+          'type': 'string'
+        'blocking_mode':
+          '$ref': '#/components/schemas/DnsBlockingMode'
+        'bootstrap_servers':
+          'description': |
+            Bootstrap DNS servers' IP addresses to resolve the hostnames of the
+            encrypted DNS server providers.
+          'items':
+            'type': 'string'
+          'type': 'array'
+        'cache_size':
+          'description': >
+            DNS cache size in bytes.
+          'format': 'int64'
+          'minimum': 0
+          'type': 'integer'
+        'cache_ttl_max':
+          'description': >
+            Set a maximum time-to-live value for entries in the DNS cache.  `0`
+            means no override.  The value is in **seconds**, like in DNS record
+            headers.
+          'format': 'int64'
+          'minimum': 0
+          'type': 'integer'
+        'cache_ttl_min':
+          'description': >
+            Extend short time-to-live values received from the upstream server
+            when caching DNS responses.  `0` means no override.  TThe value is
+            in **seconds**, like in DNS record headers.
+          'format': 'int64'
+          'minimum': 0
+          'type': 'integer'
+        'dnssec':
+          'description': >
+            If `true`, set DNSSEC flag in outcoming DNS queries and check the
+            result.  A DNSSEC-enabled resolver is required.
+          'type': 'boolean'
+        'edns_client_subnet':
+          'description': >
+            If `true`, enable EDNS Client Subnet support and send clients'
+            subnets to DNS servers.
+          'type': 'boolean'
+        'ipv6':
+          'description': >
+            If `true`, accept `AAAA` DNS queries.  If `false`, respond to them
+            with an empty answer.
+          'type': 'boolean'
+        'rate_limit':
+          'description': >
+            The number of requests per second that a single client is allowed to
+            make.  `0` means no limit.
+          'format': 'int64'
+          'minimum': 0
+          'type': 'integer'
+        'upstream_mode':
+          '$ref': '#/components/schemas/DnsUpstreamMode'
+        'upstream_servers':
+          'description': >
+            Upstream DNS servers.
+          'items':
+            '$ref': '#/components/schemas/UpstreamServerAddr'
+          'type': 'array'
+      'type': 'object'
+
+    'DnsType':
+      'description': >
+        DNS resource record type, aka `TYPE`.
+      'enum':
+      - 'a'
+      - 'aaaa'
+      - 'afsdb'
+      - 'any'
+      - 'apl'
+      - 'atma'
+      - 'avc'
+      - 'axfr'
+      - 'caa'
+      - 'cdnskey'
+      - 'cds'
+      - 'cert'
+      - 'cname'
+      - 'csync'
+      - 'dhcid'
+      - 'dlv'
+      - 'dname'
+      - 'dnskey'
+      - 'ds'
+      - 'eid'
+      - 'eui48'
+      - 'eui64'
+      - 'gid'
+      - 'gpos'
+      - 'hinfo'
+      - 'hip'
+      - 'https'
+      - 'isdn'
+      - 'ixfr'
+      - 'key'
+      - 'kx'
+      - 'l32'
+      - 'l64'
+      - 'loc'
+      - 'lp'
+      - 'maila'
+      - 'mailb'
+      - 'mb'
+      - 'md'
+      - 'mf'
+      - 'mg'
+      - 'minfo'
+      - 'mr'
+      - 'mx'
+      - 'naptr'
+      - 'nid'
+      - 'nimloc'
+      - 'ninfo'
+      - 'ns'
+      - 'nsap-ptr'
+      - 'nsec'
+      - 'nsec3'
+      - 'nsec3param'
+      - 'null'
+      - 'nxt'
+      - 'openpgpkey'
+      - 'opt'
+      - 'ptr'
+      - 'px'
+      - 'rkey'
+      - 'rp'
+      - 'rrsig'
+      - 'rt'
+      - 'sig'
+      - 'smimea'
+      - 'soa'
+      - 'spf'
+      - 'srv'
+      - 'sshfp'
+      - 'svcb'
+      - 'ta'
+      - 'talink'
+      - 'tkey'
+      - 'tlsa'
+      - 'tsig'
+      - 'txt'
+      - 'uid'
+      - 'uinfo'
+      - 'unspec'
+      - 'uri'
+      - 'x25'
+      'type': 'string'
+
+    'DnsUpstreamMode':
+      'description': |
+        Upstream request mode.
+
+         *  `fastest`: Query all DNS servers and return the IP address that was
+             returned by the fastest response.  Slows down DNS responses, since
+             it waits for responses from all upstreams, but improves the overall
+             connectivity.
+
+         *  `load_balancing`: Query one server at a time using a weighted random
+             algorithm picking the server so that the fastest server is used
+             more often.
+
+         *  `parallel`: Use parallel requests to speed up resolving by
+             simultaneously querying all upstream servers.
+      'enum':
+      - 'fastest'
+      - 'load_balancing'
+      - 'parallel'
+      'type': 'string'
+
+    'ErrorCode':
+      'description': |
+        An error code.
+
+         *  `AUT000`:  No or bad authorization credentials provided.
+
+         *  `ENT404`:  Entity not found; as opposed to path not found.
+
+         *  `JSN000`:  A JSON syntax error.
+
+         *  `JSN001`:  A JSON type error.
+
+         *  `OSS000`:  The server's operating system doesn't support the
+             requested functionality.
+
+         *  `PTH404`:  Path not found; as opposed to entity not found.
+
+         *  `RNT000`:  A server runtime error.
+
+         *  `TXT400`:  A plaintext bad request error.  Used when a plaintext
+             error is wrapped.
+
+         *  `TXT401`:  A plaintext unauthorized error.  Used when a plaintext
+             error is wrapped.
+
+         *  `TXT404`:  A plaintext not found error.  Used when a plaintext error
+             is wrapped.
+
+         *  `TXT500`:  A plaintext internal server error.  Used when a plaintext
+             error is wrapped.
+
+        TODO(a.garipov): Expand with TLS validation errors, DHCP errors, filter
+        URL reaching errors, OS and I/O errors, and so on.
+      'enum':
+      - 'AUT000'
+      - 'ENT404'
+      - 'JSN000'
+      - 'JSN001'
+      - 'OSS000'
+      - 'PTH404'
+      - 'RNT000'
+      - 'TXT400'
+      - 'TXT401'
+      - 'TXT404'
+      - 'TXT500'
+      'type': 'string'
+
+    'Filter':
+      'allOf':
+      - '$ref': '#/components/schemas/FilterPatch'
+      - 'description': >
+          A single filter list of rules.
+        'example':
+          'allowlist': false
+          'enabled': true
+          'name': 'AdMaster 5000 Super List v2.0 Final'
+          'num_rules': 36766
+          'refreshed': 1614345496000
+          'uid': 'abcd1234'
+          'url': 'https://admaster.example.com/list.txt'
+        'properties':
+          'num_rules':
+            'description': >
+              Number of rules in this filter.
+            'format': 'int64'
+            'minimum': 0
+            'type': 'integer'
+          'refreshed':
+            'description': >
+              Unix time of last refresh for this filter, in milliseconds.
+            'format': 'double'
+            'type': 'number'
+          'uid':
+            '$ref': '#/components/schemas/Uid'
+        'required':
+        - 'allowlist'
+        - 'enabled'
+        - 'name'
+        - 'num_rules'
+        - 'refreshed'
+        - 'uid'
+        - 'url'
+
+    'FilterPatch':
+      'description': >
+        A filter update object.
+      'example':
+        'enabled': true
+      'properties':
+        'allowlist':
+          'description': >
+            If `true`, this filter works as an allowlist filters.
+          'type': 'boolean'
+        'enabled':
+          'description': >
+            If `true`, this filter is applied.
+          'type': 'boolean'
+        'name':
+          'description': >
+            The name of this filter.
+          'type': 'string'
+        'url':
+          'description': |
+            A URL of the file containing filtering rules.
+
+            Examples of allowed schemes:
+
+             *  `file:///home/user/ads/rules.txt`: A local file.
+
+             *  `http://example.com/ads/rules.txt`: Remote list, fetched over
+                 plain HTTP.
+
+             *  `https://example.com/ads/rules.txt`: Remote list, fetched over
+                 HTTPS.
+          'type': 'string'
+      'type': 'object'
+
+    'FilterPost':
+      'allOf':
+      - '$ref': '#/components/schemas/FilterPatch'
+      - 'description': >
+          A filter create object.
+        'example':
+          'allowlist': false
+          'enabled': true
+          'name': 'AdMaster 5000 Super List v2.0 Final'
+          'url': 'https://admaster.example.com/list.txt'
+        'required':
+        - 'allowlist'
+        - 'enabled'
+        - 'name'
+        - 'url'
+
+    'FilteringReason':
+      'description': >
+        Request filtering status.
+      'enum':
+      - 'filtered_blocked_service'
+      - 'filtered_blocklist'
+      - 'filtered_invalid'
+      - 'filtered_parental'
+      - 'filtered_safe_browsing'
+      - 'filtered_safe_search'
+      - 'not_filtered_allowlist'
+      - 'not_filtered_error'
+      - 'not_filtered_notfound'
+      - 'rewrite'
+      - 'rewrite_etc_hosts'
+      - 'rewrite_rule'
+      'type': 'string'
+
+    'FilteringResultRule':
+      'description': >
+        Applied filtering rule.
+      'properties':
+        'filter_list_uid':
+          '$ref': '#/components/schemas/Uid'
+        'text':
+          'description': >
+            The text of the filtering rule applied to the request, if any.
+          'type': 'string'
+      'required':
+      - 'filter_list_uid'
+      - 'text'
+      'type': 'object'
+
+    'GetV1AccountsProfileResp':
+      '$ref': '#/components/schemas/Profile'
+
+    # TODO(a.garipov): Find a way to describe such XML documents using OpenAPI.
+    # If that is even possible.
+    'GetV1AppleDohMobileconfigResp':
+      'example': |
+        <?xml version="1.0" encoding="UTF-8"?>
+        <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+        <plist version="1.0">
+          <dict>
+            <key>PayloadContent</key>
+            <array>
+              <dict>
+                <key>DNSSettings</key>
+                <dict>
+                  <key>DNSProtocol</key>
+                  <string>HTTPS</string>
+                  <key>ServerName</key>
+                  <string>example.com</string>
+                  <key>ServerURL</key>
+                  <string>https://example.com/dns-query/123</string>
+                </dict>
+                <key>Name</key>
+                <string>myexample.local DoH</string>
+                <key>PayloadDescription</key>
+                <string>Configures device to use AdGuard Home</string>
+                <key>PayloadDisplayName</key>
+                <string>myexample.local DoH</string>
+                <key>PayloadIdentifier</key>
+                <string>com.apple.dnsSettings.managed.b6928468-ae3a-4368-a70d-cb7122275013</string>
+                <key>PayloadType</key>
+                <string>com.apple.dnsSettings.managed</string>
+                <key>PayloadUUID</key>
+                <string>18526b8c-6065-4b96-b635-9cde769ac0f2</string>
+                <key>PayloadVersion</key>
+                <integer>1</integer>
+              </dict>
+            </array>
+            <key>PayloadDescription</key>
+            <string>Adds AdGuard Home to Big Sur and iOS 14 or newer systems</string>
+            <key>PayloadDisplayName</key>
+            <string>myexample.local DoH</string>
+            <key>PayloadIdentifier</key>
+            <string>9a37b659-7541-4f9e-8b4d-6e2a59a123c8</string>
+            <key>PayloadRemovalDisallowed</key>
+            <false/>
+            <key>PayloadType</key>
+            <string>Configuration</string>
+            <key>PayloadUUID</key>
+            <string>255dbaf7-0c52-4855-9b22-ad8209690197</string>
+            <key>PayloadVersion</key>
+            <integer>1</integer>
+          </dict>
+        </plist>
+      'type': 'object'
+
+    # TODO(a.garipov): See the comment on GetV1AppleDohMobileconfigResp.
+    'GetV1AppleDotMobileconfigResp':
+      'example': |
+        <?xml version="1.0" encoding="UTF-8"?>
+        <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+        <plist version="1.0">
+          <dict>
+            <key>PayloadContent</key>
+            <array>
+              <dict>
+                <key>DNSSettings</key>
+                <dict>
+                  <key>DNSProtocol</key>
+                  <string>TLS</string>
+                  <key>ServerName</key>
+                  <string>123.example.com</string>
+                </dict>
+                <key>Name</key>
+                <string>example.com DoT</string>
+                <key>PayloadDescription</key>
+                <string>Configures device to use AdGuard Home</string>
+                <key>PayloadDisplayName</key>
+                <string>example.com DoT</string>
+                <key>PayloadIdentifier</key>
+                <string>com.apple.dnsSettings.managed.7807cb66-c6ec-4c78-be29-d8ffcb3321ee</string>
+                <key>PayloadType</key>
+                <string>com.apple.dnsSettings.managed</string>
+                <key>PayloadUUID</key>
+                <string>b0fb9137-e27a-4f95-abc3-556103ad9ac1</string>
+                <key>PayloadVersion</key>
+                <integer>1</integer>
+              </dict>
+            </array>
+            <key>PayloadDescription</key>
+            <string>Adds AdGuard Home to Big Sur and iOS 14 or newer systems</string>
+            <key>PayloadDisplayName</key>
+            <string>myexample.local DoT</string>
+            <key>PayloadIdentifier</key>
+            <string>f1095036-406e-4243-8210-cf0ffa52b3f6</string>
+            <key>PayloadRemovalDisallowed</key>
+            <false/>
+            <key>PayloadType</key>
+            <string>Configuration</string>
+            <key>PayloadUUID</key>
+            <string>21cd3597-0769-486a-86d0-7b5e32d24305</string>
+            <key>PayloadVersion</key>
+            <integer>1</integer>
+          </dict>
+        </plist>
+      'type': 'object'
+
+    'GetV1ClientsPersistentResp':
+      'description': >
+        Persistent clients.
+      'example':
+        'clients':
+        - 'blocked': false
+          'blocked_services': []
+          'filtering': false
+          'ids': ['client-1']
+          'name': 'Client 1'
+          'parental': false
+          'safe_browsing': false
+          'safe_search': false
+          'tags': ['user_admin']
+          'use_global_blocked_services': true
+          'use_global_settings': true
+          'uid': 'abcd1234'
+          'upstream_servers': []
+        - 'blocked': false
+          'blocked_services': []
+          'filtering': true
+          'ids': ['client-2']
+          'name': 'Client 2'
+          'parental': true
+          'safe_browsing': true
+          'safe_search': true
+          'tags': ['user_child']
+          'use_global_blocked_services': false
+          'use_global_settings': false
+          'uid': 'efgh5678'
+          'upstream_servers': []
+      'properties':
+        'clients':
+          'description': >
+            All persistent clients.
+          'items':
+            '$ref': '#/components/schemas/PersistentClient'
+          'type': 'array'
+      'required':
+      - 'clients'
+      'type': 'object'
+
+    'GetV1ClientsRuntimeResp':
+      'description': >
+        Runtime clients.
+      'example':
+        'clients':
+        - 'host': 'my-box'
+          'ip': '1.2.3.4'
+          'num_blocked_requests': 0
+          'num_requests': 100
+          'sources':
+          - 'arp'
+        - 'ip': '5.6.7.8'
+          'num_blocked_requests': 100
+          'num_requests': 100
+          'sources':
+          - 'whois'
+          'whois':
+            'city': 'Minsk'
+            'country': 'BY'
+      'properties':
+        'clients':
+          'description': >
+            All runtime clients.
+          'items':
+            '$ref': '#/components/schemas/RuntimeClient'
+          'type': 'array'
+      'required':
+      - 'clients'
+      'type': 'object'
+
+    'GetV1DhcpLeasesResp':
+      'description': >
+        All dynamic and static DHCP leases.
+      'example':
+        'leases':
+        - 'expires': 1614345496000
+          'hostname': 'my-mobile'
+          'ip': '192.168.1.2'
+          'mac': '01:23:45:67:89:ab'
+          'uid': 'abcd1234'
+        - 'expires': 1614345497000
+          'hostname': ''
+          'ip': '192.168.1.3'
+          'mac': '01:23:45:67:89:cd'
+      'properties':
+        'leases':
+          'description': >
+            Dynamic and static DHCP leases.
+          'items':
+            '$ref': '#/components/schemas/DhcpLease'
+          'type': 'array'
+      'required':
+      - 'leases'
+      'type': 'object'
+
+    'GetV1DhcpStatusResp':
+      'description': >
+        Current DHCP server status and data for enabling it.
+      'example':
+        'interfaces':
+        - 'ips':
+          - '192.168.1.1'
+          'mac': '01:23:45:67:89:ab'
+          'mtu': 1500
+          'name': 'lan0'
+          'up': true
+        'ipv4_other_servers':
+          'ips':
+          - '192.169.1.1'
+        'ipv4_static_ip':
+          'ip': '192.168.1.1'
+          'static': true
+          'supported': true
+        'ipv6_other_servers':
+          'ips': []
+          'error': 'permission denied'
+        'ipv6_static_ip':
+          'ip': '200f::1'
+          'static': true
+          'supported': true
+      'properties':
+        'interfaces':
+          'description': >
+            Available network interfaces.
+          'items':
+            '$ref': '#/components/schemas/NetworkInterface'
+          'type': 'array'
+        'ipv4_other_servers':
+          '$ref': '#/components/schemas/GetV1DhcpStatusRespOtherServer'
+        'ipv4_static_ip':
+          '$ref': '#/components/schemas/StaticIpCheckResult'
+        'ipv6_other_servers':
+          '$ref': '#/components/schemas/GetV1DhcpStatusRespOtherServer'
+        'ipv6_static_ip':
+          '$ref': '#/components/schemas/StaticIpCheckResult'
+      'required':
+      - 'interfaces'
+      - 'ipv4_other_servers'
+      - 'ipv4_static_ip'
+      - 'ipv6_other_servers'
+      - 'ipv6_static_ip'
+      'type': 'object'
+
+    'GetV1DhcpStatusRespOtherServer':
+      'properties':
+        'error':
+          'description': >
+            Error, if any.  If there is no error, this field is absent.
+          'type': 'string'
+        'ips':
+          'description': >
+            IP addresses of other DHCP servers, if found.
+      'required':
+      - 'ips'
+      'type': 'object'
+
+    'GetV1InstallInfoResp':
+      'description': >
+        AdGuard Home addresses configuration.
+      'example':
+        'dns_port': 53
+        'interfaces':
+        - 'ips':
+          - '192.168.1.1'
+          'mac': '01:23:45:67:89:ab'
+          'mtu': 1500
+          'name': 'lan0'
+          'up': true
+        'web_port': 80
+      'properties':
+        'dns_port':
+          'description': >
+            Recommended DNS port.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+        'interfaces':
+          'description': >
+            Available network interfaces.
+          'items':
+            '$ref': '#/components/schemas/NetworkInterface'
+          'type': 'array'
+        'web_port':
+          'description': >
+            Recommended web interface port.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+      'required':
+      - 'dns_port'
+      - 'interfaces'
+      - 'web_port'
+      'type': 'object'
+
+    'GetV1LogSearchResp':
+      'description': >
+        Query log search results.
+      'example':
+        'results':
+        - 'answer':
+          - 'ttl': 60
+            'type': 'a'
+            'value': '5.6.7.8'
+          'answer_dnssec': false
+          'client':
+            'blocked': false
+            'ids':
+            - '1.2.3.4'
+            - 'user-1'
+            'name': 'User 1'
+            'num': 100
+            'num_blocked': 50
+            'uid': 'abcd1234'
+            'whois':
+              'city': 'Minsk'
+              'country': 'BY'
+          'elapsed': 3.2
+          'proto': 'udp'
+          'question':
+            'class': 'in'
+            'host': 'example.com'
+            'type': 'a'
+          'rcode': 'noerror'
+          'reason': 'not_filtered_notfound'
+          'rules': []
+          'start': 1614345496000
+          'upstream': '8.8.8.8'
+      'properties':
+        'results':
+          'description': >
+            The query log.
+          'items':
+            '$ref': '#/components/schemas/LogRecord'
+          'type': 'array'
+      'required':
+      - 'results'
+      'type': 'object'
+
+    'GetV1ProtectionBlockedServicesResp':
+      '$ref': '#/components/schemas/BlockedServices'
+
+    'GetV1ProtectionCustomRulesResp':
+      '$ref': '#/components/schemas/CustomRules'
+
+    'GetV1ProtectionDnsRewritesResp':
+      'description': >
+        Classic DNS rewrites.
+      'example':
+        'rules':
+        - 'answer': 'A'
+          'domain': 'example.com'
+          'id': 'abcd1234'
+        - 'answer': '0.0.0.0'
+          'domain': '*.example.org'
+          'id': 'efgh5678'
+        - 'answer': 'my.example.net'
+          'domain': 'example.net'
+          'id': 'ijkl9012'
+      'properties':
+        'rules':
+          'description': >
+            All classic DNS rewrites.
+          'items':
+            '$ref': '#/components/schemas/DnsRewrite'
+          'type': 'array'
+      'required':
+      - 'rules'
+      'type': 'object'
+
+    'GetV1ProtectionFiltersResp':
+      'description': >
+        Filters.
+      'example':
+        'filters':
+        - 'allowlist': false
+          'enabled': true
+          'name': 'AdMaster 5000 Super List v2.0 Final'
+          'num_rules': 36766
+          'refreshed': 1614345496000
+          'uid': 'abcd1234'
+          'url': 'https://admaster.example.com/list.txt'
+        - 'allowlist': false
+          'enabled': true
+          'name': 'My personal list'
+          'num_rules': 0
+          'refreshed': 1614345497000
+          'uid': 'efgh5678'
+          'url': 'file:///home/user/Documents/ad_list.txt'
+      'properties':
+        'filters':
+          'description': >
+            All current filters.
+          'items':
+            '$ref': '#/components/schemas/Filter'
+          'type': 'array'
+      'required':
+      - 'filters'
+      'type': 'object'
+
+    # Perhaps a lot of these belong in separate APIs, but our colleagues asked
+    # to pack as much data into every request as reasonably possible.
+    'GetV1SettingsAllResp':
+      'description': >
+        Most settings.
+      # Don't add examples, as are provided by the subclasses.
+      'properties':
+        'dhcp':
+          '$ref': '#/components/schemas/DhcpSettings'
+        'dns':
+          '$ref': '#/components/schemas/DnsSettings'
+        'log':
+          '$ref': '#/components/schemas/LogSettings'
+        'protection':
+          '$ref': '#/components/schemas/ProtectionSettings'
+        'stats':
+          '$ref': '#/components/schemas/StatsSettings'
+        'tls':
+          '$ref': '#/components/schemas/TlsSettings'
+      'required':
+      - 'dhcp'
+      - 'dns'
+      - 'log'
+      - 'protection'
+      - 'stats'
+      - 'tls'
+      'type': 'object'
+
+    'GetV1SettingsDnsAccessResp':
+      '$ref': '#/components/schemas/DnsAccessSettings'
+
+    # See the comment on the GetV1SettingsAllResp schema.
+    'GetV1StatsAllResp':
+      'description': >
+        All statistics.
+      'example':
+        'dns_cache_hit_rate': 56.7
+        'dns_cache_records': 123
+        'graph_avg_processing':
+        - 3.0
+        - 0.4
+        'graph_blocked_ad_queries':
+        - 10
+        - 20
+        'graph_blocked_custom_rule_queries':
+        - 10
+        - 20
+        'graph_blocked_domains':
+        - 10
+        - 20
+        'graph_blocked_parental_control_queries':
+        - 10
+        - 20
+        'graph_blocked_safe_browsing_queries':
+        - 10
+        - 20
+        'graph_blocked_safe_search_queries':
+        - 10
+        - 20
+        'graph_blocked_service_queries':
+        - 10
+        - 20
+        'graph_blocked_tracker_queries':
+        - 10
+        - 20
+        'graph_cpu_percent':
+        - 50
+        - 75
+        'graph_domains':
+        - 20
+        - 30
+        'graph_queries':
+        - 1000
+        - 2002
+        'graph_ram_resident':
+        - 1048576
+        - 2097152
+        'time_unit': 'hour'
+        'top_blocked_domains':
+        - 'name': 'example.net'
+          'num': 100
+        'top_clients':
+        - 'blocked': false
+          'ids':
+          - '1.2.3.4'
+          - 'user-1'
+          'name': 'User 1'
+          'num': 100
+          'num_blocked': 50
+          'uid': 'abcd1234'
+          'whois':
+            'city': 'Minsk'
+            'country': 'BY'
+        - 'blocked': true
+          'ids':
+          - '5.6.7.8'
+          'num': 100
+          'num_blocked': 100
+        'top_domains':
+        - 'name': 'example.com'
+          'num': 1000
+        - 'name': 'example.net'
+          'num': 100
+        'total_blocked_ad_queries': 100
+        'total_blocked_custom_rule_queries': 10
+        'total_blocked_domains': 500
+        'total_blocked_parental_control_queries': 10
+        'total_blocked_safe_browsing_queries': 10
+        'total_blocked_safe_search_queries': 10
+        'total_blocked_service_queries': 10
+        'total_blocked_tracker_queries': 10
+        'total_domains': 1000
+        'total_queries': 10000
+      'properties':
+        'dns_cache_hit_rate':
+          'description': >
+            DNS cache hit rate, in percent.
+          'maximum': 100.0
+          'minimum': 0.0
+          'format': 'double'
+          'type': 'number'
+        'dns_cache_records':
+          'description': >
+            Number of DNS responses currently in cache.
+          'minimum': 0
+          'format': 'int64'
+          'type': 'integer'
+        'graph_avg_processing':
+          'description': >
+            Average DNS query processing duration graph information.  Each item
+            is one `time_unit` long.  The duration is in milliseconds.  Sorted
+            by time in descending order.
+          'items':
+            'format': 'double'
+            'type': 'number'
+          'type': 'array'
+        'graph_blocked_ad_queries':
+          'description': >
+            Number of queries blocked by advertising filters graph information.
+            Each item is one `time_unit` long.  Sorted by time in descending
+            order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_blocked_custom_rule_queries':
+          'description': >
+            Number of queries blocked by custom filtering rules graph
+            information.  Each item is one `time_unit` long.  Sorted by time in
+            descending order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_blocked_domains':
+          'description': >
+            Blocked queried domains graph information.  Each item is one
+            `time_unit` long.  Sorted by time in descending order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_blocked_parental_control_queries':
+          'description': >
+            Number of queries blocked by parental control services graph
+            information.  Each item is one `time_unit` long.  Sorted by time in
+            descending order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_blocked_safe_browsing_queries':
+          'description': >
+            Number of queries blocked by safe browsing services graph
+            information.  Each item is one `time_unit` long.  Sorted by time in
+            descending order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_blocked_safe_search_queries':
+          'description': >
+            Number of queries blocked by safe search services graph information.
+            Each item is one `time_unit` long.  Sorted by time in descending
+            order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_blocked_service_queries':
+          'description': >
+            Number of queries blocked by blocked service settings graph
+            information.  Each item is one `time_unit` long.  Sorted by time in
+            descending order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_blocked_tracker_queries':
+          'description': >
+            Number of queries blocked by tracker filters graph information.
+            Each item is one `time_unit` long.  Sorted by time in descending
+            order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_cpu_percent':
+          'description': >
+            CPU usage percentage graph information.  Each item is one
+            `time_unit` long.  Sorted by time in descending order.
+          'items':
+            'format': 'double'
+            'type': 'number'
+          'type': 'array'
+        'graph_domains':
+          'description': >
+            Queried domains graph information.  Each item is one `time_unit`
+            long.  Sorted by time in descending order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_queries':
+          'description': >
+            Number of served DNS queries graph information.  Each item is one
+            `time_unit` long.  Sorted by time in descending order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'graph_ram_resident':
+          'description': >
+            AdGuard Home's resident memory usage graph information.  The size is
+            in bytes.  Each item is one `time_unit` long.  Sorted by time in
+            descending order.
+          'items':
+            'format': 'int64'
+            'type': 'integer'
+          'type': 'array'
+        'time_unit':
+          '$ref': '#/components/schemas/TimeUnit'
+        'top_blocked_domains':
+          'description': >
+            Top blocked queried domains.  Sorted by number in descending order.
+          'items':
+            '$ref': '#/components/schemas/GetV1StatsAllRespTopsItem'
+          'type': 'array'
+        'top_clients':
+          'description': >
+            Top clients.  Sorted by number in descending order.
+          'items':
+            '$ref': '#/components/schemas/ClientInfo'
+          'type': 'array'
+        'top_domains':
+          'description': >
+            Top queried domains.  Sorted by number in descending order.
+          'items':
+            '$ref': '#/components/schemas/GetV1StatsAllRespTopsItem'
+          'type': 'array'
+        'total_blocked_ad_queries':
+          'description': >
+            Total number of queries blocked by advertising filters.
+          'format': 'int64'
+          'type': 'integer'
+        'total_blocked_custom_rule_queries':
+          'description': >
+            Total number of queries blocked by custom filtering rules.
+          'format': 'int64'
+          'type': 'integer'
+        'total_blocked_domains':
+          'description': >
+            Total number of blocked queried domains.
+          'format': 'int64'
+          'type': 'integer'
+        'total_blocked_parental_control_queries':
+          'description': >
+            Total number of queries blocked by parental control services.
+          'format': 'int64'
+          'type': 'integer'
+        'total_blocked_safe_browsing_queries':
+          'description': >
+            Total number of queries blocked by safe browsing services.
+          'format': 'int64'
+          'type': 'integer'
+        'total_blocked_safe_search_queries':
+          'description': >
+            Total number of queries blocked by safe search services.
+          'format': 'int64'
+          'type': 'integer'
+        'total_blocked_service_queries':
+          'description': >
+            Total number of queries blocked by blocked service settings.
+          'format': 'int64'
+          'type': 'integer'
+        'total_blocked_tracker_queries':
+          'description': >
+            Total number of queries blocked by tracker filters.
+          'format': 'int64'
+          'type': 'integer'
+        'total_domains':
+          'description': >
+            Total number of queried domains.
+          'format': 'int64'
+          'type': 'integer'
+        'total_queries':
+          'description': >
+            Total number of served DNS queries.
+          'format': 'int64'
+          'type': 'integer'
+      'required':
+      - 'dns_cache_hit_rate'
+      - 'dns_cache_records'
+      - 'graph_avg_processing'
+      - 'graph_blocked_ad_queries'
+      - 'graph_blocked_custom_rule_queries'
+      - 'graph_blocked_domains'
+      - 'graph_blocked_parental_control_queries'
+      - 'graph_blocked_safe_browsing_queries'
+      - 'graph_blocked_safe_search_queries'
+      - 'graph_blocked_service_queries'
+      - 'graph_blocked_tracker_queries'
+      - 'graph_cpu_percent'
+      - 'graph_domains'
+      - 'graph_queries'
+      - 'graph_ram_resident'
+      - 'time_unit'
+      - 'top_blocked_domains'
+      - 'top_clients'
+      - 'top_domains'
+      - 'total_blocked_ad_queries'
+      - 'total_blocked_custom_rule_queries'
+      - 'total_blocked_domains'
+      - 'total_blocked_parental_control_queries'
+      - 'total_blocked_safe_browsing_queries'
+      - 'total_blocked_safe_search_queries'
+      - 'total_blocked_service_queries'
+      - 'total_blocked_tracker_queries'
+      - 'total_domains'
+      - 'total_queries'
+      'type': 'object'
+
+    'GetV1StatsAllRespTopsItem':
+      'description': >
+        A top array item.
+      'properties':
+        'name':
+          'description': >
+            The name of the entity.  Mostly domain names.
+          'example': 'example.com'
+          'type': 'string'
+        'num':
+          'description': >
+            The value of the statistic.
+          'example': 1000
+          'format': 'int64'
+          'type': 'integer'
+      'required':
+      - 'name'
+      - 'num'
+      'type': 'object'
+
+    'GetV1SystemInfoResp':
+      'description': >
+        Information about the AdGuard Home server.
+      'example':
+        'channel': 'release'
+        'new_version': 'v0.106.1'
+        'start': 1614345496000
+        'version': 'v0.106.0'
+      'properties':
+        'channel':
+          '$ref': '#/components/schemas/Channel'
+        'new_version':
+          'description': >
+            New available version of AdGuard Home to which the server can be
+            updated, if any.  If there are none, this field is absent.
+          'type': 'string'
+        'start':
+          'description': >
+            Unix time at which AdGuard Home started working, in milliseconds.
+          'format': 'double'
+          'type': 'number'
+        'version':
+          'description': >
+            Current AdGuard Home version.
+          'type': 'string'
+      'required':
+      - 'channel'
+      - 'start'
+      - 'version'
+      'type': 'object'
+
+    'InternalServerErrorResp':
+      'example':
+        'code': 'RNT000'
+        'msg': >-
+          runtime error: invalid memory address or nil pointer dereference
+      'properties':
+        'code':
+          '$ref': '#/components/schemas/ErrorCode'
+        'msg':
+          'description': >
+            Error message string.
+          'type': 'string'
+      'required':
+      - 'code'
+      - 'msg'
+      'type': 'object'
+
+    'Lang':
+      'description': >
+        Language code.
+      # Hold the enum in sync with .twosky.json.
+      'enum':
+      - 'be'
+      - 'bg'
+      - 'cs'
+      - 'da'
+      - 'de'
+      - 'en'
+      - 'es'
+      - 'fa'
+      - 'fr'
+      - 'hr'
+      - 'hu'
+      - 'id'
+      - 'it'
+      - 'ja'
+      - 'ko'
+      - 'nl'
+      - 'no'
+      - 'pl'
+      - 'pt-br'
+      - 'pt-pt'
+      - 'ro'
+      - 'ru'
+      - 'si-lk'
+      - 'sk'
+      - 'sl'
+      - 'sr-cs'
+      - 'sv'
+      - 'th'
+      - 'tr'
+      - 'vi'
+      - 'zh-cn'
+      - 'zh-hk'
+      - 'zh-tw'
+      'type': 'string'
+
+    'LogRecord':
+      'description': >
+        Query log record.
+      'properties':
+        'answer':
+          'description': >
+            The answer given to the user.
+          'items':
+            '$ref': '#/components/schemas/LogRecordDnsAnswer'
+          'type': 'array'
+        'answer_dnssec':
+          'description': >
+            If `true`, DNSSEC was used.
+          'type': 'boolean'
+        'blocked_service':
+          'description': >
+            Set if `reason` is `filtered_blocked_service`.  Otherwise, this
+            field is absent.
+          'type': 'string'
+        'client':
+          '$ref': '#/components/schemas/ClientInfo'
+        'elapsed':
+          'description': >
+            Time it took to process the request, in milliseconds.
+          'format': 'double'
+          'type': 'number'
+        'original_answer':
+          'description': >
+            Original answer from the upstream server, if the answer was
+            rewritten.
+          'items':
+            '$ref': '#/components/schemas/LogRecordDnsAnswer'
+          'type': 'array'
+        'proto':
+          '$ref': '#/components/schemas/DnsProto'
+        'question':
+          '$ref': '#/components/schemas/LogRecordDnsQuestion'
+        'rcode':
+          '$ref': '#/components/schemas/DnsResponseCode'
+        'reason':
+          '$ref': '#/components/schemas/FilteringReason'
+        'rules':
+          'description': >
+            Applied rules.
+          'items':
+            '$ref': '#/components/schemas/FilteringResultRule'
+          'type': 'array'
+        'start':
+          'description': >
+            Request processing start Unix time, in milliseconds.
+          'format': 'double'
+          'type': 'number'
+        'upstream':
+          '$ref': '#/components/schemas/UpstreamServerAddr'
+      'required':
+      - 'answer'
+      - 'answer_dnssec'
+      - 'client'
+      - 'elapsed'
+      - 'proto'
+      - 'question'
+      - 'rcode'
+      - 'reason'
+      - 'rules'
+      - 'start'
+      - 'upstream'
+      'type': 'object'
+
+    'LogRecordDnsAnswer':
+      'description': >
+        DNS answer section.
+      'properties':
+        'ttl':
+          'description': >
+            TTL of a record.  This value is in **seconds**, like in DNS record
+            headers.
+          'format': 'int64'
+          'minimum': 0
+          'type': 'integer'
+        'type':
+          '$ref': '#/components/schemas/DnsType'
+        'value':
+          'description': >
+            An opaque string describing the result value.
+          'type': 'string'
+      'required':
+      - 'ttl'
+      - 'type'
+      - 'value'
+      'type': 'object'
+
+    'LogRecordDnsQuestion':
+      'description': >
+        DNS question section.
+      'properties':
+        'class':
+          '$ref': '#/components/schemas/DnsClass'
+        'host':
+          'description': >
+            Host from the query.
+          'type': 'string'
+        'type':
+          '$ref': '#/components/schemas/DnsType'
+      'required':
+      - 'class'
+      - 'host'
+      - 'type'
+      'type': 'object'
+
+    'LogSettings':
+      'allOf':
+      - '$ref': '#/components/schemas/LogSettingsPatch'
+      - 'description': >
+          Query logging settings.
+        'example':
+          'anonymize': true
+          'enabled': true
+          'rotation': 604800000
+        'required':
+        - 'anonymize'
+        - 'enabled'
+        - 'rotation'
+
+    'LogSettingsPatch':
+      'description': >
+        Query logging settings update object.
+      'properties':
+        'anonymize':
+          'description': >
+            If `true`, client IP address anonymization is enabled.
+          'type': 'boolean'
+        'enabled':
+          'description': >
+            If `true`, query logging is enabled.
+          'type': 'boolean'
+        'rotation':
+          'description': >
+            Log rotation interval, in milliseconds.  After that time, the log
+            file will be replaced by a new one, while the old one gets renamed.
+          'format': 'double'
+          'minimum': 86400000
+          'maximum': 7776000000
+          'type': 'number'
+      'type': 'object'
+
+    'NetworkInterface':
+      'properties':
+        'ips':
+          'description': >
+            The IP addresses of the interface, if any.
+          'items':
+            'type': 'string'
+          'type': 'array'
+        'mac':
+          'description': >
+            The MAC address of the interface.
+          'type': 'string'
+        'mtu':
+          'description': >
+            The interface's MTU, the maximum transmission unit.
+          'format': 'int64'
+          'type': 'integer'
+        'name':
+          'description': >
+            The name of the interface.
+          'type': 'string'
+        'up':
+          'description': >
+            If `true`, the interface is up.
+          'type': 'boolean'
+      'required':
+      - 'ips'
+      - 'mac'
+      - 'mtu'
+      - 'name'
+      - 'up'
+      'type': 'object'
+
+    'NotFoundResp':
+      'example':
+        'code': 'ENT404'
+        'msg': >-
+          entity not found
+      'properties':
+        'code':
+          '$ref': '#/components/schemas/ErrorCode'
+        'msg':
+          'description': >
+            Error message string.
+          'type': 'string'
+      'required':
+      - 'code'
+      - 'msg'
+      'type': 'object'
+
+    'PatchV1AccountsProfileReq':
+      'example':
+        'lang': 'ru'
+      'properties':
+        'lang':
+          '$ref': '#/components/schemas/Lang'
+      'type': 'object'
+
+    'PatchV1AccountsProfileResp':
+      '$ref': '#/components/schemas/Profile'
+
+    'PatchV1ClientPersistentReq':
+      '$ref': '#/components/schemas/PersistentClientPatch'
+
+    'PatchV1ClientPersistentResp':
+      '$ref': '#/components/schemas/PersistentClient'
+
+    'PatchV1DhcpLeaseReq':
+      '$ref': '#/components/schemas/DhcpLeasePatch'
+
+    'PatchV1DhcpLeaseResp':
+      '$ref': '#/components/schemas/DhcpLease'
+
+    'PatchV1ProtectionFilterReq':
+      '$ref': '#/components/schemas/FilterPatch'
+
+    'PatchV1ProtectionFilterResp':
+      '$ref': '#/components/schemas/Filter'
+
+    'PatchV1SettingsDhcpReq':
+      '$ref': '#/components/schemas/DhcpSettingsPatch'
+
+    'PatchV1SettingsDhcpResp':
+      '$ref': '#/components/schemas/DhcpSettings'
+
+    'PatchV1SettingsDnsReq':
+      '$ref': '#/components/schemas/DnsSettingsPatch'
+
+    'PatchV1SettingsDnsResp':
+      '$ref': '#/components/schemas/DnsSettings'
+
+    'PatchV1SettingsLogReq':
+      '$ref': '#/components/schemas/LogSettingsPatch'
+
+    'PatchV1SettingsLogResp':
+      '$ref': '#/components/schemas/LogSettings'
+
+    'PatchV1SettingsProtectionReq':
+      '$ref': '#/components/schemas/ProtectionSettingsPatch'
+
+    'PatchV1SettingsProtectionResp':
+      '$ref': '#/components/schemas/ProtectionSettings'
+
+    'PatchV1SettingsStatsReq':
+      '$ref': '#/components/schemas/StatsSettingsPatch'
+
+    'PatchV1SettingsStatsResp':
+      '$ref': '#/components/schemas/StatsSettings'
+
+    'PatchV1SettingsTlsReq':
+      '$ref': '#/components/schemas/TlsSettingsPatch'
+
+    'PatchV1SettingsTlsResp':
+      '$ref': '#/components/schemas/TlsSettings'
+
+    'PersistentClient':
+      'allOf':
+      - '$ref': '#/components/schemas/PersistentClientPatch'
+      - 'description': >
+          Persistent client.
+        'example':
+          'blocked': false
+          'blocked_services': []
+          'filtering': false
+          'ids': ['client-1']
+          'name': 'Client 1'
+          'num_blocked_requests': 50
+          'num_requests': 100
+          'parental': false
+          'safe_browsing': false
+          'safe_search': false
+          'tags': ['user_admin']
+          'use_global_blocked_services': true
+          'use_global_settings': true
+          'uid': 'abcd1234'
+          'upstream_servers': []
+        'properties':
+          'num_blocked_requests':
+            'description': >
+              Total number of blocked requests for this runtime client.
+            'format': 'int64'
+            'minimum': 0
+            'type': 'integer'
+          'num_requests':
+            'description': >
+              Total number of requests for this runtime client.
+            'format': 'int64'
+            'minimum': 0
+            'type': 'integer'
+          'uid':
+            '$ref': '#/components/schemas/Uid'
+        'required':
+        - 'blocked'
+        - 'blocked_services'
+        - 'filtering'
+        - 'ids'
+        - 'name'
+        - 'parental'
+        - 'safe_browsing'
+        - 'safe_search'
+        - 'tags'
+        - 'uid'
+        - 'upstream_servers'
+        - 'use_global_blocked_services'
+        - 'use_global_settings'
+
+    'PersistentClientPatch':
+      'description': >
+        Persistent client update object.
+      'example':
+        'filtering': false
+        'parental': false
+        'safe_browsing': false
+        'safe_search': false
+        'tags': ['user_admin']
+      'properties':
+        'blocked':
+          'description': >
+            If `true`, the client is blocked.
+          'type': 'boolean'
+        'blocked_services':
+          'description': >
+            Custom blocked services for this client.
+          'items':
+            '$ref': '#/components/schemas/BlockedServiceId'
+          'type': 'array'
+        'filtering':
+          'description': >
+            If `true`, filtering based on filter rule lists is enabled for this
+            client.
+          'type': 'boolean'
+        'ids':
+          'description': >
+            IP, CIDR, MAC, or ClientID (not to be confused with the `uid` field)
+            for client identification.
+          'items':
+            'type': 'string'
+          'type': 'array'
+        'name':
+          'description': >
+            The name of this client.
+          'type': 'string'
+        'parental':
+          'description': >
+            If `true`, parental protection is enabled for this client.
+          'type': 'boolean'
+        'safe_browsing':
+          'description': >
+            If `true`, safe browsing protection is enabled for this client.
+          'type': 'boolean'
+        'safe_search':
+          'description': >
+            If `true`, safe search protection is enabled for this client.
+          'type': 'boolean'
+        'tags':
+          'description': >
+            Client tags.
+          'items':
+            '$ref': '#/components/schemas/PersistentClientTag'
+          'type': 'array'
+        'use_global_blocked_services':
+          'description': >
+            If `true`, use global blocked services for this client instead of
+            the custom ones.
+          'type': 'boolean'
+        'use_global_settings':
+          'description': >
+            If `true`, use global protection settings for this client instead of
+            the custom ones.
+          'type': 'boolean'
+        'upstream_servers':
+          'description': >
+            Custom upstream DNS servers for this client.
+          'items':
+            '$ref': '#/components/schemas/UpstreamServerAddr'
+          'type': 'array'
+      'type': 'object'
+
+    'PersistentClientPost':
+      'allOf':
+      - '$ref': '#/components/schemas/PersistentClientPatch'
+      - 'description': >
+          Persistent client create object.
+        'example':
+          'blocked': false
+          'blocked_services': []
+          'filtering': false
+          'ids': ['client-1']
+          'name': 'Client 1'
+          'parental': false
+          'safe_browsing': false
+          'safe_search': false
+          'tags': ['user_admin']
+          'use_global_blocked_services': true
+          'use_global_settings': true
+          'upstream_servers': []
+        'required':
+        - 'blocked'
+        - 'blocked_services'
+        - 'filtering'
+        - 'ids'
+        - 'name'
+        - 'parental'
+        - 'safe_browsing'
+        - 'safe_search'
+        - 'tags'
+        - 'upstream_servers'
+        - 'use_global_blocked_services'
+        - 'use_global_settings'
+
+    'PersistentClientTag':
+      'description': >
+        Tags can be included in filtering rules to allow you to apply them more
+        accurately.
+      'enum':
+      - 'device_audio'
+      - 'device_camera'
+      - 'device_gameconsole'
+      - 'device_laptop'
+      - 'device_nas'
+      - 'device_other'
+      - 'device_pc'
+      - 'device_phone'
+      - 'device_printer'
+      - 'device_securityalarm'
+      - 'device_tablet'
+      - 'device_tv'
+      - 'os_android'
+      - 'os_ios'
+      - 'os_linux'
+      - 'os_macos'
+      - 'os_other'
+      - 'os_windows'
+      - 'user_admin'
+      - 'user_child'
+      - 'user_regular'
+      'type': 'string'
+
+    'PostV1AccountsSessionReq':
+      'example':
+        'password': 'G00dp455word!'
+        'username': 'admin'
+      'properties':
+        'password':
+          'description': >
+            Password.
+          'format': 'password'
+          'type': 'string'
+        'username':
+          'description': >
+            Username.
+          'type': 'string'
+      'required':
+      - 'password'
+      - 'username'
+      'type': 'object'
+
+    'PostV1ClientsPersistentReq':
+      '$ref': '#/components/schemas/PersistentClientPost'
+
+    'PostV1ClientsPersistentResp':
+      '$ref': '#/components/schemas/PersistentClient'
+
+    'PostV1DhcpLeasesReq':
+      '$ref': '#/components/schemas/DhcpLeasePost'
+
+    'PostV1DhcpLeasesResp':
+      '$ref': '#/components/schemas/DhcpLease'
+
+    'PostV1InstallCheckReq':
+      'description': >
+        Configuration for checking.
+      'example':
+        'dns':
+          'ip':
+          - '0.0.0.0'
+          'port': 53
+        'static_ip': false
+        'web':
+          'ip':
+          - '0.0.0.0'
+          'port': 80
+      'properties':
+        'dns':
+          '$ref': '#/components/schemas/PostV1InstallCheckReqServer'
+        'static_ip':
+          'description': >
+            If `true`, check if a static IP is set or can be set.
+          'type': 'boolean'
+        'web':
+          '$ref': '#/components/schemas/PostV1InstallCheckReqServer'
+      'required':
+      - 'dns'
+      - 'static_ip'
+      - 'web'
+      'type': 'object'
+
+    'PostV1InstallCheckReqServer':
+      'description': >
+        A configuration for a server check.
+      'properties':
+        'ip':
+          'description': >
+            IP addresses to check for availability.
+          'items':
+            'type': 'string'
+          'minItems': 1
+          'type': 'array'
+        'port':
+          'description': >
+            Port to check for availability.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+      'required':
+      - 'ip'
+      - 'port'
+      'type': 'object'
+
+    'PostV1InstallCheckResp':
+      'description': >
+        Configuration checking response.
+      'example':
+        'dns':
+          'error': 'permission denied'
+        'static_ip':
+          'ip': '192.168.1.1'
+          'static': true
+          'supported': true
+        'web': {}
+      'properties':
+        'dns':
+          '$ref': '#/components/schemas/PostV1InstallCheckRespNetwork'
+        'static_ip':
+          '$ref': '#/components/schemas/StaticIpCheckResult'
+        'web':
+          '$ref': '#/components/schemas/PostV1InstallCheckRespNetwork'
+      'required':
+      - 'dns'
+      - 'static_ip'
+      - 'web'
+      'type': 'object'
+
+    'PostV1InstallCheckRespNetwork':
+      'properties':
+        'error':
+          'description': >
+            Error, if any.  If there is no error, this field is absent.
+          'type': 'string'
+      'type': 'object'
+
+    'PostV1InstallConfigureReq':
+      'description': >
+        AdGuard Home initial configuration.
+      'example':
+        'dns_ip': '0.0.0.0'
+        'dns_port': 53
+        'password': 'G00dp455word!'
+        'username': 'admin'
+        'set_static_ip': true
+        'web_ip': '0.0.0.0'
+        'web_port': 80
+      'properties':
+        'dns_ip':
+          'description': >
+            The IP address to serve DNS queries on.
+          'type': 'string'
+        'dns_port':
+          'description': >
+            The port to serve DNS queries on.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+        'password':
+          'description': >
+            Password.
+          'type': 'string'
+        'username':
+          'description': >
+            Username.
+          'type': 'string'
+        'set_static_ip':
+          'description': >
+            If `true`, set the server's IP address to static.
+          'type': 'boolean'
+        'web_ip':
+          'description': >
+            The IP address to serve the web interface on.
+          'type': 'string'
+        'web_port':
+          'description': >
+            The port to serve the web interface on.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+      'required':
+      - 'dns_ip'
+      - 'dns_port'
+      - 'password'
+      - 'username'
+      - 'set_static_ip'
+      - 'web_ip'
+      - 'web_port'
+      'type': 'object'
+
+    'PostV1LogClearReq':
+      'description': >
+        Currently empty, may get more fields in the future.
+      'type': 'object'
+
+    'PostV1ProtectionCheckCustomRulesReq':
+      'description': >
+        Data to check using custom filtering rules.
+      'example':
+        'host': 'example.com'
+      'properties':
+        'host':
+          'description': >
+            The hostname to check.
+          'type': 'string'
+      'required':
+      - 'host'
+      'type': 'object'
+
+    'PostV1ProtectionCheckCustomRulesResp':
+      'description': >
+        Custom filtering rules check results.
+      'example':
+        'reason': 'filtered_blocklist'
+        'rules':
+        - 'filter_list_uid': 'abcd1234'
+          'text': '||example.com^'
+      'properties':
+        'cname':
+          'description': >
+            Set if `reason` is `Rewrite`.  Otherwise, this field is absent.
+          'type': 'string'
+        'ip_addrs':
+          'description': >
+            Set if `reason` is `Rewrite`.  Otherwise, this field is absent.
+          'items':
+            'type': 'string'
+          'type': 'array'
+        'reason':
+          '$ref': '#/components/schemas/FilteringReason'
+        'rules':
+          'description': >
+            Applied rules.
+          'items':
+            '$ref': '#/components/schemas/FilteringResultRule'
+          'type': 'array'
+        'service_name':
+          'description': >
+            Set if `reason` is `FilteredBlockedService`.  Otherwise, this field
+            is absent.
+          'type': 'string'
+      'required':
+      - 'reason'
+      - 'rules'
+      'type': 'object'
+
+    'PostV1ProtectionDnsRewritesReq':
+      '$ref': '#/components/schemas/DnsRewritePost'
+
+    'PostV1ProtectionDnsRewritesResp':
+      '$ref': '#/components/schemas/DnsRewrite'
+
+    'PostV1ProtectionFiltersReq':
+      '$ref': '#/components/schemas/FilterPost'
+
+    'PostV1ProtectionFiltersResp':
+      '$ref': '#/components/schemas/Filter'
+
+    'PostV1ProtectionRefreshFilterReq':
+      'description': >
+        Currently empty, may get more fields in the future.
+      'type': 'object'
+
+    'PostV1ProtectionRefreshFilterResp':
+      '$ref': '#/components/schemas/Filter'
+
+    'PostV1ProtectionRefreshFiltersReq':
+      'description': >
+        Filters refresh parameters.
+      'example':
+        'allowlist': false
+        'blocklist': true
+      'properties':
+        'allowlist':
+          'description': >
+            If `true`, refresh all allowlist filters.
+          'type': 'boolean'
+        'blocklist':
+          'description': >
+            If `true`, refresh all blocklist filters.
+          'type': 'boolean'
+      'required':
+      - 'allowlist'
+      - 'blocklist'
+      'type': 'object'
+
+    'PostV1ProtectionRefreshFiltersResp':
+      'description': >
+        Refresh results.
+      'example':
+        'errors':
+        - 'msg': 'context deadline exceeded'
+          'uid': 'efgh5678'
+        'refreshed':
+        - 'allowlist': false
+          'enabled': true
+          'name': 'AdMaster 5000 Super List v2.0 Final'
+          'num_rules': 36766
+          'refreshed': 1614345496000
+          'uid': 'abcd1234'
+          'url': 'https://admaster.example.com/list.txt'
+      'properties':
+        'errors':
+          'description': >
+            All encountered errors.
+          'items':
+            '$ref': '#/components/schemas/RefreshFilterError'
+          'type': 'array'
+        'refreshed':
+          'description': >
+            Refreshed filters.
+          'items':
+            '$ref': '#/components/schemas/Filter'
+          'type': 'array'
+      'required':
+      - 'errors'
+      - 'refreshed'
+      'type': 'object'
+
+    'PostV1SettingsDnsCheckReq':
+      'description': >
+        Validatable DNS settings.
+      'example':
+        'bootstrap_servers':
+        - '9.9.9.10'
+        - '149.112.112.10'
+        'upstream_servers':
+        - '1.1.1.1'
+        - '8.8.8.8'
+      'properties':
+        'bootstrap_servers':
+          'description': |
+            Bootstrap DNS servers' IP addresses to check.
+          'items':
+            'type': 'string'
+          'type': 'array'
+        'upstream_servers':
+          'description': >
+            Upstream DNS servers to check.
+          'items':
+            '$ref': '#/components/schemas/UpstreamServerAddr'
+          'type': 'array'
+      'required':
+      - 'bootstrap_servers'
+      - 'upstream_servers'
+      'type': 'object'
+
+    'PostV1SettingsDnsCheckResp':
+      'description': >
+        DNS settings validation results.
+      'example':
+        'bootstrap_servers':
+          '9.9.9.10': 'network is unreachable'
+        'upstream_servers':
+          '8.8.8.8': 'network is unreachable'
+      'properties':
+        'bootstrap_servers':
+          'additionalProperties':
+            'minLength': 1
+            'type': 'string'
+          'description': >
+            An IP-address-to-error mapping.  If an address is not in this
+            object, the check for that address is successful.  If there were no
+            errors, this field is absent.
+        'upstream_servers':
+          'additionalProperties':
+            'type': 'string'
+          'description': >
+            An upstream-address-to-error mapping.  If an address is not in this
+            object, the check for that address is successful.  If there were no
+            errors, this field is absent.
+      'type': 'object'
+
+    'PostV1SettingsTlsCheckReq':
+      'description': >
+        Validatable TLS settings.
+      'example':
+        'certificate_path': '/etc/ssl/example.com.cert'
+        'port_dns_over_quic': 784
+        'port_dns_over_tls': 853
+        'port_https': 443
+        'private_key_path': '/etc/ssl/example.com.key'
+        'server_name': 'dns.example.com'
+      'properties':
+        'certificate':
+          'description': |
+            Base64-encoded string with PEM-encoded certificate chain.
+
+            Should not be sent if `certificate_path` is sent.  Otherwise, must
+            be sent.
+          'format': 'byte'
+          'type': 'string'
+        'certificate_path':
+          'description': |
+            Path to the certificate file.
+
+            Should not be sent if `certificate` is sent.  Otherwise, must be
+            sent.
+          'type': 'string'
+        'port_dns_over_quic':
+          'default': 784
+          'description': >
+            The DNS-over-QUIC port.  If `0`, DNS-over-QUIC is disabled.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+        'port_dns_over_tls':
+          'default': 853
+          'description': >
+            The DNS-over-TLS port.  If `0`, DNS-over-TLS is disabled.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+        'port_https':
+          'default': 443
+          'description': >
+            The HTTPS port.  If `0`, HTTPS is disabled.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+        'private_key':
+          'description': |
+            Base64-encoded string with PEM-encoded private key.
+
+            Should not be sent if `private_key_path` is sent.  Otherwise, must
+            be sent.
+          'format': 'byte'
+          'type': 'string'
+        'private_key_path':
+          'description': |
+            Path to the private key file.
+
+            Should not be sent if `private_key` is sent.  Otherwise, must be
+            sent.
+          'type': 'string'
+        'server_name':
+          'description': >
+            The name of the server.  Used to validate the certificates as well
+            as to check ClientIDs in DNS-over-HTTP and DNS-over-TLS.
+          'type': 'string'
+      'required':
+      - 'port_dns_over_quic'
+      - 'port_dns_over_tls'
+      - 'port_https'
+      - 'server_name'
+      'type': 'object'
+
+    'PostV1SettingsTlsCheckResp':
+      'description': >
+        TLS settings validation results.
+      'example':
+        'dns_names':
+          - '*.example.com'
+          - 'example.com'
+        'issuer': 'CN=Example CA,OU=Development,O=Example CA,L=Canberra,ST=Canberra,C=AU'
+        'key_type': 'RSA'
+        'not_after': 1614345497000
+        'not_before': 1614345496000
+        'port_https_error': 'address already in use'
+        'subject': 'CN=Example CA,OU=Development,O=Example CA,L=Canberra,ST=Canberra,C=AU'
+        'warnings': []
+      'properties':
+        'cert_error':
+          'description': >
+            Certificate validation error, if any.  If the certificate is valid,
+            this field is absent.
+          'type': 'string'
+        'chain_error':
+          'description': >
+            Certificate chain validation error, if any.  If the certificate
+            chain is valid, this field is absent.
+          'type': 'string'
+        'dns_names':
+          'description': >
+            The value of the `SubjectAltNames` field of the first certificate in
+            the chain.
+          'items':
+            'type': 'string'
+          'type': 'array'
+        'issuer':
+          'description': >
+            The issuer of the first certificate in the chain.
+          'type': 'string'
+        'key_error':
+          'description': >
+            Private key pair error, if any.  If the key is valid, this field is
+            absent.
+          'type': 'string'
+        'key_type':
+          '$ref': '#/components/schemas/TlsKeyType'
+        'not_after':
+          'description': >
+            The value of the `NotAfter` field of the first certificate in the
+            chain, as a Unix time, in milliseconds.
+          'format': 'double'
+          'type': 'number'
+        'not_before':
+          'description': >
+            The value of the `NotBefore` field of the first certificate in the
+            chain, as a Unix time, in milliseconds.
+          'format': 'double'
+          'type': 'number'
+        'port_dns_over_quic_error':
+          'description': >
+            DNS-over-QUIC port checking error, if any.  If the port is
+            available, this field is absent.
+          'type': 'string'
+        'port_dns_over_tls_error':
+          'description': >
+            DNS-over-TLS port checking error, if any.  If the port is available,
+            this field is absent.
+          'type': 'string'
+        'port_https_error':
+          'description': >
+            DNS-over-HTTPS port checking error, if any.  If the port is
+            available, this field is absent.
+          'type': 'string'
+        'pair_error':
+          'description': >
+            Certificate and key pair error, if any.  If the pair is valid, this
+            field is absent.
+          'type': 'string'
+        'subject':
+          'description': >
+            The subject of the first certificate in the chain.
+          'type': 'string'
+        'warnings':
+          'description': >
+            Validation warnings, if any.
+          'items':
+            'type': 'string'
+          'type': 'array'
+      'required':
+      - 'dns_names'
+      - 'issuer'
+      - 'key_type'
+      - 'not_after'
+      - 'not_before'
+      - 'subject'
+      - 'warnings'
+      'type': 'object'
+
+    'PostV1StatsClearReq':
+      'description': >
+        Currently empty, may get more fields in the future.
+      'type': 'object'
+
+    'PostV1SystemResetReq':
+      'description': >
+        Currently empty, may get more fields in the future.
+      'type': 'object'
+
+    'PostV1SystemResetResp':
+      'description': >
+        Currently empty, may get more fields in the future.
+      'type': 'object'
+
+    'PostV1SystemUpdateReq':
+      'description': >
+        Currently empty, may get more fields in the future.
+      'type': 'object'
+
+    'PostV1SystemUpdateResp':
+      'example':
+        'reload': 10000
+      'properties':
+        'reload':
+          'description': >
+            Time, after which the frontend must reload the page, in
+            milliseconds.
+          'format': 'double'
+          'type': 'number'
+      'type': 'object'
+
+    'Profile':
+      'description': >
+        Current user's profile.
+      'example':
+        'lang': 'en'
+        'username': 'admin'
+      'properties':
+        'lang':
+          '$ref': '#/components/schemas/Lang'
+        'username':
+          'description': >
+            Current user's name.
+          'type': 'string'
+      'required':
+      - 'lang'
+      - 'username'
+      'type': 'object'
+
+    'ProtectionSettings':
+      'allOf':
+      - '$ref': '#/components/schemas/ProtectionSettingsPatch'
+      - 'description': >
+          Protection settings.
+        'example':
+          'autoupdate': 86400000
+          'filtering': true
+          'parental': true
+          'safe_browsing': false
+          'safe_search': false
+        'required':
+        - 'autoupdate'
+        - 'filtering'
+        - 'parental'
+        - 'safe_browsing'
+        - 'safe_search'
+
+    'ProtectionSettingsPatch':
+      'description': >
+        Protection settings update object.
+      'example':
+        'autoupdate': 0
+      'properties':
+        'autoupdate':
+          'description': >
+            Filter automatic update interval, in milliseconds.  Set to `0` to
+            disable automatic updates.
+          'format': 'double'
+          'minimum': 0
+          'maximum': 604800000
+          'type': 'number'
+        'filtering':
+          'description': >
+            If `true`, filtering based on filter rule lists is enabled.
+          'type': 'boolean'
+        'parental':
+          'description': >
+            If `true`, parental protection is enabled.
+          'type': 'boolean'
+        'pause_end':
+          'description': |
+            If `state` is `paused`, `pause_end` will show the Unix time until
+            which the protection is disabled in milliseconds.  Otherwise, the
+            property won't be set.
+
+            When updating, if `state` is set to `paused`, `pause_end` must be
+            set to a timestamp in the future.
+          'format': 'double'
+          'type': 'number'
+        'safe_browsing':
+          'description': >
+            If `true`, safe browsing protection is enabled.
+          'type': 'boolean'
+        'safe_search':
+          'description': >
+            If `true`, safe search protection is enabled.
+          'type': 'boolean'
+        'state':
+          '$ref': '#/components/schemas/ProtectionSettingsState'
+      'type': 'object'
+
+    'ProtectionSettingsState':
+      'description': |
+        State of protection.
+
+         *  `off`: Protection is disabled.
+
+         *  `on`: Protection is enabled.
+
+         *  `paused`: Protection is paused.  See the `pause_end` property to get
+             or set the end of the pause.
+      'enum':
+      - 'off'
+      - 'on'
+      - 'paused'
+      'type': 'string'
+
+    'PutV1ProtectionBlockedServicesReq':
+      '$ref': '#/components/schemas/BlockedServices'
+
+    'PutV1ProtectionCustomRulesReq':
+      '$ref': '#/components/schemas/CustomRules'
+
+    'PutV1SettingsDnsAccessReq':
+      '$ref': '#/components/schemas/DnsAccessSettings'
+
+    'RefreshFilterError':
+      'description': >
+        Filter refresh error.
+      'properties':
+        'msg':
+          'description': >
+            Error message.
+          'type': 'string'
+        'uid':
+          '$ref': '#/components/schemas/Uid'
+      'required':
+      - 'msg'
+      - 'uid'
+      'type': 'object'
+
+    'RuntimeClient':
+      'description': >
+        A runtime client's information.
+      'properties':
+        'host':
+          'description': >
+            The RDNS host of the runtime, if any.  If there is none, this field
+            is absent.
+          'type': 'string'
+        'ip':
+          'description': >
+            The IP-address of the runtime client.
+          'type': 'string'
+        'sources':
+          'description': >
+            The sources from which the information about this runtime client was
+            collected.
+          'items':
+            '$ref': '#/components/schemas/RuntimeClientSource'
+          'minItems': 1
+          'type': 'array'
+        'num_blocked_requests':
+          'description': >
+            Total number of blocked requests for this runtime client.
+          'format': 'int64'
+          'minimum': 0
+          'type': 'integer'
+        'num_requests':
+          'description': >
+            Total number of requests for this runtime client.
+          'format': 'int64'
+          'minimum': 0
+          'type': 'integer'
+        'whois':
+          '$ref': '#/components/schemas/Whois'
+      'required':
+      - 'ip'
+      - 'num_blocked_requests'
+      - 'num_requests'
+      - 'sources'
+      'type': 'object'
+
+    'RuntimeClientSource':
+      'description': >
+        The source from which the information about this runtime client was
+        collected.
+
+         *  `arp`: The information was collected from the `arp -a` output.
+
+         *  `dhcp`: The information was collected from our DHCP server.
+
+         *  `hosts_file`: The information was collected from the `/etc/hosts`
+             file.
+
+         *  `rdns`: The information was collected by performing a reverse DNS
+             lookup.
+
+         *  `whois`: The information was collected by performing a WHOIS lookup.
+      'enum':
+      - 'arp'
+      - 'dhcp'
+      - 'hosts_file'
+      - 'rdns'
+      - 'whois'
+      'type': 'string'
+
+    'StaticIpCheckResult':
+      'properties':
+        'error':
+          'description': >
+            Error, if any.  If there is no error, this field is absent.
+          'type': 'string'
+        'ip':
+          'description': >
+            The IP address.
+          'type': 'string'
+        'static':
+          'description': >
+            If `true`, the interface has a static IP address.
+          'type': 'boolean'
+        'supported':
+          'description': >
+            If `true`, setting a static IP on this system is supported.
+          'type': 'boolean'
+      'required':
+      - 'ip'
+      - 'static'
+      - 'supported'
+      'type': 'object'
+
+    'StatsSettings':
+      'allOf':
+      - '$ref': '#/components/schemas/StatsSettingsPatch'
+      - 'description': >
+          Statistics settings.
+        'required':
+        - 'autorefresh'
+        - 'retention'
+
+    'StatsSettingsPatch':
+      'description': >
+        Statistics settings update object.
+      'properties':
+        'autorefresh':
+          'description': >
+            Statistics UI autorefresh time in milliseconds.  `0` means
+            autorefresh is disabled.
+          'format': 'double'
+          'type': 'number'
+        'retention':
+          'description': >
+            Statistics retention interval, in milliseconds.
+          'format': 'double'
+          'type': 'number'
+      'type': 'object'
+
+    'TimeUnit':
+      'description': >
+        Time units used for statistics.  See the documentation for the
+        `GET /api/v1/stats/all` request.
+      'enum':
+      - 'hour'
+      - 'day'
+      'type': 'string'
+
+    'TlsKeyType':
+      'description': >
+        TLS key type.
+      'enum':
+      - 'ECDSA'
+      - 'RSA'
+      'type': 'string'
+
+    'TlsSettings':
+      'allOf':
+      - '$ref': '#/components/schemas/TlsSettingsPatch'
+      - 'description': >
+          TLS and encryption settings.
+        'example':
+          'certificate_path': '/etc/ssl/example.com.cert'
+          'enabled': true
+          'force_https': true
+          'port_dns_over_quic': 784
+          'port_dns_over_tls': 853
+          'port_https': 443
+          'private_key_path': '/etc/ssl/example.com.key'
+          'server_name': 'dns.example.com'
+        'required':
+        - 'enabled'
+        - 'force_https'
+        - 'port_dns_over_quic'
+        - 'port_dns_over_tls'
+        - 'port_https'
+        - 'server_name'
+
+    'TlsSettingsPatch':
+      'description': >
+        TLS and encryption settings update object.
+      'example':
+        'certificate': 'Base64KeyDatAA=='
+        'enabled': true
+        'private_key': 'Base64CertDatA=='
+      'properties':
+        'certificate':
+          'description': |
+            Base64-encoded string with PEM-encoded certificate chain.
+
+            Should not be sent if `certificate_path` is sent.  Otherwise, must
+            be sent.
+          'format': 'byte'
+          'type': 'string'
+        'certificate_path':
+          'description': |
+            Path to the certificate file.
+
+            Should not be sent if `certificate` is sent.  Otherwise, must be
+            sent.
+          'type': 'string'
+        'enabled':
+          'description': >
+            If `true`,  AdGuard Home the administration interface will be served
+            over HTTPS, and the DNS server will listen requests over
+            DNS-over-TLS and other protocols.
+          'type': 'boolean'
+        'force_https':
+          'description': >
+            If `true`, enabled the HTTP-to-HTTPS redirect.
+          'type': 'boolean'
+        'port_dns_over_quic':
+          'default': 784
+          'description': >
+            The DNS-over-QUIC port.  If `0`, DNS-over-QUIC is disabled.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+        'port_dns_over_tls':
+          'default': 853
+          'description': >
+            The DNS-over-TLS port.  If `0`, DNS-over-TLS is disabled.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+        'port_https':
+          'default': 443
+          'description': >
+            The HTTPS port.  If `0`, HTTPS is disabled.
+          'format': 'int64'
+          'maximum': 65535
+          'minimum': 0
+          'type': 'integer'
+        'private_key':
+          'description': |
+            Base64-encoded string with PEM-encoded private key.
+
+            Should not be sent if `private_key_path` is sent.  Otherwise, must
+            be sent.
+          'format': 'byte'
+          'type': 'string'
+        'private_key_path':
+          'description': |
+            Path to the private key file.
+
+            Should not be sent if `private_key` is sent.  Otherwise, must be
+            sent.
+          'type': 'string'
+        'server_name':
+          'description': >
+            The name of the server.  Used to validate the certificates as well
+            as to check ClientIDs in DNS-over-HTTP and DNS-over-TLS.
+          'type': 'string'
+      'type': 'object'
+
+    'Uid':
+      'description': >
+        A unique ID of an entity, an opaque string.
+      'pattern': '[0-9a-zA-Z_-]{1,64}'
+      'type': 'string'
+
+    'UnauthorizedResp':
+      'example':
+        'code': 'AUT000'
+        'msg': 'no or bad authorization provided'
+      'properties':
+        'code':
+          '$ref': '#/components/schemas/ErrorCode'
+        'msg':
+          'description': >
+            Error message string.
+          'type': 'string'
+      'required':
+      - 'code'
+      - 'msg'
+      'type': 'object'
+
+    'UnprocessableEntityResp':
+      'example':
+        'code': 'JSN001'
+        'msg': >-
+          json: cannot unmarshal string into Go struct field T.A of type int
+      'properties':
+        'code':
+          '$ref': '#/components/schemas/ErrorCode'
+        'msg':
+          'description': >
+            Error message string.
+          'type': 'string'
+      'required':
+      - 'code'
+      - 'msg'
+      'type': 'object'
+
+    'UpstreamServerAddr':
+      'description': |
+        Upstream DNS server address.  Supported item formats:
+
+         *  `94.140.14.140`: plain DNS-over-UDP.
+
+         *  `tls://dns-unfiltered.adguard.com`: encrypted DNS-over-TLS.
+
+         *  `https://dns-unfiltered.adguard.com/dns-query`: encrypted
+             DNS-over-HTTPS.
+
+         *  `quic://dns-unfiltered.adguard.com:784`: encrypted DNS-over-QUIC
+             (experimental).
+
+         *  `tcp://94.140.14.140`: plain DNS-over-TCP.
+
+         *  `sdns://...`: DNS Stamps for DNSCrypt or DNS-over-HTTPS
+             resolvers.
+
+         *  `[/example.local/]94.140.14.140`: DNS upstream for specific
+             domain(s).
+
+         *  `# comment`: A comment.
+      'type': 'string'
+
+    'Whois':
+      'additionalProperties':
+        'type': 'string'
+      'description': >
+        WHOIS information, if any.  If there are none, this field is usually
+        absent.
+      'minProperties': 1
+      'type': 'object'
+
+  # TODO(a.garipov): Find a way to specify a cookie authorization.
+  'securitySchemes':
+    'basicAuth':
+      'description': >
+        Basic HTTP authorization.
+      'scheme': 'basic'
+      'type': 'http'
diff --git a/scripts/make/go-build.sh b/scripts/make/go-build.sh
index 7854166c..c998a611 100644
--- a/scripts/make/go-build.sh
+++ b/scripts/make/go-build.sh
@@ -123,4 +123,14 @@ CGO_ENABLED="$cgo_enabled"
 GO111MODULE='on'
 export CGO_ENABLED GO111MODULE
 
-"$go" build --ldflags "$ldflags" "$race_flags" --trimpath "$o_flags" "$v_flags" "$x_flags"
+# Build the new binary if requested.
+if [ "${V1API:-0}" -eq '0' ]
+then
+	tags_flags='--tags='
+else
+	tags_flags='--tags=v1'
+fi
+readonly tags_flags
+
+"$go" build --ldflags "$ldflags" "$race_flags" "$tags_flags" --trimpath "$o_flags" "$v_flags"\
+	"$x_flags"
diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh
index c363e513..df2f297a 100644
--- a/scripts/make/go-lint.sh
+++ b/scripts/make/go-lint.sh
@@ -140,6 +140,7 @@ underscores() {
 			-e '_others.go'\
 			-e '_test.go'\
 			-e '_unix.go'\
+			-e '_v1.go'\
 			-e '_windows.go' \
 			-v\
 			| sed -e 's/./\t\0/'
@@ -222,7 +223,7 @@ gocyclo --over 17 ./internal/dhcpd/ ./internal/dnsforward/\
 # Apply stricter standards to new or somewhat refactored code.
 gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\
 	./internal/aghtest/ ./internal/stats/ ./internal/tools/\
-	./internal/updater/ ./internal/version/ ./main.go
+	./internal/updater/ ./internal/v1/ ./internal/version/ ./main.go\
 
 ineffassign ./...
 

From c4ff80fd3aa070b75bcfc02b0aaf6b5670fddefe Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Wed, 27 Apr 2022 11:39:48 +0300
Subject: [PATCH 050/143] Pull request: dnsforward: ddr support

Merge in DNS/adguard-home from 4463-ddr-support-1 to master

Squashed commit of the following:

commit 74d8337a9d78e00a0b01301bbf92054fc58aff0d
Merge: 7882c56e ed449c61
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Apr 27 10:32:48 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4463-ddr-support-1

commit 7882c56eced204b99a0189c839f5b5ef56fcbfd8
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 13:29:16 2022 +0200

    all: docs

commit 59593cf47f8db2131fb8a4a44ec3721de8f73567
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 13:06:49 2022 +0200

    all: docs

commit 13bfe00d91b190a2538eeee642ce40abe031ecf2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 12:58:48 2022 +0200

    all: docs

commit a663b53d211483a717a480e24e120a201dc3d9da
Merge: 53122f6a 235316e0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 12:33:07 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4463-ddr-support-1

commit 53122f6aac8e9ede69de833e367e006f4c5c75c0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 12:30:56 2022 +0200

    dnsforward: ddr support

commit 87083ded02c120e1fb3e54b885a1992efd8f780d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 11:51:06 2022 +0200

    dnsforward: ddr support

commit 3dc711e0a9ba1a024e7d24527b2a690aa36413ce
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 11:39:59 2022 +0200

    dnsforward: imp code

commit f63f6a9d65a96960ae2c06aeca2b32aef70d8f63
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 11:34:23 2022 +0200

    dnsforward: ddr support

commit e64ffcdac8f9428e4c93a6dc99cc3f1bb090af35
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 11:22:20 2022 +0200

    dnsforward: ddr support

commit 297460946bb1765137c7c3fe3e298cd574635287
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 26 11:08:59 2022 +0200

    dnsforward: imp code

commit 61b4e2e0e06e212c31b7a9d1b09fab392ae6dbc4
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 25 14:39:34 2022 +0200

    dnsforward: ddr support

commit 7c2787e12eb67a02b41cbb4fe36a12671259f9c9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 25 11:41:42 2022 +0200

    all: docs

commit 29c2c872843f6d006e6a98144a52e23a4cbe7be9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 25 11:26:07 2022 +0200

    dnsforward: ddr support

commit 2d4ba0c4ce4fbbf3d99da8dd92349da2ec9cff13
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 25 11:03:34 2022 +0200

    dnsforward: ddr support

commit 0efb5b5cd55bcba3dfae35e80209277f0643a87e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Sun Apr 24 13:07:25 2022 +0200

    dnsforward: imp code

commit 884381ef04029d5d743834555cb6601d891c2d25
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Sun Apr 24 12:56:41 2022 +0200

    dnsforward: imp code

commit 41231f24e83a9690d36546e83fd61ddd709050ed
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Apr 22 16:05:47 2022 +0200

    dnsforward: ddr support

commit 9d9da3f479efa5d5609f9b1e6b0d1a93fc253b9f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Apr 22 13:46:29 2022 +0200

    all: ddr support

commit b225363df143d599e9acbf1a6b0bf6d00044dd47
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Apr 22 13:38:27 2022 +0200

    dnsforward: imp code

... and 10 more commits
---
 CHANGELOG.md                    |   5 +-
 internal/dnsforward/config.go   |   3 +-
 internal/dnsforward/dns.go      |  76 +++++++++++++++++
 internal/dnsforward/dns_test.go | 146 ++++++++++++++++++++++++++++++++
 internal/home/config.go         |   1 +
 5 files changed, 229 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 047726ff..84f45312 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,8 @@ and this project adheres to
 
 ### Added
 
+- Support for Discovery of Designated Resolvers (DDR) according to the 
+  [RFC draft][ddr-draft-06] ([#4463]).
 - The ability to control each source of runtime clients separately via
   `clients.runtime_sources` configuration object ([#3020]).
 - The ability to customize the set of networks that are considered private
@@ -143,8 +145,9 @@ In this release, the schema version has changed from 12 to 14.
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
 [#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499
 
-[repr]:         https://reproducible-builds.org/docs/source-date-epoch/
+[ddr-draft-06]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
 [doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
+[repr]:         https://reproducible-builds.org/docs/source-date-epoch/
 
 
 
diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go
index 9a050f52..16a6325e 100644
--- a/internal/dnsforward/config.go
+++ b/internal/dnsforward/config.go
@@ -122,6 +122,7 @@ type FilteringConfig struct {
 	EnableDNSSEC           bool     `yaml:"enable_dnssec"`      // Set AD flag in outcoming DNS request
 	EnableEDNSClientSubnet bool     `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
 	MaxGoroutines          uint32   `yaml:"max_goroutines"`     // Max. number of parallel goroutines for processing incoming requests
+	HandleDDR              bool     `yaml:"handle_ddr"`         // Handle DDR requests
 
 	// IpsetList is the ipset configuration that allows AdGuard Home to add
 	// IP addresses of the specified domain names to an ipset list.  Syntax:
@@ -151,7 +152,7 @@ type TLSConfig struct {
 	PrivateKeyData       []byte `yaml:"-" json:"-"`
 
 	// ServerName is the hostname of the server.  Currently, it is only being
-	// used for ClientID checking.
+	// used for ClientID checking and Discovery of Designated Resolvers (DDR).
 	ServerName string `yaml:"-" json:"-"`
 
 	cert tls.Certificate
diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go
index d423482a..19d54d91 100644
--- a/internal/dnsforward/dns.go
+++ b/internal/dnsforward/dns.go
@@ -76,6 +76,10 @@ const (
 	resultCodeError
 )
 
+// ddrHostFQDN is the FQDN used in Discovery of Designated Resolvers (DDR) requests.
+// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html.
+const ddrHostFQDN = "_dns.resolver.arpa."
+
 // handleDNSRequest filters the incoming DNS requests and writes them to the query log
 func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
 	ctx := &dnsContext{
@@ -94,6 +98,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
 	mods := []modProcessFunc{
 		s.processRecursion,
 		s.processInitial,
+		s.processDDRQuery,
 		s.processDetermineLocal,
 		s.processInternalHosts,
 		s.processRestrictLocal,
@@ -241,6 +246,77 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
 	s.setTableIPToHost(ipToHost)
 }
 
+// processDDRQuery responds to SVCB query for a special use domain name
+// ‘_dns.resolver.arpa’.  The result contains different types of encryption
+// supported by current user configuration.
+//
+// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html.
+func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
+	d := ctx.proxyCtx
+	question := d.Req.Question[0]
+
+	if !s.conf.HandleDDR {
+		return resultCodeSuccess
+	}
+
+	if question.Name == ddrHostFQDN {
+		// TODO(a.garipov): Check DoQ support in next RFC drafts.
+		if s.dnsProxy.TLSListenAddr == nil && s.dnsProxy.HTTPSListenAddr == nil ||
+			question.Qtype != dns.TypeSVCB {
+			d.Res = s.makeResponse(d.Req)
+
+			return resultCodeFinish
+		}
+
+		d.Res = s.makeDDRResponse(d.Req)
+
+		return resultCodeFinish
+	}
+
+	return resultCodeSuccess
+}
+
+// makeDDRResponse creates DDR answer according to server configuration.
+func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
+	resp = s.makeResponse(req)
+	domainName := s.conf.ServerName
+
+	for _, addr := range s.dnsProxy.HTTPSListenAddr {
+		values := []dns.SVCBKeyValue{
+			&dns.SVCBAlpn{Alpn: []string{"h2"}},
+			&dns.SVCBPort{Port: uint16(addr.Port)},
+			&dns.SVCBDoHPath{Template: "/dns-query?dns"},
+		}
+
+		ans := &dns.SVCB{
+			Hdr:      s.hdr(req, dns.TypeSVCB),
+			Priority: 1,
+			Target:   domainName,
+			Value:    values,
+		}
+
+		resp.Answer = append(resp.Answer, ans)
+	}
+
+	for _, addr := range s.dnsProxy.TLSListenAddr {
+		values := []dns.SVCBKeyValue{
+			&dns.SVCBAlpn{Alpn: []string{"dot"}},
+			&dns.SVCBPort{Port: uint16(addr.Port)},
+		}
+
+		ans := &dns.SVCB{
+			Hdr:      s.hdr(req, dns.TypeSVCB),
+			Priority: 2,
+			Target:   domainName,
+			Value:    values,
+		}
+
+		resp.Answer = append(resp.Answer, ans)
+	}
+
+	return resp
+}
+
 // processDetermineLocal determines if the client's IP address is from
 // locally-served network and saves the result into the context.
 func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go
index 54104268..8ab7501c 100644
--- a/internal/dnsforward/dns_test.go
+++ b/internal/dnsforward/dns_test.go
@@ -14,6 +14,152 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+const ddrTestDomainName = "dns.example.net"
+
+func TestServer_ProcessDDRQuery(t *testing.T) {
+	dohSVCB := &dns.SVCB{
+		Priority: 1,
+		Target:   ddrTestDomainName,
+		Value: []dns.SVCBKeyValue{
+			&dns.SVCBAlpn{Alpn: []string{"h2"}},
+			&dns.SVCBPort{Port: 8044},
+			&dns.SVCBDoHPath{Template: "/dns-query?dns"},
+		},
+	}
+
+	dotSVCB := &dns.SVCB{
+		Priority: 2,
+		Target:   ddrTestDomainName,
+		Value: []dns.SVCBKeyValue{
+			&dns.SVCBAlpn{Alpn: []string{"dot"}},
+			&dns.SVCBPort{Port: 8043},
+		},
+	}
+
+	testCases := []struct {
+		name       string
+		host       string
+		want       []*dns.SVCB
+		wantRes    resultCode
+		portDoH    int
+		portDoT    int
+		qtype      uint16
+		ddrEnabled bool
+	}{{
+		name:       "pass_host",
+		wantRes:    resultCodeSuccess,
+		host:       "example.net.",
+		qtype:      dns.TypeSVCB,
+		ddrEnabled: true,
+		portDoH:    8043,
+	}, {
+		name:       "pass_qtype",
+		wantRes:    resultCodeFinish,
+		host:       ddrHostFQDN,
+		qtype:      dns.TypeA,
+		ddrEnabled: true,
+		portDoH:    8043,
+	}, {
+		name:       "pass_disabled_tls",
+		wantRes:    resultCodeFinish,
+		host:       ddrHostFQDN,
+		qtype:      dns.TypeSVCB,
+		ddrEnabled: true,
+	}, {
+		name:       "pass_disabled_ddr",
+		wantRes:    resultCodeSuccess,
+		host:       ddrHostFQDN,
+		qtype:      dns.TypeSVCB,
+		ddrEnabled: false,
+		portDoH:    8043,
+	}, {
+		name:       "dot",
+		wantRes:    resultCodeFinish,
+		want:       []*dns.SVCB{dotSVCB},
+		host:       ddrHostFQDN,
+		qtype:      dns.TypeSVCB,
+		ddrEnabled: true,
+		portDoT:    8043,
+	}, {
+		name:       "doh",
+		wantRes:    resultCodeFinish,
+		want:       []*dns.SVCB{dohSVCB},
+		host:       ddrHostFQDN,
+		qtype:      dns.TypeSVCB,
+		ddrEnabled: true,
+		portDoH:    8044,
+	}, {
+		name:       "dot_doh",
+		wantRes:    resultCodeFinish,
+		want:       []*dns.SVCB{dotSVCB, dohSVCB},
+		host:       ddrHostFQDN,
+		qtype:      dns.TypeSVCB,
+		ddrEnabled: true,
+		portDoT:    8043,
+		portDoH:    8044,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			s := prepareTestServer(t, tc.portDoH, tc.portDoT, tc.ddrEnabled)
+
+			req := createTestMessageWithType(tc.host, tc.qtype)
+
+			dctx := &dnsContext{
+				proxyCtx: &proxy.DNSContext{
+					Req: req,
+				},
+			}
+
+			res := s.processDDRQuery(dctx)
+			require.Equal(t, tc.wantRes, res)
+
+			if tc.wantRes != resultCodeFinish {
+				return
+			}
+
+			msg := dctx.proxyCtx.Res
+			require.NotNil(t, msg)
+
+			for _, v := range tc.want {
+				v.Hdr = s.hdr(req, dns.TypeSVCB)
+			}
+
+			assert.ElementsMatch(t, tc.want, msg.Answer)
+		})
+	}
+}
+
+func prepareTestServer(t *testing.T, portDoH, portDoT int, ddrEnabled bool) (s *Server) {
+	t.Helper()
+
+	proxyConf := proxy.Config{}
+
+	if portDoH > 0 {
+		proxyConf.HTTPSListenAddr = []*net.TCPAddr{{Port: portDoH}}
+	}
+
+	if portDoT > 0 {
+		proxyConf.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}}
+	}
+
+	s = &Server{
+		dnsProxy: &proxy.Proxy{
+			Config: proxyConf,
+		},
+		conf: ServerConfig{
+			FilteringConfig: FilteringConfig{
+				HandleDDR: ddrEnabled,
+			},
+			TLSConfig: TLSConfig{
+				ServerName: ddrTestDomainName,
+			},
+		},
+	}
+
+	return s
+}
+
 func TestServer_ProcessDetermineLocal(t *testing.T) {
 	s := &Server{
 		privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
diff --git a/internal/home/config.go b/internal/home/config.go
index 720683a1..14f5781e 100644
--- a/internal/home/config.go
+++ b/internal/home/config.go
@@ -187,6 +187,7 @@ var config = &configuration{
 			Ratelimit:          20,
 			RefuseAny:          true,
 			AllServers:         false,
+			HandleDDR:          true,
 			FastestTimeout: timeutil.Duration{
 				Duration: fastip.DefaultPingWaitTimeout,
 			},

From 5d52e68d264b77dd27ac53616027e6a26637c6e8 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 27 Apr 2022 14:06:10 +0300
Subject: [PATCH 051/143] Pull request: home: imp client finding logging

Updates #4526.

Squashed commit of the following:

commit 970476ea238cbab797912e1c50eca35e3f74a52f
Merge: 3e2dde81 c4ff80fd
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 27 14:01:17 2022 +0300

    Merge branch 'master' into 4526-add-client-logs

commit 3e2dde81d7325b75c257f333e2c4e417f4ae203d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 27 13:59:19 2022 +0300

    home: imp logs

commit 094bfe34770b4bdc504b5ae97dd2d3842b2f73cf
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 26 21:11:18 2022 +0300

    home: imp client finding logging
---
 internal/home/dns.go | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/internal/home/dns.go b/internal/home/dns.go
index e28193ee..1c04c6c3 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -330,24 +330,28 @@ func getDNSEncryption() (de dnsEncryption) {
 
 // applyAdditionalFiltering adds additional client information and settings if
 // the client has them.
-func applyAdditionalFiltering(clientAddr net.IP, clientID string, setts *filtering.Settings) {
+func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering.Settings) {
 	Context.dnsFilter.ApplyBlockedServices(setts, nil, true)
 
-	if clientAddr == nil {
+	log.Debug("looking up settings for client with ip %s and clientid %q", clientIP, clientID)
+
+	if clientIP == nil {
 		return
 	}
 
-	setts.ClientIP = clientAddr
+	setts.ClientIP = clientIP
 
 	c, ok := Context.clients.Find(clientID)
 	if !ok {
-		c, ok = Context.clients.Find(clientAddr.String())
+		c, ok = Context.clients.Find(clientIP.String())
 		if !ok {
+			log.Debug("client with ip %s and clientid %q not found", clientIP, clientID)
+
 			return
 		}
 	}
 
-	log.Debug("using settings for client %s with ip %s and clientid %q", c.Name, clientAddr, clientID)
+	log.Debug("using settings for client %q with ip %s and clientid %q", c.Name, clientIP, clientID)
 
 	if c.UseOwnBlockedServices {
 		Context.dnsFilter.ApplyBlockedServices(setts, c.BlockedServices, false)

From 6dc9e73ce4d8bdc01ed3b99facc7d4062288a760 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 27 Apr 2022 14:18:50 +0300
Subject: [PATCH 052/143] Pull request: client: upd i18n

Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 0e0a3290a02780b147aacff529c4ba3bd3ace68f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 27 14:13:24 2022 +0300

    client: upd i18n
---
 client/src/__locales/cs.json    |  4 ++--
 client/src/__locales/da.json    |  4 ++--
 client/src/__locales/de.json    | 16 ++++++++--------
 client/src/__locales/es.json    |  4 ++--
 client/src/__locales/fi.json    |  2 +-
 client/src/__locales/nl.json    |  2 +-
 client/src/__locales/ro.json    | 18 +++++++++---------
 client/src/__locales/tr.json    | 20 ++++++++++----------
 client/src/__locales/uk.json    | 22 +++++++++++-----------
 client/src/__locales/zh-cn.json | 23 +++++++++++------------
 client/src/__locales/zh-tw.json |  2 +-
 11 files changed, 58 insertions(+), 59 deletions(-)

diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json
index 26759394..8331dd50 100644
--- a/client/src/__locales/cs.json
+++ b/client/src/__locales/cs.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Bootstrap DNS servery",
     "bootstrap_dns_desc": "Servery Bootstrap DNS se používají k řešení IP adres DoH/DoT, které zadáváte jako upstreamy.",
     "local_ptr_title": "Soukromé reverzní DNS servery",
-    "local_ptr_desc": "Servery DNS, které AdGuard Home používá pro lokální dotazy PTR. Tyto servery se používají k rozlišení názvů hostitelů klientů se soukromými adresami IP, například \"192.168.12.34\" pomocí rDNS. Pokud není nastaveno, AdGuard Home automaticky použije výchozí řešitele vašeho OS s výjimkou adres samotného AdGuard Home.",
+    "local_ptr_desc": "Servery DNS, které AdGuard Home používá pro lokální dotazy PTR. Tyto servery se používají k řešení požadavků PTR na adresy v soukromých rozmezích IP, například \"192.168.12.34\", pomocí reverzního DNS. Pokud není nastaveno, AdGuard Home automaticky použije výchozí řešitele vašeho OS s výjimkou adres samotného AdGuard Home.",
     "local_ptr_default_resolver": "Ve výchozím nastavení používá AdGuard Home následující reverzní DNS řešitele: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home nemohl určit vhodné soukromé reverzní DNS řešitele pro tento systém.",
     "local_ptr_placeholder": "Zadejte jednu adresu serveru na řádek",
@@ -283,7 +283,7 @@
     "download_mobileconfig_doh": "Stáhnout .mobileconfig pro DNS skrze HTTPS",
     "download_mobileconfig_dot": "Stáhnout .mobileconfig pro DNS skrze TLS",
     "download_mobileconfig": "Stáhnout konfigurační soubor",
-    "plain_dns": "Čisté DNS",
+    "plain_dns": "Běžný DNS",
     "form_enter_rate_limit": "Zadejte rychlostní limit",
     "rate_limit": "Rychlostní limit",
     "edns_enable": "Povolit klientskou podsíť EDNS",
diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json
index 1b12ad06..4b077cf8 100644
--- a/client/src/__locales/da.json
+++ b/client/src/__locales/da.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Bootstrap DNS-servere",
     "bootstrap_dns_desc": "Bootstrap DNS-servere bruges til at fortolke IP-adresser for de DoH-/DoT-resolvere, du angiver som upstream.",
     "local_ptr_title": "Private reverse DNS-servere",
-    "local_ptr_desc": "De DNS-servere, som AdGuard Home bruger til lokale PTR-forespørgsler. Disse servere bruges til at opløse klientværtsnavne med private IP-adresser, f.eks. \"192.168.12.34\", vha. rDNS. Hvis ikke indstillet, bruger AdGuard Home dit operativsystems standard DNS-opløsere undtagen for sine egne adresser.",
+    "local_ptr_desc": "DNS-servere brugt af AdGuard Home til lokale PTR-forespørgsler. Disse servere bruges til at opløse PTR-forespørgsler fra private IP-adresseområder, f.eks. \"192.168.12.34\", vha. reverse DNS. Hvis ikke opsat, bruger AdGuard Home operativsystems standard DNS-opløsere undtagen for sine egne adresser.",
     "local_ptr_default_resolver": "AdGuard Home bruger som standard flg. reverse DNS-opløsere: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home kunne ikke fastslå egnede private reverse DNS-opløsere for dette system.",
     "local_ptr_placeholder": "Indtast en serveradresse pr. Linje",
@@ -351,7 +351,7 @@
     "install_devices_android_list_5": "Skift de aktuelle DNS 1- og DNS 2-værdier til dine AdGuard Home-serveradresser.",
     "install_devices_ios_list_1": "Tryk på Indstillinger på Hjem-skærmen.",
     "install_devices_ios_list_2": "Vælg Wi-Fi i menuen til venstre (det er umuligt at opsætte DNS for mobilnetværker).",
-    "install_devices_ios_list_3": "Tryk på navnet på det aktuelt aktive netværk.",
+    "install_devices_ios_list_3": "Tryk på navnet for det aktuelt aktive netværk.",
     "install_devices_ios_list_4": "Angiv dine AdGuard Home-serveradresser i DNS-feltet.",
     "get_started": "Komme I Gang",
     "next": "Næste",
diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json
index be5666a8..0fe6345d 100644
--- a/client/src/__locales/de.json
+++ b/client/src/__locales/de.json
@@ -149,9 +149,9 @@
     "general_settings": "Allgemeine Einstellungen",
     "dns_settings": "DNS-Einstellungen",
     "dns_blocklists": "DNS-Sperrliste",
-    "dns_allowlists": "DNS-Freigabelisten",
+    "dns_allowlists": "DNS-Positivlisten",
     "dns_blocklists_desc": "AdGuard Home sperrt Domains, die in den Sperrlisten enthalten sind.",
-    "dns_allowlists_desc": "Domains aus DNS-Freigabelisten werden auch dann zugelassen, wenn sie in einer der Sperrlisten enthalten sind.",
+    "dns_allowlists_desc": "Domains aus DNS-Positivlisten werden auch dann zugelassen, wenn sie in einer der Sperrlisten enthalten sind.",
     "custom_filtering_rules": "Benutzerdefinierte Filterregeln",
     "encryption_settings": "Verschlüsselungseinstellungen",
     "dhcp_settings": "DHCP-Einstellungen",
@@ -181,21 +181,21 @@
     "elapsed": "Verstrichen",
     "filters_and_hosts_hint": "AdGuard Home versteht grundlegende Werbefilterregeln und Host-Datei-Syntax.",
     "no_blocklist_added": "Keine Sperrliste hinzugefügt",
-    "no_whitelist_added": "Keine Freigabeliste hinzugefügt",
+    "no_whitelist_added": "Keine Positivliste hinzugefügt",
     "add_blocklist": "Sperrliste hinzufügen",
-    "add_allowlist": "Freigabeliste hinzufügen",
+    "add_allowlist": "Positivliste hinzufügen",
     "cancel_btn": "Abbrechen",
     "enter_name_hint": "Name eingeben",
     "enter_url_or_path_hint": "URL oder absoluten Pfad der Liste eingeben",
     "check_updates_btn": "Nach Aktualisierungen suchen",
     "new_blocklist": "Neue Sperrliste",
-    "new_allowlist": "Neue Freigabeliste",
+    "new_allowlist": "Neue Positivliste",
     "edit_blocklist": "Sperrliste bearbeiten",
-    "edit_allowlist": "Freigabeliste bearbeiten",
+    "edit_allowlist": "Positivliste bearbeiten",
     "choose_blocklist": "Sperrliste wählen",
-    "choose_allowlist": "Freigabeliste wählen",
+    "choose_allowlist": "Positivliste wählen",
     "enter_valid_blocklist": "Gültige Webadresse zur Sperrliste eingeben.",
-    "enter_valid_allowlist": "Gültige Webadresse zur Freigabeliste eingeben.",
+    "enter_valid_allowlist": "Gültige Webadresse zur Positivliste eingeben.",
     "form_error_url_format": "Ungültiges URL-Format",
     "form_error_url_or_path_format": "Ungültige URL oder absoluter Pfad der Liste",
     "custom_filter_rules": "Benutzerdefinierte Filterregeln",
diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json
index a83527ed..e495c81d 100644
--- a/client/src/__locales/es.json
+++ b/client/src/__locales/es.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Servidores DNS de arranque",
     "bootstrap_dns_desc": "Los servidores DNS de arranque se utilizan para resolver las direcciones IP de los resolutores DoH/DoT que especifiques como DNS de subida.",
     "local_ptr_title": "Servidores DNS inversos y privados",
-    "local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver los nombres de hosts de los clientes a direcciones IP privadas, por ejemplo \"192.168.12.34\", utilizando DNS inverso. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
+    "local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver las peticiones PTR de direcciones en rangos de IP privadas, por ejemplo \"192.168.12.34\", utilizando DNS inverso. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
     "local_ptr_default_resolver": "Por defecto, AdGuard Home utiliza los siguientes resolutores DNS inversos: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home no pudo determinar los resolutores DNS inversos y privados adecuados para este sistema.",
     "local_ptr_placeholder": "Ingresa una dirección de servidor por línea",
@@ -351,7 +351,7 @@
     "install_devices_android_list_5": "Cambia los valores de DNS 1 y DNS 2 a las direcciones de tu servidor AdGuard Home.",
     "install_devices_ios_list_1": "En la pantalla de inicio, pulsa en Configuración.",
     "install_devices_ios_list_2": "Elige Wi-Fi en el menú de la izquierda (es imposible configurar DNS para redes móviles).",
-    "install_devices_ios_list_3": "Pulsa sobre el nombre de la red activa en ese momento.",
+    "install_devices_ios_list_3": "Pulsa sobre el nombre de la red actualmente activa.",
     "install_devices_ios_list_4": "En el campo DNS ingresa las direcciones de tu servidor AdGuard Home.",
     "get_started": "Comenzar",
     "next": "Siguiente",
diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json
index 5e6c8089..3141b12a 100644
--- a/client/src/__locales/fi.json
+++ b/client/src/__locales/fi.json
@@ -338,7 +338,7 @@
     "install_devices_windows_list_2": "Avaa \"Verkko ja Internet\" -ryhmä ja sitten \"Verkko ja jakamiskeskus\".",
     "install_devices_windows_list_3": "Paina ikkunan vasemmasta laidasta \"Muuta sovittimen asetuksia\".",
     "install_devices_windows_list_4": "Paina aktiivista yhteyttäsi hiiren kakkospainikkeella ja valitse \"Ominaisuudet\".",
-    "install_devices_windows_list_5": "Etsi listasta \"Internet protokolla versio 4 (TCP/IP)\", valitse se ja paina jälleen \"Ominaisuudet\".",
+    "install_devices_windows_list_5": "Etsi listasta \"Internet Protocol Version 4 (TCP/IPv4)\" (tai IPv6:lle \"Internet Protocol Version 6 (TCP/IPv6)\"), valitse se ja paina jälleen \"Ominaisuudet\".",
     "install_devices_windows_list_6": "Valitse \"Käytä seuraavia DNS-palvelinten osoitteita\" ja syötä AdGuard Home -palvelimesi osoitteet.",
     "install_devices_macos_list_1": "Paina Omena-kuvaketta ja valitse \"Järjestelmäasetukset\".",
     "install_devices_macos_list_2": "Paina \"Verkko\".",
diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json
index 7fc19d73..83278ee3 100644
--- a/client/src/__locales/nl.json
+++ b/client/src/__locales/nl.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Bootstrap DNS-servers",
     "bootstrap_dns_desc": "Bootstrap DNS-servers worden gebruikt om IP-adressen op te lossen van de DoH / DoT-resolvers die u opgeeft als upstreams.",
     "local_ptr_title": "Private omgekeerde DNS-servers",
-    "local_ptr_desc": "De DNS-servers die AdGuard Home zal gebruiken voor lokale PTR zoekopdrachten. Deze server wordt gebruikt om de hostnamen van de clients met private IP-adressen, bijvoorbeeld \"192.168.12.34\", dmv. rDNS. Indien niet ingesteld, gebruikt AdGuard Home automatisch je standaard DNS-resolver.",
+    "local_ptr_desc": "De DNS-servers die AdGuard Home gebruikt voor lokale PTR-zoekopdrachten. Deze servers worden gebruikt om PTR-verzoeken voor adressen in privé-IP-bereiken op te lossen, bijvoorbeeld \"192.168.12.34\", met behulp van reverse DNS. Indien niet ingesteld, gebruikt AdGuard Home de adressen van de standaard DNS-resolvers van uw besturingssysteem, behalve de adressen van AdGuard Home zelf.",
     "local_ptr_default_resolver": "Standaard gebruikt AdGuard Home de volgende omgekeerde DNS-resolvers: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home kon voor dit systeem geen geschikte private omgekeerde DNS-resolvers bepalen.",
     "local_ptr_placeholder": "Voer één serveradres per regel in",
diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json
index b19b8576..097dba59 100644
--- a/client/src/__locales/ro.json
+++ b/client/src/__locales/ro.json
@@ -7,15 +7,15 @@
     "load_balancing": "Echilibrare-sarcini",
     "load_balancing_desc": "Interoghează câte un server în amonte la un moment dat. AdGuard Home utilizează un algoritm de randomizare ponderat pentru a alege serverul, astfel încât cel mai rapid server să fie utilizat mai des.",
     "bootstrap_dns": "Serverele DNS Bootstrap",
-    "bootstrap_dns_desc": "Serverele DNS Bootstrap sunt folosite pentru a rezolva adresele IP ale resolverelor DoH/DoT indicate ca upstreams.",
+    "bootstrap_dns_desc": "Serverele DNS Bootstrap sunt folosite pentru a rezolva adresele IP ale rezolvatorilor DoH/DoT indicați ca upstreams.",
     "local_ptr_title": "Servere DNS inverse private",
-    "local_ptr_desc": "Servere DNS pe care AdGuard Home le utilizează pentru interogări PTR locale. Aceste servere sunt folosite pentru a rezolva numele gazdelor de clienți cu adrese IP private, cum ar fi \"192.168.12.34\", folosind DNS inversat. Dacă nu este setat, AdGuard Home utilizează adresele resolverelor DNS implicite ale SO al dvs., cu excepția adreselor AdGuard Home înseși.",
-    "local_ptr_default_resolver": "În mod implicit, AdGuard Home utilizează următoarele resolvere DNS inverse: {{ip}}.",
-    "local_ptr_no_default_resolver": "AdGuard Home nu a putut determina resolvere DNS private adecvate pentru acest sistem.",
+    "local_ptr_desc": "Serverele DNS pe care AdGuard Home le utilizează pentru interogările PTR locale. Aceste servere sunt utilizate pentru a rezolva solicitările PTR pentru adrese din intervale IP private, de exemplu „192.168.12.34”, utilizând DNS invers. Dacă nu este configurat, AdGuard Home utilizează adresele rezolvatorilor DNS impliciți ai sistemului dvs. de operare, cu excepția adreselor AdGuard Home în sine.",
+    "local_ptr_default_resolver": "În mod implicit, AdGuard Home utilizează următorii rezolvatori DNS inverși: {{ip}}.",
+    "local_ptr_no_default_resolver": "AdGuard Home nu a putut determina rezolvatorii DNS privați adecvați pentru acest sistem.",
     "local_ptr_placeholder": "Introduceți o adresă de server per linie",
     "resolve_clients_title": "Permiteți rezolvarea inversa a adreselor IP ale clienților",
-    "resolve_clients_desc": "Rezolvă invers adresele IP ale clienților în numele lor de gazde prin trimiterea interogărilor PTR la resolverele corespunzătoare (servere DNS private pentru clienți locali, servere în amonte pentru clienți cu adrese IP publice).",
-    "use_private_ptr_resolvers_title": "Utilizați resolvere DNS inverse private",
+    "resolve_clients_desc": "Rezolvă invers adresele IP ale clienților în numele lor de gazde prin trimiterea interogărilor PTR la rezolvatorii corespunzători (servere DNS private pentru clienți locali, servere în amonte pentru clienți cu adrese IP publice).",
+    "use_private_ptr_resolvers_title": "Utilizați rezolvatori DNS inverși privați",
     "use_private_ptr_resolvers_desc": "Efectuează examinări DNS inverse pentru adresele deservite local folosind aceste servere în amonte. Dacă este dezactivată, AdGuard Home răspunde cu NXDOMAIN la toate aceste cereri PTR, cu excepția clienților cunoscuți din DHCP, /etc/hosts și așa mai departe.",
     "check_dhcp_servers": "Căutați servere DHCP",
     "save_config": "Salvare configurare",
@@ -214,7 +214,7 @@
     "example_upstream_dot": "<0>DNS-over-TLS</0> criptat;",
     "example_upstream_doh": "<0>DNS-over-HTTPS</0> criptat;",
     "example_upstream_doq": "<0>DNS-over-QUIC</0> criptat (experimental);",
-    "example_upstream_sdns": "<0>DNS Stamps</0> pentru <1>DNSCrypt</1> sau rezolvere <2>DNS-over-HTTPS</2>;",
+    "example_upstream_sdns": "<0>DNS Stamps</0> pentru <1>DNSCrypt</1> sau rezolvatori <2>DNS-over-HTTPS</2>;",
     "example_upstream_tcp": "DNS clasic (over TCP);",
     "example_upstream_tcp_hostname": "DNS obișnuit (over TCP, nume de gazdă);",
     "all_lists_up_to_date_toast": "Toate listele sunt deja la zi",
@@ -351,7 +351,7 @@
     "install_devices_android_list_5": "Schimbați valorile DNS 1 și DNS 2 la adresele serverului dvs. AdGuard Home.",
     "install_devices_ios_list_1": "Din ecranul de start, tapați Setări.",
     "install_devices_ios_list_2": "Alegeți Wi-Fi în meniul din stânga (este imposibil să configurați DNS pentru rețelele mobile).",
-    "install_devices_ios_list_3": "Tapați numele rețelei active curente.",
+    "install_devices_ios_list_3": "Apăsați pe numele rețelei active în prezent.",
     "install_devices_ios_list_4": "În câmpul DNS, introduceți adresele serverului dvs. AdGuard Home.",
     "get_started": "Să începem",
     "next": "Următor",
@@ -585,7 +585,7 @@
     "list_updated": "{{count}} listă actualizată",
     "list_updated_plural": "{{count}} liste actualizate",
     "dnssec_enable": "Activați DNSSEC",
-    "dnssec_enable_desc": "Activați semnalul DNSSEC în interogările DNS de ieșire și verificați rezultatul (este necesar un resolver compatibil DNSSEC).",
+    "dnssec_enable_desc": "Activați semnalul DNSSEC în interogările DNS de ieșire și verificați rezultatul (este necesar un rezolvator compatibil DNSSEC).",
     "validated_with_dnssec": "Validat cu DNSSEC",
     "all_queries": "Toate interogările",
     "show_blocked_responses": "Blocat",
diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json
index 74dedd8b..f26e0011 100644
--- a/client/src/__locales/tr.json
+++ b/client/src/__locales/tr.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "DNS Önyükleme sunucuları",
     "bootstrap_dns_desc": "DNS Önyükleme sunucuları, belirttiğiniz üst sunucuların DoH/DoT çözümleyicilerine ait IP adreslerinin çözümlemek için kullanılır.",
     "local_ptr_title": "Özel ters DNS sunucuları",
-    "local_ptr_desc": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, rDNS kullanarak \"192.168.12.34\" gibi özel IP adreslerine sahip istemcilerin ana makine adlarını çözmek için kullanılır. Ayarlanmadığı durumda AdGuard Home, işletim sisteminizin varsayılan DNS çözümleme adreslerini kullanır.",
+    "local_ptr_desc": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, rDNS kullanarak, örneğin \"192.168.12.34\" gibi özel IP aralıklarındaki adresler için PTR isteklerini çözmek için kullanılır. Ayarlanmadığı durumda AdGuard Home, işletim sisteminizin varsayılan DNS çözümleme adreslerini kullanır.",
     "local_ptr_default_resolver": "AdGuard Home, varsayılan olarak aşağıdaki ters DNS çözümleyicilerini kullanır: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home, bu sistem için uygun olan özel ters DNS çözümleyicilerini belirleyemedi.",
     "local_ptr_placeholder": "Her satıra bir sunucu adresi girin",
@@ -115,7 +115,7 @@
     "blocked_by": "<0>Filtreler tarafından engellenen</0>",
     "stats_malware_phishing": "Engellenen kötü amaçlı yazılım ve kimlik avı",
     "stats_adult": "Engellenen yetişkin içerikli siteler",
-    "stats_query_domain": "En fazla sorgulanan alan adları",
+    "stats_query_domain": "Başlıca sorgulanan alan adları",
     "for_last_24_hours": "son 24 saat içindekiler",
     "for_last_days": "son {{count}} gün boyunca",
     "for_last_days_plural": "son {{count}} gün boyunca",
@@ -123,8 +123,8 @@
     "stats_disabled_short": "İstatistikler devre dışı bırakıldı",
     "no_domains_found": "Alan adı bulunamadı",
     "requests_count": "İstek sayısı",
-    "top_blocked_domains": "En fazla engellenen alan adları",
-    "top_clients": "En aktif istemciler",
+    "top_blocked_domains": "Başlıca engellenen alan adları",
+    "top_clients": "Başlıca istemciler",
     "no_clients_found": "İstemci bulunamadı",
     "general_statistics": "Genel istatistikler",
     "number_of_dns_query_days": "Son {{count}} gün boyunca işlenen DNS sorgularının sayısı",
@@ -351,7 +351,7 @@
     "install_devices_android_list_5": "DNS 1 ve DNS 2 değerlerini AdGuard Home sunucunuzun adresleriyle değiştirin.",
     "install_devices_ios_list_1": "Ana ekrandan Ayarlar'a dokunun.",
     "install_devices_ios_list_2": "Sol menüde bulunan Wi-Fi bölümüne girin (mobil ağlar için özel DNS sunucusu ayarlanamaz).",
-    "install_devices_ios_list_3": "Bağlı olduğunuz ağın ismine dokunun.",
+    "install_devices_ios_list_3": "O anda aktif olan ağın adına dokunun.",
     "install_devices_ios_list_4": "DNS alanına AdGuard Home sunucunuzun adreslerini girin.",
     "get_started": "Başlayın",
     "next": "Sonraki",
@@ -602,14 +602,14 @@
     "milliseconds_abbreviation": "ms",
     "cache_size": "Önbellek boyutu",
     "cache_size_desc": "DNS önbellek boyutu (bayt cinsinden).",
-    "cache_ttl_min_override": "Minimum TTL'i değiştir",
-    "cache_ttl_max_override": "Maksimum TTL'i değiştir",
+    "cache_ttl_min_override": "Minimum kullanım süresini geçersiz kıl",
+    "cache_ttl_max_override": "Maksimum kullanım süresini geçersiz kıl",
     "enter_cache_size": "Önbellek boyutunu girin (bayt)",
-    "enter_cache_ttl_min_override": "Minimum TTL değerini girin (saniye olarak)",
-    "enter_cache_ttl_max_override": "Maksimum TTL değerini girin (saniye olarak)",
+    "enter_cache_ttl_min_override": "Minimum kullanım süresi girin (saniye olarak)",
+    "enter_cache_ttl_max_override": "Maksimum kullanım süresi girin (saniye olarak)",
     "cache_ttl_min_override_desc": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan kullanım süresi değerini uzatın (saniye olarak).",
     "cache_ttl_max_override_desc": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak).",
-    "ttl_cache_validation": "Minimum önbellek TTL geçersiz kılma, maksimuma eşit veya bundan küçük olmalıdır",
+    "ttl_cache_validation": "Minimum önbellek kullanım süresi geçersiz kılma, maksimum değerden küçük veya ona eşit olmalıdır",
     "cache_optimistic": "İyimser önbelleğe alma",
     "cache_optimistic_desc": "Girişlerin süresi dolduğunda bile AdGuard Home'un önbellekten yanıt vermesini sağlayın ve bunları yenilemeye çalışın.",
     "filter_category_general": "Genel",
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index 5d349618..9fcbd460 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -65,7 +65,7 @@
     "dhcp_ip_addresses": "IP-адреси",
     "ip": "IP",
     "dhcp_table_hostname": "Назва вузла",
-    "dhcp_table_expires": "Термін дії",
+    "dhcp_table_expires": "Закінчується",
     "dhcp_warning": "Якщо ви однаково хочете увімкнути DHCP-сервер, переконайтеся, що у вашій мережі немає інших активних DHCP-серверів. Інакше, це може порушити роботу інтернету на під'єднаних пристроях!",
     "dhcp_error": "AdGuard Home не зміг визначити, чи є в мережі інший DHCP-сервер",
     "dhcp_static_ip_error": "Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Нам не вдалося визначити, чи цей мережевий інтерфейс налаштовано для використання статичної IP-адреси. Встановіть статичну IP-адресу вручну.",
@@ -137,15 +137,15 @@
     "number_of_dns_query_to_safe_search": "Кількість DNS-запитів до пошукових систем, для яких примусово застосований безпечний пошук",
     "average_processing_time": "Середній час обробки",
     "average_processing_time_hint": "Середній час обробки DNS запиту в мілісекундах",
-    "block_domain_use_filters_and_hosts": "Блокувати домени з використанням фільтрів та hosts-файлів",
+    "block_domain_use_filters_and_hosts": "Блокування доменів за допомогою фільтрів та hosts-файлів",
     "filters_block_toggle_hint": "Ви можете налаштувати правила блокування в розділі <a>Фільтри</a>.",
-    "use_adguard_browsing_sec": "Використовувати веб-службу безпечного перегляду AdGuard",
+    "use_adguard_browsing_sec": "Використовувати Безпечну навігацію AdGuard",
     "use_adguard_browsing_sec_hint": "AdGuard Home перевірятиме, чи додано домен до списку веб-служби безпечного перегляду браузера. Він використовуватиме API для перевірки — на сервер надсилається лише короткий префікс хешу SHA256 доменного імені.",
     "use_adguard_parental": "Використовувати вебсервіс Батьківського контролю AdGuard",
-    "use_adguard_parental_hint": "AdGuard Home перевірятиме, чи домен містить матеріали для дорослих. Він використовує той самий орієнтований на приватність API, що й веб-служба безпечного перегляду.",
-    "enforce_safe_search": "Використовувати безпечний пошук",
+    "use_adguard_parental_hint": "AdGuard Home перевірить, чи містить домен матеріали для дорослих. Він використовує то же API, що й Безпечна навігація AdGuard.",
+    "enforce_safe_search": "Використовувати Безпечний пошук",
     "enforce_save_search_hint": "AdGuard Home може примусово застосовувати безпечний пошук в таких пошукових системах: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
-    "no_servers_specified": "Не вказано сервери",
+    "no_servers_specified": "Сервери не вказано",
     "general_settings": "Загальні налаштування",
     "dns_settings": "Налаштування DNS",
     "dns_blocklists": "Список блокування DNS",
@@ -158,7 +158,7 @@
     "upstream_dns": "Upstream DNS-сервери",
     "upstream_dns_help": "Введіть адреси серверів по одній на рядок. <a>Докладніше</a> про налаштування DNS-серверів.",
     "upstream_dns_configured_in_file": "Налаштовано в {{path}}",
-    "test_upstream_btn": "Тест upstream серверів",
+    "test_upstream_btn": "Перевірити сервери",
     "upstreams": "Upstreams",
     "apply_btn": "Застосувати",
     "disabled_filtering_toast": "Фільтрування вимкнено",
@@ -266,7 +266,7 @@
     "dns_cache_config": "Конфігурація кешу DNS",
     "dns_cache_config_desc": "Тут ви можете налаштувати DNS-кеш",
     "blocking_mode": "Режим блокування",
-    "default": "Типовий",
+    "default": "Усталено",
     "nxdomain": "NXDOMAIN",
     "refused": "REFUSED",
     "null_ip": "Нульовий IP",
@@ -316,7 +316,7 @@
     "install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:",
     "install_settings_all_interfaces": "Усі інтерфейси",
     "install_auth_title": "Авторизація",
-    "install_auth_desc": "Необходно налаштувати автентифікацію паролем для вебінтерфейсу AdGuard Home. Навіть якщо він доступний лише у вашій локальній мережі, важливо захистити його від необмеженого доступу.\n\nДолжна быть настроена аутентификация паролем для веб-интерфейса AdGuard Home. Даже если он доступен только в вашей локальной сети, важно защитить его от неограниченного доступа.",
+    "install_auth_desc": "Необхідно налаштувати автентифікацію паролем для вебінтерфейсу AdGuard Home. Навіть якщо він доступний лише у вашій локальній мережі, важливо захистити його від необмеженого доступу.",
     "install_auth_username": "Ім'я користувача",
     "install_auth_password": "Пароль",
     "install_auth_confirm": "Підтвердьте пароль",
@@ -336,7 +336,7 @@
     "install_devices_router_list_4": "Ви не можете встановити власний DNS-сервер на деяких типах маршрутизаторів. У цьому разі вам може допомогти налаштування AdGuard Home в якості <0>DHCP-сервера</0>. В іншому разі вам потрібно знайти інструкцію щодо налаштування DNS-сервера для вашої конкретної моделі маршрутизатора.",
     "install_devices_windows_list_1": "Відкрийте Панель керування через меню «Пуск» або пошук Windows.",
     "install_devices_windows_list_2": "Перейдіть до категорії Мережа й Інтернет, а потім до Центру мереж і спільного доступу.",
-    "install_devices_windows_list_3": "Зліва на екрані натисніть на «Змінити настройки адаптера».",
+    "install_devices_windows_list_3": "Зліва на екрані натисніть на «Змінити налаштування адаптера».",
     "install_devices_windows_list_4": "Клацніть на активному з'єднанні правою кнопкою миші та виберіть «Властивості».",
     "install_devices_windows_list_5": "Знайдіть у списку пункт «Internet Protocol Version 4 (TCP/IPv4)» або «Internet Protocol Version 6 (TCP/IPv6)», виберіть його та натисніть кнопку Властивості ще раз.",
     "install_devices_windows_list_6": "Виберіть «Використовувати наступні адреси DNS-серверів» та введіть адреси вашого сервера AdGuard Home.",
@@ -375,7 +375,7 @@
     "encryption_certificates_desc": "Для використання шифрування потрібно надати дійсний ланцюжок сертифікатів SSL для вашого домену. Ви можете отримати безкоштовний сертифікат на <0>{{link}}</0> або придбати його в одному з надійних Центрів Сертифікації.",
     "encryption_certificates_input": "Скопіюйте/вставте сюди свої кодовані PEM сертифікати.",
     "encryption_status": "Статус",
-    "encryption_expire": "Закічнується",
+    "encryption_expire": "Закінчується",
     "encryption_key": "Приватний ключ",
     "encryption_key_input": "Скопіюйте/вставте сюди свій приватний ключ кодований PEM для вашого сертифіката.",
     "encryption_enable": "Увімкнути шифрування (HTTPS, DNS-over-HTTPS і DNS-over-TLS)",
diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json
index 69d5b8b0..4640432f 100644
--- a/client/src/__locales/zh-cn.json
+++ b/client/src/__locales/zh-cn.json
@@ -148,8 +148,8 @@
     "no_servers_specified": "未找到指定的服务器",
     "general_settings": "常规设置",
     "dns_settings": "DNS 设置",
-    "dns_blocklists": "DNS封锁清单",
-    "dns_allowlists": "DNS允许清单",
+    "dns_blocklists": "DNS 拦截列表",
+    "dns_allowlists": "DNS 允许列表",
     "dns_blocklists_desc": "AdGuard Home将阻止匹配DNS拦截清单的域名",
     "dns_allowlists_desc": "来自DNS允许列表的域将被允许,即使它们位于任意阻止列表中也是如此",
     "custom_filtering_rules": "自定义过滤规则",
@@ -335,23 +335,22 @@
     "install_devices_router_list_3": "请在此处输入您的 AdGuard Home 服务器地址。",
     "install_devices_router_list_4": "在某些类型的路由器上无法设置自定义 DNS 服务器。在此情况下将 AdGuard Home 设置为 <0>DHCP 服务器</0>,可能会有所帮助。否则您应该查找如何根据特定路由器型号设置 DNS 服务器的使用手册。",
     "install_devices_windows_list_1": "通过开始菜单或 Windows 搜索功能打开控制面板。",
-    "install_devices_windows_list_2": "点击进入 ”网络和 Internet“ 后,再次点击进入 “网络和共享中心”",
+    "install_devices_windows_list_2": "点击进入「网络和 Internet」后,再次点击进入「网络和共享中心」",
     "install_devices_windows_list_3": "在窗口的左侧点击「更改适配器设置」。",
     "install_devices_windows_list_4": "选择您正在连接的网络设备,右击它并选择「属性”」。",
-    "install_devices_windows_list_5": "在列表中找到 ”Internet 协议版本 4 (TCP/IPv4)“ ,选择并再次点击 ”属性“ 。",
+    "install_devices_windows_list_5": "在列表中找到「Internet 协议版本 4 (TCP/IPv4)」,选择并再次点击「属性」。",
     "install_devices_windows_list_6": "选择“使用下面的 DNS 服务器地址”,并输入您的 AdGuard Home 服务器地址。",
     "install_devices_macos_list_1": "点击苹果图标,进入「系统首选项」。",
     "install_devices_macos_list_2": "点击「网络」。",
-    "install_devices_macos_list_3": "选择在列表中的第一个连接,并点击 ”高级“ 。",
-    "install_devices_macos_list_4": "选择 ”DNS“ 选项卡,并输入您的 AdGuard Home 服务器地址。",
+    "install_devices_macos_list_3": "选择在列表中的第一个连接,并点击「高级」。",
+    "install_devices_macos_list_4": "选择「DNS」选项卡,并输入您的 AdGuard Home 服务器地址。",
     "install_devices_android_list_1": "在安卓主屏幕菜单中点击设置。",
-    "install_devices_android_list_2": "点击菜单上的 ”无线局域网“ 选项。在屏幕上将列出所有可用的网络(蜂窝移动网络不支持修改 DNS )。",
-    "install_devices_android_list_3": "长按当前已连接的网络,然后点击 ”修改网络设置“ 。",
-    "install_devices_android_list_4": "在某些设备上,您可能需要选中 ”高级“ 复选框以查看进一步的设置。您可能需要调整您安卓设备的 DNS 设置,或是需要将 IP 设置从 DHCP 切换到静态。",
+    "install_devices_android_list_2": "点击菜单上的「无线局域网」选项。在屏幕上将列出所有可用的网络(蜂窝移动网络不支持修改 DNS )。",
+    "install_devices_android_list_3": "长按当前已连接的网络,然后点击「修改网络设置」。",
+    "install_devices_android_list_4": "在某些设备上,您可能需要选中「高级」复选框以查看进一步的设置。您可能需要调整您安卓设备的 DNS 设置,或是需要将 IP 设置从 DHCP 切换到静态。",
     "install_devices_android_list_5": "将 DNS 1 和 DNS 2 的值改为您的 AdGuard Home 服务器地址。",
-    "install_devices_ios_list_1": "从主屏幕中点击 ”设置“ 。",
-    "install_devices_ios_list_2": "从左侧目录中选择 ”无线局域网“(移动数据网络环境下不支持修改 DNS )。",
-    "install_devices_ios_list_3": "点击当前已连接网络的名称。",
+    "install_devices_ios_list_1": "从主屏幕中点击「设置」。",
+    "install_devices_ios_list_2": "从左侧目录中选择「无线局域网」(移动数据网络环境下不支持修改 DNS )。",
     "install_devices_ios_list_4": "在 DNS 字段中输入您的 AdGuard Home 服务器地址。",
     "get_started": "开始配置",
     "next": "下一步",
diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json
index 666ddcc3..435d6b01 100644
--- a/client/src/__locales/zh-tw.json
+++ b/client/src/__locales/zh-tw.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "自我啟動(Bootstrap)DNS 伺服器",
     "bootstrap_dns_desc": "自我啟動(Bootstrap)DNS 伺服器被用於解析您明確指定作為上游的 DoH/DoT 解析器之 IP 位址。",
     "local_ptr_title": "私人反向的 DNS 伺服器",
-    "local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析含私人 IP 位址的用戶端之主機名稱,例如,\"192.168.12.34\",使用反向的 DNS。如果未被設定,除 AdGuard Home 它本身的位址之外,AdGuard Home 使用您的作業系統之預設 DNS 解析器的位址。",
+    "local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析有關在私人 IP 範圍的位址之區域指標查詢,例如,\"192.168.12.34\",使用反向的 DNS。如果未被設定,AdGuard Home 使用您的作業系統之預設 DNS 解析器的位址。",
     "local_ptr_default_resolver": "預設下,AdGuard Home 使用以下反向的 DNS 解析器:{{ip}}。",
     "local_ptr_no_default_resolver": "AdGuard Home 無法為此系統決定合適的私人反向的 DNS 解析器。",
     "local_ptr_placeholder": "每行輸入一個伺服器位址",

From a580149ad67b604a1e3c6e7b3483d856a51c905a Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 27 Apr 2022 14:32:29 +0300
Subject: [PATCH 053/143] Pull request: all: upd dnsproxy, tools

Merge in DNS/adguard-home from upd-dnsproxy to master

Squashed commit of the following:

commit ea2a88dfd6e3820f0b3319d6aa09313de467e423
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 27 14:24:49 2022 +0300

    all: upd dnsproxy, tools
---
 go.mod                | 12 ++++++------
 go.sum                | 21 ++++++++++++---------
 internal/tools/go.mod |  8 ++++----
 internal/tools/go.sum | 15 ++++++++-------
 4 files changed, 30 insertions(+), 26 deletions(-)

diff --git a/go.mod b/go.mod
index 30d8b377..3b64e7db 100644
--- a/go.mod
+++ b/go.mod
@@ -3,13 +3,13 @@ module github.com/AdguardTeam/AdGuardHome
 go 1.17
 
 require (
-	github.com/AdguardTeam/dnsproxy v0.42.1
+	github.com/AdguardTeam/dnsproxy v0.42.2
 	github.com/AdguardTeam/golibs v0.10.8
 	github.com/AdguardTeam/urlfilter v0.16.0
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/ameshkov/dnscrypt/v2 v2.2.3
 	github.com/digineo/go-ipset/v2 v2.2.1
-	github.com/fsnotify/fsnotify v1.5.1
+	github.com/fsnotify/fsnotify v1.5.4
 	github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
 	github.com/google/go-cmp v0.5.7
 	github.com/google/gopacket v1.1.19
@@ -28,8 +28,8 @@ require (
 	github.com/ti-mo/netfilter v0.4.0
 	go.etcd.io/bbolt v1.3.6
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
-	golang.org/x/net v0.0.0-20220412020605-290c469a71a5
-	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
+	golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
+	golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v2 v2.4.0
 	howett.net/plist v1.0.0
@@ -57,10 +57,10 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/stretchr/objx v0.1.1 // indirect
 	github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
-	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a // indirect
+	golang.org/x/tools v0.1.11-0.20220426200323-dcaea06afc12 // indirect
 	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
diff --git a/go.sum b/go.sum
index e75df993..06ae9b96 100644
--- a/go.sum
+++ b/go.sum
@@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/AdguardTeam/dnsproxy v0.42.1 h1:RZAtW75cvMX1d9Mibg0CA343V7VWV5PLrXsLhBZfdYc=
-github.com/AdguardTeam/dnsproxy v0.42.1/go.mod h1:thHuk3599mgmucsv5J9HR9lBVQHnf4YleE08EbxNrN0=
+github.com/AdguardTeam/dnsproxy v0.42.2 h1:aBhbuvqg/rZN8Rab5ILSfPFJDkiTviWXXcceJgajnNs=
+github.com/AdguardTeam/dnsproxy v0.42.2/go.mod h1:thHuk3599mgmucsv5J9HR9lBVQHnf4YleE08EbxNrN0=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
@@ -58,8 +58,9 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
 github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
@@ -292,8 +293,9 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -329,8 +331,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
-golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -390,8 +392,9 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -416,8 +419,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
-golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II=
-golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.1.11-0.20220426200323-dcaea06afc12 h1:pODAJF0uBqx6zFa1MYaiTobVo5FzCbnTVUXeO8o71fE=
+golang.org/x/tools v0.1.11-0.20220426200323-dcaea06afc12/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index 2ad6ce16..bb587070 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/securego/gosec/v2 v2.11.0
 	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
 	golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a
-	honnef.co/go/tools v0.3.0
+	honnef.co/go/tools v0.3.1
 	mvdan.cc/gofumpt v0.3.1
 	mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b
 )
@@ -19,16 +19,16 @@ require (
 require (
 	github.com/BurntSushi/toml v1.1.0 // indirect
 	github.com/client9/misspell v0.3.4 // indirect
-	github.com/google/go-cmp v0.5.7 // indirect
+	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/gookit/color v1.5.0 // indirect
 	github.com/kyoh86/nolint v0.0.1 // indirect
 	github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
 	github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
-	golang.org/x/exp/typeparams v0.0.0-20220407100705-7b9b53b0aca4 // indirect
+	golang.org/x/exp/typeparams v0.0.0-20220426173459-3bcf042a4bf5 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
-	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
+	golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
 	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 )
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index 7dbf5491..2f04e4a6 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -157,8 +157,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -420,8 +421,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
 golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo=
 golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
 golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
-golang.org/x/exp/typeparams v0.0.0-20220407100705-7b9b53b0aca4 h1:P5yukcpQfG1ZDKR0pGdaZCVwaNPntMxLFKYg81li58M=
-golang.org/x/exp/typeparams v0.0.0-20220407100705-7b9b53b0aca4/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
+golang.org/x/exp/typeparams v0.0.0-20220426173459-3bcf042a4bf5 h1:pKfHvPtBtqS0+V/V9Y0cZQa2h8HJV/qSRJiGgYu+LQA=
+golang.org/x/exp/typeparams v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -559,8 +560,8 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
-golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -751,8 +752,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.3.0 h1:2LdYUZ7CIxnYgskbUZfY7FPggmqnh6shBqfWa8Tn3XU=
-honnef.co/go/tools v0.3.0/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70=
+honnef.co/go/tools v0.3.1 h1:1kJlrWJLkaGXgcaeosRXViwviqjI7nkBvU2+sZW0AYc=
+honnef.co/go/tools v0.3.1/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70=
 mvdan.cc/gofumpt v0.3.1 h1:avhhrOmv0IuvQVK7fvwV91oFSGAk5/6Po8GXTzICeu8=
 mvdan.cc/gofumpt v0.3.1/go.mod h1:w3ymliuxvzVx8DAutBnVyDqYb1Niy/yCJt/lk821YCE=
 mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b h1:C8Pi6noat8BcrL9WnSRYeQ63fpkJk3hKVHtF5731kIw=

From 21905d98693a58a24560ceac5461a470beb70af8 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Fri, 29 Apr 2022 14:39:02 +0300
Subject: [PATCH 054/143] Pull request: home: imp openbsd init script

Closes #4533.

Squashed commit of the following:

commit 48ca9e100619e714eab565273daeb4ee9adb5b74
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 28 20:25:15 2022 +0300

    home: imp openbsd init script
---
 CHANGELOG.md              |  8 ++++++--
 internal/home/service.go  |  2 +-
 internal/v1/cmd/signal.go | 10 +++++-----
 3 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84f45312..289b1d2e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,8 +23,8 @@ and this project adheres to
 
 ### Added
 
-- Support for Discovery of Designated Resolvers (DDR) according to the 
-  [RFC draft][ddr-draft-06] ([#4463]).
+- Support for Discovery of Designated Resolvers (DDR) according to the [RFC
+  draft][ddr-draft-06] ([#4463]).
 - The ability to control each source of runtime clients separately via
   `clients.runtime_sources` configuration object ([#3020]).
 - The ability to customize the set of networks that are considered private
@@ -40,6 +40,9 @@ and this project adheres to
 
 ### Changed
 
+- On OpenBSD, the daemon script now uses the recommended `/bin/ksh` shell
+  instead of the `/bin/sh` one ([#4533]).  To apply this change, backup your
+  data and run `AdGuardHome -s uninstall && AdGuardHome -s install`.
 - The default DNS-over-QUIC port number is now `853` instead of `754` in
   accordance with the latest [RFC draft][doq-draft-10] ([#4276]).
 - Reverse DNS now has a greater priority as the source of runtime clients'
@@ -144,6 +147,7 @@ In this release, the schema version has changed from 12 to 14.
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
 [#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499
+[#4533]: https://github.com/AdguardTeam/AdGuardHome/issues/4533
 
 [ddr-draft-06]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
 [doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
diff --git a/internal/home/service.go b/internal/home/service.go
index 081974e2..70ab3c78 100644
--- a/internal/home/service.go
+++ b/internal/home/service.go
@@ -609,7 +609,7 @@ command_args="-P ${pidfile} -p ${pidfile_child} -T ${name} -r {{.WorkingDirector
 run_rc_command "$1"
 `
 
-const openBSDScript = `#!/bin/sh
+const openBSDScript = `#!/bin/ksh
 #
 # $OpenBSD: {{ .SvcInfo }}
 
diff --git a/internal/v1/cmd/signal.go b/internal/v1/cmd/signal.go
index b9f09673..b66075f6 100644
--- a/internal/v1/cmd/signal.go
+++ b/internal/v1/cmd/signal.go
@@ -19,10 +19,10 @@ type signalHandler struct {
 
 // handle processes OS signals.
 func (h *signalHandler) handle() {
-	defer log.OnPanic("signalProcessor.handle")
+	defer log.OnPanic("signalHandler.handle")
 
 	for sig := range h.signal {
-		log.Info("sigproc: received signal %q", sig)
+		log.Info("sighdlr: received signal %q", sig)
 
 		if aghos.IsShutdownSignal(sig) {
 			h.shutdown()
@@ -43,16 +43,16 @@ func (h *signalHandler) shutdown() {
 
 	status := statusSuccess
 
-	log.Info("sigproc: shutting down services")
+	log.Info("sighdlr: shutting down services")
 	for i, service := range h.services {
 		err := service.Shutdown(ctx)
 		if err != nil {
-			log.Error("sigproc: shutting down service at index %d: %s", i, err)
+			log.Error("sighdlr: shutting down service at index %d: %s", i, err)
 			status = statusError
 		}
 	}
 
-	log.Info("sigproc: shutting down adguard home")
+	log.Info("sighdlr: shutting down adguard home")
 
 	os.Exit(status)
 }

From 58515fce4304538ba374305c27d71640993f046e Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 4 May 2022 21:01:41 +0300
Subject: [PATCH 055/143] Pull request: 4542 clientid case

Merge in DNS/adguard-home from 4542-clientid-case to master

Updates #4542.

Squashed commit of the following:

commit 2a3111ebcef09460b407cd1c870cad2391cd5650
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed May 4 20:44:18 2022 +0300

    all: fix changelog link

commit 3732def83e2a36eeff2d682149dc4dcef4e92a7d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed May 4 20:43:37 2022 +0300

    all: log changes

commit 9fe1001cf586669ae238c9c4818070cf94e23ce8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed May 4 19:37:33 2022 +0300

    dnsforward: lowercase clientid
---
 CHANGELOG.md                         |  2 ++
 internal/dnsforward/clientid.go      |  4 ++--
 internal/dnsforward/clientid_test.go | 21 +++++++++++++++++++++
 internal/home/clients.go             |  3 ++-
 4 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 289b1d2e..3c2fa06c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -127,6 +127,7 @@ In this release, the schema version has changed from 12 to 14.
 
 ### Fixed
 
+- Case-sensitive ClientID ([#4542]).
 - Slow version update queries making other HTTP APIs unresponsible ([#4499]).
 - ARP tables refreshing process causing excessive PTR requests ([#3157]).
 
@@ -148,6 +149,7 @@ In this release, the schema version has changed from 12 to 14.
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
 [#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499
 [#4533]: https://github.com/AdguardTeam/AdGuardHome/issues/4533
+[#4542]: https://github.com/AdguardTeam/AdGuardHome/issues/4542
 
 [ddr-draft-06]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
 [doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
diff --git a/internal/dnsforward/clientid.go b/internal/dnsforward/clientid.go
index 481fb84d..bb687a41 100644
--- a/internal/dnsforward/clientid.go
+++ b/internal/dnsforward/clientid.go
@@ -65,7 +65,7 @@ func clientIDFromClientServerName(
 		return "", err
 	}
 
-	return clientID, nil
+	return strings.ToLower(clientID), nil
 }
 
 // clientIDFromDNSContextHTTPS extracts the client's ID from the path of the
@@ -104,7 +104,7 @@ func clientIDFromDNSContextHTTPS(pctx *proxy.DNSContext) (clientID string, err e
 		return "", fmt.Errorf("clientid check: %w", err)
 	}
 
-	return clientID, nil
+	return strings.ToLower(clientID), nil
 }
 
 // tlsConn is a narrow interface for *tls.Conn to simplify testing.
diff --git a/internal/dnsforward/clientid_test.go b/internal/dnsforward/clientid_test.go
index e62dbe58..6e23d639 100644
--- a/internal/dnsforward/clientid_test.go
+++ b/internal/dnsforward/clientid_test.go
@@ -143,6 +143,22 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
 		wantErrMsg: `clientid check: client server name "cli.myexample.com" ` +
 			`doesn't match host server name "example.com"`,
 		strictSNI: true,
+	}, {
+		name:         "tls_case",
+		proto:        proxy.ProtoTLS,
+		hostSrvName:  "example.com",
+		cliSrvName:   "InSeNsItIvE.example.com",
+		wantClientID: "insensitive",
+		wantErrMsg:   ``,
+		strictSNI:    true,
+	}, {
+		name:         "quic_case",
+		proto:        proxy.ProtoQUIC,
+		hostSrvName:  "example.com",
+		cliSrvName:   "InSeNsItIvE.example.com",
+		wantClientID: "insensitive",
+		wantErrMsg:   ``,
+		strictSNI:    true,
 	}}
 
 	for _, tc := range testCases {
@@ -210,6 +226,11 @@ func TestClientIDFromDNSContextHTTPS(t *testing.T) {
 		path:         "/dns-query/cli/",
 		wantClientID: "cli",
 		wantErrMsg:   "",
+	}, {
+		name:         "clientid_case",
+		path:         "/dns-query/InSeNsItIvE",
+		wantClientID: "insensitive",
+		wantErrMsg:   ``,
 	}, {
 		name:         "bad_url",
 		path:         "/foo",
diff --git a/internal/home/clients.go b/internal/home/clients.go
index d4d6b959..4ba6b884 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"net"
 	"sort"
+	"strings"
 	"sync"
 	"time"
 
@@ -546,7 +547,7 @@ func (clients *clientsContainer) check(c *Client) (err error) {
 		} else if mac, err = net.ParseMAC(id); err == nil {
 			c.IDs[i] = mac.String()
 		} else if err = dnsforward.ValidateClientID(id); err == nil {
-			c.IDs[i] = id
+			c.IDs[i] = strings.ToLower(id)
 		} else {
 			return fmt.Errorf("invalid clientid at index %d: %q", i, id)
 		}

From b7eedb3feb5fadbac3bf780e3429d07f160491b8 Mon Sep 17 00:00:00 2001
From: jumpsmm7 <49514613+jumpsmm7@users.noreply.github.com>
Date: Fri, 6 May 2022 21:34:06 -0400
Subject: [PATCH 056/143] Update README.md

* Add Asuswrt-Merlin-AdGuardHome-Installer (a.k.a. the installer that made adguardhome available to Asuswrt-Merlin Asus Routers)
---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 00664365..316ffa5f 100644
--- a/README.md
+++ b/README.md
@@ -345,6 +345,7 @@ Here's what you can also do to contribute:
 * [Prometheus exporter for AdGuard Home](https://github.com/ebrianne/adguard-exporter) by [@ebrianne](https://github.com/ebrianne)
 * [AdGuard Home on GLInet routers](https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) by [Gl-Inet](https://gl-inet.com/)
 * [Cloudron app](https://git.cloudron.io/cloudron/adguard-home-app) by [@gramakri](https://github.com/gramakri)
+* [Asuswrt-Merlin-AdGuardHome-Installer](https://github.com/jumpsmm7/Asuswrt-Merlin-AdGuardHome-Installer) by [@jumpsmm7](https://github.com/jumpsmm7) a.k.a [@SomeWhereOverTheRainBow](https://www.snbforums.com/members/somewhereovertherainbow.64179/)
 
 <a id="acknowledgments"></a>
 ## Acknowledgments

From f289f4b1b6f91f38d99ac8b9e3f9bdc9536609e7 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 12 May 2022 17:41:39 +0300
Subject: [PATCH 057/143] Pull request: websvc: add system info

Merge in DNS/adguard-home from websvc-system-info to master

Squashed commit of the following:

commit 333aaa0602da254e25e0262a10080bf44a3718a7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu May 12 16:32:32 2022 +0300

    websvc: fmt

commit d8a35bf71dcc59fdd595494e5b220e3d24516728
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu May 12 16:10:11 2022 +0300

    websvc: refactor, imp tests

commit dfeb24f3f35513bf51323d3ab6f717f582a1defc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed May 11 20:52:02 2022 +0300

    websvc: add system info
---
 go.mod                            |  1 +
 go.sum                            |  2 +
 internal/v1/cmd/cmd.go            |  4 +-
 internal/v1/websvc/json.go        | 61 +++++++++++++++++++++++++++++++
 internal/v1/websvc/middleware.go  | 16 ++++++++
 internal/v1/websvc/path.go        |  8 ++++
 internal/v1/websvc/system.go      | 35 ++++++++++++++++++
 internal/v1/websvc/system_test.go | 36 ++++++++++++++++++
 internal/v1/websvc/websvc.go      | 46 ++++++++++++++++++++++-
 internal/v1/websvc/websvc_test.go | 52 ++++++++++++++++++++------
 openapi/v1.yaml                   | 16 +++++++-
 11 files changed, 260 insertions(+), 17 deletions(-)
 create mode 100644 internal/v1/websvc/json.go
 create mode 100644 internal/v1/websvc/middleware.go
 create mode 100644 internal/v1/websvc/path.go
 create mode 100644 internal/v1/websvc/system.go
 create mode 100644 internal/v1/websvc/system_test.go

diff --git a/go.mod b/go.mod
index 3b64e7db..8da34653 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/ameshkov/dnscrypt/v2 v2.2.3
 	github.com/digineo/go-ipset/v2 v2.2.1
+	github.com/dimfeld/httptreemux/v5 v5.4.0
 	github.com/fsnotify/fsnotify v1.5.4
 	github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
 	github.com/google/go-cmp v0.5.7
diff --git a/go.sum b/go.sum
index 06ae9b96..807a6849 100644
--- a/go.sum
+++ b/go.sum
@@ -52,6 +52,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g=
 github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
+github.com/dimfeld/httptreemux/v5 v5.4.0 h1:IiHYEjh+A7pYbhWyjmGnj5HZK6gpOOvyBXCJ+BE8/Gs=
+github.com/dimfeld/httptreemux/v5 v5.4.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
diff --git a/internal/v1/cmd/cmd.go b/internal/v1/cmd/cmd.go
index 4c4e252f..1f1cc64e 100644
--- a/internal/v1/cmd/cmd.go
+++ b/internal/v1/cmd/cmd.go
@@ -20,7 +20,8 @@ import (
 func Main(clientBuildFS fs.FS) {
 	// # Initial Configuration
 
-	rand.Seed(time.Now().UnixNano())
+	start := time.Now()
+	rand.Seed(start.UnixNano())
 
 	// TODO(a.garipov): Set up logging.
 
@@ -35,6 +36,7 @@ func Main(clientBuildFS fs.FS) {
 			IP:   net.IP{127, 0, 0, 1},
 			Port: 3001,
 		}},
+		Start:   start,
 		Timeout: 60 * time.Second,
 	})
 
diff --git a/internal/v1/websvc/json.go b/internal/v1/websvc/json.go
new file mode 100644
index 00000000..beb7f7ec
--- /dev/null
+++ b/internal/v1/websvc/json.go
@@ -0,0 +1,61 @@
+package websvc
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"strconv"
+	"time"
+
+	"github.com/AdguardTeam/golibs/log"
+)
+
+// JSON Utilities
+
+// jsonTime is a time.Time that can be decoded from JSON and encoded into JSON
+// according to our API conventions.
+type jsonTime time.Time
+
+// type check
+var _ json.Marshaler = jsonTime{}
+
+// nsecPerMsec is the number of nanoseconds in a millisecond.
+const nsecPerMsec = float64(time.Millisecond / time.Nanosecond)
+
+// MarshalJSON implements the json.Marshaler interface for jsonTime.  err is
+// always nil.
+func (t jsonTime) MarshalJSON() (b []byte, err error) {
+	msec := float64(time.Time(t).UnixNano()) / nsecPerMsec
+	b = strconv.AppendFloat(nil, msec, 'f', 3, 64)
+
+	return b, nil
+}
+
+// type check
+var _ json.Unmarshaler = (*jsonTime)(nil)
+
+// UnmarshalJSON implements the json.Marshaler interface for *jsonTime.
+func (t *jsonTime) UnmarshalJSON(b []byte) (err error) {
+	if t == nil {
+		return fmt.Errorf("json time is nil")
+	}
+
+	msec, err := strconv.ParseFloat(string(b), 64)
+	if err != nil {
+		return fmt.Errorf("parsing json time: %w", err)
+	}
+
+	*t = jsonTime(time.Unix(0, int64(msec*nsecPerMsec)).UTC())
+
+	return nil
+}
+
+// writeJSONResponse encodes v into w and logs any errors it encounters.  r is
+// used to get additional information from the request.
+func writeJSONResponse(w io.Writer, r *http.Request, v interface{}) {
+	err := json.NewEncoder(w).Encode(v)
+	if err != nil {
+		log.Error("websvc: writing resp to %s %s: %s", r.Method, r.URL.Path, err)
+	}
+}
diff --git a/internal/v1/websvc/middleware.go b/internal/v1/websvc/middleware.go
new file mode 100644
index 00000000..c87c57d5
--- /dev/null
+++ b/internal/v1/websvc/middleware.go
@@ -0,0 +1,16 @@
+package websvc
+
+import "net/http"
+
+// Middlewares
+
+// jsonMw sets the content type of the response to application/json.
+func jsonMw(h http.Handler) (wrapped http.HandlerFunc) {
+	f := func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+
+		h.ServeHTTP(w, r)
+	}
+
+	return http.HandlerFunc(f)
+}
diff --git a/internal/v1/websvc/path.go b/internal/v1/websvc/path.go
new file mode 100644
index 00000000..cfd67fd9
--- /dev/null
+++ b/internal/v1/websvc/path.go
@@ -0,0 +1,8 @@
+package websvc
+
+// Path constants
+const (
+	PathHealthCheck = "/health-check"
+
+	PathV1SystemInfo = "/api/v1/system/info"
+)
diff --git a/internal/v1/websvc/system.go b/internal/v1/websvc/system.go
new file mode 100644
index 00000000..47d0c63c
--- /dev/null
+++ b/internal/v1/websvc/system.go
@@ -0,0 +1,35 @@
+package websvc
+
+import (
+	"net/http"
+	"runtime"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/version"
+)
+
+// System Handlers
+
+// RespGetV1SystemInfo describes the response of the GET /api/v1/system/info
+// HTTP API.
+type RespGetV1SystemInfo struct {
+	Arch       string   `json:"arch"`
+	Channel    string   `json:"channel"`
+	OS         string   `json:"os"`
+	NewVersion string   `json:"new_version,omitempty"`
+	Start      jsonTime `json:"start"`
+	Version    string   `json:"version"`
+}
+
+// handleGetV1SystemInfo is the handler for the GET /api/v1/system/info HTTP
+// API.
+func (svc *Service) handleGetV1SystemInfo(w http.ResponseWriter, r *http.Request) {
+	writeJSONResponse(w, r, &RespGetV1SystemInfo{
+		Arch:    runtime.GOARCH,
+		Channel: version.Channel(),
+		OS:      runtime.GOOS,
+		// TODO(a.garipov): Fill this when we have an updater.
+		NewVersion: "",
+		Start:      jsonTime(svc.start),
+		Version:    version.Version(),
+	})
+}
diff --git a/internal/v1/websvc/system_test.go b/internal/v1/websvc/system_test.go
new file mode 100644
index 00000000..49579ca5
--- /dev/null
+++ b/internal/v1/websvc/system_test.go
@@ -0,0 +1,36 @@
+package websvc_test
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/url"
+	"runtime"
+	"testing"
+	"time"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/v1/websvc"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestService_handleGetV1SystemInfo(t *testing.T) {
+	_, addr := newTestServer(t)
+	u := &url.URL{
+		Scheme: "http",
+		Host:   addr,
+		Path:   websvc.PathV1SystemInfo,
+	}
+
+	body := httpGet(t, u, http.StatusOK)
+	resp := &websvc.RespGetV1SystemInfo{}
+	err := json.Unmarshal(body, resp)
+	require.NoError(t, err)
+
+	// TODO(a.garipov): Consider making version.Channel and version.Version
+	// testable and test these better.
+	assert.NotEmpty(t, resp.Channel)
+
+	assert.Equal(t, resp.Arch, runtime.GOARCH)
+	assert.Equal(t, resp.OS, runtime.GOOS)
+	assert.Equal(t, testStart, time.Time(resp.Start))
+}
diff --git a/internal/v1/websvc/websvc.go b/internal/v1/websvc/websvc.go
index e741ff3d..9af22a15 100644
--- a/internal/v1/websvc/websvc.go
+++ b/internal/v1/websvc/websvc.go
@@ -17,6 +17,7 @@ import (
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/golibs/netutil"
+	httptreemux "github.com/dimfeld/httptreemux/v5"
 )
 
 // Config is the AdGuard Home web service configuration structure.
@@ -32,6 +33,9 @@ type Config struct {
 	// SecureAddresses is not empty, TLS must not be nil.
 	SecureAddresses []*netutil.IPPort
 
+	// Start is the time of start of AdGuard Home.
+	Start time.Time
+
 	// Timeout is the timeout for all server operations.
 	Timeout time.Duration
 }
@@ -41,6 +45,7 @@ type Config struct {
 type Service struct {
 	tls     *tls.Config
 	servers []*http.Server
+	start   time.Time
 	timeout time.Duration
 }
 
@@ -53,11 +58,11 @@ func New(c *Config) (svc *Service) {
 
 	svc = &Service{
 		tls:     c.TLS,
+		start:   c.Start,
 		timeout: c.Timeout,
 	}
 
-	mux := http.NewServeMux()
-	mux.HandleFunc("/health-check", svc.handleGetHealthCheck)
+	mux := newMux(svc)
 
 	for _, a := range c.Addresses {
 		addr := a.String()
@@ -91,6 +96,43 @@ func New(c *Config) (svc *Service) {
 	return svc
 }
 
+// newMux returns a new HTTP request multiplexor for the AdGuard Home web
+// service.
+func newMux(svc *Service) (mux *httptreemux.ContextMux) {
+	mux = httptreemux.NewContextMux()
+
+	routes := []struct {
+		handler http.HandlerFunc
+		method  string
+		path    string
+		isJSON  bool
+	}{{
+		handler: svc.handleGetHealthCheck,
+		method:  http.MethodGet,
+		path:    PathHealthCheck,
+		isJSON:  false,
+	}, {
+		handler: svc.handleGetV1SystemInfo,
+		method:  http.MethodGet,
+		path:    PathV1SystemInfo,
+		isJSON:  true,
+	}}
+
+	for _, r := range routes {
+		var h http.HandlerFunc
+		if r.isJSON {
+			// TODO(a.garipov): Consider using httptreemux's MiddlewareFunc.
+			h = jsonMw(r.handler)
+		} else {
+			h = r.handler
+		}
+
+		mux.Handle(r.method, r.path, h)
+	}
+
+	return mux
+}
+
 // Addrs returns all addresses on which this server serves the HTTP API.  Addrs
 // must not be called until Start returns.
 func (svc *Service) Addrs() (addrs []string) {
diff --git a/internal/v1/websvc/websvc_test.go b/internal/v1/websvc/websvc_test.go
index 01b892cd..459ffd14 100644
--- a/internal/v1/websvc/websvc_test.go
+++ b/internal/v1/websvc/websvc_test.go
@@ -18,7 +18,17 @@ import (
 
 const testTimeout = 1 * time.Second
 
-func TestService_Start_getHealthCheck(t *testing.T) {
+// testStart is the server start value for tests.
+var testStart = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
+
+// newTestServer creates and starts a new web service instance as well as its
+// sole address.  It also registers a cleanup procedure, which shuts the
+// instance down.
+//
+// TODO(a.garipov): Use svc or remove it.
+func newTestServer(t testing.TB) (svc *websvc.Service, addr string) {
+	t.Helper()
+
 	c := &websvc.Config{
 		TLS: nil,
 		Addresses: []*netutil.IPPort{{
@@ -27,9 +37,10 @@ func TestService_Start_getHealthCheck(t *testing.T) {
 		}},
 		SecureAddresses: nil,
 		Timeout:         testTimeout,
+		Start:           testStart,
 	}
 
-	svc := websvc.New(c)
+	svc = websvc.New(c)
 
 	err := svc.Start()
 	require.NoError(t, err)
@@ -44,26 +55,43 @@ func TestService_Start_getHealthCheck(t *testing.T) {
 	addrs := svc.Addrs()
 	require.Len(t, addrs, 1)
 
-	u := &url.URL{
-		Scheme: "http",
-		Host:   addrs[0],
-		Path:   "/health-check",
-	}
+	return svc, addrs[0]
+}
+
+// httpGet is a helper that performs an HTTP GET request and returns the body of
+// the response as well as checks that the status code is correct.
+//
+// TODO(a.garipov): Add helpers for other methods.
+func httpGet(t testing.TB, u *url.URL, wantCode int) (body []byte) {
+	t.Helper()
+
 	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	require.NoError(t, err)
+	require.NoErrorf(t, err, "creating req")
 
 	httpCli := &http.Client{
 		Timeout: testTimeout,
 	}
 	resp, err := httpCli.Do(req)
-	require.NoError(t, err)
+	require.NoErrorf(t, err, "performing req")
+	require.Equal(t, wantCode, resp.StatusCode)
 
 	testutil.CleanupAndRequireSuccess(t, resp.Body.Close)
 
-	assert.Equal(t, http.StatusOK, resp.StatusCode)
+	body, err = io.ReadAll(resp.Body)
+	require.NoErrorf(t, err, "reading body")
 
-	body, err := io.ReadAll(resp.Body)
-	require.NoError(t, err)
+	return body
+}
+
+func TestService_Start_getHealthCheck(t *testing.T) {
+	_, addr := newTestServer(t)
+	u := &url.URL{
+		Scheme: "http",
+		Host:   addr,
+		Path:   websvc.PathHealthCheck,
+	}
+
+	body := httpGet(t, u, http.StatusOK)
 
 	assert.Equal(t, []byte("OK"), body)
 }
diff --git a/openapi/v1.yaml b/openapi/v1.yaml
index 30c318bc..a9092c98 100644
--- a/openapi/v1.yaml
+++ b/openapi/v1.yaml
@@ -3393,11 +3393,17 @@
       'description': >
         Information about the AdGuard Home server.
       'example':
+        'arch': 'amd64'
         'channel': 'release'
-        'new_version': 'v0.106.1'
+        'new_version': 'v0.108.1'
+        'os': 'linux'
         'start': 1614345496000
-        'version': 'v0.106.0'
+        'version': 'v0.108.0'
       'properties':
+        'arch':
+          'description': >
+            CPU architecture.
+          'type': 'string'
         'channel':
           '$ref': '#/components/schemas/Channel'
         'new_version':
@@ -3405,6 +3411,10 @@
             New available version of AdGuard Home to which the server can be
             updated, if any.  If there are none, this field is absent.
           'type': 'string'
+        'os':
+          'description': >
+            Operating system type.
+          'type': 'string'
         'start':
           'description': >
             Unix time at which AdGuard Home started working, in milliseconds.
@@ -3415,7 +3425,9 @@
             Current AdGuard Home version.
           'type': 'string'
       'required':
+      - 'arch'
       - 'channel'
+      - 'os'
       - 'start'
       - 'version'
       'type': 'object'

From 79d85a24e970450767997d2e3a1b1876f652f0f7 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 18 May 2022 15:00:36 +0300
Subject: [PATCH 058/143] Pull request: all: log changes

Updates #4273.

Squashed commit of the following:

commit ebae1a4d0944fa348b7dcb7e73e59d083c7a5e97
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed May 18 14:48:16 2022 +0300

    all: log changes
---
 CHANGELOG.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c2fa06c..d183c45d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -127,6 +127,7 @@ In this release, the schema version has changed from 12 to 14.
 
 ### Fixed
 
+- Detection of the stopped service status on macOS and Linux ([#4273]).
 - Case-sensitive ClientID ([#4542]).
 - Slow version update queries making other HTTP APIs unresponsible ([#4499]).
 - ARP tables refreshing process causing excessive PTR requests ([#3157]).
@@ -146,6 +147,7 @@ In this release, the schema version has changed from 12 to 14.
 [#4213]: https://github.com/AdguardTeam/AdGuardHome/issues/4213
 [#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
+[#4273]: https://github.com/AdguardTeam/AdGuardHome/issues/4273
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
 [#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499
 [#4533]: https://github.com/AdguardTeam/AdGuardHome/issues/4533

From 24d7dc8e8a36654bc8d2d64c2014419bd7a9eb25 Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Mon, 23 May 2022 16:04:14 +0300
Subject: [PATCH 059/143] Pull request: all: upd dnsproxy

Merge in DNS/adguard-home from 4503-upstream-conf to master

Squashed commit of the following:

commit c6cb1babd4cbf9aacafe902e3d54ce17e8d2cc81
Merge: 75d85ed1 79d85a24
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 23 13:06:00 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4503-upstream-conf

commit 75d85ed1f4d8d5060800b2f8a4cde662db02ae30
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 20 13:14:16 2022 +0200

    all: upd dnsproxy

commit 781768d639388a60fc90631f819cfc5dd90b9eba
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 16 16:27:25 2022 +0200

    all: docs

commit 0dafb5b3fe11b1952d9a04294bcaaa8091b9c2a7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 16 16:17:35 2022 +0200

    all: docs

commit 0d5463e4157132b0e6be78fd97eaf5a5cb8d1edc
Merge: e2c86909 f289f4b1
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 16 16:01:40 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4503-upstream-conf

    # Conflicts:
    #	go.mod
    #	go.sum

commit e2c869091b1386065076f44dbf9498a31c9d5451
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 16 15:29:17 2022 +0200

    all: upd dnsrpoxy
---
 CHANGELOG.md                | 2 ++
 go.mod                      | 2 +-
 go.sum                      | 4 ++--
 internal/dnsforward/http.go | 1 +
 4 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d183c45d..8aca8f15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ and this project adheres to
 
 ### Added
 
+- Support upstreams for subdomains of a domain only ([#4503]).
 - Support for Discovery of Designated Resolvers (DDR) according to the [RFC
   draft][ddr-draft-06] ([#4463]).
 - The ability to control each source of runtime clients separately via
@@ -150,6 +151,7 @@ In this release, the schema version has changed from 12 to 14.
 [#4273]: https://github.com/AdguardTeam/AdGuardHome/issues/4273
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
 [#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499
+[#4503]: https://github.com/AdguardTeam/AdGuardHome/issues/4503
 [#4533]: https://github.com/AdguardTeam/AdGuardHome/issues/4533
 [#4542]: https://github.com/AdguardTeam/AdGuardHome/issues/4542
 
diff --git a/go.mod b/go.mod
index 8da34653..4d9d2191 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
 go 1.17
 
 require (
-	github.com/AdguardTeam/dnsproxy v0.42.2
+	github.com/AdguardTeam/dnsproxy v0.42.4
 	github.com/AdguardTeam/golibs v0.10.8
 	github.com/AdguardTeam/urlfilter v0.16.0
 	github.com/NYTimes/gziphandler v1.1.1
diff --git a/go.sum b/go.sum
index 807a6849..73accb25 100644
--- a/go.sum
+++ b/go.sum
@@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/AdguardTeam/dnsproxy v0.42.2 h1:aBhbuvqg/rZN8Rab5ILSfPFJDkiTviWXXcceJgajnNs=
-github.com/AdguardTeam/dnsproxy v0.42.2/go.mod h1:thHuk3599mgmucsv5J9HR9lBVQHnf4YleE08EbxNrN0=
+github.com/AdguardTeam/dnsproxy v0.42.4 h1:Rf45a3H6U/8XqWMYAMEsC1g/dVudyfgx4WY8N2syJMw=
+github.com/AdguardTeam/dnsproxy v0.42.4/go.mod h1:thHuk3599mgmucsv5J9HR9lBVQHnf4YleE08EbxNrN0=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go
index 2b7cfd13..50ab9643 100644
--- a/internal/dnsforward/http.go
+++ b/internal/dnsforward/http.go
@@ -510,6 +510,7 @@ func separateUpstream(upstreamStr string) (upstream string, isDomainSpec bool, e
 			continue
 		}
 
+		host = strings.TrimPrefix(host, "*.")
 		err = netutil.ValidateDomainName(host)
 		if err != nil {
 			return "", true, fmt.Errorf("domain at index %d: %w", i, err)

From c0ac82be6a91cd4f5e43e7de6281d2854bbf63c2 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 24 May 2022 14:43:54 +0300
Subject: [PATCH 060/143] Pull request: 4480 fix sysv service script

Merge in DNS/adguard-home from 4480-sysv-boot to master

Updates #4480.

Squashed commit of the following:

commit c9645b1f3bd22a249c666e4485818bab6769f32d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue May 24 14:25:09 2022 +0300

    home: imp sysv script

commit cc323364ba6cce0284cbc6be9133a50a51b71f56
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon May 23 21:13:06 2022 +0300

    home: fix sysv service script
---
 CHANGELOG.md             | 2 ++
 internal/home/service.go | 9 ++++++---
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8aca8f15..9d837c95 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -128,6 +128,7 @@ In this release, the schema version has changed from 12 to 14.
 
 ### Fixed
 
+- Service startup on boot on systems using SysV-init ([#4480]).
 - Detection of the stopped service status on macOS and Linux ([#4273]).
 - Case-sensitive ClientID ([#4542]).
 - Slow version update queries making other HTTP APIs unresponsible ([#4499]).
@@ -150,6 +151,7 @@ In this release, the schema version has changed from 12 to 14.
 [#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
 [#4273]: https://github.com/AdguardTeam/AdGuardHome/issues/4273
 [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
+[#4480]: https://github.com/AdguardTeam/AdGuardHome/issues/4480
 [#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499
 [#4503]: https://github.com/AdguardTeam/AdGuardHome/issues/4503
 [#4533]: https://github.com/AdguardTeam/AdGuardHome/issues/4533
diff --git a/internal/home/service.go b/internal/home/service.go
index 70ab3c78..831a80d0 100644
--- a/internal/home/service.go
+++ b/internal/home/service.go
@@ -433,8 +433,11 @@ EnvironmentFile=-/etc/sysconfig/{{.Name}}
 WantedBy=multi-user.target
 `
 
-// Note: we should keep it in sync with the template from service_sysv_linux.go file
-// Use "ps | grep -v grep | grep $(get_pid)" because "ps PID" may not work on OpenWrt
+// sysvScript is the source of the daemon script for SysV-based Linux systems.
+// Keep as close as possible to the https://github.com/kardianos/service/blob/29f8c79c511bc18422bb99992779f96e6bc33921/service_sysv_linux.go#L187.
+//
+// Use ps command instead of reading the procfs since it's a more
+// implementation-independent approach.
 const sysvScript = `#!/bin/sh
 # For RedHat and cousins:
 # chkconfig: - 99 01
@@ -465,7 +468,7 @@ get_pid() {
 }
 
 is_running() {
-    [ -f "$pid_file" ] && ps | grep -v grep | grep $(get_pid) > /dev/null 2>&1
+    [ -f "$pid_file" ] && ps -p "$(get_pid)" > /dev/null 2>&1
 }
 
 case "$1" in

From a82ec09afdfbc66a8f07614b77e825f527a3b35b Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 24 May 2022 19:47:09 +0300
Subject: [PATCH 061/143] Pull request: all: upd dnsproxy, supp rfc 9250

Updates #4592.

Squashed commit of the following:

commit 1a80875d6aa7811d7d1d978f6fa8d558dec1ca87
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue May 24 19:28:27 2022 +0300

    all: upd dnsproxy, supp rfc 9250
---
 CHANGELOG.md                         | 10 ++++++----
 go.mod                               |  4 ++--
 go.sum                               | 14 ++++----------
 internal/dnsforward/clientid.go      | 12 ++++++------
 internal/dnsforward/clientid_test.go | 25 +++++++++++++------------
 5 files changed, 31 insertions(+), 34 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d837c95..e0c32ce7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ and this project adheres to
 
 ### Added
 
+- Support for the final DNS-over-QUIC standard, [RFC 9250][rfc-9250] ([#4592]).
 - Support upstreams for subdomains of a domain only ([#4503]).
 - Support for Discovery of Designated Resolvers (DDR) according to the [RFC
   draft][ddr-draft-06] ([#4463]).
@@ -45,7 +46,7 @@ and this project adheres to
   instead of the `/bin/sh` one ([#4533]).  To apply this change, backup your
   data and run `AdGuardHome -s uninstall && AdGuardHome -s install`.
 - The default DNS-over-QUIC port number is now `853` instead of `754` in
-  accordance with the latest [RFC draft][doq-draft-10] ([#4276]).
+  accordance with [RFC 9250][rfc-9250] ([#4276]).
 - Reverse DNS now has a greater priority as the source of runtime clients'
   information than ARP neighborhood.
 - Improved detection of runtime clients through more resilient ARP processing
@@ -100,7 +101,7 @@ In this release, the schema version has changed from 12 to 14.
   `dns.resolve_clients` property.  To rollback this change, remove the
   `runtime_sources` property, move the contents of `persistent` into the
   `clients` itself, the value of `clients.runtime_sources.rdns` into the
-  `dns.resolve_clietns`, and change the `schema_version` back to `13`.
+  `dns.resolve_clients`, and change the `schema_version` back to `13`.
 - Property `local_domain_name`, which in schema versions 12 and earlier used to
   be a part of the `dns` object, is now a part of the `dhcp` object:
 
@@ -131,7 +132,7 @@ In this release, the schema version has changed from 12 to 14.
 - Service startup on boot on systems using SysV-init ([#4480]).
 - Detection of the stopped service status on macOS and Linux ([#4273]).
 - Case-sensitive ClientID ([#4542]).
-- Slow version update queries making other HTTP APIs unresponsible ([#4499]).
+- Slow version update queries making other HTTP APIs unresponsive ([#4499]).
 - ARP tables refreshing process causing excessive PTR requests ([#3157]).
 
 [#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730
@@ -156,9 +157,10 @@ In this release, the schema version has changed from 12 to 14.
 [#4503]: https://github.com/AdguardTeam/AdGuardHome/issues/4503
 [#4533]: https://github.com/AdguardTeam/AdGuardHome/issues/4533
 [#4542]: https://github.com/AdguardTeam/AdGuardHome/issues/4542
+[#4592]: https://github.com/AdguardTeam/AdGuardHome/issues/4592
 
+[rfc-9250]:     https://datatracker.ietf.org/doc/html/rfc9250
 [ddr-draft-06]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
-[doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2
 [repr]:         https://reproducible-builds.org/docs/source-date-epoch/
 
 
diff --git a/go.mod b/go.mod
index 4d9d2191..d3c3e1de 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
 go 1.17
 
 require (
-	github.com/AdguardTeam/dnsproxy v0.42.4
+	github.com/AdguardTeam/dnsproxy v0.43.0
 	github.com/AdguardTeam/golibs v0.10.8
 	github.com/AdguardTeam/urlfilter v0.16.0
 	github.com/NYTimes/gziphandler v1.1.1
@@ -17,7 +17,7 @@ require (
 	github.com/google/renameio v1.0.1
 	github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41
 	github.com/kardianos/service v1.2.1
-	github.com/lucas-clemente/quic-go v0.26.0
+	github.com/lucas-clemente/quic-go v0.27.1
 	github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
 	github.com/mdlayher/netlink v1.6.0
 	// TODO(a.garipov): This package is deprecated; find a new one or use
diff --git a/go.sum b/go.sum
index 73accb25..ae65fdab 100644
--- a/go.sum
+++ b/go.sum
@@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/AdguardTeam/dnsproxy v0.42.4 h1:Rf45a3H6U/8XqWMYAMEsC1g/dVudyfgx4WY8N2syJMw=
-github.com/AdguardTeam/dnsproxy v0.42.4/go.mod h1:thHuk3599mgmucsv5J9HR9lBVQHnf4YleE08EbxNrN0=
+github.com/AdguardTeam/dnsproxy v0.43.0 h1:K082nx37DaNqSyT3kDtAfgBACNWc+ZDI1Yr/kGppu1k=
+github.com/AdguardTeam/dnsproxy v0.43.0/go.mod h1:JUGTm5dmlll47JltztsT0N//pVJjdg6zu0SNeUeaA7g=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
@@ -143,21 +143,15 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
-github.com/lucas-clemente/quic-go v0.26.0 h1:ALBQXr9UJ8A1LyzvceX4jd9QFsHvlI0RR6BkV16o00A=
-github.com/lucas-clemente/quic-go v0.26.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
+github.com/lucas-clemente/quic-go v0.27.1 h1:sOw+4kFSVrdWOYmUjufQ9GBVPqZ+tu+jMtXxXNmRJyk=
+github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
 github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
 github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
-github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
-github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
 github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
 github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
-github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
 github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
 github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
-github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
-github.com/marten-seemann/qtls-go1-18 v0.1.0/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
 github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y=
 github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
diff --git a/internal/dnsforward/clientid.go b/internal/dnsforward/clientid.go
index bb687a41..16bac881 100644
--- a/internal/dnsforward/clientid.go
+++ b/internal/dnsforward/clientid.go
@@ -112,8 +112,8 @@ type tlsConn interface {
 	ConnectionState() (cs tls.ConnectionState)
 }
 
-// quicSession is a narrow interface for quic.Session to simplify testing.
-type quicSession interface {
+// quicConnection is a narrow interface for quic.Connection to simplify testing.
+type quicConnection interface {
 	ConnectionState() (cs quic.ConnectionState)
 }
 
@@ -148,16 +148,16 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
 
 		cliSrvName = tc.ConnectionState().ServerName
 	case proxy.ProtoQUIC:
-		qs, ok := pctx.QUICSession.(quicSession)
+		conn, ok := pctx.QUICConnection.(quicConnection)
 		if !ok {
 			return "", fmt.Errorf(
-				"proxy ctx quic session of proto %s is %T, want quic.Session",
+				"proxy ctx quic conn of proto %s is %T, want quic.Connection",
 				proto,
-				pctx.QUICSession,
+				pctx.QUICConnection,
 			)
 		}
 
-		cliSrvName = qs.ConnectionState().TLS.ServerName
+		cliSrvName = conn.ConnectionState().TLS.ServerName
 	}
 
 	clientID, err = clientIDFromClientServerName(
diff --git a/internal/dnsforward/clientid_test.go b/internal/dnsforward/clientid_test.go
index 6e23d639..31c55fcd 100644
--- a/internal/dnsforward/clientid_test.go
+++ b/internal/dnsforward/clientid_test.go
@@ -29,17 +29,18 @@ func (c testTLSConn) ConnectionState() (cs tls.ConnectionState) {
 	return cs
 }
 
-// testQUICSession is a quicSession for tests.
-type testQUICSession struct {
-	// Session is embedded here simply to make testQUICSession a quic.Session
-	// without actually implementing all methods.
-	quic.Session
+// testQUICConnection is a quicConnection for tests.
+type testQUICConnection struct {
+	// Connection is embedded here simply to make testQUICConnection a
+	// quic.Connection without actually implementing all methods.
+	quic.Connection
 
 	serverName string
 }
 
-// ConnectionState implements the quicSession interface for testQUICSession.
-func (c testQUICSession) ConnectionState() (cs quic.ConnectionState) {
+// ConnectionState implements the quicConnection interface for
+// testQUICConnection.
+func (c testQUICConnection) ConnectionState() (cs quic.ConnectionState) {
 	cs.TLS.ServerName = c.serverName
 
 	return cs
@@ -179,17 +180,17 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
 				}
 			}
 
-			var qs quic.Session
+			var qconn quic.Connection
 			if tc.proto == proxy.ProtoQUIC {
-				qs = testQUICSession{
+				qconn = testQUICConnection{
 					serverName: tc.cliSrvName,
 				}
 			}
 
 			pctx := &proxy.DNSContext{
-				Proto:       tc.proto,
-				Conn:        conn,
-				QUICSession: qs,
+				Proto:          tc.proto,
+				Conn:           conn,
+				QUICConnection: qconn,
 			}
 
 			clientID, err := srv.clientIDFromDNSContext(pctx)

From 75f01d51f7312601cd323bdbdd4648dfd673ea75 Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Wed, 25 May 2022 14:31:32 +0300
Subject: [PATCH 062/143] Pull request: all: filters json

Merge in DNS/adguard-home from 4581-filters-json to master

Squashed commit of the following:

commit da0b86983432ac1791645da328df5848daac5ea6
Merge: 62fa4fc6 a82ec09a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 25 12:58:25 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4581-filters-json

commit 62fa4fc6ff150ebb8dbd8888a58819fb644d43ad
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 25 11:55:52 2022 +0200

    all: filters json

commit 96486ffbb41947b5e748f6e35eb96ee73867eba1
Merge: 9956f0af c0ac82be
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 24 15:57:52 2022 +0200

    Merge branch 'master' into 4581-filters-json

commit 9956f0aff1b7029f336d22013a62f2871a964322
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 24 15:53:43 2022 +0200

    all: filters json
---
 client/src/helpers/filters/filters.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/client/src/helpers/filters/filters.json b/client/src/helpers/filters/filters.json
index 565cd74d..63fe0995 100644
--- a/client/src/helpers/filters/filters.json
+++ b/client/src/helpers/filters/filters.json
@@ -81,8 +81,8 @@
         "urlhaus-filter-online": {
             "name": "Online Malicious URL Blocklist",
             "categoryId": "security",
-            "homepage": "https://gitlab.com/curben/urlhaus-filter",
-            "source": "https://curben.gitlab.io/malware-filter/urlhaus-filter-agh-online.txt"
+            "homepage": "https://gitlab.com/malware-filter/urlhaus-filter",
+            "source": "https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-agh-online.txt"
         },
         "dandelion-sprouts-anti-malware-list": {
             "name": "Dandelion Sprout's Anti-Malware List",

From 549b20bdea3c3215bb355ffc39546f7bb3260d80 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 25 May 2022 18:00:50 +0300
Subject: [PATCH 063/143] Pull request: querylog: fix oldest calc

Updates #4591.

Squashed commit of the following:

commit 70b70c78c85311363535536c7ea12336b21accf8
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed May 25 17:35:54 2022 +0300

    querylog: fix oldest calc
---
 CHANGELOG.md                   |  2 ++
 internal/dnsforward/stats.go   |  2 +-
 internal/home/dns.go           |  2 +-
 internal/querylog/http.go      |  2 +-
 internal/querylog/qlog.go      |  2 +-
 internal/querylog/qlog_test.go |  2 +-
 internal/querylog/querylog.go  | 18 +++++++++---------
 internal/querylog/search.go    | 17 ++++++++++++-----
 8 files changed, 28 insertions(+), 19 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0c32ce7..4a72447c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -129,6 +129,7 @@ In this release, the schema version has changed from 12 to 14.
 
 ### Fixed
 
+- Query log occasionally going into an infinite loop ([#4591]).
 - Service startup on boot on systems using SysV-init ([#4480]).
 - Detection of the stopped service status on macOS and Linux ([#4273]).
 - Case-sensitive ClientID ([#4542]).
@@ -157,6 +158,7 @@ In this release, the schema version has changed from 12 to 14.
 [#4503]: https://github.com/AdguardTeam/AdGuardHome/issues/4503
 [#4533]: https://github.com/AdguardTeam/AdGuardHome/issues/4533
 [#4542]: https://github.com/AdguardTeam/AdGuardHome/issues/4542
+[#4591]: https://github.com/AdguardTeam/AdGuardHome/issues/4591
 [#4592]: https://github.com/AdguardTeam/AdGuardHome/issues/4592
 
 [rfc-9250]:     https://datatracker.ietf.org/doc/html/rfc9250
diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go
index 56cc19c5..9a7b1ddb 100644
--- a/internal/dnsforward/stats.go
+++ b/internal/dnsforward/stats.go
@@ -64,9 +64,9 @@ func (s *Server) logQuery(
 		Answer:            pctx.Res,
 		OrigAnswer:        dctx.origResp,
 		Result:            dctx.result,
-		Elapsed:           elapsed,
 		ClientID:          dctx.clientID,
 		ClientIP:          ip,
+		Elapsed:           elapsed,
 		AuthenticatedData: dctx.responseAD,
 	}
 
diff --git a/internal/home/dns.go b/internal/home/dns.go
index 1c04c6c3..9eabfefa 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -58,6 +58,7 @@ func initDNSServer() (err error) {
 	}
 
 	conf := querylog.Config{
+		Anonymizer:        anonymizer,
 		ConfigModified:    onConfigModified,
 		HTTPRegister:      httpRegister,
 		FindClient:        Context.clients.findMultiple,
@@ -67,7 +68,6 @@ func initDNSServer() (err error) {
 		Enabled:           config.DNS.QueryLogEnabled,
 		FileEnabled:       config.DNS.QueryLogFileEnabled,
 		AnonymizeClientIP: config.DNS.AnonymizeClientIP,
-		Anonymizer:        anonymizer,
 	}
 	Context.queryLog = querylog.New(conf)
 
diff --git a/internal/querylog/http.go b/internal/querylog/http.go
index 6a2bdcee..11f62d0d 100644
--- a/internal/querylog/http.go
+++ b/internal/querylog/http.go
@@ -19,10 +19,10 @@ import (
 )
 
 type qlogConfig struct {
-	Enabled bool `json:"enabled"`
 	// Use float64 here to support fractional numbers and not mess the API
 	// users by changing the units.
 	Interval          float64 `json:"interval"`
+	Enabled           bool    `json:"enabled"`
 	AnonymizeClientIP bool    `json:"anonymize_client_ip"`
 }
 
diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go
index 8856fd9c..24eec40e 100644
--- a/internal/querylog/qlog.go
+++ b/internal/querylog/qlog.go
@@ -149,7 +149,7 @@ func (l *queryLog) clear() {
 		log.Error("removing log file %q: %s", l.logFile, err)
 	}
 
-	log.Debug("Query log: cleared")
+	log.Debug("querylog: cleared")
 }
 
 func (l *queryLog) Add(params *AddParams) {
diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go
index fbfc459d..6beed1be 100644
--- a/internal/querylog/qlog_test.go
+++ b/internal/querylog/qlog_test.go
@@ -285,8 +285,8 @@ func addEntry(l *queryLog, host string, answerStr, client net.IP) {
 		Answer:     &a,
 		OrigAnswer: &a,
 		Result:     &res,
-		ClientIP:   client,
 		Upstream:   "upstream",
+		ClientIP:   client,
 	}
 
 	l.Add(params)
diff --git a/internal/querylog/querylog.go b/internal/querylog/querylog.go
index bd6e1569..a854c2c4 100644
--- a/internal/querylog/querylog.go
+++ b/internal/querylog/querylog.go
@@ -28,8 +28,11 @@ type QueryLog interface {
 	WriteDiskConfig(c *Config)
 }
 
-// Config - configuration object
+// Config is the query log configuration structure.
 type Config struct {
+	// Anonymizer processes the IP addresses to anonymize those if needed.
+	Anonymizer *aghnet.IPMut
+
 	// ConfigModified is called when the configuration is changed, for
 	// example by HTTP requests.
 	ConfigModified func()
@@ -68,9 +71,6 @@ type Config struct {
 	// AnonymizeClientIP tells if the query log should anonymize clients' IP
 	// addresses.
 	AnonymizeClientIP bool
-
-	// Anonymizer processes the IP addresses to anonymize those if needed.
-	Anonymizer *aghnet.IPMut
 }
 
 // AddParams is the parameters for adding an entry.
@@ -91,18 +91,18 @@ type AddParams struct {
 	// Result is the filtering result (optional).
 	Result *filtering.Result
 
-	// Elapsed is the time spent for processing the request.
-	Elapsed time.Duration
-
 	ClientID string
 
-	ClientIP net.IP
-
 	// Upstream is the URL of the upstream DNS server.
 	Upstream string
 
 	ClientProto ClientProto
 
+	ClientIP net.IP
+
+	// Elapsed is the time spent for processing the request.
+	Elapsed time.Duration
+
 	// Cached indicates if the response is served from cache.
 	Cached bool
 
diff --git a/internal/querylog/search.go b/internal/querylog/search.go
index 4a3de979..8fb32e60 100644
--- a/internal/querylog/search.go
+++ b/internal/querylog/search.go
@@ -73,7 +73,7 @@ func (l *queryLog) searchMemory(params *searchParams, cache clientCache) (entrie
 
 // search - searches log entries in the query log using specified parameters
 // returns the list of entries found + time of the oldest entry
-func (l *queryLog) search(params *searchParams) ([]*logEntry, time.Time) {
+func (l *queryLog) search(params *searchParams) (entries []*logEntry, oldest time.Time) {
 	now := time.Now()
 
 	if params.limit == 0 {
@@ -88,7 +88,7 @@ func (l *queryLog) search(params *searchParams) ([]*logEntry, time.Time) {
 	totalLimit := params.offset + params.limit
 
 	// now let's get a unified collection
-	entries := append(memoryEntries, fileEntries...)
+	entries = append(memoryEntries, fileEntries...)
 	if len(entries) > totalLimit {
 		// remove extra records
 		entries = entries[:totalLimit]
@@ -111,13 +111,18 @@ func (l *queryLog) search(params *searchParams) ([]*logEntry, time.Time) {
 		}
 	}
 
-	if len(entries) > 0 && len(entries) <= totalLimit {
+	if len(entries) > 0 {
 		// Update oldest after merging in the memory buffer.
 		oldest = entries[len(entries)-1].Time
 	}
 
-	log.Debug("QueryLog: prepared data (%d/%d) older than %s in %s",
-		len(entries), total, params.olderThan, time.Since(now))
+	log.Debug(
+		"querylog: prepared data (%d/%d) older than %s in %s",
+		len(entries),
+		total,
+		params.olderThan,
+		time.Since(now),
+	)
 
 	return entries, oldest
 }
@@ -180,6 +185,8 @@ func (l *queryLog) searchFiles(
 		e, ts, err = l.readNextEntry(r, params, cache)
 		if err != nil {
 			if err == io.EOF {
+				oldestNano = 0
+
 				break
 			}
 

From 1a49d2f0c9eff0e084de83598e5e5825125d5905 Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Thu, 26 May 2022 12:49:13 +0300
Subject: [PATCH 064/143] Pull request: client: reset filtered logs on url
 params clear

Merge in DNS/adguard-home from fix-querylog-link to master

Squashed commit of the following:

commit fc4043258eb1e427a76ee44d2a4a525a6d659ab9
Merge: 25b91504 549b20bd
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu May 26 12:42:02 2022 +0300

    Merge branch 'master' into fix-querylog-link

commit 25b91504e8949bd381e6774148e4a7ecbb81610e
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu May 26 12:21:57 2022 +0300

    fix

commit f567b9b1e4eeb6499c79b05e4d837e905850a6b9
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu May 26 12:20:48 2022 +0300

    client: reset filtered logs on url params clear
---
 client/src/components/Logs/index.js | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js
index c3b30703..3e89c3b1 100644
--- a/client/src/components/Logs/index.js
+++ b/client/src/components/Logs/index.js
@@ -152,6 +152,16 @@ const Logs = () => {
         };
     }, []);
 
+    useEffect(() => {
+        if (!history.location.search) {
+            (async () => {
+                setIsLoading(true);
+                await dispatch(setFilteredLogs());
+                setIsLoading(false);
+            })();
+        }
+    }, [history.location.search]);
+
     const renderPage = () => <>
         <Filters
                 filter={{

From 65a33a121527766de02a678462839c6e58a6ea35 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 26 May 2022 14:20:36 +0300
Subject: [PATCH 065/143] Pull request: client: upd i18n

Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 7ddd8cb01f8136ad4690a439ee3b810043af749e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu May 26 14:10:31 2022 +0300

    client: upd i18n
---
 client/src/__locales/cs.json    |  2 +-
 client/src/__locales/fi.json    |  4 +--
 client/src/__locales/pt-br.json |  2 +-
 client/src/__locales/pt-pt.json |  2 +-
 client/src/__locales/sl.json    |  4 +--
 client/src/__locales/tr.json    | 12 ++++----
 client/src/__locales/uk.json    | 50 ++++++++++++++++-----------------
 client/src/__locales/zh-cn.json |  1 +
 8 files changed, 39 insertions(+), 38 deletions(-)

diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json
index 8331dd50..8467df91 100644
--- a/client/src/__locales/cs.json
+++ b/client/src/__locales/cs.json
@@ -85,7 +85,7 @@
     "form_enter_hostname": "Zadejte název hostitele",
     "error_details": "Podrobnosti chyby",
     "response_details": "Detail odpovědi",
-    "request_details": "Detail požadavku",
+    "request_details": "Detaily požadavku",
     "client_details": "Detaily klienta",
     "details": "Detaily",
     "back": "Zpět",
diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json
index 3141b12a..a5f3fa01 100644
--- a/client/src/__locales/fi.json
+++ b/client/src/__locales/fi.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Bootstrap DNS-palvelimet",
     "bootstrap_dns_desc": "Bootstrap DNS-palvelimia käytetään ylävirroiksi määritettyjen DoH/DoT-resolvereiden IP-osoitteiden selvitykseen.",
     "local_ptr_title": "Yksityiset käänteiset DNS-palvelimet",
-    "local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-pyynnöille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien päätelaitteiden osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteisen DNS:n avulla. Jos ei käytössä, käyttää AdGuard Home käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.",
+    "local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-kyselyille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien PTR-kyselyiden osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteisen DNS:n avulla. Jos ei käytössä, AdGuard Home käyttää käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.",
     "local_ptr_default_resolver": "Oletusarvoisesti AdGuard Home käyttää seuraavia käänteisiä DNS-resolvereita: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home ei voinut määrittää tälle järjestelmälle sopivaa yksityistä käänteistä DNS-resolveria.",
     "local_ptr_placeholder": "Syötä yksi palvelimen osoite per rivi",
@@ -351,7 +351,7 @@
     "install_devices_android_list_5": "Syötä \"DNS 1\" ja \"DNS 2\" -kenttiin AdGuard Home -palvelimesi osoitteet.",
     "install_devices_ios_list_1": "Napauta aloitusnäytöstä \"Asetukset\".",
     "install_devices_ios_list_2": "Valitse vasemmalta \"Wi-Fi\" (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).",
-    "install_devices_ios_list_3": "Valitse yhdistetty verkko.",
+    "install_devices_ios_list_3": "Valitse tällä hetkellä aktiivinen verkko.",
     "install_devices_ios_list_4": "Syötä \"DNS\" -kenttään AdGuard Home -palvelimesi osoitteet.",
     "get_started": "Aloita",
     "next": "Seuraava",
diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json
index 48d82e0d..d52d6961 100644
--- a/client/src/__locales/pt-br.json
+++ b/client/src/__locales/pt-br.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Servidores DNS de inicialização",
     "bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams.",
     "local_ptr_title": "Servidores DNS reversos privados",
-    "local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver os nomes de host de clientes com endereços IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não for definido, o AdGuard Home usa os endereços dos resolvedores DNS padrão do seu sistema operacional, exceto os endereços do AdGuard Home.",
+    "local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver solicitações de PTR para endereços em intervalos de IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não estiver definido, o AdGuard Home usa os endereços dos resolvedores de DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
     "local_ptr_default_resolver": "Por padrão, o AdGuard Home usa os seguintes resolvedores de DNS reverso: {{ip}}.",
     "local_ptr_no_default_resolver": "A página inicial do AdGuard não conseguiu determinar resolvedores DNS reversos privados adequados para este sistema.",
     "local_ptr_placeholder": "Insira um endereço de servidor por linha",
diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json
index 1a2d5c7c..6502f9f9 100644
--- a/client/src/__locales/pt-pt.json
+++ b/client/src/__locales/pt-pt.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Servidores DNS de arranque",
     "bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que especifica como upstreams.",
     "local_ptr_title": "Servidores DNS reversos privados",
-    "local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver os nomes de host de clientes com endereços IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não for definido, o AdGuard Home usa os endereços dos resolvedores DNS padrão do seu sistema operacional, exceto os endereços do AdGuard Home.",
+    "local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver solicitações de PTR para endereços em intervalos de IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não estiver definido, o AdGuard Home usa os endereços dos resolvedores de DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
     "local_ptr_default_resolver": "Por predefinição, o AdGuard Home usa os seguintes resolvedores de DNS reverso: {{ip}}.",
     "local_ptr_no_default_resolver": "A página inicial do AdGuard não conseguiu determinar resolvedores DNS reversos privados adequados para este sistema.",
     "local_ptr_placeholder": "Insira um endereço de servidor por linha",
diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json
index 8dffefc7..80dfd9aa 100644
--- a/client/src/__locales/sl.json
+++ b/client/src/__locales/sl.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Zagonski DNS strežniki",
     "bootstrap_dns_desc": "Zagonski DNS strežniki se uporabljajo za razreševanje IP naslovov DoH/DoT reševalcev, ki jih določite kot navzgornje.",
     "local_ptr_title": "Zasebni povratni strežniki DNS",
-    "local_ptr_desc": "Strežniki DNS, ki jih AdGuard Home uporablja za lokalne poizvedbe PTR. Ti strežniki se uporabljajo za razreševanje imen gostiteljev z zasebnimi naslovi IP, na primer \"192.168.12.34\" uporablja DNS. Če ni nastavljen, uporablja naslove privzetih razreševalnikov DNS vašega OS, razen naslovov samega AdGuard Home.",
+    "local_ptr_desc": "Strežniki DNS, ki jih AdGuard Home uporablja za lokalne PTR poizvedbe. Ti strežniki se uporabljajo za reševanje zahtev PTR za naslove v zasebnih obsegih IP, na primer \"192.168.12.34\", z uporabo obratnega DNS. Če ni nastavljen, AdGuard Home uporablja naslove privzetih razreševalnikov DNS vašega OS, razen naslovov samega AdGuard Home.",
     "local_ptr_default_resolver": "AdGuard Home privzeto uporablja te povratne razreševalnike DNS: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home ni mogel določiti ustreznih zasebnih povratnih reševalcev DNS za ta sistem.",
     "local_ptr_placeholder": "V vrstico vnesite en naslov strežnika",
@@ -351,7 +351,7 @@
     "install_devices_android_list_5": "Spremeni nastavitev vrednosti DNS 1 in DNS 2 na naslove strežnikov AdGuard Home.",
     "install_devices_ios_list_1": "Na začetnem zaslonu izberite Nastavitve.",
     "install_devices_ios_list_2": "V levem meniju izberite Wi-Fi (nemogoče je konfigurirati DNS za mobilna omrežja).",
-    "install_devices_ios_list_3": "Dotaknite se imena trenutno aktivnega omrežja.",
+    "install_devices_ios_list_3": "Tapnite na ime trenutno aktivnega omrežja.",
     "install_devices_ios_list_4": "V polje DNS vnesite vaše naslove AdGuard Home strežnika.",
     "get_started": "Začnimo",
     "next": "Naprej",
diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json
index f26e0011..6fd679ac 100644
--- a/client/src/__locales/tr.json
+++ b/client/src/__locales/tr.json
@@ -336,16 +336,16 @@
     "install_devices_router_list_4": "Bazı yönlendirici türlerinde özel bir DNS sunucusu ayarlanamaz. Bu durumda, AdGuard Home'u <0>DHCP sunucusu</0> olarak ayarlamak yardımcı olabilir. Aksi takdirde, yönlendirici modeliniz için DNS sunucularını nasıl ayarlayacağınız konusunda yönlendirici kılavuzuna bakmalısınız.",
     "install_devices_windows_list_1": "Başlat menüsünden veya Windows araması aracılığıyla Denetim Masası'nı açın.",
     "install_devices_windows_list_2": "Ağ ve İnternet kategorisine girin ve ardından Ağ ve Paylaşım Merkezi'ne girin.",
-    "install_devices_windows_list_3": "Sol panelde \"Bağdaştırıcı ayarlarını değiştirin'e\" tıklayın.",
+    "install_devices_windows_list_3": "Sol panelde \"Bağdaştırıcı ayarlarını değiştirin\" öğesine tıklayın.",
     "install_devices_windows_list_4": "Kullandığınız aktif bağlantının üzerine sağ tıklayın ve Özellikler öğesine tıklayın.",
-    "install_devices_windows_list_5": "Listede \"İnternet Protokolü Sürüm 4 (TCP/IPv4)\" (veya IPv6 için \"İnternet Protokolü Sürüm 6 (TCP/IPv6)\") öğesini bulun, seçin ve ardından tekrar Özellikler'e tıklayın.",
+    "install_devices_windows_list_5": "Listede \"İnternet Protokolü Sürüm 4 (TCP/IPv4)\" (veya IPv6 için \"İnternet Protokolü Sürüm 6 (TCP/IPv6)\") öğesini bulun, seçin ve ardından tekrar Özellikler öğesine tıklayın.",
     "install_devices_windows_list_6": "\"Aşağıdaki DNS sunucu adreslerini kullan\"ı seçin ve AdGuard Home sunucu adreslerinizi girin.",
-    "install_devices_macos_list_1": "Apple simgesine tıklayın ve Sistem Tercihleri'ne gidin.",
-    "install_devices_macos_list_2": "Ağ'a tıklayın.",
+    "install_devices_macos_list_1": "Apple simgesine tıklayın ve Sistem Tercihleri öğesine gidin.",
+    "install_devices_macos_list_2": "Ağ öğesine tıklayın.",
     "install_devices_macos_list_3": "Listedeki ilk bağlantıyı seçin ve Gelişmiş öğesine tıklayın.",
     "install_devices_macos_list_4": "DNS sekmesini seçin ve AdGuard Home sunucunuzun adreslerini girin.",
     "install_devices_android_list_1": "Android Menüsü ana ekranından Ayarlar'a dokunun.",
-    "install_devices_android_list_2": "Menüde bulunan Wi-Fi seçeneğine dokunun. Mevcut tüm ağlar listelenecektir (mobil ağlar için özel DNS sunucusu ayarlanamaz).",
+    "install_devices_android_list_2": "Menüde bulunan Wi-Fi öğesine dokunun. Mevcut tüm ağlar listelenecektir (mobil ağlar için özel DNS sunucusu ayarlanamaz).",
     "install_devices_android_list_3": "Bağlı olduğunuz ağın üzerine basılı tutun ve Ağı Değiştir'e dokunun.",
     "install_devices_android_list_4": "Bazı cihazlarda, diğer ayarları görmek için \"Gelişmiş\" seçeneğini seçmeniz gerekebilir. Android DNS ayarlarınızı yapmak için IP ayarlarını DHCP modundan Statik moda almanız gerekecektir.",
     "install_devices_android_list_5": "DNS 1 ve DNS 2 değerlerini AdGuard Home sunucunuzun adresleriyle değiştirin.",
@@ -453,7 +453,7 @@
     "setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> <1>{{address}}</1> dizesini kullan.",
     "setup_dns_privacy_3": "<0>İşte, kullanabileceğiniz yazılımların bir listesi.</0>",
     "setup_dns_privacy_4": "Bir iOS 14 veya macOS Big Sur cihazında, DNS ayarlarına <highlight>DNS-over-HTTPS</highlight> veya <highlight>DNS-over-TLS</highlight> sunucuları ekleyen özel '.mobileconfig' dosyasını indirebilirsiniz.",
-    "setup_dns_privacy_android_1": "Android 9, yerel olarak DNS-over-TLS protokolünü destekler. Yapılandırmak için Ayarlar → Ağ ve İnternet → Gelişmiş → Özel DNS seçeneğine gidin ve alan adınızı girin.",
+    "setup_dns_privacy_android_1": "Android 9, yerel olarak DNS-over-TLS protokolünü destekler. Yapılandırmak için Ayarlar → Ağ ve İnternet → Gelişmiş → Özel DNS öğesine gidin ve alan adınızı girin.",
     "setup_dns_privacy_android_2": "<0>Android için AdGuard</0>, <1>DNS-over-HTTPS</1> ve <1>DNS-over-TLS</1> protokolünü destekler.",
     "setup_dns_privacy_android_3": "<0>Intra</0> Android'e <1>DNS-over-HTTPS</1> protokol desteğini ekler.",
     "setup_dns_privacy_ios_1": "<0>DNSCloak</0>, <1>DNS-over-HTTPS</1> protokolünü destekler, ancak kendi sunucunuzu kullanacak şekilde yapılandırmak için bir <2>DNS Damgası</2> oluşturmanız gerekir.",
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index 9fcbd460..84a6974e 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -7,16 +7,16 @@
     "load_balancing": "Балансування навантаження",
     "load_balancing_desc": "Запитувати один сервер за раз. AdGuard Home використовуватиме зважений випадковий алгоритм для вибору сервера, щоб найшвидший сервер використовувався частіше.",
     "bootstrap_dns": "Bootstrap DNS-сервери",
-    "bootstrap_dns_desc": "Bootstrap DNS-сервери використовуються для пошуку IP-адреси DoH/DoT серверів, які ви встановили.",
+    "bootstrap_dns_desc": "Bootstrap DNS-сервери використовуються для вирішення IP-адрес встановлених серверів DoH/DoT.",
     "local_ptr_title": "Приватні сервери для зворотного DNS",
-    "local_ptr_desc": "DNS-сервери, які AdGuard Home використовує для локальних PTR-запитів. Ці сервери, використовуючи rDNS, використовуються для отримання доменних імен клієнтів у приватних мережах, наприклад, «192.168.12.34». Якщо список порожній, буде використовуватись системний DNS-сервер.",
-    "local_ptr_default_resolver": "AdGuard Home усталено використовує такі зворотні DNS-резолвери: {{ip}}.",
-    "local_ptr_no_default_resolver": "AdGuard Home не зміг визначити приватні реверсивні DNS-резолвери, що були б придатними для цієї системи.",
+    "local_ptr_desc": "DNS-сервери, які AdGuard Home використовує для локальних PTR-запитів. Ці сервери використовуються для вирішення PTR-запитів для адрес у приватних мережах, наприклад, «192.168.12.34». Якщо список порожній, AdGuard Home буде усталено використовувати системний DNS-сервер.",
+    "local_ptr_default_resolver": "Стандартно AdGuard Home користується такими зворотними DNS-вирішувачами: {{ip}}.",
+    "local_ptr_no_default_resolver": "AdGuard Home не зміг визначити приватні зворотні DNS-вирішувачі, які підійшли б для цієї системи.",
     "local_ptr_placeholder": "Вводьте одну адресу на рядок",
-    "resolve_clients_title": "Увімкнути запитування доменних імен для IP-адрес клієнтів",
+    "resolve_clients_title": "Увімкнути зворотне вирішення IP-адрес клієнтів",
     "resolve_clients_desc": "Визначати доменні імена клієнтів за допомогою PTR-запитів до відповідних серверів — приватних DNS-серверів для локальних клієнтів та upstream-серверів для клієнтів з публічними IP-адресами.",
     "use_private_ptr_resolvers_title": "Використовувати приватні зворотні DNS-резолвери",
-    "use_private_ptr_resolvers_desc": "Надсилати зворотні DNS-запити до вказаних серверів для клієнтів, що обслуговуються локально. Якщо вимкнено, AdGuard Home буде відповідати NXDOMAIN на всі такі PTR-запити, окрім запитів про клієнтів, що уже відомі по DHCP, /etc/hosts тощо.",
+    "use_private_ptr_resolvers_desc": "Надсилати зворотні DNS-запити до вказаних серверів для клієнтів, що обслуговуються локально. Якщо вимкнено, AdGuard Home буде відповідати NXDOMAIN на всі такі PTR-запити, окрім запитів про клієнтів, що уже відомі завдяки DHCP, /etc/hosts тощо.",
     "check_dhcp_servers": "Перевірити DHCP-сервери",
     "save_config": "Зберегти конфігурацію",
     "enabled_dhcp": "DHCP-сервер увімкнено",
@@ -60,7 +60,7 @@
     "dhcp_form_range_end": "Кінець діапазону",
     "dhcp_form_lease_title": "Час оренди DHCP (в секундах)",
     "dhcp_form_lease_input": "Тривалість оренди",
-    "dhcp_interface_select": "Оберіть інтерфейс DHCP",
+    "dhcp_interface_select": "Вибрати DHCP-інтерфейс",
     "dhcp_hardware_address": "Апаратна адреса",
     "dhcp_ip_addresses": "IP-адреси",
     "ip": "IP",
@@ -117,11 +117,11 @@
     "stats_adult": "Заблоковано вебсайтів для дорослих",
     "stats_query_domain": "Найчастіші запити доменів",
     "for_last_24_hours": "за останні 24 години",
-    "for_last_days": "за останній день",
+    "for_last_days": "за останній {{count}} день",
     "for_last_days_plural": "за останні {{count}} днів",
     "stats_disabled": "Статистику вимкнено. Ви можете увімкнути її на <0>сторінці налаштувань</0>.",
     "stats_disabled_short": "Статистику вимкнено",
-    "no_domains_found": "Доменів не знайдено",
+    "no_domains_found": "Не знайдено жодного домену",
     "requests_count": "Кількість запитів",
     "top_blocked_domains": "Найчастіше блоковані домени",
     "top_clients": "Найактивніші клієнти",
@@ -131,7 +131,7 @@
     "number_of_dns_query_days_plural": "Кількість DNS-запитів, оброблених за останні {{count}} днів",
     "number_of_dns_query_24_hours": "Кількість DNS-запитів, оброблених за останні 24 години",
     "number_of_dns_query_blocked_24_hours": "Кількість DNS-запитів, заблокованих фільтрами і списками блокування hosts",
-    "number_of_dns_query_blocked_24_hours_by_sec": "Кількість DNS-запитів, заблокованих модулем безпеки перегляду AdGuard",
+    "number_of_dns_query_blocked_24_hours_by_sec": "Кількість DNS-запитів, заблокованих модулем «Безпека перегляду» AdGuard",
     "number_of_dns_query_blocked_24_hours_adult": "Кількість заблокованих вебсайтів для дорослих",
     "enforced_save_search": "Примусовий безпечний пошук",
     "number_of_dns_query_to_safe_search": "Кількість DNS-запитів до пошукових систем, для яких примусово застосований безпечний пошук",
@@ -139,10 +139,10 @@
     "average_processing_time_hint": "Середній час обробки DNS запиту в мілісекундах",
     "block_domain_use_filters_and_hosts": "Блокування доменів за допомогою фільтрів та hosts-файлів",
     "filters_block_toggle_hint": "Ви можете налаштувати правила блокування в розділі <a>Фільтри</a>.",
-    "use_adguard_browsing_sec": "Використовувати Безпечну навігацію AdGuard",
-    "use_adguard_browsing_sec_hint": "AdGuard Home перевірятиме, чи додано домен до списку веб-служби безпечного перегляду браузера. Він використовуватиме API для перевірки — на сервер надсилається лише короткий префікс хешу SHA256 доменного імені.",
-    "use_adguard_parental": "Використовувати вебсервіс Батьківського контролю AdGuard",
-    "use_adguard_parental_hint": "AdGuard Home перевірить, чи містить домен матеріали для дорослих. Він використовує то же API, що й Безпечна навігація AdGuard.",
+    "use_adguard_browsing_sec": "Використовувати вебслужбу «Безпека перегляду» AdGuard",
+    "use_adguard_browsing_sec_hint": "AdGuard Home перевірятиме, чи підлягає домен блокуванню завдяки вебслужбі «Безпека перегляду». Для перевірки буде використано безпечний API — на сервер надсилається лише короткий префікс хешу SHA256 доменного імені.",
+    "use_adguard_parental": "Використовувати вебслужбу «Батьківський контроль» AdGuard",
+    "use_adguard_parental_hint": "AdGuard Home перевірить, чи містить домен матеріали для дорослих. Буде використано той же безпечний API, що й для «Безпеки перегляду» AdGuard.",
     "enforce_safe_search": "Використовувати Безпечний пошук",
     "enforce_save_search_hint": "AdGuard Home може примусово застосовувати безпечний пошук в таких пошукових системах: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
     "no_servers_specified": "Сервери не вказано",
@@ -165,8 +165,8 @@
     "enabled_filtering_toast": "Фільтрування увімкнено",
     "disabled_safe_browsing_toast": "Безпечний перегляд вимкнено",
     "enabled_safe_browsing_toast": "Безпечний перегляд увімкнено",
-    "disabled_parental_toast": "Батьківський контроль вимкнено",
-    "enabled_parental_toast": "Батьківський контроль увімкнено",
+    "disabled_parental_toast": "«Батьківський контроль» вимкнено",
+    "enabled_parental_toast": "«Батьківський контроль» увімкнено",
     "disabled_safe_search_toast": "Безпечний пошук вимкнено",
     "enabled_save_search_toast": "Безпечний пошук увімкнено",
     "enabled_table_header": "Увімкнено",
@@ -193,7 +193,7 @@
     "edit_blocklist": "Змінити список блокування",
     "edit_allowlist": "Змінити список дозволів",
     "choose_blocklist": "Виберіть списки блокування",
-    "choose_allowlist": "Обрати списки дозволених сайтів",
+    "choose_allowlist": "Виберіть списки дозволів",
     "enter_valid_blocklist": "Введіть дійсну URL-адресу в список блокування.",
     "enter_valid_allowlist": "Введіть дійсну URL-адресу в список дозволів.",
     "form_error_url_format": "Неправильний формат URL",
@@ -214,7 +214,7 @@
     "example_upstream_dot": "зашифрований <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "зашифрований <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "зашифрований <0>DNS-over-QUIC</0> (експериментальний);",
-    "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt</1> або <2>DNS-over-HTTPS</2> серверів;",
+    "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt-</1> або <2>DNS-over-HTTPS-</2>вирішувачів;",
     "example_upstream_tcp": "звичайний DNS (через TCP);",
     "example_upstream_tcp_hostname": "звичайний DNS (поверх TCP, з назвою вузла);",
     "all_lists_up_to_date_toast": "Всі списки вже оновлені",
@@ -351,7 +351,7 @@
     "install_devices_android_list_5": "Змініть встановлені значення DNS 1 і DNS 2 на адреси вашого домашнього сервера AdGuard.",
     "install_devices_ios_list_1": "На головному екрані торкніться Налаштування.",
     "install_devices_ios_list_2": "Виберіть Wi-Fi у меню ліворуч (неможливо налаштувати DNS для мобільних мереж).",
-    "install_devices_ios_list_3": "Натисніть на назву поточно активної мережі.",
+    "install_devices_ios_list_3": "Натисніть на назву поточної активної мережі.",
     "install_devices_ios_list_4": "У полі DNS введіть адреси вашого сервера AdGuard Home.",
     "get_started": "Розпочати",
     "next": "Наступні",
@@ -372,7 +372,7 @@
     "encryption_doq": "Порт DNS-over-QUIC (експериментальний)",
     "encryption_doq_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-QUIC. Це експериментально і може бути ненадійним. Крім того, зараз не так багато клієнтів, які це підтримують.",
     "encryption_certificates": "Сертифікати",
-    "encryption_certificates_desc": "Для використання шифрування потрібно надати дійсний ланцюжок сертифікатів SSL для вашого домену. Ви можете отримати безкоштовний сертифікат на <0>{{link}}</0> або придбати його в одному з надійних Центрів Сертифікації.",
+    "encryption_certificates_desc": "Для використання шифрування потрібно надати дійсний ланцюжок сертифікатів SSL для вашого домену. Ви можете отримати безплатний сертифікат на <0>{{link}}</0> або придбати його в одному з надійних Центрів Сертифікації.",
     "encryption_certificates_input": "Скопіюйте/вставте сюди свої кодовані PEM сертифікати.",
     "encryption_status": "Статус",
     "encryption_expire": "Закінчується",
@@ -552,16 +552,16 @@
     "fastest_addr": "Найшвидша IP-адреса",
     "fastest_addr_desc": "Опитати всі DNS-сервери й повернути найшвидшу IP-адресу серед усіх наданих. Це сповільнить швидкість DNS-запитів, оскільки AdGuard Home повинен буде чекати відповіді усіх DNS-серверів, але водночас може покращити якість з'єднання.",
     "autofix_warning_text": "Якщо ви натиснете «Виправити», AdGuard Home налаштує вашу систему на використання DNS-сервера AdGuard Home.",
-    "autofix_warning_list": "Це виконає наступні завдання: <0>Деактивує систему DNSStubListener</0> <0>Змінить адресу DNS сервера на 127.0.0.1</0> <0>Замінить символічне посилання /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Зупинить DNSStubListener (перезапустить сервіс systemd-resolved)</0>",
+    "autofix_warning_list": "Будуть виконані такі дії: <0>Деактивація системи DNSStubListener</0> <0>Зміна адреси DNS-сервера на «127.0.0.1»</0> <0>Заміна символічного посилання «/etc/resolv.conf» на «/run/systemd/resolve/resolv.conf»</0> <0>Зупинка DNSStubListener (перезапуск системної служби systemd-resolved)</0>",
     "autofix_warning_result": "В результаті буде усталено, що усі DNS-запити вашої системи будуть опрацьовані AdGuard Home.",
     "tags_title": "Теги",
     "tags_desc": "Ви можете вибрати теги, які відповідають клієнту. Теги можна використати в правилах фільтрування, щоб точніше застосовувати їх. <0>Докладніше</0>.",
     "form_select_tags": "Виберіть теги клієнта",
-    "check_title": "Перевірте фільтрування",
+    "check_title": "Перевірити фільтрування",
     "check_desc": "Перевірити чи фільтрується назва вузла.",
     "check": "Перевірити",
     "form_enter_host": "Введіть назву вузла",
-    "filtered_custom_rules": "Відфільтровано за власними правилами фільтрування",
+    "filtered_custom_rules": "Відфільтровано завдяки власним правилам фільтрування",
     "choose_from_list": "Виберіть зі списку",
     "add_custom_list": "Додати власний список",
     "host_whitelisted": "Вузол додано до списку дозволів",
@@ -585,14 +585,14 @@
     "list_updated": "{{count}} список оновлено",
     "list_updated_plural": "{{count}} списки оновлено",
     "dnssec_enable": "Увімкнути DNSSEC",
-    "dnssec_enable_desc": "Встановити прапорець DNSSEC для вихідних DNS запитів та перевірити результат (потрібен розпізнавач з підтримкою DNSSEC).",
+    "dnssec_enable_desc": "Увімкнути DNSSEC для вихідних DNS-запитів та перевірити результат (потрібен вирішувач з підтримкою DNSSEC).",
     "validated_with_dnssec": "Засвідчено DNSSEC",
     "all_queries": "Усі запити",
     "show_blocked_responses": "Заблоковані",
     "show_whitelisted_responses": "Дозволені",
     "show_processed_responses": "Оброблені",
     "blocked_safebrowsing": "Заблоковано Безпечним переглядом",
-    "blocked_adult_websites": "Заблоковано Батьківським контролем",
+    "blocked_adult_websites": "Заблоковано «Батьківським контролем»",
     "blocked_threats": "Заблоковано загроз",
     "allowed": "Дозволено",
     "filtered": "Відфільтровано",
diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json
index 4640432f..8903052f 100644
--- a/client/src/__locales/zh-cn.json
+++ b/client/src/__locales/zh-cn.json
@@ -351,6 +351,7 @@
     "install_devices_android_list_5": "将 DNS 1 和 DNS 2 的值改为您的 AdGuard Home 服务器地址。",
     "install_devices_ios_list_1": "从主屏幕中点击「设置」。",
     "install_devices_ios_list_2": "从左侧目录中选择「无线局域网」(移动数据网络环境下不支持修改 DNS )。",
+    "install_devices_ios_list_3": "点击当前已连接网络的名称。",
     "install_devices_ios_list_4": "在 DNS 字段中输入您的 AdGuard Home 服务器地址。",
     "get_started": "开始配置",
     "next": "下一步",

From c3d5fcc6692bbbf7271c5ee44dac7d984fa7cd80 Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu, 26 May 2022 16:21:59 +0300
Subject: [PATCH 066/143] Pull request: locales: DoQ status

Merge in DNS/adguard-home from 4592-doq-status to master

Squashed commit of the following:

commit 96bc041b736a45711419160aebb79296f13ff84d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 26 14:21:10 2022 +0200

    all: locales
---
 client/src/__locales/en.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index f39d2f51..a5c21109 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "regular DNS (over UDP, hostname);",
     "example_upstream_dot": "encrypted <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "encrypted <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "encrypted <0>DNS-over-QUIC</0> (experimental);",
+    "example_upstream_doq": "encrypted <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers;",
     "example_upstream_tcp": "regular DNS (over TCP);",
     "example_upstream_tcp_hostname": "regular DNS (over TCP, hostname);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '/dns-query' location.",
     "encryption_dot": "DNS-over-TLS port",
     "encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.",
-    "encryption_doq": "DNS-over-QUIC port (experimental)",
-    "encryption_doq_desc": "If this port is configured, AdGuard Home will run a DNS-over-QUIC server on this port. It's experimental and may not be reliable. Also, there are not too many clients that support it at the moment.",
+    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq_desc": "If this port is configured, AdGuard Home will run a DNS-over-QUIC server on this port.",
     "encryption_certificates": "Certificates",
     "encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}</0> or you can buy it from one of the trusted Certificate Authorities.",
     "encryption_certificates_input": "Copy/paste your PEM-encoded certificates here.",

From 756c932e372e36b5a6814d73fa6dac6c8799c9a1 Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu, 26 May 2022 17:53:11 +0300
Subject: [PATCH 067/143] Pull request: dnsforward: add doq alpn

Merge in DNS/adguard-home from 4592-doq-alpn to master

Squashed commit of the following:

commit 5985445dbf5158ae1e5b0235b404dd188c856e60
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 26 16:42:06 2022 +0200

    dnsforward: add doq alpn

commit 9dcd6fee615a1a5ac1f80641ac16c18371b67096
Merge: 2564c870 c3d5fcc6
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 26 15:24:07 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4592-doq-alpn

commit 2564c870e704ff453d0ad2fb22fa295ef725dd13
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 26 15:20:16 2022 +0200

    dnsforward: add doq alpn
---
 internal/dnsforward/dns.go      | 21 ++++++++++++++++++---
 internal/dnsforward/dns_test.go | 26 ++++++++++++++++++++++++--
 2 files changed, 42 insertions(+), 5 deletions(-)

diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go
index 19d54d91..e049bef0 100644
--- a/internal/dnsforward/dns.go
+++ b/internal/dnsforward/dns.go
@@ -260,9 +260,8 @@ func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
 	}
 
 	if question.Name == ddrHostFQDN {
-		// TODO(a.garipov): Check DoQ support in next RFC drafts.
-		if s.dnsProxy.TLSListenAddr == nil && s.dnsProxy.HTTPSListenAddr == nil ||
-			question.Qtype != dns.TypeSVCB {
+		if s.dnsProxy.TLSListenAddr == nil && s.dnsProxy.HTTPSListenAddr == nil &&
+			s.dnsProxy.QUICListenAddr == nil || question.Qtype != dns.TypeSVCB {
 			d.Res = s.makeResponse(d.Req)
 
 			return resultCodeFinish
@@ -314,6 +313,22 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
 		resp.Answer = append(resp.Answer, ans)
 	}
 
+	for _, addr := range s.dnsProxy.QUICListenAddr {
+		values := []dns.SVCBKeyValue{
+			&dns.SVCBAlpn{Alpn: []string{"doq"}},
+			&dns.SVCBPort{Port: uint16(addr.Port)},
+		}
+
+		ans := &dns.SVCB{
+			Hdr:      s.hdr(req, dns.TypeSVCB),
+			Priority: 3,
+			Target:   domainName,
+			Value:    values,
+		}
+
+		resp.Answer = append(resp.Answer, ans)
+	}
+
 	return resp
 }
 
diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go
index 8ab7501c..b40b7bc2 100644
--- a/internal/dnsforward/dns_test.go
+++ b/internal/dnsforward/dns_test.go
@@ -36,6 +36,15 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
 		},
 	}
 
+	doqSVCB := &dns.SVCB{
+		Priority: 3,
+		Target:   ddrTestDomainName,
+		Value: []dns.SVCBKeyValue{
+			&dns.SVCBAlpn{Alpn: []string{"doq"}},
+			&dns.SVCBPort{Port: 8042},
+		},
+	}
+
 	testCases := []struct {
 		name       string
 		host       string
@@ -43,6 +52,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
 		wantRes    resultCode
 		portDoH    int
 		portDoT    int
+		portDoQ    int
 		qtype      uint16
 		ddrEnabled bool
 	}{{
@@ -88,6 +98,14 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
 		qtype:      dns.TypeSVCB,
 		ddrEnabled: true,
 		portDoH:    8044,
+	}, {
+		name:       "doq",
+		wantRes:    resultCodeFinish,
+		want:       []*dns.SVCB{doqSVCB},
+		host:       ddrHostFQDN,
+		qtype:      dns.TypeSVCB,
+		ddrEnabled: true,
+		portDoQ:    8042,
 	}, {
 		name:       "dot_doh",
 		wantRes:    resultCodeFinish,
@@ -101,7 +119,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			s := prepareTestServer(t, tc.portDoH, tc.portDoT, tc.ddrEnabled)
+			s := prepareTestServer(t, tc.portDoH, tc.portDoT, tc.portDoQ, tc.ddrEnabled)
 
 			req := createTestMessageWithType(tc.host, tc.qtype)
 
@@ -130,7 +148,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
 	}
 }
 
-func prepareTestServer(t *testing.T, portDoH, portDoT int, ddrEnabled bool) (s *Server) {
+func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled bool) (s *Server) {
 	t.Helper()
 
 	proxyConf := proxy.Config{}
@@ -143,6 +161,10 @@ func prepareTestServer(t *testing.T, portDoH, portDoT int, ddrEnabled bool) (s *
 		proxyConf.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}}
 	}
 
+	if portDoQ > 0 {
+		proxyConf.QUICListenAddr = []*net.UDPAddr{{Port: portDoQ}}
+	}
+
 	s = &Server{
 		dnsProxy: &proxy.Proxy{
 			Config: proxyConf,

From 7ce7e908654579789f23ea5302416b67885494ae Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 30 May 2022 16:04:28 +0300
Subject: [PATCH 068/143] Pull request: dnsforward: fix ddr target

Updates #4463.

Squashed commit of the following:

commit 047155b585a1c762d709874f44abb2d8c5a9dbca
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon May 30 15:34:38 2022 +0300

    dnsforward: imp code

commit b0508ffec13ccf5fc5d3d2e37c9e1bd83c3c039e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon May 30 15:27:02 2022 +0300

    dnsforward: fix ddr target
---
 internal/dnsforward/dns.go      |  4 +++-
 internal/dnsforward/dns_test.go | 11 +++++++----
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go
index e049bef0..2865bc84 100644
--- a/internal/dnsforward/dns.go
+++ b/internal/dnsforward/dns.go
@@ -278,7 +278,9 @@ func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
 // makeDDRResponse creates DDR answer according to server configuration.
 func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
 	resp = s.makeResponse(req)
-	domainName := s.conf.ServerName
+	// TODO(e.burkov):  Think about stroing the FQDN version of the server's
+	// name somewhere.
+	domainName := dns.Fqdn(s.conf.ServerName)
 
 	for _, addr := range s.dnsProxy.HTTPSListenAddr {
 		values := []dns.SVCBKeyValue{
diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go
index b40b7bc2..129e3c2f 100644
--- a/internal/dnsforward/dns_test.go
+++ b/internal/dnsforward/dns_test.go
@@ -14,12 +14,15 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-const ddrTestDomainName = "dns.example.net"
+const (
+	ddrTestDomainName = "dns.example.net"
+	ddrTestFQDN       = ddrTestDomainName + "."
+)
 
 func TestServer_ProcessDDRQuery(t *testing.T) {
 	dohSVCB := &dns.SVCB{
 		Priority: 1,
-		Target:   ddrTestDomainName,
+		Target:   ddrTestFQDN,
 		Value: []dns.SVCBKeyValue{
 			&dns.SVCBAlpn{Alpn: []string{"h2"}},
 			&dns.SVCBPort{Port: 8044},
@@ -29,7 +32,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
 
 	dotSVCB := &dns.SVCB{
 		Priority: 2,
-		Target:   ddrTestDomainName,
+		Target:   ddrTestFQDN,
 		Value: []dns.SVCBKeyValue{
 			&dns.SVCBAlpn{Alpn: []string{"dot"}},
 			&dns.SVCBPort{Port: 8043},
@@ -38,7 +41,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
 
 	doqSVCB := &dns.SVCB{
 		Priority: 3,
-		Target:   ddrTestDomainName,
+		Target:   ddrTestFQDN,
 		Value: []dns.SVCBKeyValue{
 			&dns.SVCBAlpn{Alpn: []string{"doq"}},
 			&dns.SVCBPort{Port: 8042},

From 4b884ace622f86fa2b77efb7c5ade513ff5ae3b5 Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Tue, 31 May 2022 17:28:50 +0300
Subject: [PATCH 069/143] Pull request: all: fix doh ddr

Merge in DNS/adguard-home from fix-ddr-doh to master

Squashed commit of the following:

commit 53d3147b22044061d78b3bf4badca60505ac245a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 31 15:02:17 2022 +0200

    all: fix doh ddr
---
 internal/dnsforward/config.go   | 5 +++--
 internal/dnsforward/dns.go      | 6 +++---
 internal/dnsforward/dns_test.go | 8 ++++----
 internal/home/dns.go            | 4 ++++
 4 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go
index 16a6325e..f9234155 100644
--- a/internal/dnsforward/config.go
+++ b/internal/dnsforward/config.go
@@ -134,8 +134,9 @@ type FilteringConfig struct {
 
 // TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
 type TLSConfig struct {
-	TLSListenAddrs  []*net.TCPAddr `yaml:"-" json:"-"`
-	QUICListenAddrs []*net.UDPAddr `yaml:"-" json:"-"`
+	TLSListenAddrs   []*net.TCPAddr `yaml:"-" json:"-"`
+	QUICListenAddrs  []*net.UDPAddr `yaml:"-" json:"-"`
+	HTTPSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
 
 	// Reject connection if the client uses server name (in SNI) that doesn't match the certificate
 	StrictSNICheck bool `yaml:"strict_sni_check" json:"-"`
diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go
index 2865bc84..55a38a2f 100644
--- a/internal/dnsforward/dns.go
+++ b/internal/dnsforward/dns.go
@@ -260,7 +260,7 @@ func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
 	}
 
 	if question.Name == ddrHostFQDN {
-		if s.dnsProxy.TLSListenAddr == nil && s.dnsProxy.HTTPSListenAddr == nil &&
+		if s.dnsProxy.TLSListenAddr == nil && s.conf.HTTPSListenAddrs == nil &&
 			s.dnsProxy.QUICListenAddr == nil || question.Qtype != dns.TypeSVCB {
 			d.Res = s.makeResponse(d.Req)
 
@@ -278,11 +278,11 @@ func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
 // makeDDRResponse creates DDR answer according to server configuration.
 func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
 	resp = s.makeResponse(req)
-	// TODO(e.burkov):  Think about stroing the FQDN version of the server's
+	// TODO(e.burkov):  Think about storing the FQDN version of the server's
 	// name somewhere.
 	domainName := dns.Fqdn(s.conf.ServerName)
 
-	for _, addr := range s.dnsProxy.HTTPSListenAddr {
+	for _, addr := range s.conf.HTTPSListenAddrs {
 		values := []dns.SVCBKeyValue{
 			&dns.SVCBAlpn{Alpn: []string{"h2"}},
 			&dns.SVCBPort{Port: uint16(addr.Port)},
diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go
index 129e3c2f..b9c7e47b 100644
--- a/internal/dnsforward/dns_test.go
+++ b/internal/dnsforward/dns_test.go
@@ -156,10 +156,6 @@ func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled b
 
 	proxyConf := proxy.Config{}
 
-	if portDoH > 0 {
-		proxyConf.HTTPSListenAddr = []*net.TCPAddr{{Port: portDoH}}
-	}
-
 	if portDoT > 0 {
 		proxyConf.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}}
 	}
@@ -182,6 +178,10 @@ func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled b
 		},
 	}
 
+	if portDoH > 0 {
+		s.conf.TLSConfig.HTTPSListenAddrs = []*net.TCPAddr{{Port: portDoH}}
+	}
+
 	return s
 }
 
diff --git a/internal/home/dns.go b/internal/home/dns.go
index 9eabfefa..d51a6dd2 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -221,6 +221,10 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
 		newConf.TLSConfig = tlsConf.TLSConfig
 		newConf.TLSConfig.ServerName = tlsConf.ServerName
 
+		if tlsConf.PortHTTPS != 0 {
+			newConf.HTTPSListenAddrs = ipsToTCPAddrs(hosts, tlsConf.PortHTTPS)
+		}
+
 		if tlsConf.PortDNSOverTLS != 0 {
 			newConf.TLSListenAddrs = ipsToTCPAddrs(hosts, tlsConf.PortDNSOverTLS)
 		}

From f46c9f74d5b03f8caa60d8e648bbc46fe306714d Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 2 Jun 2022 15:57:06 +0300
Subject: [PATCH 070/143] Pull request: all: upd go

Merge in DNS/adguard-home from upd-go to master

Squashed commit of the following:

commit b0bec8926508fb7ee3f26c2303d9628e21f0b62f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 2 15:47:51 2022 +0300

    all: upd go
---
 CHANGELOG.md              | 15 +++++++++++----
 bamboo-specs/release.yaml |  6 +++---
 bamboo-specs/test.yaml    |  2 +-
 3 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a72447c..3bdd37ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,9 @@ and this project adheres to
 
 ### Security
 
+- Go version was updated to prevent the possibility of exploiting the
+  [CVE-2022-29526], [CVE-2022-30634], [CVE-2022-30629], [CVE-2022-30580], and
+  [CVE-2022-29804] vulnerabilities.
 - Enforced password strength policy ([#3503]).
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
@@ -161,10 +164,14 @@ In this release, the schema version has changed from 12 to 14.
 [#4591]: https://github.com/AdguardTeam/AdGuardHome/issues/4591
 [#4592]: https://github.com/AdguardTeam/AdGuardHome/issues/4592
 
-[rfc-9250]:     https://datatracker.ietf.org/doc/html/rfc9250
-[ddr-draft-06]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
-[repr]:         https://reproducible-builds.org/docs/source-date-epoch/
-
+[CVE-2022-29526]: https://www.cvedetails.com/cve/CVE-2022-29526
+[CVE-2022-29804]: https://www.cvedetails.com/cve/CVE-2022-29804
+[CVE-2022-30580]: https://www.cvedetails.com/cve/CVE-2022-30580
+[CVE-2022-30629]: https://www.cvedetails.com/cve/CVE-2022-30629
+[CVE-2022-30634]: https://www.cvedetails.com/cve/CVE-2022-30634
+[ddr-draft-06]:   https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
+[repr]:           https://reproducible-builds.org/docs/source-date-epoch/
+[rfc-9250]:       https://datatracker.ietf.org/doc/html/rfc9250
 
 
 <!--
diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index d0513f0f..694430ac 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -7,7 +7,7 @@
 # Make sure to sync any changes with the branch overrides below.
 'variables':
   'channel': 'edge'
-  'dockerGo': 'adguard/golang-ubuntu:4.3'
+  'dockerGo': 'adguard/golang-ubuntu:4.4'
 
 'stages':
 - 'Make release':
@@ -285,7 +285,7 @@
     # need to build a few of these.
     'variables':
       'channel': 'beta'
-      'dockerGo': 'adguard/golang-ubuntu:4.3'
+      'dockerGo': 'adguard/golang-ubuntu:4.4'
 # release-vX.Y.Z branches are the branches from which the actual final release
 # is built.
 - '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -300,4 +300,4 @@
     # are the ones that actually get released.
     'variables':
       'channel': 'release'
-      'dockerGo': 'adguard/golang-ubuntu:4.3'
+      'dockerGo': 'adguard/golang-ubuntu:4.4'
diff --git a/bamboo-specs/test.yaml b/bamboo-specs/test.yaml
index e973b77c..cf11aa2e 100644
--- a/bamboo-specs/test.yaml
+++ b/bamboo-specs/test.yaml
@@ -5,7 +5,7 @@
   'key': 'AHBRTSPECS'
   'name': 'AdGuard Home - Build and run tests'
 'variables':
-  'dockerGo': 'adguard/golang-ubuntu:4.3'
+  'dockerGo': 'adguard/golang-ubuntu:4.4'
 
 'stages':
 - 'Tests':

From cbe32c5a73952c33f40579b97a7c8177424e822e Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Fri, 3 Jun 2022 15:53:21 +0300
Subject: [PATCH 071/143] Pull request: all: replace uuid pkg; upd deps

Merge in DNS/adguard-home from 4622-upd-deps to master

Squashed commit of the following:

commit 36f407d8ab103da0f7eacdf91c153c23a5b7c3f2
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 3 15:22:47 2022 +0300

    home: imp mobileconfig uuid gen

commit dddd162461a4830f7c0636338430cd6e77199214
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 3 13:54:29 2022 +0300

    all: replace uuid pkg; upd deps
---
 go.mod                        |  8 ++------
 go.sum                        | 13 +++++++++----
 internal/home/mobileconfig.go | 26 +++++++++++---------------
 3 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/go.mod b/go.mod
index d3c3e1de..a4e82f61 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ require (
 	github.com/google/go-cmp v0.5.7
 	github.com/google/gopacket v1.1.19
 	github.com/google/renameio v1.0.1
+	github.com/google/uuid v1.3.0
 	github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41
 	github.com/kardianos/service v1.2.1
 	github.com/lucas-clemente/quic-go v0.27.1
@@ -23,8 +24,7 @@ require (
 	// TODO(a.garipov): This package is deprecated; find a new one or use
 	// our own code for that.
 	github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b
-	github.com/miekg/dns v1.1.48
-	github.com/satori/go.uuid v1.2.0
+	github.com/miekg/dns v1.1.49
 	github.com/stretchr/testify v1.7.0
 	github.com/ti-mo/netfilter v0.4.0
 	go.etcd.io/bbolt v1.3.6
@@ -45,7 +45,6 @@ require (
 	github.com/cheekybits/genny v1.0.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
-	github.com/google/uuid v1.3.0 // indirect
 	github.com/josharian/native v1.0.0 // indirect
 	github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
 	github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
@@ -66,6 +65,3 @@ require (
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
-
-// TODO(a.garipov): Return to the main repo once miekg/dns#1359 is merged.
-replace github.com/miekg/dns => github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8
diff --git a/go.sum b/go.sum
index ae65fdab..8a700ddd 100644
--- a/go.sum
+++ b/go.sum
@@ -28,8 +28,6 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
 github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
 github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
 github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
-github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8 h1:Hp2waLwK989ui3bDkFpedlIHfyWdZ77gynvd+GPEqXY=
-github.com/ainar-g/dns v1.1.49-0.20220411125901-8a162bbc18d8/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 github.com/ameshkov/dnscrypt/v2 v2.2.3 h1:X9UP5AHtwp46Ji+sGFfF/1Is6OPI/SjxLqhKpx0P5UI=
 github.com/ameshkov/dnscrypt/v2 v2.2.3/go.mod h1:xJB9cE1/GF+NB6EEQqRlkoa4bjcV2w7VYn1G+zVq7Bs=
 github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
@@ -176,6 +174,11 @@ github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL
 github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
 github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
 github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
+github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
+github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
+github.com/miekg/dns v1.1.44/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
+github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
+github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
@@ -209,8 +212,6 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:
 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
-github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
 github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
@@ -307,6 +308,7 @@ 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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -356,6 +358,7 @@ golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -372,6 +375,7 @@ golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -410,6 +414,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
diff --git a/internal/home/mobileconfig.go b/internal/home/mobileconfig.go
index 82d0dbf1..40094a6a 100644
--- a/internal/home/mobileconfig.go
+++ b/internal/home/mobileconfig.go
@@ -11,7 +11,7 @@ import (
 	"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
-	uuid "github.com/satori/go.uuid"
+	"github.com/google/uuid"
 	"howett.net/plist"
 )
 
@@ -47,9 +47,9 @@ type payloadContent struct {
 
 	PayloadType        string
 	PayloadIdentifier  string
-	PayloadUUID        string
 	PayloadDisplayName string
 	PayloadDescription string
+	PayloadUUID        uuid.UUID
 	PayloadVersion     int
 }
 
@@ -63,18 +63,14 @@ const dnsSettingsPayloadType = "com.apple.dnsSettings.managed"
 type mobileConfig struct {
 	PayloadDescription       string
 	PayloadDisplayName       string
-	PayloadIdentifier        string
 	PayloadType              string
-	PayloadUUID              string
 	PayloadContent           []*payloadContent
+	PayloadIdentifier        uuid.UUID
+	PayloadUUID              uuid.UUID
 	PayloadVersion           int
 	PayloadRemovalDisallowed bool
 }
 
-func genUUIDv4() string {
-	return uuid.NewV4().String()
-}
-
 const (
 	dnsProtoHTTPS = "HTTPS"
 	dnsProtoTLS   = "TLS"
@@ -104,23 +100,23 @@ func encodeMobileConfig(d *dnsSettings, clientID string) ([]byte, error) {
 		return nil, fmt.Errorf("bad dns protocol %q", proto)
 	}
 
-	payloadID := fmt.Sprintf("%s.%s", dnsSettingsPayloadType, genUUIDv4())
+	payloadID := fmt.Sprintf("%s.%s", dnsSettingsPayloadType, uuid.New())
 	data := &mobileConfig{
-		PayloadDescription: "Adds AdGuard Home to macOS Big Sur " +
-			"and iOS 14 or newer systems",
+		PayloadDescription: "Adds AdGuard Home to macOS Big Sur and iOS 14 or newer systems",
 		PayloadDisplayName: dspName,
-		PayloadIdentifier:  genUUIDv4(),
 		PayloadType:        "Configuration",
-		PayloadUUID:        genUUIDv4(),
 		PayloadContent: []*payloadContent{{
+			DNSSettings: d,
+
 			PayloadType:        dnsSettingsPayloadType,
 			PayloadIdentifier:  payloadID,
-			PayloadUUID:        genUUIDv4(),
 			PayloadDisplayName: dspName,
 			PayloadDescription: "Configures device to use AdGuard Home",
+			PayloadUUID:        uuid.New(),
 			PayloadVersion:     1,
-			DNSSettings:        d,
 		}},
+		PayloadIdentifier:        uuid.New(),
+		PayloadUUID:              uuid.New(),
 		PayloadVersion:           1,
 		PayloadRemovalDisallowed: false,
 	}

From 368a98fb296da349ed7fb99b03c4ed6b7d0dda3a Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Fri, 3 Jun 2022 16:39:36 +0300
Subject: [PATCH 072/143] Pull request: client: upd i18n

Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 3feadfe31609ef52726b582ad6ba18bfa435a081
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 3 16:34:36 2022 +0300

    client: upd i18n
---
 client/src/__locales/be.json    | 6 +++---
 client/src/__locales/cs.json    | 6 +++---
 client/src/__locales/da.json    | 6 +++---
 client/src/__locales/de.json    | 6 +++---
 client/src/__locales/es.json    | 6 +++---
 client/src/__locales/fi.json    | 6 +++---
 client/src/__locales/fr.json    | 6 +++---
 client/src/__locales/hr.json    | 4 ++--
 client/src/__locales/hu.json    | 8 ++++----
 client/src/__locales/id.json    | 6 +++---
 client/src/__locales/it.json    | 6 +++---
 client/src/__locales/ja.json    | 6 +++---
 client/src/__locales/ko.json    | 6 +++---
 client/src/__locales/nl.json    | 6 +++---
 client/src/__locales/no.json    | 6 +++---
 client/src/__locales/pl.json    | 6 +++---
 client/src/__locales/pt-br.json | 6 +++---
 client/src/__locales/pt-pt.json | 6 +++---
 client/src/__locales/ro.json    | 6 +++---
 client/src/__locales/ru.json    | 6 +++---
 client/src/__locales/sk.json    | 6 +++---
 client/src/__locales/sl.json    | 6 +++---
 client/src/__locales/sr-cs.json | 6 +++---
 client/src/__locales/sv.json    | 6 +++---
 client/src/__locales/tr.json    | 6 +++---
 client/src/__locales/uk.json    | 6 +++---
 client/src/__locales/vi.json    | 6 +++---
 client/src/__locales/zh-cn.json | 6 +++---
 client/src/__locales/zh-tw.json | 6 +++---
 29 files changed, 87 insertions(+), 87 deletions(-)

diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json
index c01cbaa4..7231b448 100644
--- a/client/src/__locales/be.json
+++ b/client/src/__locales/be.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "звычайны DNS (праз UDP, імя хаста);",
     "example_upstream_dot": "зашыфраваны <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "зашыфраваны <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "зашыфраваны <0>DNS-over-QUIC (эксперыментальны)</0>;",
+    "example_upstream_doq": "зашыфраваны <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt</1> ці <2>DNS-over-HTTPS</2> рэзалвераў;",
     "example_upstream_tcp": "звычайны DNS (наўзверх TCP);",
     "example_upstream_tcp_hostname": "звычайны DNS (праз TCP, імя хаста);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Калі порт HTTPS наладжаны, ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны праз HTTPS, а таксама DNS-over-HTTPS сервер будзе даступны па шляху '/dns-query'.",
     "encryption_dot": "Порт DNS-over-TLS",
     "encryption_dot_desc": "Калі гэты порт наладжаны, AdGuard Home запусціць DNS-over-TLS-сервер на гэтаму порту.",
-    "encryption_doq": "Порт DNS-over-QUIC (эксперыментальны)",
-    "encryption_doq_desc": "Калі гэты порт наладжаны, AdGuard Home запусціць сервер DNS-over-QUIC на гэтым порце. Гэта эксперыментальна і можа быць ненадзейна. Апроч таго, не так шмат кліентаў падтрымвае гэты спосаб цяпер.",
+    "encryption_doq": "Порт DNS-over-QUIC",
+    "encryption_doq_desc": "Калі гэты порт наладжаны, AdGuard Home запусціць сервер DNS-over-QUIC на гэтым порце.",
     "encryption_certificates": "Сертыфікаты",
     "encryption_certificates_desc": "Для выкарыстання шыфравання вам трэба падаць валідны ланцужок SSL-сертыфікатаў для вашага дамена. Вы можаце атрымаць дармовы сертыфікат на <0>{{link}}</0> ці вы можаце купіць яго ў аднаго з давераных Цэнтраў Сертыфікацыі.",
     "encryption_certificates_input": "Скапіюйце сюды сертыфікаты ў PEM-кадоўцы.",
diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json
index 8467df91..32443267 100644
--- a/client/src/__locales/cs.json
+++ b/client/src/__locales/cs.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "obvyklý DNS (skrze UDP, název hostitele);",
     "example_upstream_dot": "šifrovaný <0>DNS skrze TLS</0>;",
     "example_upstream_doh": "šifrovaný <0>DNS skrze HTTPS</0>;",
-    "example_upstream_doq": "šifrovaný <0>DNS skrze QUIC</0> (experimentální);",
+    "example_upstream_doq": "šifrovaný <0>DNS skrze QUIC</0>;",
     "example_upstream_sdns": "<0>DNS razítka</0> pro <1>DNSCrypt</1> nebo <2>DNS skrze HTTPS</2> řešitele;",
     "example_upstream_tcp": "obvyklý DNS (přes TCP);",
     "example_upstream_tcp_hostname": "obvyklý DNS (skrze TCP, název hostitele);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Pokud je nakonfigurován port HTTPS, AdGuard Home administrátorské rozhraní bude přístupné přes HTTPS a bude také poskytovat DNS skrze HTTPS na '/dns-query'.",
     "encryption_dot": "DNS skrze TLS port",
     "encryption_dot_desc": "Pokud je tento port nakonfigurován, AdGuard Home bude na tomto portu spouštět DNS skrze TLS server.",
-    "encryption_doq": "Port DNS skrze QUIC (experimentální)",
-    "encryption_doq_desc": "Pokud je tento port nakonfigurován, AdGuard Home spustí na tomto portu server DNS skrze QUIC. Je to experimentální a nemusí být spolehlivé. V současnosti také není příliš mnoho klientů, kteří to podporují.",
+    "encryption_doq": "Port DNS skrze QUIC",
+    "encryption_doq_desc": "Pokud je tento port nakonfigurován, AdGuard Home bude na tomto portu spouštět DNS skrze QUIC server.",
     "encryption_certificates": "Certifikáty",
     "encryption_certificates_desc": "Chcete-li používat šifrování, musíte pro svou doménu poskytnout platný řetězec certifikátů SSL. Certifikát můžete získat bezplatně na adrese <0>{{link}}</ 0>, nebo jej můžete zakoupit od jednoho z důvěryhodných certifikačních úřadů.",
     "encryption_certificates_input": "Zde můžete nakopírovat/vložit certifikáty PEM.",
diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json
index 4b077cf8..721870f7 100644
--- a/client/src/__locales/da.json
+++ b/client/src/__locales/da.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "almindelig DNS (over UDP, værtsnavn);",
     "example_upstream_dot": "krypteret <0>DNS-over-TLS</0>",
     "example_upstream_doh": "krypteret <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "krypteret <0>DNS-over-QUIC</0>(eksperimentel);",
+    "example_upstream_doq": "krypteret <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS Stamps</0> til <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-opløsere;",
     "example_upstream_tcp": "almindelig DNS (over TCP)",
     "example_upstream_tcp_hostname": "almindelig DNS (over TCP, værtsnavn);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Er HTTPS-porten opsat, vil AdGuard Home admin grænsefladen være tilgængelig via HTTPS, og den vil muliggøre DNS-over-HTTPS på '/dns-query' placeringen.",
     "encryption_dot": "DNS-over-TLS port",
     "encryption_dot_desc": "Er denne port opsat, vil AdGuard Home køre en DNS-over-TLS server på denne port.",
-    "encryption_doq": "DNS-over-QUIC port (eksperimentel)",
-    "encryption_doq_desc": "Er denne port opsat, vil AdGuard Home køre en DNS-over-QUIC server på denne port. Den er eksperimentel og er måske ikke pålidelig. Derudover understøttes den pt. heller ikke af ret mange klienter.",
+    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq_desc": "Er denne port opsat, vil AdGuard Home køre en DNS-over-QUIC server på denne port. ",
     "encryption_certificates": "Certifikater",
     "encryption_certificates_desc": "For at kunne bruge kryptering skal du angive en gyldig SSL-certifikatkæde til dit domæne. Du kan få et gratis certifikat via <0>{{link}}</ 0>, eller du kan købe det via en af de betroede Certifikatmyndigheder.",
     "encryption_certificates_input": "Kopiér/indsæt dine PEM-kodede certifikater hér.",
diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json
index 0fe6345d..5241e4a6 100644
--- a/client/src/__locales/de.json
+++ b/client/src/__locales/de.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "normales DNS (über UDP, Hostname);",
     "example_upstream_dot": "verschlüsseltes <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "verschlüsseltes <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "verschlüsseltes <0>DNS-over-QUIC</0> (experimentell);",
+    "example_upstream_doq": "verschlüsseltes <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS-Stempel</0> für <1>DNSCrypt</1> oder <2>DNS-over-HTTPS</2> Resolver;",
     "example_upstream_tcp": "reguläres DNS (over TCP);",
     "example_upstream_tcp_hostname": "normales DNS (über TCP, Hostname);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Wenn der HTTPS-Port konfiguriert ist, ist die AdGuard Home-Administrationsschnittstelle über HTTPS zugänglich und bietet auch DNS-over-HTTPS am Server „/dns-query“.",
     "encryption_dot": "DNS-over-TLS",
     "encryption_dot_desc": "Wenn dieser Port konfiguriert ist, führt AdGuard Home auf diesem Port einen DNS-over-TLS-Server aus.",
-    "encryption_doq": "Port für DNS-over-QUIC (experimentell)",
-    "encryption_doq_desc": "Wenn dieser Port eingerichtet ist, wird AdGuard Home einen DNS-over-QUIC-Server auf diesem Port ausführen. Es ist experimentell und möglicherweise nicht zuverlässig. Außerdem gibt es im Moment nicht allzu viele Clients, die ihn unterstützen.",
+    "encryption_doq": "Port für DNS-over-QUIC",
+    "encryption_doq_desc": "Wenn dieser Port eingerichtet ist, wird AdGuard Home einen DNS-over-QUIC-Server auf diesem Port ausführen. ",
     "encryption_certificates": "Zertifikate",
     "encryption_certificates_desc": "Um die Verschlüsselung verwenden zu können, müssen Sie eine gültige SSL-Zertifikatskette für Ihre Domain angeben. Sie können ein kostenloses Zertifikat für <0>{{link}}</0> erhalten oder es bei einer der vertrauenswürdigen Zertifizierungsstellen kaufen.",
     "encryption_certificates_input": "Kopieren Sie Ihre PEM-codierten Zertifikate und fügen Sie sie hier ein.",
diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json
index e495c81d..b08dc5e0 100644
--- a/client/src/__locales/es.json
+++ b/client/src/__locales/es.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "DNS regular (mediante UDP, nombre del host).",
     "example_upstream_dot": "cifrado <0>DNS mediante TLS</0>.",
     "example_upstream_doh": "cifrado <0>DNS mediante HTTPS</0>.",
-    "example_upstream_doq": "cifrado <0>DNS mediante QUIC</0> (experimental).",
+    "example_upstream_doq": "cifrado <0>DNS mediante QUIC</0>.",
     "example_upstream_sdns": "<0>DNS Stamps</0> para <1>DNSCrypt</1> o resolutores <2>DNS mediante HTTPS</2>.",
     "example_upstream_tcp": "DNS regular (mediante TCP).",
     "example_upstream_tcp_hostname": "DNS regular (mediante TCP, nombre del host).",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Si el puerto HTTPS está configurado, la interfaz de administración de AdGuard Home será accesible a través de HTTPS, y también proporcionará DNS mediante HTTPS en la ubicación '/dns-query'.",
     "encryption_dot": "Puerto DNS mediante TLS",
     "encryption_dot_desc": "Si este puerto está configurado, AdGuard Home ejecutará un servidor DNS mediante TLS en este puerto.",
-    "encryption_doq": "Puerto DNS mediante QUIC (experimental)",
-    "encryption_doq_desc": "Si este puerto está configurado, AdGuard Home ejecutará un servidor DNS mediante QUIC en este puerto. Es experimental y puede no ser confiable. Además, no hay muchos clientes que lo soporten por el momento.",
+    "encryption_doq": "Puerto DNS mediante QUIC",
+    "encryption_doq_desc": "Si este puerto está configurado, AdGuard Home ejecutará un servidor DNS mediante QUIC en este puerto.",
     "encryption_certificates": "Certificados",
     "encryption_certificates_desc": "Para utilizar el cifrado, debes proporcionar una cadena de certificado SSL válida para tu dominio. Puedes obtener un certificado gratuito en <0>{{link}}</0> o puedes comprarlo en una de las autoridades de certificación de confianza.",
     "encryption_certificates_input": "Copia/pega aquí tu certificado codificado PEM.",
diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json
index a5f3fa01..11baddb6 100644
--- a/client/src/__locales/fi.json
+++ b/client/src/__locales/fi.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "tavallinen DNS (UDP, isäntänimi);",
     "example_upstream_dot": "salattu <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "salattu <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "salattu <0>DNS-over-QUIC</0> (kokeellinen);",
+    "example_upstream_doq": "salattu <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS Stamp</0> -merkinnät <1>DNSCrypt</1> tai <2>DNS-over-HTTPS</2> -resolvereille;",
     "example_upstream_tcp": "tavallinen DNS (TCP:n välityksellä);",
     "example_upstream_tcp_hostname": "tavallinen DNS (TCP, isäntänimi);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Jos HTTPS-portti on määritetty, on AdGuard Homen hallintapaneeli käytettävissä HTTPS-yhteydellä ja lisäksi tämä mahdollistaa myös DNS-over-HTTPS -yhteyden '/dns-query' -kohteessa.",
     "encryption_dot": "DNS-over-TLS -portti",
     "encryption_dot_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-TLS -palvelimen tässä portissa.",
-    "encryption_doq": "DNS-over-QUIC -portti (kokeellinen)",
-    "encryption_doq_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-QUIC -palvelimen tässä portissa. Ominaisuus on kokeellinen, eikä välttämättä luotettava. Lisäksi tätä tukevia päätelaitteita ei vielä ole kovin paljon.",
+    "encryption_doq": "DNS-over-QUIC-portti",
+    "encryption_doq_desc": "Jos portti on määritetty, AdGuard Home suorittaa DNS-over-QUIC-palvelimen tässä portissa.",
     "encryption_certificates": "Varmenteet",
     "encryption_certificates_desc": "Salauksen käyttämiseksi, on syötettävä verkkotunnuksellesi myönnetty, aito SSL-varmenneketju. Voit hankkia ilmaisen varmenteen osoitteesta <0>{{link}}</0> tai ostaa sellaisen joltakin luotetulta varmentajalta.",
     "encryption_certificates_input": "Kopioi/liitä PEM-koodatut varmenteesi tähän.",
diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json
index afbe91cd..fe0a451b 100644
--- a/client/src/__locales/fr.json
+++ b/client/src/__locales/fr.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "DNS normal (sur UDP, nom d’hôte) ;",
     "example_upstream_dot": "<0>DNS-over-TLS</0> chiffré ;",
     "example_upstream_doh": "<0>DNS-over-HTTPS</0> chiffré ;",
-    "example_upstream_doq": "<0>DNS-over-QUIC</0> chiffré (expérimental) ;",
+    "example_upstream_doq": "<0>DNS-over-QUIC</0> chiffré;",
     "example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps</0> pour <1>DNSCrypt</1> ou les résolveurs <2>DNS_over_HTTPS</2> ;",
     "example_upstream_tcp": "DNS classique (au-dessus de TCP) ;",
     "example_upstream_tcp_hostname": "DNS normal (sur TCP, nom d’hôte) ;",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Si le port HTTPS est configuré, l'interface administrateur de AdGuard Home sera accessible via HTTPS et fournira aussi un service DNS-over-HTTPS sur l'emplacement '/dns-query'.",
     "encryption_dot": "Port DNS-over-TLS",
     "encryption_dot_desc": "Si ce port est configuré, AdGuard Home exécutera un serveur DNS-over-TLS sur ce port.",
-    "encryption_doq": "Port DNS sur QUIC (expérimental)",
-    "encryption_doq_desc": "Si ce port est configuré, AdGuard Home exécutera un serveur DNS sur QUIC sur ce port. Ceci est expérimental et possiblement pas entièrement fiable. Peu de clients le prennent en charge actuellement.",
+    "encryption_doq": "Port DNS sur QUIC",
+    "encryption_doq_desc": "Si ce port est configuré, AdGuard Home exécutera un serveur DNS sur QUIC sur ce port. ",
     "encryption_certificates": "Certificats",
     "encryption_certificates_desc": "Pour utiliser le chiffrement, vous devez fournir une chaîne de certificats SSL valide pour votre domaine. Vous pouvez en obtenir une gratuitement sur <0>{{link}}</0> ou vous pouvez en acheter une via les Autorités de Certification de confiance.",
     "encryption_certificates_input": "Copiez/coller vos certificats encodés PEM ici.",
diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json
index 47f2296c..8aecb338 100644
--- a/client/src/__locales/hr.json
+++ b/client/src/__locales/hr.json
@@ -370,7 +370,7 @@
     "encryption_dot": "DNS-over-TLS port",
     "encryption_dot_desc": "Ako je ovaj port postavljen, AdGuard Home će pokrenuti DNS-over-TLS poslužitelj na ovom portu.",
     "encryption_doq": "DNS-over-QUIC port (eksperimentalno)",
-    "encryption_doq_desc": "Ako je ovaj port postavljen, AdGuard Home će na ovom portu pokrenuti DNS-over-QUIC poslužitelj. Eksperimentalno je i možda nije pouzdano. Također, trenutno nema previše klijenata koji to podržavaju.",
+    "encryption_doq_desc": "Ako je ovaj priključak konfiguriran, AdGuard Home će na ovom priključku pokretati DNS-over-QUIC poslužitelj.",
     "encryption_certificates": "Certifikati",
     "encryption_certificates_desc": "Da biste koristili šifriranje, za svoju domenu morate osigurati važeći lanac SSL certifikata. Besplatan certifikat možete dobiti na <0>{{link}}</0> ili ga možete kupiti od jednog od pouzdanih izdavatelja certifikata.",
     "encryption_certificates_input": "Zalijepite svoje PEM-kodirane certifikate ovdje.",
@@ -511,7 +511,7 @@
     "statistics_configuration": "Postavke statistike",
     "statistics_retention": "Spremanje statistike",
     "statistics_retention_desc": "Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni",
-    "statistics_clear": " Poništi statistiku",
+    "statistics_clear": "Poništi statistiku",
     "statistics_clear_confirm": "Jeste li sigurni da želite poništiti statistiku?",
     "statistics_retention_confirm": "Jeste li sigurni da želite promijeniti zadržavanje statistike? Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni",
     "statistics_cleared": "Statistika je uspješno uklonjenja",
diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json
index a47d069c..952a1ef7 100644
--- a/client/src/__locales/hu.json
+++ b/client/src/__locales/hu.json
@@ -9,7 +9,7 @@
     "bootstrap_dns": "Bootstrap DNS kiszolgálók",
     "bootstrap_dns_desc": "A Bootstrap DNS szerverek a DoH/DoT feloldók IP-címeinek feloldására szolgálnak.",
     "local_ptr_title": "Privát DNS szerverek",
-    "local_ptr_desc": "Azok a DNS szerverek, amiket az AdGuard Home a helyi PTR kérésekhez használ. Ezeket a szervereket arra használjuk, hogy reverse DNS által feloldjuk a kliensek hosztneveit privát IP címekre, például \"192.168.12.34\". Ha nincs beállítva, akkor az AdGuard Home, kivéve az ő saját címét, az operációs rendszer alapértelmezett DNS feloldók címeit fogja használni.",
+    "local_ptr_desc": "Azok a DNS szerverek, amiket az AdGuard Home a helyi PTR kérésekhez használ. ",
     "local_ptr_default_resolver": "Alapesetben az AdGuard Home a következő reverse DNS feloldókat használja: {{ip}}.",
     "local_ptr_no_default_resolver": "Az AdGuard Home nem tudta meghatározni a privát reverse DNS feloldókat ehhez a rendszerhez.",
     "local_ptr_placeholder": "Adjon meg soronként egy kiszolgáló címet",
@@ -213,7 +213,7 @@
     "example_upstream_udp": "normál DNS (UDP felett, hostnév);",
     "example_upstream_dot": "titkosított <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "titkosított <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "titkosított <0>DNS-over-QUIC</0> (kísérleti);",
+    "example_upstream_doq": "titkosított <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS Stamps</0> a <1>DNSCrypt</1> vagy <2>DNS-over-HTTPS</2> feloldókhoz;",
     "example_upstream_tcp": "hagyományos DNS (TCP felett);",
     "example_upstream_tcp_hostname": "normál DNS (TCP felett, hostnév);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Ha a HTTPS port konfigurálva van, akkor az AdGuard Home admin felülete elérhető lesz a HTTPS-en keresztül, és ezenkívül DNS-over-HTTPS-t is biztosít a '/dns-query' helyen.",
     "encryption_dot": "DNS-over-TLS port",
     "encryption_dot_desc": "Ha ez a port be van állítva, az AdGuard Home DNS-over-TLS szerverként tud futni ezen a porton.",
-    "encryption_doq": "DNS-over-QUIC port (kísérleti)",
-    "encryption_doq_desc": "Ha ez a port be van állítva, akkor az AdGuard Home egy DNS-over-QUIC szerverként fog futni ezen a porton. Ez egy kísérleti funkció és nem biztos, hogy megbízható. Emellett nincs sok olyan kliens, ami támogatná ezt jelenleg.",
+    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq_desc": "Ha ez a port be van állítva, akkor az AdGuard Home egy DNS-over-QUIC szerverként fog futni ezen a porton. ",
     "encryption_certificates": "Tanúsítványok",
     "encryption_certificates_desc": "A titkosítás használatához érvényes SSL tanúsítványláncot kell megadnia a domainjéhez. Ingyenes tanúsítványt kaphat a <0>{{link}}</0> webhelyen, vagy megvásárolhatja az egyik megbízható tanúsítványkibocsátó hatóságtól.",
     "encryption_certificates_input": "Másolja be ide a PEM-kódolt tanúsítványt.",
diff --git a/client/src/__locales/id.json b/client/src/__locales/id.json
index 75e87b0d..73dc7f93 100644
--- a/client/src/__locales/id.json
+++ b/client/src/__locales/id.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "DNS biasa (lebih dari UDP, nama host);",
     "example_upstream_dot": "terenkripsi <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "terenkripsi <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "terenkripsi <0>DNS-over-QUIC</0> (eksperimental);",
+    "example_upstream_doq": "terenkripsi <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>Stempel DNS</0> untuk <1>DNSCrypt</1> atau pengarah <2>DNS-over-HTTPS</2>;",
     "example_upstream_tcp": "DNS reguler (melalui TCP);",
     "example_upstream_tcp_hostname": "DNS biasa (lebih dari TCP, nama host);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Jika port HTTPS dikonfigurasi, antarmuka admin Home AdGuard akan dapat diakses melalui HTTPS, dan itu juga akan memberikan DNS-over-HTTPS di lokasi '/ dns-query'.",
     "encryption_dot": "Port DNS-over-TLS",
     "encryption_dot_desc": "Jika port ini terkonfigurasi, AdGuard Home akan menjalankan server DNS-over-TLS dalam port ini",
-    "encryption_doq": "Port DNS-over-QUIC (eksperimental)",
-    "encryption_doq_desc": "Jika port ini diatur secara sepesifik, AdGuard Home akan menjalankan server DNS-lewat-QUIC pada port ini. Ini adalah eksperimental dan mungkin tidak dapat diandalkan. Juga, tidak banyak klien yang mendukungnya saat ini.",
+    "encryption_doq": "Port DNS-over-QUIC ",
+    "encryption_doq_desc": "Jika port ini diatur secara sepesifik, AdGuard Home akan menjalankan server DNS-lewat-QUIC pada port ini.",
     "encryption_certificates": "Sertifikat",
     "encryption_certificates_desc": "Untuk menggunakan enkripsi, Anda perlu memberikan rantai sertifikat SSL yang valid untuk domain Anda. Anda bisa mendapatkan sertifikat gratis di <0>{{link}}</0> atau Anda dapat membelinya dari salah satu Otoritas Sertifikat tepercaya.",
     "encryption_certificates_input": "Salin / rekatkan sertifikat PEM yang disandikan di sini.",
diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json
index cd499740..6bb28e5d 100644
--- a/client/src/__locales/it.json
+++ b/client/src/__locales/it.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "DNS regolare (over UDP, nome host);",
     "example_upstream_dot": "<0>DNS su TLS</0> crittografato;",
     "example_upstream_doh": "<0>DNS su HTTPS</0> crittografato;",
-    "example_upstream_doq": "<0>DNS su QUIC</0> crittografato (sperimentale);",
+    "example_upstream_doq": "<0>DNS su QUIC</0> crittografato;",
     "example_upstream_sdns": "<0>DNS Stamps</0> per <1>DNSCrypt</1> oppure i risolutori <2>DNS su HTTPS</2>;",
     "example_upstream_tcp": "DNS regolare (over TCP);",
     "example_upstream_tcp_hostname": "DNS regolare (over TCP, nome host);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Se la porta HTTPS è configurata, l'interfaccia di amministrazione di AdGuard Home sarà accessibile tramite HTTPS e fornirà anche DNS su HTTPS nella posizione \"/ dns-query\".",
     "encryption_dot": "DNS su porta TLS",
     "encryption_dot_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su TLS su questa porta.",
-    "encryption_doq": "Porta DNS su QUIC (sperimentale)",
-    "encryption_doq_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su porta QUIC. Questa opzione è sperimentale e potrebbe non risultare affidabile. Inoltre, al momento non sono molti i client a supportarla.",
+    "encryption_doq": "Porta DNS su QUIC",
+    "encryption_doq_desc": "Se questa porta è configurata, AdGuard Home eseguirà un server DNS su porta QUIC. ",
     "encryption_certificates": "Certificati",
     "encryption_certificates_desc": "Per utilizzare la crittografia, è necessario fornire una catena di certificati SSL valida per il proprio dominio. Puoi ottenere un certificato gratuito su <0> {{link}} </ 0> o puoi acquistarlo da una delle Autorità di certificazione attendibili.",
     "encryption_certificates_input": "Copia / incolla qui i certificati codificati PEM.",
diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json
index 0cd45548..a253afab 100644
--- a/client/src/__locales/ja.json
+++ b/client/src/__locales/ja.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "通常のDNS(over UDP, ホスト名)。",
     "example_upstream_dot": "暗号化されている <0>DNS-over-TLS</0>。",
     "example_upstream_doh": "暗号化されている <0>DNS-over-HTTPS</0>。",
-    "example_upstream_doq": "暗号化 <0>DNS-over-QUIC</0>(実験的)。",
+    "example_upstream_doq": "暗号化 <0>DNS-over-QUIC</0>。",
     "example_upstream_sdns": "<1>DNSCrypt</1> または <2>DNS-over-HTTPS</2> リゾルバのための <0>DNS Stamps</0>。",
     "example_upstream_tcp": "通常のDNS(over TCP)。",
     "example_upstream_tcp_hostname": "通常のDNS(over TCP, ホスト名)。",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "HTTPSポートが設定されていると、AdGuard Home 管理インターフェースはHTTPS経由でアクセス可能になり、そして「/dns-query」の場所にDNS-over-HTTPSも提供されます。",
     "encryption_dot": "DNS-over-TLS ポート",
     "encryption_dot_desc": "このポートが設定されていると、AdGuard HomeはこのポートでDNS-over-TLSサーバを実行します。",
-    "encryption_doq": "DNS-over-QUIC ポート (実験的)",
-    "encryption_doq_desc": "このポートが設定されていると、AdGuard HomeはこのポートにてDNS-over-QUICサーバーを実行します。これは実験的なものであり、頼りにならない可能性があります。また、現時点ではこのサーバーをサポートするクライアントも少ないです。",
+    "encryption_doq": "DNS-over-QUIC ポート",
+    "encryption_doq_desc": "このポートが設定されていると、AdGuard HomeはこのポートにてDNS-over-QUICサーバーを実行します。",
     "encryption_certificates": "証明書",
     "encryption_certificates_desc": "暗号化を使用するには、ドメインに有効なSSL証明書チェーンを提供する必要があります。無料の証明書は<0> {{link}} </0>で入手できます。または、信頼できる認証局のいずれかから購入することもできます。",
     "encryption_certificates_input": "ここにPEM形式の証明書をコピー/ペーストしてください。",
diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json
index 19440d13..c1d12499 100644
--- a/client/src/__locales/ko.json
+++ b/client/src/__locales/ko.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "일반 DNS (UDP를 통한, 호스트명);",
     "example_upstream_dot": "암호화된 <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "암호화된 <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "암호화된 <0>DNS-over-QUIC</0> (실험);",
+    "example_upstream_doq": "암호화된 <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<1>DNSCrypt</1> 또는 <2>DNS-over-HTTPS</2> 리졸버를 위한 <0>DNS 스탬프</0>;",
     "example_upstream_tcp": "일반 DNS (TCP를 통한 접속);",
     "example_upstream_tcp_hostname": "일반 DNS (TCP를 통한, 호스트명);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "HTTPS 포트가 구성되면 HTTPS를 통해 AdGuard Home 관리자 인터페이스에 액세스할 수 있으며, '/dns-query' 위치에 DNS-over-HTTPS도 제공합니다.",
     "encryption_dot": "DNS-over-TLS 포트",
     "encryption_dot_desc": "이 포트가 구성된 경우 AdGuard Home 이 포트에서 DNS-over-TLS 서버를 실행합니다.",
-    "encryption_doq": "DNS-over-QUIC 포트 (실험)",
-    "encryption_doq_desc": "이 포트가 설정된 경우 AdGuard Home은 해당 포트에서 DNS-over-QUIC 서버를 실행합니다. 이것은 실험적이며 신뢰할 수 없습니다. 또한 현재 이를 지원하는 클라이언트가 많지 않습니다.",
+    "encryption_doq": "DNS-over-QUIC 포트",
+    "encryption_doq_desc": "이 포트가 설정된 경우 AdGuard Home은 해당 포트에서 DNS-over-QUIC 서버를 실행합니다. ",
     "encryption_certificates": "인증서",
     "encryption_certificates_desc": "암호화를 사용하려면 도메인에 대해 올바른 SSL 인증서 체인을 제공해야 합니다. <0>{{link}}</0>에서 무료 증명서를 받을 수도 있고, 신뢰할 수있는 인증 기관에서 구입할 수 있습니다.",
     "encryption_certificates_input": "PEM으로 인코딩된 인증서 여기에 복사/붙여넣기하세요.",
diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json
index 83278ee3..05c09c74 100644
--- a/client/src/__locales/nl.json
+++ b/client/src/__locales/nl.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "standaard DNS (via UDP, hostnaam);",
     "example_upstream_dot": "versleutelde <0>DNS-via-TLS</0>;",
     "example_upstream_doh": "versleutelde <0>DNS-via-HTTPS</0>;",
-    "example_upstream_doq": "versleutelde <0>DNS-via-QUIC</0> (experimenteel);",
+    "example_upstream_doq": "versleutelde <0>DNS-via-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS Stamps</0> voor <1>DNSCrypt</1> of <2>DNS-via-HTTPS</2> oplossingen;",
     "example_upstream_tcp": "standaard DNS (over TCP);",
     "example_upstream_tcp_hostname": "standaard DNS (via TCP, hostnaam);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Als de HTTPS-poort is geconfigureerd, is de AdGuard Home beheerders interface toegankelijk via HTTPS en biedt deze ook DNS-via-HTTPS op de locatie '/ dns-query'.",
     "encryption_dot": "DNS-via-TLS poort",
     "encryption_dot_desc": "Indien deze poort is geconfigureerd, zal AdGuard Home gebruik maken van een DNS-via-TLS server via deze poort.",
-    "encryption_doq": "DNS-via-QUIC poort (experimenteel)",
-    "encryption_doq_desc": "Als deze poort is geconfigureerd, zal AdGuard Home een DNS-via-QUIC server gebruiken via deze poort. Dit is experimenteel en kan onbetrouwbaar zijn. Er zijn overigens nog niet veel systemen die dit nu al ondersteunen.",
+    "encryption_doq": "DNS-over-QUIC poort",
+    "encryption_doq_desc": "Als deze poort is geconfigureerd, zal AdGuard Home een DNS-via-QUIC server gebruiken via deze poort.",
     "encryption_certificates": "Certificaten",
     "encryption_certificates_desc": "Om encryptie te gebruiken, moet u een geldige SSL certificaat voor uw domein opgeven. U kunt een gratis certificaat krijgen op <0> {{link}} </0> of u kunt het kopen bij een van de vertrouwde certificaatautoriteiten.",
     "encryption_certificates_input": "Kopieër en plak je PEM-gecodeerde certificaten hier.",
diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json
index 0259d267..4225aa3c 100644
--- a/client/src/__locales/no.json
+++ b/client/src/__locales/no.json
@@ -199,7 +199,7 @@
     "example_upstream_regular": "vanlig DNS (over UDP)",
     "example_upstream_dot": "kryptert <0>DNS-over-TLS</0>",
     "example_upstream_doh": "kryptert <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "kryptert <0>DNS-over-QUIC</0>",
+    "example_upstream_doq": "kryptert <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "du kan bruke <0>DNS-stempler</0> med <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-behandlere",
     "example_upstream_tcp": "vanlig DNS (over TCP)",
     "all_lists_up_to_date_toast": "Alle listene er allerede oppdatert",
@@ -354,8 +354,8 @@
     "encryption_https_desc": "Dersom HTTPS-porten er satt opp, vil AdGuard Home sitt admin-grensesnitt være tilgjengelig gjennom HTTPS, og vil også sørge for DNS-over-HTTPS på «/dns-query»-plasseringen.",
     "encryption_dot": "'DNS-over-TLS'-port",
     "encryption_dot_desc": "Dersom denne porten er satt opp, vil AdGuard Home kjøre en 'DNS-over-TLS'-tjener på denne porten.",
-    "encryption_doq": "'DNS-over-QUIC'-port",
-    "encryption_doq_desc": "Dersom denne porten er satt opp, vil AdGuard Home kjøre en DNS-over-QUIC-tjener på denne porten. Den er eksperimentell og vil kanskje ikke være pålitelig. I tillegg er det ikke så altfor mange klienter som støtter det for øyeblikket.",
+    "encryption_doq": "DNS-over-QUIC-port",
+    "encryption_doq_desc": "Dersom denne porten er satt opp, vil AdGuard Home kjøre en DNS-over-QUIC-tjener på denne porten. ",
     "encryption_certificates": "Sertifikater",
     "encryption_certificates_desc": "For å bruke kryptering, må du skrive inn et gyldig SSL-sertifikatkjede for domenet ditt. Du kan få et gratis sertifikat hos <0>{{link}}</0>, eller kjøpe et fra en av de troverdige sertifikatsautoritetene.",
     "encryption_certificates_input": "Kopier / lim inn dine PEM-kodede sertifikater her.",
diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json
index 1d1f5f18..96f93e5b 100644
--- a/client/src/__locales/pl.json
+++ b/client/src/__locales/pl.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "zwykły DNS (przez UDP, nazwa hosta);",
     "example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "zaszyfrowany <0>DNS-over-QUIC</0> (eksperymentalny);",
+    "example_upstream_doq": "zaszyfrowany <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>Stempel DNS</0> dla resolwerów <1>DNSCrypt</1> lub <2>DNS-over-HTTPS</2>;",
     "example_upstream_tcp": "zwykły DNS (przez TCP);",
     "example_upstream_tcp_hostname": "zwykły DNS (przez TCP, nazwa hosta);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Jeśli port HTTPS jest skonfigurowany, interfejs administratora AdGuard Home będzie dostępny za pośrednictwem protokołu HTTPS i zapewni DNS przez HTTPS w lokalizacji zapytania '/dns-query'.",
     "encryption_dot": "Port DNS-over-TLS",
     "encryption_dot_desc": "Jeśli ten port jest skonfigurowany, AdGuard Home uruchomi serwer DNS-over-TLS na tym porcie.",
-    "encryption_doq": "Port DNS-over-QUIC (eksperymentalny)",
-    "encryption_doq_desc": "Jeśli ten port jest skonfigurowany, AdGuard Home uruchomi serwer DNS-over-QUIC na tym porcie. Jest to funkcja eksperymentalna i może nie być stabilna. Ponadto, w tej chwili nie ma zbyt wielu klientów, którzy go obsługują.",
+    "encryption_doq": "Port DNS-over-QUIC",
+    "encryption_doq_desc": "Jeśli ten port jest skonfigurowany, AdGuard Home uruchomi serwer DNS-over-QUIC na tym porcie.",
     "encryption_certificates": "Certyfikaty",
     "encryption_certificates_desc": "Aby korzystać z szyfrowania, musisz podać prawidłowy łańcuch certyfikatów SSL dla swojej domeny. Możesz uzyskać bezpłatny certyfikat na  <0>{{link}}</0> lub możesz go kupić od jednego z zaufanych urzędów certyfikacji.",
     "encryption_certificates_input": "Kopiuj/wklej tutaj swoje zakodowane certyfikaty PEM.",
diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json
index d52d6961..2f8770b9 100644
--- a/client/src/__locales/pt-br.json
+++ b/client/src/__locales/pt-br.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "DNS normal (através do UDP, nome do servidor);",
     "example_upstream_dot": "<0>DNS-sobre-TLS</0> criptografado;",
     "example_upstream_doh": "<0>DNS-sobre-HTTPS</0> criptografado;",
-    "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado (experimental);",
+    "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado;",
     "example_upstream_sdns": "<0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>;",
     "example_upstream_tcp": "DNS regular (através do TCP);",
     "example_upstream_tcp_hostname": "DNS normal (através do TCP, nome do servidor);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home será acessível via HTTPS e também fornecerá o DNS-sobre-HTTPS no local '/dns-query'.",
     "encryption_dot": "Porta DNS-sobre-TLS",
     "encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home irá executar o servidor DNS-sobre- TSL nesta porta.",
-    "encryption_doq": "Porta DNS-sobre-QUIC (experimental)",
-    "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. É experimental e pode não ser confiável. Além disso, não há muitos clientes que ofereçam suporte no momento.",
+    "encryption_doq": "Porta DNS-sobre-QUIC",
+    "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. ",
     "encryption_certificates": "Certificados",
     "encryption_certificates_desc": "Para usar criptografia, você precisa fornecer uma cadeia de certificados SSL válida para seu domínio. Você pode obter um certificado gratuito em <0> {{link}}</0> ou pode comprá-lo de uma das autoridades de certificação confiáveis.",
     "encryption_certificates_input": "Copie/cole aqui seu certificado codificado em PEM.",
diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json
index 6502f9f9..2698f60a 100644
--- a/client/src/__locales/pt-pt.json
+++ b/client/src/__locales/pt-pt.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "DNS normal (através do UDP, nome do servidor);",
     "example_upstream_dot": "<0>DNS-sobre-TLS</0> criptografado;",
     "example_upstream_doh": "<0>DNS-sobre-HTTPS</0> criptografado;",
-    "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado (experimental);",
+    "example_upstream_doq": "<0>DNS-sobre-QUIC</0> criptografado;",
     "example_upstream_sdns": "<0>DNS Stamps</0> para o <1>DNSCrypt</1> ou usar os resolvedores <2>DNS-sobre-HTTPS</2>;",
     "example_upstream_tcp": "DNS regular (através do TCP);",
     "example_upstream_tcp_hostname": "DNS normal (através do TCP, nome do servidor);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home será acessível via HTTPS e também fornecerá o DNS-sobre-HTTPS no local '/dns-query'.",
     "encryption_dot": "Porta DNS-sobre-TLS",
     "encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home irá executar o servidor DNS-sobre- TSL nesta porta.",
-    "encryption_doq": "Porta DNS-sobre-QUIC (experimental)",
-    "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. É experimental e pode não ser confiável. Além disso, não há demasiados clientes que ofereçam suporte no momento.",
+    "encryption_doq": "Porta DNS-sobre-QUIC",
+    "encryption_doq_desc": "Se esta porta estiver configurada, o AdGuard Home executará um servidor DNS-sobre-QUIC nesta porta. ",
     "encryption_certificates": "Certificados",
     "encryption_certificates_desc": "Para usar criptografia, precisa de fornecer uma cadeia de certificados SSL válida para o seu domínio. Pode obter um certificado gratuito em <0> {{link}}</0> ou pode comprá-lo numa das autoridades de certificação confiáveis.",
     "encryption_certificates_input": "Copie/cole aqui o seu certificado codificado em PEM.",
diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json
index 097dba59..a360ce5c 100644
--- a/client/src/__locales/ro.json
+++ b/client/src/__locales/ro.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "DNS obișnuit (over UDP, nume de gazdă);",
     "example_upstream_dot": "<0>DNS-over-TLS</0> criptat;",
     "example_upstream_doh": "<0>DNS-over-HTTPS</0> criptat;",
-    "example_upstream_doq": "<0>DNS-over-QUIC</0> criptat (experimental);",
+    "example_upstream_doq": "criptat <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS Stamps</0> pentru <1>DNSCrypt</1> sau rezolvatori <2>DNS-over-HTTPS</2>;",
     "example_upstream_tcp": "DNS clasic (over TCP);",
     "example_upstream_tcp_hostname": "DNS obișnuit (over TCP, nume de gazdă);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Dacă portul HTTPS este configurat, interfața administrator AdGuard Home va fi accesibilă prin HTTPS și va oferi de asemenea DNS-over-HTTPS în locația '/DNS-query'.",
     "encryption_dot": "Port DNS-over-TLS",
     "encryption_dot_desc": "Dacă acest port este configurat, AdGuard Home va rula un server DNS-over-TLS pe acest port.",
-    "encryption_doq": "Port DNS-over-QUIC (experimental)",
-    "encryption_doq_desc": "Dacă acest port este configurat, AdGuard Home va rula un server DNS-over-QUIC pe acest port. Este experimental și este posibil să nu fie fiabil. De asemenea, nu există prea mulți clienți care să-l susțină în acest moment.",
+    "encryption_doq": "Portul DNS-over-QUIC",
+    "encryption_doq_desc": "Dacă este configurat acest port, AdGuard Home va rula un server DNS-over-QUIC pe acest port.",
     "encryption_certificates": "Certificate",
     "encryption_certificates_desc": "Pentru a utiliza criptarea, trebuie furnizate o serie de certificate SSL valabile pentru domeniul dvs.. Puteți obține un certificat gratuit pe <0>{{link}}</0> sau îl puteți cumpăra de la una din Autoritățile Certificate de încredere.",
     "encryption_certificates_input": "Copiați/lipiți certificatele dvs. PEM-codate aici.",
diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json
index d439d0ff..431dfcc9 100644
--- a/client/src/__locales/ru.json
+++ b/client/src/__locales/ru.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "обычный DNS (поверх UDP, с именем хоста);",
     "example_upstream_dot": "зашифрованный <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "зашифрованный <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "зашифрованный <0>DNS-over-QUIC</0> (эксперементальный);",
+    "example_upstream_doq": "зашифрован <0>DNS-over-QUIC</0>",
     "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt</1> или <2>DNS-over-HTTPS</2> серверов;",
     "example_upstream_tcp": "обычный DNS (поверх TCP);",
     "example_upstream_tcp_hostname": "обычный DNS (поверх TCP, с именем хоста);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Если порт HTTPS настроен, веб-интерфейс администрирования AdGuard Home будет доступен через HTTPS, а также DNS-over-HTTPS сервер будет доступен по пути '/dns-query'.",
     "encryption_dot": "Порт DNS-over-TLS",
     "encryption_dot_desc": "Если этот порт настроен, AdGuard Home запустит DNS-over-TLS-сервер на этому порту.",
-    "encryption_doq": "Порт DNS-over-QUIC (экспериментальный)",
-    "encryption_doq_desc": "Если этот порт настроен, AdGuard Home запустит сервер DNS-over-QUIC на этом порте. Это экспериментально и может быть ненадёжно. Кроме того, не так много клиентов поддерживает этот способ в настоящий момент.",
+    "encryption_doq": "Порт DNS-over-QUIC",
+    "encryption_doq_desc": "Если этот порт настроен, AdGuard Home запустит сервер DNS-over-QUIC на этом порте.",
     "encryption_certificates": "Сертификаты",
     "encryption_certificates_desc": "Для использования шифрования вам необходимо предоставить корректную цепочку SSL-сертификатов для вашего домена. Вы можете получить бесплатный сертификат на <0>{{link}}</0> или вы можете купить его у одного из доверенных Центров Сертификации.",
     "encryption_certificates_input": "Скопируйте сюда сертификаты в PEM-кодировке.",
diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json
index 52048c04..5982bb52 100644
--- a/client/src/__locales/sk.json
+++ b/client/src/__locales/sk.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "štandardné DNS (cez UDP, hostname);",
     "example_upstream_dot": "šifrované <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "šifrované <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "šifrované <0>DNS-over-QUIC</0> (experimentálne);",
+    "example_upstream_doq": "šifrované <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS pečiatky</0> pre <1>DNSCrypt</1> alebo <2>DNS-over-HTTPS</2> rezolvery;",
     "example_upstream_tcp": "obyčajná DNS (cez TCP);",
     "example_upstream_tcp_hostname": "štandardné DNS (cez TCP, hostname);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Ak je nakonfigurovaný HTTPS port, AdGuard Home administrátorské rozhranie bude prístupné cez HTTPS a bude tiež poskytovať DNS-cez-HTTPS na '/dns-query'.",
     "encryption_dot": "Port DNS-cez-TLS",
     "encryption_dot_desc": "Ak je tento port nakonfigurovaný, AdGuard Home bude na tomto porte spúšťať DNS-cez-TLS server.",
-    "encryption_doq": "DNS-over-QUIC (experimentálne)",
-    "encryption_doq_desc": "Ak je tento port nakonfigurovaný, AdGuard Home na tomto porte spustí server DNS-over-QUIC. Je to experimentálne a nemusí to byť spoľahlivé. Momentálne tiež nie je príliš veľa klientov, ktorí by ju podporovali.",
+    "encryption_doq": "Port DNS-cez-QUIC",
+    "encryption_doq_desc": "Ak je tento port nakonfigurovaný, AdGuard Home na tomto porte spustí server DNS-over-QUIC. ",
     "encryption_certificates": "Certifikáty",
     "encryption_certificates_desc": "Ak chcete používať šifrovanie, musíte pre svoju doménu poskytnúť platný reťazec certifikátov SSL. Certifikát môžete získať bezplatne na adrese <0>{{link}}</0> alebo si ho môžete kúpiť od jedného z dôveryhodných certifikačných orgánov.",
     "encryption_certificates_input": "Skopírujte alebo prilepte sem certifikáty vo formáte PEM.",
diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json
index 80dfd9aa..0c4ddb35 100644
--- a/client/src/__locales/sl.json
+++ b/client/src/__locales/sl.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "redni DNS (nad UDP, ime gostitelja);",
     "example_upstream_dot": "šifriran <0>DNS-prek-TLS</0>;",
     "example_upstream_doh": "šifriran <0>DNS-prek-HTTPS</0>;",
-    "example_upstream_doq": "šifriran <0>DNS-prek-QUIC</0> (eksperimentalno);",
+    "example_upstream_doq": "šifriran <0>DNS-prek-QUIC</0>;",
     "example_upstream_sdns": "lahko uporabite <0>DNS Žige</0> za reševalce <1>DNSCrypt</1> ali <2>DNS-prek-HTTPS</2>;",
     "example_upstream_tcp": "redni DNS (nad TCP);",
     "example_upstream_tcp_hostname": "redni DNS (nad TCP, ime gostitelja);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Če so vrata HTTPS konfigurirana, bo skrbniški vmesnik AdGuard Home dostopen prek protokola HTTPS, prav tako pa bo zagotovil DNS-prek-HTTPS na mestu '/dns-query'.",
     "encryption_dot": "Vrata DNS-prek-TLS",
     "encryption_dot_desc": "Če so ta vrata konfigurirana, bo AdGuard Home na teh vratih zagnal DNS-prek-TLS strežnika.",
-    "encryption_doq": "DNS-prek-vrat QUIC (eksperimentalno)",
-    "encryption_doq_desc": "Če so nastavljena ta vrata bo AdGuard Home na teh vratih zagnal strežnik DNS-prek-QUIC. To je eksperimentalno in morda ni zanesljivo. Prav tako trenutno ni preveč odjemalcev, ki to podpirajo.",
+    "encryption_doq": "DNS-prek-vrat QUIC",
+    "encryption_doq_desc": "Če so nastavljena ta vrata bo AdGuard Home na teh vratih zagnal strežnik DNS-prek-QUIC. ",
     "encryption_certificates": "Digitalna potrdila",
     "encryption_certificates_desc": "Za uporabo šifriranja morate za svojo domeno zagotoviti veljavno verigo potrdil SSL. Brezplačno digitalno potrdilo lahko dobite na <0>{{link}}</0> ali pa ga kupite pri enem od   zaupanja vrednih overiteljev.\n\n",
     "encryption_certificates_input": "Tukaj kopirajte/prilepite PEM šifrirana digitalna potrdila.",
diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json
index 1fff431a..2a68e53c 100644
--- a/client/src/__locales/sr-cs.json
+++ b/client/src/__locales/sr-cs.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "uobičajen DNS (preko UDP, imena domaćina);",
     "example_upstream_dot": "šifrovano <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "šifrovano <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "šifrovano <0>DNS-over-QUIC</0> (eksperimentalno);",
+    "example_upstream_doq": "šifrovano <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS brojeve</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2> razrešivače;",
     "example_upstream_tcp": "uobičajeni DNS (preko TCP);",
     "example_upstream_tcp_hostname": "uobičajen DNS (preko TCP, imena domaćina);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Ako je HTTPS port konfigurisan, AdGuard Home administratorskom okruženju će se moći pristupati preko HTTPS, a to će takođe omogućiti DNS-over-HTTPS na '/dns-query' lokaciji.",
     "encryption_dot": "DNS-over-TLS port",
     "encryption_dot_desc": "Ako je ovaj port konfigurisan, AdGuard Home će pokretati DNS-over-TLS server na ovom portu.",
-    "encryption_doq": "DNS-over-QUIC port (eksperimentalno)",
-    "encryption_doq_desc": "Ako je ovaj port konfigurisan, AdGuard Home će pokrenuti DNS-over-QUIC server na tom portu. To je eksperiment i možda neće biti stabilno. Takođe, u ovom trenutku ne postoji puno klijenata koji ovo podržavaju.",
+    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq_desc": "Ako je ovaj port konfigurisan, AdGuard Home će pokrenuti DNS-over-QUIC server na tom portu.",
     "encryption_certificates": "Sertifikati",
     "encryption_certificates_desc": "Da biste koristili šifrovanje, morate obezbediti važeći lanac SSL sertifikata za vaš domen. Besplatan sertifikat možete nabaviti na <0>{{link}}</0> ili ga možete kupiti od nekog od pouzdanih izdavalaca sertifikata.",
     "encryption_certificates_input": "Kopirajte/nalepite vaše PEM-kodirane sertifikate ovde.",
diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json
index a107e8c7..1e4789a8 100644
--- a/client/src/__locales/sv.json
+++ b/client/src/__locales/sv.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "vanlig DNS (över UDP, värdnamn);",
     "example_upstream_dot": "krypterat <0>DNS-over-TLS</0>",
     "example_upstream_doh": "krypterat <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "krypterat <0>DNS-over-QUIC</0> (experimentell);",
+    "example_upstream_doq": "krypterat <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "Du kan använda <0>DNS-stamps</0> för <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-resolvers",
     "example_upstream_tcp": "vanlig DNS (över UDP)",
     "example_upstream_tcp_hostname": "vanlig DNS (över TCP, värdnamn);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Om en HTTPS-port är inställd kommer gränssnittet till AdGuard Home administrering att kunna nås via HTTPS och kommer också att erbjuda DNS-over-HTTPS på '/dns-query' plats.",
     "encryption_dot": "DNS-över-TLS port",
     "encryption_dot_desc": "Om den här porten ställs in kommer AdGuard Home att använda DNS-over-TLS-server på porten.",
-    "encryption_doq": "DNS-over-QUIC port (experimentell)",
-    "encryption_doq_desc": "Om denna port är konfigurerad kommer AdGuard Home att köra en DNS-over-QUIC-server på denna port. Det är experimentellt och kanske inte är tillförlitligt. Dessutom finns det inte så många klienter som stödjer det för tillfället.",
+    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq_desc": "Om denna port är konfigurerad kommer AdGuard Home att köra en DNS-over-QUIC-server på denna port. ",
     "encryption_certificates": "Certifikat",
     "encryption_certificates_desc": "För att använda kryptering måste du ange ett giltigt SSL-certifikat för din domän. Du kan skaffa ett certifikat gratis på <0>{{link}}</0> eller köpa ett från någon av de godkända certifikatutfärdare.",
     "encryption_certificates_input": "Kopiera/klistra in dina PEM-kodade certifikat här.",
diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json
index 6fd679ac..49412d85 100644
--- a/client/src/__locales/tr.json
+++ b/client/src/__locales/tr.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "normal DNS (UDP üzerinden, ana makine adı);",
     "example_upstream_dot": "şifrelenmiş <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "şifrelenmiş <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "şifrelenmiş <0>DNS-over-QUIC</0> (deneysel);",
+    "example_upstream_doq": "şifrelenmiş <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<1>DNSCrypt</1> veya <2>DNS-over-HTTPS</2> çözümleyicileri için <0>DNS Damgaları</0>;",
     "example_upstream_tcp": "normal DNS (TCP üzerinden);",
     "example_upstream_tcp_hostname": "normal DNS (TCP üzerinden, ana makine adı);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "HTTPS bağlantı noktası yapılandırılırsa, AdGuard Home yönetici arayüzüne HTTPS aracılığıyla erişilebilir olacak ve ayrıca '/dns-query' üzerinden DNS-over-HTTPS bağlantısı sağlayacaktır.",
     "encryption_dot": "DNS-over-TLS bağlantı noktası",
     "encryption_dot_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, DNS-over-TLS sunucusunu bu bağlantı noktası üzerinden çalıştıracaktır.",
-    "encryption_doq": "DNS-over-QUIC bağlantı noktası (deneysel)",
-    "encryption_doq_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, DNS-over-QUIC sunucusunu bu bağlantı noktası üzerinden çalıştıracaktır. Bu özellik deneme aşamasındadır ve güvenilir olmayabilir. Ayrıca, şu anda bu özelliği destekleyen çok fazla istemci yok.",
+    "encryption_doq": "DNS-over-QUIC bağlantı noktası",
+    "encryption_doq_desc": "Bu bağlantı noktası yapılandırılırsa, AdGuard Home, bu bağlantı noktasında bir DNS-over-QUIC sunucusu çalıştıracaktır.",
     "encryption_certificates": "Sertifikalar",
     "encryption_certificates_desc": "Şifrelemeyi kullanmak için alan adınıza geçerli bir SSL sertifika zinciri sağlamanız gerekir. <0>{{link}}</0> adresinden ücretsiz bir sertifika alabilir veya güvenilir Sertifika Yetkililerinden satın alabilirsiniz.",
     "encryption_certificates_input": "PEM biçimindeki sertifikalarınızı kopyalayıp buraya yapıştırın.",
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index 84a6974e..cbbdfe80 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "звичайний DNS (поверх UDP, з назвою вузла);",
     "example_upstream_dot": "зашифрований <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "зашифрований <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "зашифрований <0>DNS-over-QUIC</0> (експериментальний);",
+    "example_upstream_doq": "зашифрований <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS Stamps</0> для <1>DNSCrypt-</1> або <2>DNS-over-HTTPS-</2>вирішувачів;",
     "example_upstream_tcp": "звичайний DNS (через TCP);",
     "example_upstream_tcp_hostname": "звичайний DNS (поверх TCP, з назвою вузла);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Якщо HTTPS-порт налаштовано, інтерфейс адміністратора AdGuard Home буде доступний через HTTPS, а також DNS-over-HTTPS-сервер буде доступний за адресою /dns-query.",
     "encryption_dot": "Порт DNS-over-TLS",
     "encryption_dot_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-TLS.",
-    "encryption_doq": "Порт DNS-over-QUIC (експериментальний)",
-    "encryption_doq_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на цьому порту сервер DNS-over-QUIC. Це експериментально і може бути ненадійним. Крім того, зараз не так багато клієнтів, які це підтримують.",
+    "encryption_doq": "Порт DNS-over-QUIC",
+    "encryption_doq_desc": "Якщо цей порт налаштовано, AdGuard Home запустить на ньому сервер DNS-over-QUIC.",
     "encryption_certificates": "Сертифікати",
     "encryption_certificates_desc": "Для використання шифрування потрібно надати дійсний ланцюжок сертифікатів SSL для вашого домену. Ви можете отримати безплатний сертифікат на <0>{{link}}</0> або придбати його в одному з надійних Центрів Сертифікації.",
     "encryption_certificates_input": "Скопіюйте/вставте сюди свої кодовані PEM сертифікати.",
diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json
index 7c9c2fd4..a4d15e24 100644
--- a/client/src/__locales/vi.json
+++ b/client/src/__locales/vi.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "DNS thông thường (qua UDP, tên máy chủ);",
     "example_upstream_dot": "được mã hoá <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "được mã hoá <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "được mã hoá <0>DNS-over-QUIC</0> (thử nghiệm);",
+    "example_upstream_doq": "được mã hoá <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "bạn có thể sử dụng <0>DNS Stamps</0> for <1>DNSCrypt</1> hoặc <2>DNS-over-HTTPS</2> ",
     "example_upstream_tcp": "DNS thông thường(dùng TCP);",
     "example_upstream_tcp_hostname": "DNS thông thường (qua TCP, tên máy chủ);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "Nếu cổng HTTPS được định cấu hình, giao diện quản trị viên AdGuard Home sẽ có thể truy cập thông qua HTTPS và nó cũng sẽ cung cấp DNS-over-HTTPS trên vị trí '/dns-query'.",
     "encryption_dot": "Cổng DNS-over-TLS",
     "encryption_dot_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS-over-TLS trên cổng này.",
-    "encryption_doq": "Cổng DNS-over-QUIC (thử nghiệm)",
-    "encryption_doq_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS qua QUIC trên cổng này. Đó là thử nghiệm và có thể không đáng tin cậy. Ngoài ra, không có quá nhiều khách hàng hỗ trợ nó vào lúc này.",
+    "encryption_doq": "Cổng DNS-over-QUIC",
+    "encryption_doq_desc": "Nếu cổng này được định cấu hình, AdGuard Home sẽ chạy máy chủ DNS qua QUIC trên cổng này. ",
     "encryption_certificates": "Chứng chỉ",
     "encryption_certificates_desc": "Để sử dụng mã hóa, bạn cần cung cấp chuỗi chứng chỉ SSL hợp lệ cho miền của mình. Bạn có thể nhận chứng chỉ miễn phí trên <0>{{link}}</0> hoặc bạn có thể mua chứng chỉ từ một trong các Cơ Quan Chứng Nhận tin cậy.",
     "encryption_certificates_input": "Sao chép/dán chứng chỉ được mã hóa PEM của bạn tại đây.",
diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json
index 8903052f..9882b41a 100644
--- a/client/src/__locales/zh-cn.json
+++ b/client/src/__locales/zh-cn.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "常规 DNS(通过 UDP、主机名);",
     "example_upstream_dot": "加密 <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "加密 <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "加密 <0>DNS-over-QUIC</0>(实验性的);",
+    "example_upstream_doq": "加密 <0>DNS-over-QUIC</0>",
     "example_upstream_sdns": "<1>DNSCrypt</1> 的 <0>DNS Stamps</0> 或者 <2>DNS-over-HTTPS</2> 解析器;",
     "example_upstream_tcp": "常规 DNS(基于 TCP );",
     "example_upstream_tcp_hostname": "常规 DNS(通过 TCP、主机名);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "如果配置了 HTTPS 端口,AdGuard Home 管理界面将可以通过 HTTPS 访问,它还将在在 '/dns-query' 位置提供 DNS-over-HTTPS 。",
     "encryption_dot": "DNS-over-TLS 端口",
     "encryption_dot_desc": "如果配置了此端口,AdGuard Home 将在此端口上运行一个 DNS-over-TLS 服务器。",
-    "encryption_doq": "DNS-over-QUIC 端口(实验性的)",
-    "encryption_doq_desc": "如果配置了此端口,AdGuard Home将在此端口上运行一个DNS-over-QUIC服务器。这是实验性的,可能不可靠。而且,支持此特性的客户端并不多。",
+    "encryption_doq": "DNS-over-QUIC 端口",
+    "encryption_doq_desc": "如果配置了此端口,AdGuard Home 将在此端口上运行一个 DNS-over-QUIC 服务器。",
     "encryption_certificates": "证书",
     "encryption_certificates_desc": "为了使用加密,您需要为域提供有效的 SSL 证书链。您可以在 <0>{{link}}</0> 上获得免费证书,也可以从受信任的证书颁发机构购买证书。",
     "encryption_certificates_input": "将您以 PEM 格式编码的证书复制粘贴到此处。",
diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json
index 435d6b01..f183d749 100644
--- a/client/src/__locales/zh-tw.json
+++ b/client/src/__locales/zh-tw.json
@@ -213,7 +213,7 @@
     "example_upstream_udp": "常規 DNS(透過 UDP,主機名稱);",
     "example_upstream_dot": "加密的 <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "加密的 <0>DNS-over-HTTPS</0>;",
-    "example_upstream_doq": "加密的 <0>DNS-over-QUIC</0>(實驗性的);",
+    "example_upstream_doq": "加密的 <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "關於 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2> 解析器之 <0>DNS 戳記</0>;",
     "example_upstream_tcp": "常規 DNS(透過 TCP);",
     "example_upstream_tcp_hostname": "常規 DNS(透過 TCP,主機名稱);",
@@ -369,8 +369,8 @@
     "encryption_https_desc": "如果 HTTPS 連接埠被配置,AdGuard Home 管理員介面透過 HTTPS 將為可存取的,且它也將於 '/dns-query' 位置上提供 DNS-over-HTTPS。",
     "encryption_dot": "DNS-over-TLS 連接埠",
     "encryption_dot_desc": "如果該連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-TLS 伺服器。",
-    "encryption_doq": "DNS-over-QUIC 連接埠(實驗性的)",
-    "encryption_doq_desc": "如果此連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-QUIC 伺服器。它是實驗性的並可能為不可靠的。再者,此刻沒有太多支援它的用戶端。",
+    "encryption_doq": "DNS-over-QUIC 連接埠",
+    "encryption_doq_desc": "如果此連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-QUIC 伺服器。",
     "encryption_certificates": "憑證",
     "encryption_certificates_desc": "為了使用加密,您需要提供有效的安全通訊端層(SSL)憑證鏈結供您的網域。於 <0>{{link}}</0> 上您可取得免費的憑證或您可從受信任的憑證授權單位之一購買它。",
     "encryption_certificates_input": "於此複製/貼上您的隱私增強郵件編碼之(PEM-encoded)憑證。",

From 3ce04f48ca01e9fe4e1344e5baaac0b15fd68585 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 6 Jun 2022 15:34:08 +0300
Subject: [PATCH 073/143] Pull request: all: fix quic reply id

Merge in DNS/adguard-home from upd-dnsproxy-quic-fix to master

Squashed commit of the following:

commit a6ffa24769259c73e397e02d087dc155ed58a3e2
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jun 6 15:06:00 2022 +0300

    all: fix quic reply id
---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index a4e82f61..6a17f351 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
 go 1.17
 
 require (
-	github.com/AdguardTeam/dnsproxy v0.43.0
+	github.com/AdguardTeam/dnsproxy v0.43.1
 	github.com/AdguardTeam/golibs v0.10.8
 	github.com/AdguardTeam/urlfilter v0.16.0
 	github.com/NYTimes/gziphandler v1.1.1
diff --git a/go.sum b/go.sum
index 8a700ddd..e9d5920b 100644
--- a/go.sum
+++ b/go.sum
@@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/AdguardTeam/dnsproxy v0.43.0 h1:K082nx37DaNqSyT3kDtAfgBACNWc+ZDI1Yr/kGppu1k=
-github.com/AdguardTeam/dnsproxy v0.43.0/go.mod h1:JUGTm5dmlll47JltztsT0N//pVJjdg6zu0SNeUeaA7g=
+github.com/AdguardTeam/dnsproxy v0.43.1 h1:E777KfQAi+VurOoWEdGQ5iqjSOOAzzbTfLOEzj8heCs=
+github.com/AdguardTeam/dnsproxy v0.43.1/go.mod h1:JUGTm5dmlll47JltztsT0N//pVJjdg6zu0SNeUeaA7g=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=

From a497dc09ca3fd43b7a8dc1cb030a009f1821992c Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 6 Jun 2022 18:43:35 +0300
Subject: [PATCH 074/143] Pull request: all: upd chlog

Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 1ca912f4be4a452abc0c8c95a8d6022d547b3394
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jun 6 18:35:12 2022 +0300

    all: upd chlog
---
 CHANGELOG.md | 64 ++++++++++++++++++++++++++++++++--------------------
 1 file changed, 40 insertions(+), 24 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bdd37ae..ed949cc3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,24 +12,52 @@ and this project adheres to
 ## [Unreleased]
 
 <!--
-## [v0.108.0] - 2022-07-01 (APPROX.)
+## [v0.108.0] - 2022-10-01 (APPROX.)
 -->
 
 ### Security
 
+- Weaker cipher suites that use the CBC (cipher block chaining) mode of
+  operation have been disabled ([#2993]).
+
+### Added
+
+- Support for Discovery of Designated Resolvers (DDR) according to the [RFC
+  draft][ddr-draft-06] ([#4463]).
+- `windows/arm64` support ([#3057]).
+
+### Deprecated
+
+- Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
+
+[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
+
+[ddr-draft-06]:   https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
+
+
+
+<!--
+## [v0.107.8] - 2022-07-12 (APPROX.)
+-->
+
+
+
+## [v0.107.7] - 2022-06-06
+
+See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
+
+### Security
+
 - Go version was updated to prevent the possibility of exploiting the
   [CVE-2022-29526], [CVE-2022-30634], [CVE-2022-30629], [CVE-2022-30580], and
   [CVE-2022-29804] vulnerabilities.
 - Enforced password strength policy ([#3503]).
-- Weaker cipher suites that use the CBC (cipher block chaining) mode of
-  operation have been disabled ([#2993]).
 
 ### Added
 
 - Support for the final DNS-over-QUIC standard, [RFC 9250][rfc-9250] ([#4592]).
 - Support upstreams for subdomains of a domain only ([#4503]).
-- Support for Discovery of Designated Resolvers (DDR) according to the [RFC
-  draft][ddr-draft-06] ([#4463]).
 - The ability to control each source of runtime clients separately via
   `clients.runtime_sources` configuration object ([#3020]).
 - The ability to customize the set of networks that are considered private
@@ -41,7 +69,6 @@ and this project adheres to
   ([#4166]).
 - Logs are now collected by default on FreeBSD and OpenBSD when AdGuard Home is
   installed as a service ([#4213]).
-- `windows/arm64` support ([#3057]).
 
 ### Changed
 
@@ -125,10 +152,9 @@ In this release, the schema version has changed from 12 to 14.
 
 ### Deprecated
 
-- The `--no-etc-hosts` option.  Its' functionality is now controlled by
+- The `--no-etc-hosts` option.  Its functionality is now controlled by
   `clients.runtime_sources.hosts` configuration property.  v0.109.0 will remove
   the flag completely.
-- Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
 
 ### Fixed
 
@@ -140,9 +166,7 @@ In this release, the schema version has changed from 12 to 14.
 - ARP tables refreshing process causing excessive PTR requests ([#3157]).
 
 [#1730]: https://github.com/AdguardTeam/AdGuardHome/issues/1730
-[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3020]: https://github.com/AdguardTeam/AdGuardHome/issues/3020
-[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
 [#3142]: https://github.com/AdguardTeam/AdGuardHome/issues/3142
 [#3157]: https://github.com/AdguardTeam/AdGuardHome/issues/3157
 [#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
@@ -169,20 +193,10 @@ In this release, the schema version has changed from 12 to 14.
 [CVE-2022-30580]: https://www.cvedetails.com/cve/CVE-2022-30580
 [CVE-2022-30629]: https://www.cvedetails.com/cve/CVE-2022-30629
 [CVE-2022-30634]: https://www.cvedetails.com/cve/CVE-2022-30634
-[ddr-draft-06]:   https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
-[repr]:           https://reproducible-builds.org/docs/source-date-epoch/
+[ms-v0.107.7]:    https://github.com/AdguardTeam/AdGuardHome/milestone/43?closed=1
 [rfc-9250]:       https://datatracker.ietf.org/doc/html/rfc9250
 
 
-<!--
-## [v0.107.7] - 2022-05-18 (APPROX.)
-
-See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
-
-[ms-v0.107.7]: https://github.com/AdguardTeam/AdGuardHome/milestone/43?closed=1
--->
-
-
 
 ## [v0.107.6] - 2022-04-13
 
@@ -234,6 +248,7 @@ See also the [v0.107.6 GitHub milestone][ms-v0.107.6].
 [CVE-2022-28327]: https://www.cvedetails.com/cve/CVE-2022-28327
 [dns-draft-02]:   https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02#section-5.1
 [ms-v0.107.6]:    https://github.com/AdguardTeam/AdGuardHome/milestone/42?closed=1
+[repr]:           https://reproducible-builds.org/docs/source-date-epoch/
 [svcb-draft-08]:  https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-08.html
 
 
@@ -995,11 +1010,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
 
 
 <!--
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...HEAD
-[v0.107.7]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...v0.107.7
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...HEAD
+[v0.107.8]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...v0.107.8
 -->
 
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...HEAD
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...HEAD
+[v0.107.7]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...v0.107.7
 [v0.107.6]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...v0.107.6
 [v0.107.5]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5
 [v0.107.4]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.3...v0.107.4

From 1c1ca1c6e3d490ddfaf2f176a54f5b93b24a9c1a Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Thu, 9 Jun 2022 11:57:58 +0300
Subject: [PATCH 075/143] Pull request: 4641 fix button clickable area

Updates #4641

Squashed commit of the following:

commit f9f018388a198d7712e5caabba94035e42e393c4
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Jun 7 16:21:37 2022 +0300

    client: fix button clickable area
---
 client/src/components/Settings/Settings.css | 1 +
 1 file changed, 1 insertion(+)

diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css
index 4efb0868..3fd560f9 100644
--- a/client/src/components/Settings/Settings.css
+++ b/client/src/components/Settings/Settings.css
@@ -113,6 +113,7 @@
     width: 30px;
     height: 30px;
     background-color: transparent;
+    overflow: hidden;
 }
 
 .btn-icon--green {

From 302faca32f849cb1d63202b0a905077fba56bfa9 Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Thu, 9 Jun 2022 12:07:29 +0300
Subject: [PATCH 076/143] Pull request: 4642 update dns addresses on encryption
 update

Updates #4642

Squashed commit of the following:

commit 75729120d3532dc2bd12b6c9e724a691043a1870
Merge: 5b681867 1c1ca1c6
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Jun 9 11:58:13 2022 +0300

    Merge branch 'master' into 4642-dns-privacy

commit 5b68186705c3a9287a44e33c8cf7ab79060f35a4
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Jun 7 18:39:02 2022 +0300

    fix

commit 46a9346154d33206e829a97021f3ef47ac2a5611
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Jun 7 18:18:18 2022 +0300

    client: update dns addresses on encryption update
---
 client/src/actions/encryption.js | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/client/src/actions/encryption.js b/client/src/actions/encryption.js
index 36faf2ec..2f58abd3 100644
--- a/client/src/actions/encryption.js
+++ b/client/src/actions/encryption.js
@@ -24,6 +24,7 @@ export const getTlsStatus = () => async (dispatch) => {
 export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST');
 export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE');
 export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS');
+export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
 
 export const setTlsConfig = (config) => async (dispatch, getState) => {
     dispatch(setTlsConfigRequest());
@@ -39,6 +40,12 @@ export const setTlsConfig = (config) => async (dispatch, getState) => {
         const response = await apiClient.setTlsConfig(values);
         response.certificate_chain = atob(response.certificate_chain);
         response.private_key = atob(response.private_key);
+
+        const dnsStatus = await apiClient.getGlobalStatus();
+        if (dnsStatus) {
+            dispatch(dnsStatusSuccess(dnsStatus));
+        }
+
         dispatch(setTlsConfigSuccess(response));
         dispatch(addSuccessToast('encryption_config_saved'));
         redirectToCurrentProtocol(response, httpPort);

From e738508d7a5e09e257724c557069000506e055ad Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 9 Jun 2022 17:47:05 +0300
Subject: [PATCH 077/143] Pull request: all: imp updater

Merge in DNS/adguard-home from imp-updater to master

Squashed commit of the following:

commit 6ed487359e56a35b36f13dcbf2efbf2a7a2d8734
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 9 16:29:35 2022 +0300

    all: imp logs, err handling

commit e930044cb619a43e5a44c230dadbe2228e9a93f5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 9 15:53:35 2022 +0300

    all: imp updater
---
 client/src/__locales/en.json                |  2 +-
 internal/aghalg/nullbool.go                 | 59 +++++++++++++++++++++
 internal/{dhcpd => aghalg}/nullbool_test.go | 23 ++++----
 internal/dhcpd/http.go                      | 19 +++----
 internal/dhcpd/nullbool.go                  | 58 --------------------
 internal/home/controlupdate.go              |  7 +--
 internal/updater/check.go                   | 30 +++++------
 internal/updater/updater.go                 | 34 +++++++-----
 internal/updater/updater_test.go            | 16 ++----
 scripts/make/build-release.sh               |  1 +
 10 files changed, 124 insertions(+), 125 deletions(-)
 create mode 100644 internal/aghalg/nullbool.go
 rename internal/{dhcpd => aghalg}/nullbool_test.go (72%)
 delete mode 100644 internal/dhcpd/nullbool.go

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index a5c21109..dd885677 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Disallowed domains",
     "access_blocked_desc": "Not to be confused with filters. AdGuard Home drops DNS queries matching these domains, and these queries don't even appear in the query log. You can specify exact domain names, wildcards, or URL filter rules, e.g. \"example.org\", \"*.example.org\", or \"||example.org^\" correspondingly.",
     "access_settings_saved": "Access settings successfully saved",
-    "updates_checked": "Updates successfully checked",
+    "updates_checked": "A new version of AdGuard Home is available",
     "updates_version_equal": "AdGuard Home is up-to-date",
     "check_updates_now": "Check for updates now",
     "dns_privacy": "DNS Privacy",
diff --git a/internal/aghalg/nullbool.go b/internal/aghalg/nullbool.go
new file mode 100644
index 00000000..3c5633e3
--- /dev/null
+++ b/internal/aghalg/nullbool.go
@@ -0,0 +1,59 @@
+package aghalg
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+)
+
+// NullBool is a nullable boolean.  Use these in JSON requests and responses
+// instead of pointers to bool.
+type NullBool uint8
+
+// NullBool values
+const (
+	NBNull NullBool = iota
+	NBTrue
+	NBFalse
+)
+
+// String implements the fmt.Stringer interface for NullBool.
+func (nb NullBool) String() (s string) {
+	switch nb {
+	case NBNull:
+		return "null"
+	case NBTrue:
+		return "true"
+	case NBFalse:
+		return "false"
+	}
+
+	return fmt.Sprintf("!invalid NullBool %d", uint8(nb))
+}
+
+// BoolToNullBool converts a bool into a NullBool.
+func BoolToNullBool(cond bool) (nb NullBool) {
+	if cond {
+		return NBTrue
+	}
+
+	return NBFalse
+}
+
+// type check
+var _ json.Unmarshaler = (*NullBool)(nil)
+
+// UnmarshalJSON implements the json.Unmarshaler interface for *NullBool.
+func (nb *NullBool) UnmarshalJSON(b []byte) (err error) {
+	if len(b) == 0 || bytes.Equal(b, []byte("null")) {
+		*nb = NBNull
+	} else if bytes.Equal(b, []byte("true")) {
+		*nb = NBTrue
+	} else if bytes.Equal(b, []byte("false")) {
+		*nb = NBFalse
+	} else {
+		return fmt.Errorf("unmarshalling json data into aghalg.NullBool: bad value %q", b)
+	}
+
+	return nil
+}
diff --git a/internal/dhcpd/nullbool_test.go b/internal/aghalg/nullbool_test.go
similarity index 72%
rename from internal/dhcpd/nullbool_test.go
rename to internal/aghalg/nullbool_test.go
index 549df608..0fe7f203 100644
--- a/internal/dhcpd/nullbool_test.go
+++ b/internal/aghalg/nullbool_test.go
@@ -1,9 +1,10 @@
-package dhcpd
+package aghalg_test
 
 import (
 	"encoding/json"
 	"testing"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -14,37 +15,37 @@ func TestNullBool_UnmarshalJSON(t *testing.T) {
 		name       string
 		wantErrMsg string
 		data       []byte
-		want       nullBool
+		want       aghalg.NullBool
 	}{{
 		name:       "empty",
 		wantErrMsg: "",
 		data:       []byte{},
-		want:       nbNull,
+		want:       aghalg.NBNull,
 	}, {
 		name:       "null",
 		wantErrMsg: "",
 		data:       []byte("null"),
-		want:       nbNull,
+		want:       aghalg.NBNull,
 	}, {
 		name:       "true",
 		wantErrMsg: "",
 		data:       []byte("true"),
-		want:       nbTrue,
+		want:       aghalg.NBTrue,
 	}, {
 		name:       "false",
 		wantErrMsg: "",
 		data:       []byte("false"),
-		want:       nbFalse,
+		want:       aghalg.NBFalse,
 	}, {
 		name:       "invalid",
-		wantErrMsg: `invalid nullBool value "invalid"`,
+		wantErrMsg: `unmarshalling json data into aghalg.NullBool: bad value "invalid"`,
 		data:       []byte("invalid"),
-		want:       nbNull,
+		want:       aghalg.NBNull,
 	}}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			var got nullBool
+			var got aghalg.NullBool
 			err := got.UnmarshalJSON(tc.data)
 			testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
 
@@ -53,9 +54,9 @@ func TestNullBool_UnmarshalJSON(t *testing.T) {
 	}
 
 	t.Run("json", func(t *testing.T) {
-		want := nbTrue
+		want := aghalg.NBTrue
 		var got struct {
-			A nullBool
+			A aghalg.NullBool
 		}
 
 		err := json.Unmarshal([]byte(`{"A":true}`), &got)
diff --git a/internal/dhcpd/http.go b/internal/dhcpd/http.go
index e340addb..62a08f66 100644
--- a/internal/dhcpd/http.go
+++ b/internal/dhcpd/http.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/golibs/errors"
@@ -145,7 +146,7 @@ type dhcpServerConfigJSON struct {
 	V4            *v4ServerConfJSON `json:"v4"`
 	V6            *v6ServerConfJSON `json:"v6"`
 	InterfaceName string            `json:"interface_name"`
-	Enabled       nullBool          `json:"enabled"`
+	Enabled       aghalg.NullBool   `json:"enabled"`
 }
 
 func (s *Server) handleDHCPSetConfigV4(
@@ -156,7 +157,7 @@ func (s *Server) handleDHCPSetConfigV4(
 	}
 
 	v4Conf := v4JSONToServerConf(conf.V4)
-	v4Conf.Enabled = conf.Enabled == nbTrue
+	v4Conf.Enabled = conf.Enabled == aghalg.NBTrue
 	if len(v4Conf.RangeStart) == 0 {
 		v4Conf.Enabled = false
 	}
@@ -183,7 +184,7 @@ func (s *Server) handleDHCPSetConfigV6(
 	}
 
 	v6Conf := v6JSONToServerConf(conf.V6)
-	v6Conf.Enabled = conf.Enabled == nbTrue
+	v6Conf.Enabled = conf.Enabled == aghalg.NBTrue
 	if len(v6Conf.RangeStart) == 0 {
 		v6Conf.Enabled = false
 	}
@@ -206,7 +207,7 @@ func (s *Server) handleDHCPSetConfigV6(
 
 func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
 	conf := &dhcpServerConfigJSON{}
-	conf.Enabled = boolToNullBool(s.conf.Enabled)
+	conf.Enabled = aghalg.BoolToNullBool(s.conf.Enabled)
 	conf.InterfaceName = s.conf.InterfaceName
 
 	err := json.NewDecoder(r.Body).Decode(conf)
@@ -230,7 +231,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if conf.Enabled == nbTrue && !v4Enabled && !v6Enabled {
+	if conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled {
 		aghhttp.Error(r, w, http.StatusBadRequest, "dhcpv4 or dhcpv6 configuration must be complete")
 
 		return
@@ -243,8 +244,8 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if conf.Enabled != nbNull {
-		s.conf.Enabled = conf.Enabled == nbTrue
+	if conf.Enabled != aghalg.NBNull {
+		s.conf.Enabled = conf.Enabled == aghalg.NBTrue
 	}
 
 	if conf.InterfaceName != "" {
@@ -279,11 +280,11 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
 
 type netInterfaceJSON struct {
 	Name         string   `json:"name"`
-	GatewayIP    net.IP   `json:"gateway_ip"`
 	HardwareAddr string   `json:"hardware_address"`
+	Flags        string   `json:"flags"`
+	GatewayIP    net.IP   `json:"gateway_ip"`
 	Addrs4       []net.IP `json:"ipv4_addresses"`
 	Addrs6       []net.IP `json:"ipv6_addresses"`
-	Flags        string   `json:"flags"`
 }
 
 func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/dhcpd/nullbool.go b/internal/dhcpd/nullbool.go
deleted file mode 100644
index b07f6768..00000000
--- a/internal/dhcpd/nullbool.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package dhcpd
-
-import (
-	"bytes"
-	"fmt"
-)
-
-// nullBool is a nullable boolean.  Use these in JSON requests and responses
-// instead of pointers to bool.
-//
-// TODO(a.garipov): Inspect uses of *bool, move this type into some new package
-// if we need it somewhere else.
-type nullBool uint8
-
-// nullBool values
-const (
-	nbNull nullBool = iota
-	nbTrue
-	nbFalse
-)
-
-// String implements the fmt.Stringer interface for nullBool.
-func (nb nullBool) String() (s string) {
-	switch nb {
-	case nbNull:
-		return "null"
-	case nbTrue:
-		return "true"
-	case nbFalse:
-		return "false"
-	}
-
-	return fmt.Sprintf("!invalid nullBool %d", uint8(nb))
-}
-
-// boolToNullBool converts a bool into a nullBool.
-func boolToNullBool(cond bool) (nb nullBool) {
-	if cond {
-		return nbTrue
-	}
-
-	return nbFalse
-}
-
-// UnmarshalJSON implements the json.Unmarshaler interface for *nullBool.
-func (nb *nullBool) UnmarshalJSON(b []byte) (err error) {
-	if len(b) == 0 || bytes.Equal(b, []byte("null")) {
-		*nb = nbNull
-	} else if bytes.Equal(b, []byte("true")) {
-		*nb = nbTrue
-	} else if bytes.Equal(b, []byte("false")) {
-		*nb = nbFalse
-	} else {
-		return fmt.Errorf("invalid nullBool value %q", b)
-	}
-
-	return nil
-}
diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go
index ae469598..08ddd455 100644
--- a/internal/home/controlupdate.go
+++ b/internal/home/controlupdate.go
@@ -12,6 +12,7 @@ import (
 	"syscall"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/AdGuardHome/internal/updater"
@@ -147,8 +148,8 @@ type versionResponse struct {
 // setAllowedToAutoUpdate sets CanAutoUpdate to true if AdGuard Home is actually
 // allowed to perform an automatic update by the OS.
 func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
-	if vr.CanAutoUpdate == nil || !*vr.CanAutoUpdate {
-		return
+	if vr.CanAutoUpdate != aghalg.NBTrue {
+		return nil
 	}
 
 	tlsConf := &tlsConfigSettings{}
@@ -162,7 +163,7 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
 		}
 	}
 
-	vr.CanAutoUpdate = &canUpdate
+	vr.CanAutoUpdate = aghalg.BoolToNullBool(canUpdate)
 
 	return nil
 }
diff --git a/internal/updater/check.go b/internal/updater/check.go
index ec7176b2..2bac6153 100644
--- a/internal/updater/check.go
+++ b/internal/updater/check.go
@@ -5,9 +5,9 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"strings"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghio"
 	"github.com/AdguardTeam/golibs/errors"
 )
@@ -17,11 +17,12 @@ const versionCheckPeriod = 8 * time.Hour
 
 // VersionInfo contains information about a new version.
 type VersionInfo struct {
-	CanAutoUpdate        *bool  `json:"can_autoupdate,omitempty"`
-	NewVersion           string `json:"new_version,omitempty"`
-	Announcement         string `json:"announcement,omitempty"`
-	AnnouncementURL      string `json:"announcement_url,omitempty"`
-	SelfUpdateMinVersion string `json:"-"`
+	NewVersion      string `json:"new_version,omitempty"`
+	Announcement    string `json:"announcement,omitempty"`
+	AnnouncementURL string `json:"announcement_url,omitempty"`
+	// TODO(a.garipov): See if the frontend actually still cares about
+	// nullability.
+	CanAutoUpdate aghalg.NullBool `json:"can_autoupdate,omitempty"`
 }
 
 // MaxResponseSize is responses on server's requests maximum length in bytes.
@@ -67,15 +68,13 @@ func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
 }
 
 func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
-	var canAutoUpdate bool
 	info := VersionInfo{
-		CanAutoUpdate: &canAutoUpdate,
+		CanAutoUpdate: aghalg.NBFalse,
 	}
 	versionJSON := map[string]string{
-		"version":                "",
-		"announcement":           "",
-		"announcement_url":       "",
-		"selfupdate_min_version": "",
+		"version":          "",
+		"announcement":     "",
+		"announcement_url": "",
 	}
 	err := json.Unmarshal(data, &versionJSON)
 	if err != nil {
@@ -91,14 +90,9 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
 	info.NewVersion = versionJSON["version"]
 	info.Announcement = versionJSON["announcement"]
 	info.AnnouncementURL = versionJSON["announcement_url"]
-	info.SelfUpdateMinVersion = versionJSON["selfupdate_min_version"]
 
 	packageURL, ok := u.downloadURL(versionJSON)
-	if ok &&
-		info.NewVersion != u.version &&
-		strings.TrimPrefix(u.version, "v") >= strings.TrimPrefix(info.SelfUpdateMinVersion, "v") {
-		canAutoUpdate = true
-	}
+	info.CanAutoUpdate = aghalg.BoolToNullBool(ok && info.NewVersion != u.version)
 
 	u.newVersion = info.NewVersion
 	u.packageURL = packageURL
diff --git a/internal/updater/updater.go b/internal/updater/updater.go
index 0cc49f9e..d975d977 100644
--- a/internal/updater/updater.go
+++ b/internal/updater/updater.go
@@ -104,11 +104,14 @@ func NewUpdater(conf *Config) *Updater {
 }
 
 // Update performs the auto-update.
-func (u *Updater) Update() error {
+func (u *Updater) Update() (err error) {
 	u.mu.Lock()
 	defer u.mu.Unlock()
 
-	err := u.prepare()
+	log.Info("updater: updating")
+	defer func() { log.Info("updater: finished; errors: %v", err) }()
+
+	err = u.prepare()
 	if err != nil {
 		return err
 	}
@@ -178,7 +181,12 @@ func (u *Updater) prepare() (err error) {
 	u.backupExeName = filepath.Join(u.backupDir, exeName)
 	u.updateExeName = filepath.Join(u.updateDir, exeName)
 
-	log.Info("Updating from %s to %s.  URL:%s", version.Version(), u.newVersion, u.packageURL)
+	log.Debug(
+		"updater: updating from %s to %s using url: %s",
+		version.Version(),
+		u.newVersion,
+		u.packageURL,
+	)
 
 	// TODO(a.garipov): Use os.Args[0] instead?
 	u.currentExeName = filepath.Join(u.workDir, exeName)
@@ -194,7 +202,7 @@ func (u *Updater) unpack() error {
 	var err error
 	_, pkgNameOnly := filepath.Split(u.packageURL)
 
-	log.Debug("updater: unpacking the package")
+	log.Debug("updater: unpacking package")
 	if strings.HasSuffix(pkgNameOnly, ".zip") {
 		u.unpackedFiles, err = zipFileUnpack(u.packageName, u.updateDir)
 		if err != nil {
@@ -229,7 +237,7 @@ func (u *Updater) check() error {
 }
 
 func (u *Updater) backup() error {
-	log.Debug("updater: backing up the current configuration")
+	log.Debug("updater: backing up current configuration")
 	_ = os.Mkdir(u.backupDir, 0o755)
 	err := copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"))
 	if err != nil {
@@ -252,7 +260,7 @@ func (u *Updater) replace() error {
 		return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", u.updateDir, u.workDir, err)
 	}
 
-	log.Debug("updater: renaming: %s -> %s", u.currentExeName, u.backupExeName)
+	log.Debug("updater: renaming: %s to %s", u.currentExeName, u.backupExeName)
 	err = os.Rename(u.currentExeName, u.backupExeName)
 	if err != nil {
 		return err
@@ -268,7 +276,7 @@ func (u *Updater) replace() error {
 		return err
 	}
 
-	log.Debug("updater: renamed: %s -> %s", u.updateExeName, u.currentExeName)
+	log.Debug("updater: renamed: %s to %s", u.updateExeName, u.currentExeName)
 
 	return nil
 }
@@ -297,7 +305,7 @@ func (u *Updater) downloadPackageFile(url, filename string) (err error) {
 		return fmt.Errorf("http request failed: %w", err)
 	}
 
-	log.Debug("updater: reading HTTP body")
+	log.Debug("updater: reading http body")
 	// This use of ReadAll is now safe, because we limited body's Reader.
 	body, err := io.ReadAll(r)
 	if err != nil {
@@ -343,7 +351,7 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
 	}
 
 	if hdr.Typeflag != tar.TypeReg {
-		log.Debug("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag)
+		log.Info("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag)
 
 		return "", nil
 	}
@@ -364,7 +372,7 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
 		return "", fmt.Errorf("io.Copy(): %w", err)
 	}
 
-	log.Tracef("updater: created file %s", outputName)
+	log.Debug("updater: created file %q", outputName)
 
 	return name, nil
 }
@@ -440,7 +448,7 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
 			return "", fmt.Errorf("os.Mkdir(%q): %w", outputName, err)
 		}
 
-		log.Tracef("created directory %q", outputName)
+		log.Debug("updater: created directory %q", outputName)
 
 		return "", nil
 	}
@@ -457,7 +465,7 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
 		return "", fmt.Errorf("io.Copy(): %w", err)
 	}
 
-	log.Tracef("created file %s", outputName)
+	log.Debug("updater: created file %q", outputName)
 
 	return name, nil
 }
@@ -516,7 +524,7 @@ func copySupportingFiles(files []string, srcdir, dstdir string) error {
 			return err
 		}
 
-		log.Debug("updater: copied: %q -> %q", src, dst)
+		log.Debug("updater: copied: %q to %q", src, dst)
 	}
 
 	return nil
diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go
index 3a29d277..771fb6d4 100644
--- a/internal/updater/updater_test.go
+++ b/internal/updater/updater_test.go
@@ -10,6 +10,7 @@ import (
 	"strconv"
 	"testing"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
 	"github.com/AdguardTeam/AdGuardHome/internal/version"
 	"github.com/AdguardTeam/golibs/testutil"
@@ -92,10 +93,7 @@ func TestUpdateGetVersion(t *testing.T) {
 	assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)
 	assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement)
 	assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL)
-	assert.Equal(t, "v0.0", info.SelfUpdateMinVersion)
-	if assert.NotNil(t, info.CanAutoUpdate) {
-		assert.True(t, *info.CanAutoUpdate)
-	}
+	assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
 
 	// check cached
 	_, err = u.VersionInfo(false)
@@ -290,10 +288,7 @@ func TestUpdater_VersionInto_ARM(t *testing.T) {
 	assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)
 	assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement)
 	assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL)
-	assert.Equal(t, "v0.0", info.SelfUpdateMinVersion)
-	if assert.NotNil(t, info.CanAutoUpdate) {
-		assert.True(t, *info.CanAutoUpdate)
-	}
+	assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
 }
 
 func TestUpdater_VersionInto_MIPS(t *testing.T) {
@@ -330,8 +325,5 @@ func TestUpdater_VersionInto_MIPS(t *testing.T) {
 	assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)
 	assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement)
 	assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL)
-	assert.Equal(t, "v0.0", info.SelfUpdateMinVersion)
-	if assert.NotNil(t, info.CanAutoUpdate) {
-		assert.True(t, *info.CanAutoUpdate)
-	}
+	assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
 }
diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh
index 36fc98f7..2cf95086 100644
--- a/scripts/make/build-release.sh
+++ b/scripts/make/build-release.sh
@@ -363,6 +363,7 @@ else
 fi
 readonly announcement_url
 
+# TODO(a.garipov): Remove "selfupdate_min_version" in future versions.
 rm -f "$version_json"
 echo "{
   \"version\": \"${version}\",

From d3f39b0aa168ddf89755e769a1477ae3c91ece3a Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Fri, 10 Jun 2022 12:41:20 +0300
Subject: [PATCH 078/143] Pull request: 4637 fix blocked services icons and
 actions highlight

Updates #4637

Squashed commit of the following:

commit d69887586d15582406fab642e576a46f8984107b
Merge: 65453371 e738508d
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 10 12:07:29 2022 +0300

    Merge branch 'master' into 4637-table

commit 65453371fc7309e772a12fb9f522247e1392a64a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Jun 9 18:43:44 2022 +0300

    client: fix blocked services icons and actions highlight
---
 client/src/components/Filters/Rewrites/Table.js          | 2 ++
 client/src/components/Filters/Table.js                   | 4 +++-
 client/src/components/Logs/Logs.css                      | 9 +++++++++
 client/src/components/Settings/Clients/ClientsTable.js   | 4 +++-
 .../src/components/Settings/Dhcp/StaticLeases/index.js   | 2 ++
 5 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/client/src/components/Filters/Rewrites/Table.js b/client/src/components/Filters/Rewrites/Table.js
index 45638ec0..28009ca3 100644
--- a/client/src/components/Filters/Rewrites/Table.js
+++ b/client/src/components/Filters/Rewrites/Table.js
@@ -29,6 +29,8 @@ class Table extends Component {
             Header: this.props.t('actions_table_header'),
             accessor: 'actions',
             maxWidth: 100,
+            sortable: false,
+            resizable: false,
             Cell: (value) => (
                 <div className="logs__row logs__row--center">
                     <button
diff --git a/client/src/components/Filters/Table.js b/client/src/components/Filters/Table.js
index 4eee8e9b..3030c0e7 100644
--- a/client/src/components/Filters/Table.js
+++ b/client/src/components/Filters/Table.js
@@ -36,6 +36,7 @@ class Table extends Component {
             Cell: this.renderCheckbox,
             width: 90,
             className: 'text-center',
+            resizable: false,
         },
         {
             Header: <Trans>name_table_header</Trans>,
@@ -77,10 +78,11 @@ class Table extends Component {
         },
         {
             Header: <Trans>actions_table_header</Trans>,
-            accessor: 'url',
+            accessor: 'actions',
             className: 'text-center',
             width: 100,
             sortable: false,
+            resizable: false,
             Cell: (row) => {
                 const { value } = row;
                 const { t, toggleFilteringModal, handleDelete } = this.props;
diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css
index 25015461..b6410411 100644
--- a/client/src/components/Logs/Logs.css
+++ b/client/src/components/Logs/Logs.css
@@ -338,10 +338,19 @@
     text-overflow: ellipsis;
 }
 
+.logs__row--icons {
+    flex-wrap: wrap;
+}
+
 .logs__table .logs__row {
     border-bottom: 2px solid var(--gray-216);
 }
 
+.logs__tag {
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
 /* QUERY_STATUS_COLORS */
 .logs__row--blue {
     background-color: var(--blue);
diff --git a/client/src/components/Settings/Clients/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable.js
index 065157bd..2f08eb61 100644
--- a/client/src/components/Settings/Clients/ClientsTable.js
+++ b/client/src/components/Settings/Clients/ClientsTable.js
@@ -193,7 +193,7 @@ class ClientsTable extends Component {
                     <div className="logs__row o-hidden">
                         <span className="logs__text">
                             {value.map((tag) => (
-                                <div key={tag} title={tag} className="small">
+                                <div key={tag} title={tag} className="logs__tag small">
                                     {tag}
                                 </div>
                             ))}
@@ -225,6 +225,8 @@ class ClientsTable extends Component {
             Header: this.props.t('actions_table_header'),
             accessor: 'actions',
             maxWidth: 100,
+            sortable: false,
+            resizable: false,
             Cell: (row) => {
                 const clientName = row.original.name;
                 const {
diff --git a/client/src/components/Settings/Dhcp/StaticLeases/index.js b/client/src/components/Settings/Dhcp/StaticLeases/index.js
index bdd7cec5..a63f78cd 100644
--- a/client/src/components/Settings/Dhcp/StaticLeases/index.js
+++ b/client/src/components/Settings/Dhcp/StaticLeases/index.js
@@ -70,6 +70,8 @@ const StaticLeases = ({
                         Header: <Trans>actions_table_header</Trans>,
                         accessor: 'actions',
                         maxWidth: 150,
+                        sortable: false,
+                        resizable: false,
                         // eslint-disable-next-line react/display-name
                         Cell: (row) => {
                             const { ip, mac, hostname } = row.original;

From 5956b97e7fe8806575d2bfd6899af0bcc4dbe1cb Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 14 Jun 2022 14:01:51 +0300
Subject: [PATCH 079/143] Pull request: more sysv

Merge in DNS/adguard-home from 4480-sysv-again to master

Updates #4480.

Squashed commit of the following:

commit 263fa05ab19de95b18fb07f6c89e4b9a1b24657b
Merge: 360a6468 d3f39b0a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 14 13:36:15 2022 +0300

    Merge branch 'master' into 4480-sysv-again

commit 360a646833ca9e0e01cb6d085e70b898a30dc2d0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 9 18:15:41 2022 +0300

    home: rename linux file

commit c3032533b7e00136c25d15a4ad771bb8a9c13e31
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 9 18:06:25 2022 +0300

    home: imp code

commit 2381c4a6ab4f6dca88123ff7b0a92f2cf9a420a8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 9 17:48:22 2022 +0300

    home: wrap sysv service
---
 internal/home/service_linux.go  | 76 +++++++++++++++++++++++++++++++++
 internal/home/service_others.go |  6 ++-
 2 files changed, 80 insertions(+), 2 deletions(-)
 create mode 100644 internal/home/service_linux.go

diff --git a/internal/home/service_linux.go b/internal/home/service_linux.go
new file mode 100644
index 00000000..f27b7717
--- /dev/null
+++ b/internal/home/service_linux.go
@@ -0,0 +1,76 @@
+//go:build linux
+// +build linux
+
+package home
+
+import (
+	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
+	"github.com/kardianos/service"
+)
+
+func chooseSystem() {
+	if sys := service.ChosenSystem(); sys.String() == "unix-systemv" {
+		service.ChooseSystem(sysvSystem{System: sys})
+	}
+}
+
+// sysvSystem is a wrapper for service.System that wraps the service.Service
+// while creating a new one.
+//
+// TODO(e.burkov):  File a PR to github.com/kardianos/service.
+type sysvSystem struct {
+	// System is expected to have an unexported type
+	// *service.linuxSystemService.
+	service.System
+}
+
+// New returns a wrapped service.Service.
+func (sys sysvSystem) New(i service.Interface, c *service.Config) (s service.Service, err error) {
+	s, err = sys.System.New(i, c)
+	if err != nil {
+		return s, err
+	}
+
+	return sysvService{
+		Service: s,
+		name:    c.Name,
+	}, nil
+}
+
+// sysvService is a wrapper for a service.Service that also calls update-rc.d in
+// a proper way on installing and uninstalling.
+type sysvService struct {
+	// Service is expected to have an unexported type *service.sysv.
+	service.Service
+	// name stores the name of the service to call updating script with it.
+	name string
+}
+
+// Install wraps service.Service.Install call with calling the updating script.
+func (svc sysvService) Install() (err error) {
+	err = svc.Service.Install()
+	if err != nil {
+		// Don't wrap an error since it's informative enough as is.
+		return err
+	}
+
+	_, _, err = aghos.RunCommand("update-rc.d", svc.name, "defaults")
+
+	// Don't wrap an error since it's informative enough as is.
+	return err
+}
+
+// Uninstall wraps service.Service.Uninstall call with calling the updating
+// script.
+func (svc sysvService) Uninstall() (err error) {
+	err = svc.Service.Uninstall()
+	if err != nil {
+		// Don't wrap an error since it's informative enough as is.
+		return err
+	}
+
+	_, _, err = aghos.RunCommand("update-rc.d", svc.name, "remove")
+
+	// Don't wrap an error since it's informative enough as is.
+	return err
+}
diff --git a/internal/home/service_others.go b/internal/home/service_others.go
index 83aa63ea..6e2afd10 100644
--- a/internal/home/service_others.go
+++ b/internal/home/service_others.go
@@ -1,6 +1,8 @@
-//go:build !openbsd
-// +build !openbsd
+//go:build !(openbsd || linux)
+// +build !openbsd,!linux
 
 package home
 
+// chooseSystem checks the current system detected and substitutes it with local
+// implementation if needed.
 func chooseSystem() {}

From 0edf71a4af0602106f09cb3270904e2abdbce28c Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Tue, 14 Jun 2022 18:55:08 +0300
Subject: [PATCH 080/143] Pull request: 4659 fix url value in filter table
 actions

Updates #4659

Squashed commit of the following:

commit e1bcda9566bd9f1cca965f4308c337a9adf2ce04
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Jun 14 17:40:09 2022 +0300

    client: fix url value in filter table actions
---
 client/src/components/Filters/Table.js | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/client/src/components/Filters/Table.js b/client/src/components/Filters/Table.js
index 3030c0e7..53c22721 100644
--- a/client/src/components/Filters/Table.js
+++ b/client/src/components/Filters/Table.js
@@ -84,7 +84,8 @@ class Table extends Component {
             sortable: false,
             resizable: false,
             Cell: (row) => {
-                const { value } = row;
+                const { original } = row;
+                const { url } = original;
                 const { t, toggleFilteringModal, handleDelete } = this.props;
 
                 return (
@@ -95,7 +96,7 @@ class Table extends Component {
                             title={t('edit_table_action')}
                             onClick={() => toggleFilteringModal({
                                 type: MODAL_TYPE.EDIT_FILTERS,
-                                url: value,
+                                url,
                             })
                             }
                         >
@@ -106,7 +107,7 @@ class Table extends Component {
                         <button
                             type="button"
                             className="btn btn-icon btn-outline-secondary btn-sm"
-                            onClick={() => handleDelete(value)}
+                            onClick={() => handleDelete(url)}
                             title={t('delete_table_action')}
                         >
                             <svg className="icons">

From a7a5e50620b34a2401912292471dcb008e84c389 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 14 Jun 2022 19:09:49 +0300
Subject: [PATCH 081/143] Pull request: client: upd i18n

Updates #4665.

Squashed commit of the following:

commit 848dee4caf1f52b7b71dcef8c488ba939003e56f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 14 18:59:56 2022 +0300

    client: upd i18n
---
 client/src/__locales/be.json    | 2 +-
 client/src/__locales/cs.json    | 2 +-
 client/src/__locales/da.json    | 2 +-
 client/src/__locales/de.json    | 2 +-
 client/src/__locales/es.json    | 2 +-
 client/src/__locales/fa.json    | 4 +---
 client/src/__locales/fi.json    | 2 +-
 client/src/__locales/fr.json    | 2 +-
 client/src/__locales/hr.json    | 2 +-
 client/src/__locales/hu.json    | 2 +-
 client/src/__locales/id.json    | 2 +-
 client/src/__locales/it.json    | 2 +-
 client/src/__locales/ja.json    | 2 +-
 client/src/__locales/ko.json    | 2 +-
 client/src/__locales/nl.json    | 2 +-
 client/src/__locales/no.json    | 2 +-
 client/src/__locales/pl.json    | 2 +-
 client/src/__locales/pt-br.json | 2 +-
 client/src/__locales/pt-pt.json | 2 +-
 client/src/__locales/ro.json    | 2 +-
 client/src/__locales/ru.json    | 2 +-
 client/src/__locales/sk.json    | 2 +-
 client/src/__locales/sl.json    | 2 +-
 client/src/__locales/sr-cs.json | 2 +-
 client/src/__locales/sv.json    | 2 +-
 client/src/__locales/tr.json    | 2 +-
 client/src/__locales/uk.json    | 2 +-
 client/src/__locales/vi.json    | 4 ++--
 client/src/__locales/zh-cn.json | 2 +-
 client/src/__locales/zh-tw.json | 2 +-
 30 files changed, 31 insertions(+), 33 deletions(-)

diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json
index 7231b448..8d20bccb 100644
--- a/client/src/__locales/be.json
+++ b/client/src/__locales/be.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Заблакаваныя дамены",
     "access_blocked_desc": "Не блытайце гэта з фільтрамі. AdGuard Home будзе ігнараваць DNS-запыты з гэтымі даменамі.",
     "access_settings_saved": "Налады доступу паспяхова захаваны",
-    "updates_checked": "Праверка абнаўленняў прайшла паспяхова",
+    "updates_checked": "Даступная новая версія AdGuard Home",
     "updates_version_equal": "Версія AdGuard Home актуальная",
     "check_updates_now": "Праверыць абнаўленні",
     "dns_privacy": "Зашыфраваны DNS",
diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json
index 32443267..fd89077d 100644
--- a/client/src/__locales/cs.json
+++ b/client/src/__locales/cs.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Blokované domény",
     "access_blocked_desc": "Nezaměňujte to s filtry. AdGuard Home zruší dotazy DNS odpovídající těmto doménám a tyto dotazy se neobjeví ani v protokolu dotazů. Zde můžete určit přesné názvy domén, zástupné znaky a pravidla filtrování URL adres, např. \"example.org\", \"*.example.org\" nebo \"||example.org^\".",
     "access_settings_saved": "Nastavení přístupu bylo úspěšně uloženo",
-    "updates_checked": "Aktualizace úspěšně zkontrolovány",
+    "updates_checked": "Nová verze AdGuard Home je k dispozici\n",
     "updates_version_equal": "AdGuard Home je aktuální",
     "check_updates_now": "Zkontrolovat aktualizace nyní",
     "dns_privacy": "Soukromí DNS",
diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json
index 721870f7..2b21f4e8 100644
--- a/client/src/__locales/da.json
+++ b/client/src/__locales/da.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Ikke tilladte domæner",
     "access_blocked_desc": "Ikke at forveksle med filtre. AdGuard Home dropper DNS-forespørgsler matchende disse domæner, ej heller vil forespørgslerne optræde i forespørgselsloggen. Der kan angives præcise domænenavne, jokertegn eller URL-filterregler, f.eks. \"eksempel.org\", \"*.eksempel.org\", \"||eksempel.org^\" eller tilsvarende.",
     "access_settings_saved": "Adgangsindstillinger gemt",
-    "updates_checked": "Opdateringstjek foretaget",
+    "updates_checked": "En ny version af AdGuard Home er tilgængelig\n",
     "updates_version_equal": "AdGuard Home er opdateret",
     "check_updates_now": "Søg efter opdateringer nu",
     "dns_privacy": "DNS-fortrolighed",
diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json
index 5241e4a6..edb2eb80 100644
--- a/client/src/__locales/de.json
+++ b/client/src/__locales/de.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Nicht zugelassene Domains",
     "access_blocked_desc": "Verwechseln Sie dies nicht mit Filtern. AdGuard Home verwirft DNS-Abfragen, die mit diesen Domänen übereinstimmen, und diese Abfragen erscheinen nicht einmal im Abfrageprotokoll. Hier können Sie die genauen Domain-Namen, Wildcards und URL-Filter-Regeln angeben, z.B. 'beispiel.org', '*.beispiel.org' oder '||beispiel.org^'.",
     "access_settings_saved": "Zugriffseinstellungen erfolgreich gespeichert",
-    "updates_checked": "Erfolgreich auf Aktualisierungen geprüft",
+    "updates_checked": "Neue Version von AdGuard Home ist jetzt verfügbar",
     "updates_version_equal": "AdGuard Home ist aktuell",
     "check_updates_now": "Jetzt nach Aktualisierungen suchen",
     "dns_privacy": "DNS-Datenschutz",
diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json
index b08dc5e0..47712ee1 100644
--- a/client/src/__locales/es.json
+++ b/client/src/__locales/es.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Dominios no permitidos",
     "access_blocked_desc": "No debe confundirse con filtros. AdGuard Home descartará las consultas DNS que coincidan con estos dominios, y estas consultas ni siquiera aparecerán en el registro de consultas. Puedes especificar nombres de dominio exactos, comodines o reglas de filtrado de URL, por ejemplo: \"ejemplo.org\", \"*.ejemplo.org\" o \"||ejemplo.org^\" correspondientemente.",
     "access_settings_saved": "Configuración de acceso guardado correctamente",
-    "updates_checked": "Actualizaciones comprobadas correctamente",
+    "updates_checked": "La nueva versión de AdGuard Home está disponible",
     "updates_version_equal": "AdGuard Home está actualizado",
     "check_updates_now": "Buscar actualizaciones ahora",
     "dns_privacy": "DNS cifrado",
diff --git a/client/src/__locales/fa.json b/client/src/__locales/fa.json
index 20cc372d..aef9e5d3 100644
--- a/client/src/__locales/fa.json
+++ b/client/src/__locales/fa.json
@@ -6,7 +6,6 @@
     "bootstrap_dns": "خودراه انداز سرورهای DNS",
     "bootstrap_dns_desc": "خودراه انداز سرورهای DNS برای تفکیک آدرس آی پی  تفکیک کننده های DoH/DoT که شما بعنوان جریان ارسالی تعیین کردید استفاده میشود.",
     "local_ptr_title": "سرورهای خصوصی DNS",
-    "local_ptr_desc": "سرور یا سرور های DNS ای که AdGuard Home برای درخواست های منابع محلی ارائه شده مورد استفاده قرار خواهد داد. برای مثال، این سرور برای تعیین نام های سرویس دهنده برای سرویس گیرنده با آدرس های آی پی خصوصی مورد استفاده قرار خواهد گرفت. اگر تعیین نشود،AdGuard Home به طور خودکار از تعیین کننده ی DNS پیش فرض شما استفاده خواهد کرد.",
     "local_ptr_default_resolver": "به طور پیش فرض، AdGuard Home از تعیین کننده های DNS معکوس زیر استفاده می کند: {{ip}}.",
     "local_ptr_no_default_resolver": "AdGuard Home نتوانست برای این دستگاه تعیین کننده های DNS معکوس محرمانه مناسب را معین کند.",
     "local_ptr_placeholder": "در هر خط یک آدرس سرور را وارد کنید",
@@ -321,7 +320,6 @@
     "install_devices_android_list_5": "گروه مقادیر DNS 1 و DNS 2 را به آدرس سرور AdGuard Home خود تغییر دهید.",
     "install_devices_ios_list_1": "از صفحه خانه،تنظیمات را فشار دهید.",
     "install_devices_ios_list_2": "وای فای را از منوی چپ انتخاب کنید (پیکربندی DNS دستی برای ارتباط موبایلی غیرممکن است).",
-    "install_devices_ios_list_3": "روی نام شبکه فعال فعلی کلیک کنید.",
     "install_devices_ios_list_4": "در فیلد DNS آدرس سرور AdGuard Home را وارد کنید",
     "get_started": "شروع به کار",
     "next": "بعدی",
@@ -413,7 +411,7 @@
     "access_blocked_title": "دامنه های مسدود شده",
     "access_blocked_desc": "این را با فیلتر ها به اشتباه نگیرید.AdGuard Home  جستار DNS را با این دامنه ها در جستار سوال ها نمی پذیرد.",
     "access_settings_saved": "تنظیمات دسترسی با موفقیت ذخیره شد",
-    "updates_checked": "بروز رسانی با موفقیت بررسی شد",
+    "updates_checked": "نسخه جدیدی از AdGuard Home در دسترس است",
     "updates_version_equal": "AdGuard Home بروز است",
     "check_updates_now": "حالا بررسی برای بروز رسانی",
     "dns_privacy": "حریم خصوصی DNS",
diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json
index 11baddb6..130a7313 100644
--- a/client/src/__locales/fi.json
+++ b/client/src/__locales/fi.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Kielletyt verkkotunnukset",
     "access_blocked_desc": "Ei pidä sekoittaa suodattimiin. AdGuard Home hylkää näiden verkkotunnusten DNS-pyynnöt, eivätkä nämä pyynnöt näy edes pyyntöhistoriassa. Tähän voidaan syöttää tarkkoja verkkotunnuksia, jokerimerkkejä tai URL-suodatussääntöjä, kuten \"example.org\", \"*.example.org\" tai \"||example.org^\".",
     "access_settings_saved": "Käytön asetukset tallennettiin",
-    "updates_checked": "Päivitykset tarkastettiin",
+    "updates_checked": "Uusi versio AdGuard Home -ohjelmasta on saatavana\n",
     "updates_version_equal": "AdGuard Home on ajan tasalla",
     "check_updates_now": "Tarkista päivitykset nyt",
     "dns_privacy": "DNS-tietosuoja",
diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json
index fe0a451b..9c12c3a9 100644
--- a/client/src/__locales/fr.json
+++ b/client/src/__locales/fr.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Domaines interdits",
     "access_blocked_desc": "A ne pas confondre avec les filtres. AdGuard Home rejette les requêtes DNS correspondant à ces domaines, et ces requêtes n'apparaissent même pas dans le journal des requêtes. Vous pouvez spécifier des noms de domaine exacts, des caractères génériques ou des règles de filtrage d'URL, par exemple « exemple.org », « *.exemple.org » ou « ||example.org^ » de manière correspondante.",
     "access_settings_saved": "Paramètres d'accès enregistrés avec succès",
-    "updates_checked": "Mises à jour vérifiées",
+    "updates_checked": "Une nouvelle version de AdGuard Home est disponible",
     "updates_version_equal": "AdGuard Home est à jour",
     "check_updates_now": "Vérifier les mises à jour",
     "dns_privacy": "Confidentialité DNS",
diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json
index 8aecb338..4f07548e 100644
--- a/client/src/__locales/hr.json
+++ b/client/src/__locales/hr.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Nedopuštene domene",
     "access_blocked_desc": "Ne smije se miješati s filterima. AdGuard Home ispušta DNS upite koji odgovaraju tim domenama, a ti se upiti čak i ne pojavljuju u zapisniku upita. Možete navesti točne nazive domena, zamjenske znakove ili pravila filtriranja URL-a, npr || example.org example.org. example.org^\" u skladu s tim.",
     "access_settings_saved": "Postavke pristupa su uspješno spremljene",
-    "updates_checked": "Uspješna provjera ažuriranja",
+    "updates_checked": "Dostupna je nova verzija AdGuard Home-a",
     "updates_version_equal": "AdGuard Home je ažuriran",
     "check_updates_now": "Provjeri ažuriranja sada",
     "dns_privacy": "DNS privatnost",
diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json
index 952a1ef7..2f76426f 100644
--- a/client/src/__locales/hu.json
+++ b/client/src/__locales/hu.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Nem engedélyezett domainek",
     "access_blocked_desc": "Ne keverje össze ezt a szűrőkkel. Az AdGuard Home az összes DNS kérést el fogja dobni, ami ezekkel a domainekkel megegyezik, és ezek a lekérések nem is fognak megjelenni a lekérdezési naplóban sem. Megadhatja a pontos domain neveket, a helyettesítő karaktereket vagy az URL szűrési szabályokat, pl. ennek megfelelően \"example.org\", \"*.example.org\", vagy \"||example.org^\".",
     "access_settings_saved": "A hozzáférési beállítások sikeresen mentésre kerültek",
-    "updates_checked": "A frissítések sikeresen ellenőrizve lettek",
+    "updates_checked": "Elérhető az AdGuard Home új verziója",
     "updates_version_equal": "Az AdGuard Home naprakész",
     "check_updates_now": "Frissítések ellenőrzése most",
     "dns_privacy": "DNS Adatvédelem",
diff --git a/client/src/__locales/id.json b/client/src/__locales/id.json
index 73dc7f93..4f655806 100644
--- a/client/src/__locales/id.json
+++ b/client/src/__locales/id.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Domain yang diblokir",
     "access_blocked_desc": "Jangan bingung dengan filter. AdGuard Home menghapus kueri DNS yang cocok dengan domain ini, dan kueri ini bahkan tidak muncul di log kueri. Anda dapat menentukan nama domain, karakter pengganti, atau aturan filter URL yang tepat, mis. \"example.org\", \"*.example.org\", atau \"||example.org^\" yang sesuai.",
     "access_settings_saved": "Pengaturan akses berhasil disimpan",
-    "updates_checked": "Pembaruan berhasil dicek",
+    "updates_checked": "Versi baru AdGuard Home tersedia\n",
     "updates_version_equal": "AdGuard Home sudah tebaru",
     "check_updates_now": "Periksa pembaruan sekarang",
     "dns_privacy": "DNS Privasi",
diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json
index 6bb28e5d..1b6d584b 100644
--- a/client/src/__locales/it.json
+++ b/client/src/__locales/it.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Domini bloccati",
     "access_blocked_desc": "Da non confondere con i filtri. AdGuard Home eliminerà le richieste DNS corrispondenti a questi domini e queste richieste non verranno visualizzate nel relativo registro. Puoi specificare nomi di dominio esatti, caratteri jolly o regole di filtraggio URL, ad esempio \"esempio.org\", \"*.esempio.org\" o \"||esempio.org^\".",
     "access_settings_saved": "Impostazioni di accesso salvate correttamente",
-    "updates_checked": "Verifica aggiornamenti riuscita",
+    "updates_checked": "Nuova versione di AdGuard Home è disponibile",
     "updates_version_equal": "AdGuard Home è aggiornato",
     "check_updates_now": "Ricerca aggiornamenti ora",
     "dns_privacy": "Privacy DNS",
diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json
index a253afab..bd11b6d5 100644
--- a/client/src/__locales/ja.json
+++ b/client/src/__locales/ja.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "拒否するドメイン",
     "access_blocked_desc": "こちらをフィルタと混同しないでください。AdGuard Homeは、ここで入力されたドメインに一致するDNSクエリをドロップし、そういったクエリはクエリログにも表示されません。ここでは、「example.org」、「*.example.org」、「 ||example.org^ 」など、特定のドメイン名、ワイルドカード、URLフィルタルールを入力できます。",
     "access_settings_saved": "アクセス設定の保存に成功しました",
-    "updates_checked": "アップデートの確認に成功しました",
+    "updates_checked": "AdGuard Homeの新バージョンが利用可能です。",
     "updates_version_equal": "AdGuard Homeは既に最新です",
     "check_updates_now": "今すぐアップデートを確認する",
     "dns_privacy": "DNSプライバシー",
diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json
index c1d12499..440251d1 100644
--- a/client/src/__locales/ko.json
+++ b/client/src/__locales/ko.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "차단된 도메인",
     "access_blocked_desc": "이 기능을 필터와 혼동하지 마세요. AdGuard Home은 이 도메인에 대한 DNS 요청을 무시합니다. 여기에서는 'example.org' '*. example.org', '|| example.org ^'와 같은 특정 도메인 이름, 와일드 카드, URL 필터 규칙을 지정할 수 있습니다.",
     "access_settings_saved": "액세스 설정이 성공적으로 저장되었습니다.",
-    "updates_checked": "업데이트가 성공적으로 확인되었습니다",
+    "updates_checked": "AdGuard Home의 새 버전을 사용할 수 있습니다",
     "updates_version_equal": "AdGuard Home 최신 상태입니다.",
     "check_updates_now": "지금 업데이트 확인",
     "dns_privacy": "DNS 프라이버시",
diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json
index 05c09c74..f6266d46 100644
--- a/client/src/__locales/nl.json
+++ b/client/src/__locales/nl.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Niet toegelaten domeinen",
     "access_blocked_desc": "Verwar dit niet met filters. AdGuard Home zal deze DNS-zoekopdrachten niet uitvoeren die deze domeinen in de zoekopdracht bevatten. Hier kan je de exacte domeinnamen, wildcards en URL-filter-regels specifiëren, bijv. \"example.org\", \"*.example.org\" of \"||example.org^\".",
     "access_settings_saved": "Toegangsinstellingen succesvol opgeslagen",
-    "updates_checked": "Met succes op updates gecontroleerd",
+    "updates_checked": "Een nieuwe versie van AdGuard Home is beschikbaar\n",
     "updates_version_equal": "AdGuard Home is actueel",
     "check_updates_now": "Controleer op updates",
     "dns_privacy": "DNS Privacy",
diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json
index 4225aa3c..edb63617 100644
--- a/client/src/__locales/no.json
+++ b/client/src/__locales/no.json
@@ -429,7 +429,7 @@
     "access_blocked_title": "Blokkerte domener",
     "access_blocked_desc": "Ikke forveksle dette med filtre. AdGuard Home vil nekte å behandle DNS-forespørsler som har disse domenene, og disse forespørslene dukker ikke engang opp i forespørselsloggen. Du kan spesifisere nøyaktige domene navn, jokertegn, eller URL-filterregler, f.eks. «example.org», «*.example.log» eller «||example.org^» derav.",
     "access_settings_saved": "Tilgangsinnstillingene ble vellykket lagret",
-    "updates_checked": "Oppdateringene ble vellykket sett etter",
+    "updates_checked": "En ny versjon av AdGuard Home er tilgjengelig",
     "updates_version_equal": "AdGuard Home er fullt oppdatert",
     "check_updates_now": "Se etter oppdateringer nå",
     "dns_privacy": "DNS-privatliv",
diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json
index 96f93e5b..08e5afb6 100644
--- a/client/src/__locales/pl.json
+++ b/client/src/__locales/pl.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Niedozwolone domeny",
     "access_blocked_desc": "Nie należy ich mylić z filtrami. AdGuard Home usuwa zapytania DNS pasujące do tych domen, a zapytania te nie pojawiają się nawet w dzienniku zapytań. Możesz określić dokładne nazwy domen, symbole wieloznaczne lub reguły filtrowania adresów URL, np. \"example.org\", \"*.example.org\" lub \"||example.org^\".",
     "access_settings_saved": "Ustawienia dostępu zostały pomyślnie zapisane",
-    "updates_checked": "Aktualizacje pomyślnie sprawdzone",
+    "updates_checked": "Dostępna jest nowa wersja programu AdGuard Home\n",
     "updates_version_equal": "AdGuard Home jest aktualny",
     "check_updates_now": "Sprawdź aktualizacje teraz",
     "dns_privacy": "Prywatny DNS",
diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json
index 2f8770b9..27a30fa1 100644
--- a/client/src/__locales/pt-br.json
+++ b/client/src/__locales/pt-br.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Domínios bloqueados",
     "access_blocked_desc": "Não deve ser confundido com filtros. O AdGuard Home elimina as consultas DNS que correspondem a esses domínios, e essas consultas nem aparecem no registro de consultas. Você pode especificar nomes de domínio exatos, caracteres curinga ou regras de filtro de URL, por exemplo \"exemplo.org\", \"*.exemplo.org\", ou \"||exemplo.org^\" correspondentemente.",
     "access_settings_saved": "Configurações de acesso foram salvas com sucesso",
-    "updates_checked": "Atualizações verificadas com sucesso",
+    "updates_checked": "Uma nova versão do AdGuard Home está disponível\n",
     "updates_version_equal": "O AdGuard Home está atualizado.",
     "check_updates_now": "Verificar atualizações",
     "dns_privacy": "Privacidade de DNS",
diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json
index 2698f60a..7d1d6e7f 100644
--- a/client/src/__locales/pt-pt.json
+++ b/client/src/__locales/pt-pt.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Domínios bloqueados",
     "access_blocked_desc": "Não deve ser confundido com filtros. O AdGuard Home elimina as consultas DNS que correspondem a esses domínios, e essas consultas nem aparecem no registro de consultas. Você pode especificar nomes de domínio exatos, caracteres curinga ou regras de filtro de URL, por exemplo \"exemplo.org\", \"*.exemplo.org\", ou \"||exemplo.org^\" correspondentemente.",
     "access_settings_saved": "Definições de acesso foram guardadas com sucesso",
-    "updates_checked": "Atualizações verificadas com sucesso",
+    "updates_checked": "Uma nova versão do AdGuard Home está disponível\n",
     "updates_version_equal": "O AdGuard Home está atualizado",
     "check_updates_now": "Verificar atualizações",
     "dns_privacy": "Privacidade de DNS",
diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json
index a360ce5c..9e6ac0a0 100644
--- a/client/src/__locales/ro.json
+++ b/client/src/__locales/ro.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Domenii blocate",
     "access_blocked_desc": "A nu se confunda cu filtrele. AdGuard Home respinge cererile DNS pentru aceste domenii, iar aceste cereri nici măcar nu apar în jurnalul de solicitări. Puteți specifica nume exacte de domenii, metacaractere sau reguli de filtrare URL, cum ar fi \"example.org\", \"*.exemple.org\" sau \"||example.org^\" în mod corespunzător.",
     "access_settings_saved": "Setările de acces au fost salvate cu succes",
-    "updates_checked": "Actualizările au fost verificate cu succes",
+    "updates_checked": "Este disponibilă o nouă versiune de AdGuard Home\n",
     "updates_version_equal": "AdGuard Home este la zi",
     "check_updates_now": "Verificați actualizările acum",
     "dns_privacy": "Confidențialitate DNS",
diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json
index 431dfcc9..f2be1e68 100644
--- a/client/src/__locales/ru.json
+++ b/client/src/__locales/ru.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Неразрешённые домены",
     "access_blocked_desc": "Не путать с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами. Здесь вы можете уточнить точные имена доменов, шаблоны, правила URL-фильтрации, например, «example.org», «*.example.org» или «||example.org».",
     "access_settings_saved": "Настройки доступа успешно сохранены",
-    "updates_checked": "Проверка обновлений прошла успешно",
+    "updates_checked": "Доступна новая версия AdGuard Home",
     "updates_version_equal": "Версия AdGuard Home актуальна",
     "check_updates_now": "Проверить обновления",
     "dns_privacy": "Зашифрованный DNS",
diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json
index 5982bb52..229ebee2 100644
--- a/client/src/__locales/sk.json
+++ b/client/src/__locales/sk.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Nepovolené domény",
     "access_blocked_desc": "Nesmie byť zamieňaná s filtrami. AdGuard Home zruší DNS dopyty, ktoré sa zhodujú s týmito doménami, a tieto dopyty sa nezobrazia ani v denníku dopytov. Môžete určiť presné názvy domén, zástupné znaky alebo pravidlá filtrovania URL adries, napr. \"example.org\", \"*.example.org\" alebo ||example.org^\" zodpovedajúcim spôsobom.",
     "access_settings_saved": "Nastavenia prístupu úspešne uložené",
-    "updates_checked": "Aktualizácie úspešne skontrolované",
+    "updates_checked": "K dispozícii je nová verzia aplikácie AdGuard Home\n",
     "updates_version_equal": "AdGuard Home je aktuálny",
     "check_updates_now": "Skontrolovať aktualizácie teraz",
     "dns_privacy": "DNS súkromie",
diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json
index 0c4ddb35..7e69c7c0 100644
--- a/client/src/__locales/sl.json
+++ b/client/src/__locales/sl.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Prepovedane domene",
     "access_blocked_desc": "Ne gre zamenjati s filtri. AdGuard Home spusti poizvedbe DNS, ki se ujemajo s temi domenami, in te poizvedbe se niti ne pojavijo v dnevniku poizvedb. Določite lahko natančna imena domen, nadomestne znake ali pravila filtriranja URL-jev, npr. ustrezno \"example.org\", \"*.example.org\" ali \"|| example.org ^\".",
     "access_settings_saved": "Nastavitve dostopa so uspešno shranjene",
-    "updates_checked": "Posodobitve so uspešno preverjene",
+    "updates_checked": "Na voljo je nova različica programa AdGuard Home\n",
     "updates_version_equal": "AdGuard Home je posodobljen",
     "check_updates_now": "Preveri obstoj posodobitev zdaj",
     "dns_privacy": "Zasebnost DNS",
diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json
index 2a68e53c..bdeaf6aa 100644
--- a/client/src/__locales/sr-cs.json
+++ b/client/src/__locales/sr-cs.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Blokirani domeni",
     "access_blocked_desc": "Da ne bude zabune sa filterima. AdGuard Home odustaje od DNS upita koji se podudaraju sa ovim domenima, a ovi upiti se čak i ne pojavljuju u evidenciji upita. Možete da navedete tačna imena domena, džoker znakove ili pravila URL filtera, npr. \"example.org\", \"*.example.org\" ili \"|| example.org^\" dopisno.",
     "access_settings_saved": "Postavke pristupa su uspešno sačuvane",
-    "updates_checked": "Ažuriranja su uspešno proverena",
+    "updates_checked": "Dostupna je nova verzija AdGuard Home-a",
     "updates_version_equal": "AdGuard Home je ažuriran na najnoviju verziju",
     "check_updates_now": "Proveri da li postoje ispravke",
     "dns_privacy": "DNS privatnost",
diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json
index 1e4789a8..e0b7e70e 100644
--- a/client/src/__locales/sv.json
+++ b/client/src/__locales/sv.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Blockerade domäner",
     "access_blocked_desc": "Ej att förväxla med filter. AdGuard Home kastar DNS-frågor som matchar dessa domäner, och dessa frågor visas inte ens i frågeloggen. Du kan ange exakta domännamn, jokertecken eller URL-filterregler, t.ex. \"example.org\", \"*.example.org\" eller \"||example.org^\" på motsvarande sätt.",
     "access_settings_saved": "Åtkomstinställningar sparade",
-    "updates_checked": "Sökning efter uppdateringar genomförd",
+    "updates_checked": "En ny version av AdGuard Home är tillgänglig\n",
     "updates_version_equal": "AdGuard Home är uppdaterat",
     "check_updates_now": "Sök efter uppdateringar nu",
     "dns_privacy": "DNS-Integritet",
diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json
index 49412d85..b9f481e5 100644
--- a/client/src/__locales/tr.json
+++ b/client/src/__locales/tr.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "İzin verilmeyen alan adları",
     "access_blocked_desc": "Bu işlem filtrelerle ilgili değildir. AdGuard Home, bu alan adlarından gelen DNS sorgularını yanıtsız bırakır ve bu sorgular sorgu günlüğünde görünmez. Tam alan adlarını, joker karakterleri veya URL filtre kurallarını belirtebilirsiniz, ör. \"example.org\", \"*.example.org\" veya \"||example.org^\".",
     "access_settings_saved": "Erişim ayarları başarıyla kaydedildi!",
-    "updates_checked": "Güncelleme kontrolü başarılı",
+    "updates_checked": "AdGuard Home'un yeni bir sürümü mevcut",
     "updates_version_equal": "AdGuard Home yazılımı güncel durumda",
     "check_updates_now": "Güncellemeleri şimdi denetle",
     "dns_privacy": "DNS Gizliliği",
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index cbbdfe80..3434c814 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "Заборонені домени",
     "access_blocked_desc": "Не плутайте з фільтрами. AdGuard Home буде ігнорувати DNS-запити з цими доменами, такі запити навіть не будуть записані до журналу. Ви можете вказати точні доменні імена, замінні знаки та правила фільтрування URL-адрес, наприклад, 'example.org', '*.example.org' або '||example.org^' відповідно.",
     "access_settings_saved": "Налаштування доступу успішно збережено",
-    "updates_checked": "Оновлення успішно перевірені",
+    "updates_checked": "Доступна нова версія AdGuard Home",
     "updates_version_equal": "AdGuard Home останньої версії",
     "check_updates_now": "Перевірити наявність оновлень",
     "dns_privacy": "Конфіденційність DNS",
diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json
index a4d15e24..a8aab1ad 100644
--- a/client/src/__locales/vi.json
+++ b/client/src/__locales/vi.json
@@ -220,7 +220,7 @@
     "all_lists_up_to_date_toast": "Tất cả danh sách đã ở phiên bản mới nhất",
     "updated_upstream_dns_toast": "Các máy chủ thượng nguồn đã được lưu thành công",
     "dns_test_ok_toast": "Máy chủ DNS có thể sử dụng",
-    "dns_test_not_ok_toast": "Máy chủ \"\"': không thể sử dụng, vui lòng kiểm tra lại",
+    "dns_test_not_ok_toast": "Máy chủ \"{{key}}\"': không thể sử dụng, vui lòng kiểm tra lại",
     "unblock": "Bỏ chặn",
     "block": "Chặn",
     "disallow_this_client": "Không cho phép client này",
@@ -445,7 +445,7 @@
     "access_blocked_title": "Tên miền bị chặn",
     "access_blocked_desc": "Đừng nhầm lẫn điều này với các bộ lọc. AdGuard Home sẽ bỏ các truy vấn DNS với các tên miền này trong câu hỏi của truy vấn.",
     "access_settings_saved": "Cài đặt truy cập đã lưu thành công",
-    "updates_checked": "Đã kiểm tra thành công cập nhật",
+    "updates_checked": "Phiên bản mới của AdGuard Home có sẵn",
     "updates_version_equal": "AdGuard Home đã được cập nhật",
     "check_updates_now": "Kiểm tra cập nhật ngay bây giờ",
     "dns_privacy": "DNS Riêng Tư",
diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json
index 9882b41a..4d1a8637 100644
--- a/client/src/__locales/zh-cn.json
+++ b/client/src/__locales/zh-cn.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "不允许的域名",
     "access_blocked_desc": "不要将此功能与过滤器混淆。AdGuard Home 将排除匹配这些网域的 DNS 查询,并且这些查询将不会在查询日志中显示。在此可以明确指定域名、通配符(wildcard)和网址过滤的规则,例如 \"example.org\"、\"*.example.org\" 或 \"||example.org^\"。",
     "access_settings_saved": "访问设置保存成功",
-    "updates_checked": "检查更新成功",
+    "updates_checked": "AdGuard Home 的新版本现在可用",
     "updates_version_equal": "AdGuard Home已经是最新版本",
     "check_updates_now": "立即检查更新",
     "dns_privacy": "DNS 隐私",
diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json
index f183d749..b01c39b3 100644
--- a/client/src/__locales/zh-tw.json
+++ b/client/src/__locales/zh-tw.json
@@ -445,7 +445,7 @@
     "access_blocked_title": "未被允許的網域",
     "access_blocked_desc": "不要把這個和過濾器混淆。AdGuard Home 排除與這些網域相符的 DNS 查詢,且這些查詢甚至不會出現在查詢記錄中。您可相應地明確指定確切的域名、萬用字元(wildcard)或網址過濾器的規則,例如,\"example.org\"、\"*.example.org\" 或 \"||example.org^\"。",
     "access_settings_saved": "存取設定被成功地儲存",
-    "updates_checked": "更新被成功地檢查",
+    "updates_checked": "AdGuard Home 的新版本為可用的",
     "updates_version_equal": "AdGuard Home 為最新的",
     "check_updates_now": "立即檢查更新",
     "dns_privacy": "DNS 隱私",

From f987c2559825923b22e910d01c2d42fb06231acc Mon Sep 17 00:00:00 2001
From: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Tue, 14 Jun 2022 20:02:12 +0300
Subject: [PATCH 082/143] scripts: imp docs; upd alpine

---
 scripts/make/Dockerfile      | 2 +-
 scripts/make/build-docker.sh | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/scripts/make/Dockerfile b/scripts/make/Dockerfile
index a731e3a6..b87ee360 100644
--- a/scripts/make/Dockerfile
+++ b/scripts/make/Dockerfile
@@ -1,6 +1,6 @@
 # A docker file for scripts/make/build-docker.sh.
 
-FROM alpine:3.13
+FROM alpine:3.16
 
 ARG BUILD_DATE
 ARG VERSION
diff --git a/scripts/make/build-docker.sh b/scripts/make/build-docker.sh
index 844d7da2..917e7771 100644
--- a/scripts/make/build-docker.sh
+++ b/scripts/make/build-docker.sh
@@ -50,6 +50,11 @@ readonly docker_image_name
 
 # Set DOCKER_OUTPUT to 'type=image,name=adguard/adguard-home,push=true' if you
 # want (and are allowed) to push to DockerHub.
+#
+# If you want to inspect the resulting image using commands like "docker image
+# ls", change type to docker and also set docker_platforms to a single platform.
+#
+# See https://github.com/docker/buildx/issues/166.
 docker_output="${DOCKER_OUTPUT:-type=image,name=${docker_image_name},push=false}"
 readonly docker_output
 

From 8f4acce44a892e7cf7cf62693307dd829fe86d04 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 20 Jun 2022 15:24:11 +0300
Subject: [PATCH 083/143] Pull request: 4677 openwrt service

Merge in DNS/adguard-home from 4677-openwrt-service to master

Updates #4677.

Squashed commit of the following:

commit 6aed4036d3338a601a7ec5ef1ca74a407ae4c0e2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 20 14:49:33 2022 +0300

    home: imp docs

commit 54e32fa47ed11e50c6405ced90a400e4e69f021d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 20 14:30:08 2022 +0300

    home: fix wrt svc
---
 CHANGELOG.md                   | 5 +++++
 internal/home/service_linux.go | 9 ++++++++-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed949cc3..6ac34558 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,8 +30,13 @@ and this project adheres to
 
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
 
+### Fixed
+
+- Broken service installation on OpenWrt ([#4677]).
+
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
+[#4677]: https://github.com/AdguardTeam/AdGuardHome/issues/4677
 
 [ddr-draft-06]:   https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
 
diff --git a/internal/home/service_linux.go b/internal/home/service_linux.go
index f27b7717..c885529b 100644
--- a/internal/home/service_linux.go
+++ b/internal/home/service_linux.go
@@ -9,7 +9,14 @@ import (
 )
 
 func chooseSystem() {
-	if sys := service.ChosenSystem(); sys.String() == "unix-systemv" {
+	sys := service.ChosenSystem()
+	// By default, package service uses the SysV system if it cannot detect
+	// anything other, but the update-rc.d fix should not be applied on OpenWrt,
+	// so exclude it explicitly.
+	//
+	// See https://github.com/AdguardTeam/AdGuardHome/issues/4480 and
+	// https://github.com/AdguardTeam/AdGuardHome/issues/4677.
+	if sys.String() == "unix-systemv" && !aghos.IsOpenWrt() {
 		service.ChooseSystem(sysvSystem{System: sys})
 	}
 }

From ce1b2bc4f17b2123e9a16ad0665061505c437376 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 20 Jun 2022 17:48:56 +0300
Subject: [PATCH 084/143] Pull request: 4463 Improve DDR priority

Merge in DNS/adguard-home from 4463-ddr-prior to master

Updates #4463.

Squashed commit of the following:

commit 30b470abe6ea6a58b50a49715c77342018be9491
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 20 17:33:56 2022 +0300

    dnsforward: imp docs

commit 1ba099c2cc318e00b390c4e1b8770aee970d5c20
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 20 17:12:46 2022 +0300

    dnsforward: imp ddr priority
---
 internal/dnsforward/dns.go      | 10 +++++++---
 internal/dnsforward/dns_test.go |  4 ++--
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go
index 55a38a2f..3c444e2c 100644
--- a/internal/dnsforward/dns.go
+++ b/internal/dnsforward/dns.go
@@ -275,7 +275,11 @@ func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
 	return resultCodeSuccess
 }
 
-// makeDDRResponse creates DDR answer according to server configuration.
+// makeDDRResponse creates DDR answer according to server configuration.  The
+// contructed SVCB resource records have the priority of 1 for each entry,
+// similar to examples provided by https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html.
+//
+// TODO(a.meshkov):  Consider setting the priority values based on the protocol.
 func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
 	resp = s.makeResponse(req)
 	// TODO(e.burkov):  Think about storing the FQDN version of the server's
@@ -307,7 +311,7 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
 
 		ans := &dns.SVCB{
 			Hdr:      s.hdr(req, dns.TypeSVCB),
-			Priority: 2,
+			Priority: 1,
 			Target:   domainName,
 			Value:    values,
 		}
@@ -323,7 +327,7 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
 
 		ans := &dns.SVCB{
 			Hdr:      s.hdr(req, dns.TypeSVCB),
-			Priority: 3,
+			Priority: 1,
 			Target:   domainName,
 			Value:    values,
 		}
diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go
index b9c7e47b..1e0537dc 100644
--- a/internal/dnsforward/dns_test.go
+++ b/internal/dnsforward/dns_test.go
@@ -31,7 +31,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
 	}
 
 	dotSVCB := &dns.SVCB{
-		Priority: 2,
+		Priority: 1,
 		Target:   ddrTestFQDN,
 		Value: []dns.SVCBKeyValue{
 			&dns.SVCBAlpn{Alpn: []string{"dot"}},
@@ -40,7 +40,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
 	}
 
 	doqSVCB := &dns.SVCB{
-		Priority: 3,
+		Priority: 1,
 		Target:   ddrTestFQDN,
 		Value: []dns.SVCBKeyValue{
 			&dns.SVCBAlpn{Alpn: []string{"doq"}},

From 006cd98869989d1b66844eb5ce21f7d0fcc3b1a9 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 27 Jun 2022 15:50:50 +0300
Subject: [PATCH 085/143] Pull request: client: upd i18n

Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 0d2956422f6b417b0a58da176315818a92e8c466
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jun 27 15:42:07 2022 +0300

    client: upd i18n
---
 client/src/__locales/fi.json    | 10 +++++-----
 client/src/__locales/no.json    |  4 ++--
 client/src/__locales/pl.json    |  2 +-
 client/src/__locales/ru.json    |  2 +-
 client/src/__locales/si-lk.json |  2 +-
 client/src/__locales/uk.json    |  4 ++--
 client/src/__locales/zh-tw.json |  2 +-
 7 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json
index 130a7313..fe18c7d2 100644
--- a/client/src/__locales/fi.json
+++ b/client/src/__locales/fi.json
@@ -344,12 +344,12 @@
     "install_devices_macos_list_2": "Paina \"Verkko\".",
     "install_devices_macos_list_3": "Valitse listan ensimmäinen yhteys ja paina \"Lisävalinnat\".",
     "install_devices_macos_list_4": "Valitse DNS-välilehti ja syötä AdGuard Home -palvelimesi osoitteet.",
-    "install_devices_android_list_1": "Napauta Android-laitteesi aloitusnäytöstä tai sovellusvalikosta \"Asetukset\".",
-    "install_devices_android_list_2": "Napauta \"Yhteydet\" ja sitten \"Wi-Fi\". Näytetään kaikki käytettävissä olevat langattomat verkot (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).",
-    "install_devices_android_list_3": "Napauta yhdistetyn verkon vieressä olevaa asetuskuvaketta tai paina verkkoa pitkään ja valitse \"Muokkaa verkkoa\".",
-    "install_devices_android_list_4": "Saatat joutua napauttamaan \"Lisäasetukset\" nähdäksesi lisää valintoja. Muuttaaksesi DNS-asetuksia, on \"IP-asetukset\" -kohdan \"DHCP\" -valinta vaihdettava \"Staattinen\" -valintaan.",
+    "install_devices_android_list_1": "Paina Android-laitteesi aloitusnäytöstä tai sovellusvalikosta \"Asetukset\".",
+    "install_devices_android_list_2": "Paina \"Yhteydet\" ja sitten \"Wi-Fi\". Kaikki käytettävissä olevat langattomat verkot näytetään (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).",
+    "install_devices_android_list_3": "Paina yhdistetyn verkon vieressä olevaa asetuskuvaketta tai paina verkkoa pitkään ja valitse \"Muokkaa verkkoa\".",
+    "install_devices_android_list_4": "Saatat joutua painamaan \"Lisäasetukset\" nähdäksesi enemmän valintoja. Muuttaaksesi DNS-asetuksia, on \"IP-asetukset\" -kohdan \"DHCP\" -valinta vaihdettava \"Staattinen\" -valintaan.",
     "install_devices_android_list_5": "Syötä \"DNS 1\" ja \"DNS 2\" -kenttiin AdGuard Home -palvelimesi osoitteet.",
-    "install_devices_ios_list_1": "Napauta aloitusnäytöstä \"Asetukset\".",
+    "install_devices_ios_list_1": "Paina aloitusnäytöstä \"Asetukset\".",
     "install_devices_ios_list_2": "Valitse vasemmalta \"Wi-Fi\" (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).",
     "install_devices_ios_list_3": "Valitse tällä hetkellä aktiivinen verkko.",
     "install_devices_ios_list_4": "Syötä \"DNS\" -kenttään AdGuard Home -palvelimesi osoitteet.",
diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json
index edb63617..665df717 100644
--- a/client/src/__locales/no.json
+++ b/client/src/__locales/no.json
@@ -104,7 +104,7 @@
     "stats_adult": "Blokkerte voksennettsteder",
     "stats_query_domain": "Mest forespurte domener",
     "for_last_24_hours": "de siste 24 timene",
-    "for_last_days": "det siste døgnet",
+    "for_last_days": "for den siste {{count}} dagen",
     "for_last_days_plural": "de siste {{count}} dagene",
     "stats_disabled": "Statistikkene har blitt skrudd av. Du kan skru den på fra <0>innstillingssiden</0>.",
     "stats_disabled_short": "Statistikkene har blitt skrudd av",
@@ -114,7 +114,7 @@
     "top_clients": "Vanligste klienter",
     "no_clients_found": "Ingen klienter ble funnet",
     "general_statistics": "Generelle statistikker",
-    "number_of_dns_query_days": "Antall DNS-forespørsler som ble behandlet det siste døgnet",
+    "number_of_dns_query_days": "Antall DNS-spørringer behandlet for de siste {{count}} dagene",
     "number_of_dns_query_days_plural": "Antall DNS-forespørsler som ble behandlet de siste {{count}} dagene",
     "number_of_dns_query_24_hours": "Antall DNS-forespørsler som ble behandlet de siste 24 timene",
     "number_of_dns_query_blocked_24_hours": "Antall DNS-forespørsler som ble blokkert av adblock-filtre, hosts-lister, og domene-lister",
diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json
index 08e5afb6..7cd4179a 100644
--- a/client/src/__locales/pl.json
+++ b/client/src/__locales/pl.json
@@ -117,7 +117,7 @@
     "stats_adult": "Zablokowane witryny dla dorosłych",
     "stats_query_domain": "Najczęściej wyszukiwane domeny",
     "for_last_24_hours": "przez ostatnie 24 godziny",
-    "for_last_days": "z ostatniego dnia",
+    "for_last_days": "za ostatni dzień {{count}}",
     "for_last_days_plural": "z ostatnich {{count}} dni",
     "stats_disabled": "Statystyki zostały wyłączone. Można je włączyć na <0>stronie ustawień</0>.",
     "stats_disabled_short": "Statystyki zostały wyłączone",
diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json
index f2be1e68..c96fccae 100644
--- a/client/src/__locales/ru.json
+++ b/client/src/__locales/ru.json
@@ -589,7 +589,7 @@
     "validated_with_dnssec": "Подтверждено с помощью DNSSEC",
     "all_queries": "Все запросы",
     "show_blocked_responses": "Заблокировано",
-    "show_whitelisted_responses": "В белом списке",
+    "show_whitelisted_responses": "Разрешённые",
     "show_processed_responses": "Обработан",
     "blocked_safebrowsing": "Заблокировано согласно базе данных Safe Browsing",
     "blocked_adult_websites": "Заблокировано Родительским контролем",
diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json
index ea6d29b1..adfb71f7 100644
--- a/client/src/__locales/si-lk.json
+++ b/client/src/__locales/si-lk.json
@@ -414,12 +414,12 @@
     "setup_dns_privacy_android_2": "<1>HTTPS-හරහා-ව.නා.ප.</1> සහ <1>TLS-හරහා-ව.නා.ප.</1> සඳහා <0>ඇන්ඩ්‍රොයිඩ් සඳහා ඇඩ්ගාර්ඩ්</0> සහාය දක්වයි.",
     "setup_dns_privacy_other_title": "වෙනත් ක්‍රියාවට නැංවූ දෑ",
     "setup_dns_privacy_other_2": "<0>ඩීඑන්එස්ප්‍රොක්සි</0> දන්නා සියලුම ආරක්‍ෂිත ව.නා.ප. කෙටුම්පත් සඳහා සහාය දක්වයි.",
-    "setup_dns_privacy_other_3": "<1>DNS-over-HTTPS</1> සඳහා <0>dnscrypt-පෙරකලාසිය</0> සහාය දක්වයි.",
     "setup_dns_privacy_other_4": "<1>DNS-over-HTTPS</1> සඳහා <0>මොසිල්ලා ෆයර්ෆොක්ස්</0> සහාය දක්වයි.",
     "setup_dns_privacy_other_5": "<0>මෙහි</0> සහ <1>මෙහි</1> තවත් ක්‍රියාවට නැංවූ දෑ ඔබට හමුවනු ඇත.",
     "setup_dns_privacy_ioc_mac": "අයිඕඑස් සහ මැක්ඕඑස් වින්‍යාසය",
     "setup_dns_notice": "ඔබට <1>DNS-over-HTTPS</1> හෝ <1>DNS-over-TLS</1> භාවිතයට ඇඩ්ගාර්ඩ් හෝම් සැකසුම් තුළ <0>සංකේතනය වින්‍යාසගත</0> කිරීමට ඇවැසිය.",
     "rewrite_added": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම සාර්ථකව එකතු කෙරිණි",
+    "rewrite_deleted": "\"{{key}}\" සඳහා ව. නා. ප. නැවත ලිවීම සාර්ථකව ඉවත් කෙරිණි",
     "rewrite_add": "ව.නා.ප. නැවත ලිවීමක් එකතු කරන්න",
     "rewrite_not_found": "ව.නා.ප. නැවත ලිවීම් හමු නොවිණි",
     "rewrite_confirm_delete": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?",
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index 3434c814..abc37b2e 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -105,7 +105,7 @@
     "copyright": "Авторське право",
     "homepage": "Домашня сторінка",
     "report_an_issue": "Повідомити про проблему",
-    "privacy_policy": "Політика приватності",
+    "privacy_policy": "Політика конфіденційності",
     "enable_protection": "Увімкнути захист",
     "enabled_protection": "Захист увімкнено",
     "disable_protection": "Вимкнути захист",
@@ -591,7 +591,7 @@
     "show_blocked_responses": "Заблоковані",
     "show_whitelisted_responses": "Дозволені",
     "show_processed_responses": "Оброблені",
-    "blocked_safebrowsing": "Заблоковано Безпечним переглядом",
+    "blocked_safebrowsing": "Заблоковано модулем «Безпека перегляду»",
     "blocked_adult_websites": "Заблоковано «Батьківським контролем»",
     "blocked_threats": "Заблоковано загроз",
     "allowed": "Дозволено",
diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json
index b01c39b3..7ee2ac31 100644
--- a/client/src/__locales/zh-tw.json
+++ b/client/src/__locales/zh-tw.json
@@ -630,6 +630,6 @@
     "use_saved_key": "使用該先前已儲存的金鑰",
     "parental_control": "家長控制",
     "safe_browsing": "安全瀏覽",
-    "served_from_cache": "{{value}} <i>(由快取提供)</i>",
+    "served_from_cache": "{{value}} <i>(由快取提供)</i>",
     "form_error_password_length": "密碼必須為至少長 {{value}} 個字元"
 }

From 14d8f58592cbb192216cfe98e28e9b38e40a97c5 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 28 Jun 2022 19:09:26 +0300
Subject: [PATCH 086/143] Pull request: 4699 dhcp ptr

Merge in DNS/adguard-home from 4699-dhcp-ptr to master

Closes #4699.

Squashed commit of the following:

commit 0a8e2b3e22b7fad28a53db65031cc39d8755ecf4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 18:40:53 2022 +0300

    dnsforward: imp naming again

commit 0b0884a8305f18f7f69560b86be8837933e220e9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 18:26:58 2022 +0300

    dnsforward: imp naming

commit e193f53d9a1dd76d41396c06e2ec5a1e7d176557
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 17:26:00 2022 +0300

    all: imp chlog

commit 8ac9f84f086d9cb0b0f9da72bfc51f9b70a3dab7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 17:18:48 2022 +0300

    all: log changes

commit 7cdc175d02b6eacfcb6ba62a5424d11e2561a879
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 17:03:52 2022 +0300

    dnsforward: add tld to dhcp leased hostnames
---
 CHANGELOG.md                           |  3 ++
 internal/dnsforward/dns.go             | 27 +++++------
 internal/dnsforward/dns_test.go        | 66 +++++++++++++-------------
 internal/dnsforward/dnsforward.go      | 15 +-----
 internal/dnsforward/dnsforward_test.go |  8 ++--
 5 files changed, 55 insertions(+), 64 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ac34558..0490a5f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,11 +32,14 @@ and this project adheres to
 
 ### Fixed
 
+- PTR requests for addresses leased by DHCP will now be resolved into hostnames
+  under `dhcp.local_domain_name` ([#4699]).
 - Broken service installation on OpenWrt ([#4677]).
 
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
 [#4677]: https://github.com/AdguardTeam/AdGuardHome/issues/4677
+[#4699]: https://github.com/AdguardTeam/AdGuardHome/issues/4699
 
 [ddr-draft-06]:   https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
 
diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go
index 3c444e2c..f6008e36 100644
--- a/internal/dnsforward/dns.go
+++ b/internal/dnsforward/dns.go
@@ -100,9 +100,9 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
 		s.processInitial,
 		s.processDDRQuery,
 		s.processDetermineLocal,
-		s.processInternalHosts,
+		s.processDHCPHosts,
 		s.processRestrictLocal,
-		s.processInternalIPAddrs,
+		s.processDHCPAddrs,
 		s.processFilteringBeforeRequest,
 		s.processLocalPTR,
 		s.processUpstream,
@@ -230,12 +230,10 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
 				)
 			}
 
-			lowhost := strings.ToLower(l.Hostname)
+			lowhost := strings.ToLower(l.Hostname + "." + s.localDomainSuffix)
+			ip := netutil.CloneIP(l.IP)
 
-			ipToHost.Set(l.IP, lowhost)
-
-			ip := make(net.IP, 4)
-			copy(ip, l.IP.To4())
+			ipToHost.Set(ip, lowhost)
 			hostToIP[lowhost] = ip
 		}
 
@@ -376,11 +374,11 @@ func (s *Server) hostToIP(host string) (ip net.IP, ok bool) {
 	return ip, true
 }
 
-// processInternalHosts respond to A requests if the target hostname is known to
+// processDHCPHosts respond to A requests if the target hostname is known to
 // the server.
 //
 // TODO(a.garipov): Adapt to AAAA as well.
-func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
+func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
 	if !s.dhcpServer.Enabled() {
 		return resultCodeSuccess
 	}
@@ -395,11 +393,10 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
 		return resultCodeSuccess
 	}
 
-	reqHost := strings.ToLower(q.Name)
+	reqHost := strings.ToLower(q.Name[:len(q.Name)-1])
 	// TODO(a.garipov): Move everything related to DHCP local domain to the DHCP
 	// server.
-	host := strings.TrimSuffix(reqHost, s.localDomainSuffix)
-	if host == reqHost {
+	if !strings.HasSuffix(reqHost, s.localDomainSuffix) {
 		return resultCodeSuccess
 	}
 
@@ -412,7 +409,7 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
 		return resultCodeFinish
 	}
 
-	ip, ok := s.hostToIP(host)
+	ip, ok := s.hostToIP(reqHost)
 	if !ok {
 		// TODO(e.burkov): Inspect special cases when user want to apply some
 		// rules handled by other processors to the hosts with TLD.
@@ -469,7 +466,7 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
 
 	// Restrict an access to local addresses for external clients.  We also
 	// assume that all the DHCP leases we give are locally-served or at least
-	// don't need to be inaccessible externally.
+	// don't need to be accessible externally.
 	if !s.privateNets.Contains(ip) {
 		log.Debug("dns: addr %s is not from locally-served network", ip)
 
@@ -526,7 +523,7 @@ func (s *Server) ipToHost(ip net.IP) (host string, ok bool) {
 
 // Respond to PTR requests if the target IP is leased by our DHCP server and the
 // requestor is inside the local network.
-func (s *Server) processInternalIPAddrs(ctx *dnsContext) (rc resultCode) {
+func (s *Server) processDHCPAddrs(ctx *dnsContext) (rc resultCode) {
 	d := ctx.proxyCtx
 	if d.Res != nil {
 		return resultCodeSuccess
diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go
index 1e0537dc..25e28afd 100644
--- a/internal/dnsforward/dns_test.go
+++ b/internal/dnsforward/dns_test.go
@@ -229,7 +229,7 @@ func TestServer_ProcessDetermineLocal(t *testing.T) {
 	}
 }
 
-func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
+func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
 	knownIP := net.IP{1, 2, 3, 4}
 
 	testCases := []struct {
@@ -270,7 +270,7 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
 				dhcpServer:        &testDHCP{},
 				localDomainSuffix: defaultLocalDomainSuffix,
 				tableHostToIP: hostToIPTable{
-					"example": knownIP,
+					"example." + defaultLocalDomainSuffix: knownIP,
 				},
 			}
 
@@ -292,7 +292,7 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
 				isLocalClient: tc.isLocalCli,
 			}
 
-			res := s.processInternalHosts(dctx)
+			res := s.processDHCPHosts(dctx)
 			require.Equal(t, tc.wantRes, res)
 			pctx := dctx.proxyCtx
 			if tc.wantRes == resultCodeFinish {
@@ -318,10 +318,10 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
 	}
 }
 
-func TestServer_ProcessInternalHosts(t *testing.T) {
+func TestServer_ProcessDHCPHosts(t *testing.T) {
 	const (
 		examplecom = "example.com"
-		examplelan = "example.lan"
+		examplelan = "example." + defaultLocalDomainSuffix
 	)
 
 	knownIP := net.IP{1, 2, 3, 4}
@@ -370,41 +370,41 @@ func TestServer_ProcessInternalHosts(t *testing.T) {
 	}, {
 		name:    "success_custom_suffix",
 		host:    "example.custom",
-		suffix:  ".custom.",
+		suffix:  "custom",
 		wantIP:  knownIP,
 		wantRes: resultCodeSuccess,
 		qtyp:    dns.TypeA,
 	}}
 
 	for _, tc := range testCases {
+		s := &Server{
+			dhcpServer:        &testDHCP{},
+			localDomainSuffix: tc.suffix,
+			tableHostToIP: hostToIPTable{
+				"example." + tc.suffix: knownIP,
+			},
+		}
+
+		req := &dns.Msg{
+			MsgHdr: dns.MsgHdr{
+				Id: 1234,
+			},
+			Question: []dns.Question{{
+				Name:   dns.Fqdn(tc.host),
+				Qtype:  tc.qtyp,
+				Qclass: dns.ClassINET,
+			}},
+		}
+
+		dctx := &dnsContext{
+			proxyCtx: &proxy.DNSContext{
+				Req: req,
+			},
+			isLocalClient: true,
+		}
+
 		t.Run(tc.name, func(t *testing.T) {
-			s := &Server{
-				dhcpServer:        &testDHCP{},
-				localDomainSuffix: tc.suffix,
-				tableHostToIP: hostToIPTable{
-					"example": knownIP,
-				},
-			}
-
-			req := &dns.Msg{
-				MsgHdr: dns.MsgHdr{
-					Id: 1234,
-				},
-				Question: []dns.Question{{
-					Name:   dns.Fqdn(tc.host),
-					Qtype:  tc.qtyp,
-					Qclass: dns.ClassINET,
-				}},
-			}
-
-			dctx := &dnsContext{
-				proxyCtx: &proxy.DNSContext{
-					Req: req,
-				},
-				isLocalClient: true,
-			}
-
-			res := s.processInternalHosts(dctx)
+			res := s.processDHCPHosts(dctx)
 			pctx := dctx.proxyCtx
 			assert.Equal(t, tc.wantRes, res)
 			if tc.wantRes == resultCodeFinish {
diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go
index c0cd0e55..9d0e8854 100644
--- a/internal/dnsforward/dnsforward.go
+++ b/internal/dnsforward/dnsforward.go
@@ -107,7 +107,7 @@ type Server struct {
 // when no suffix is provided.
 //
 // See the documentation for Server.localDomainSuffix.
-const defaultLocalDomainSuffix = ".lan."
+const defaultLocalDomainSuffix = "lan"
 
 // DNSCreateParams are parameters to create a new server.
 type DNSCreateParams struct {
@@ -120,17 +120,6 @@ type DNSCreateParams struct {
 	LocalDomain string
 }
 
-// domainNameToSuffix converts a domain name into a local domain suffix.
-func domainNameToSuffix(tld string) (suffix string) {
-	l := len(tld) + 2
-	b := make([]byte, l)
-	b[0] = '.'
-	copy(b[1:], tld)
-	b[l-1] = '.'
-
-	return string(b)
-}
-
 const (
 	// recursionTTL is the time recursive request is cached for.
 	recursionTTL = 1 * time.Second
@@ -151,7 +140,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
 			return nil, fmt.Errorf("local domain: %w", err)
 		}
 
-		localDomainSuffix = domainNameToSuffix(p.LocalDomain)
+		localDomainSuffix = p.LocalDomain
 	}
 
 	if p.Anonymizer == nil {
diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go
index 36761f41..d6cad013 100644
--- a/internal/dnsforward/dnsforward_test.go
+++ b/internal/dnsforward/dnsforward_test.go
@@ -1016,10 +1016,13 @@ func (d *testDHCP) Leases(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) {
 func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {}
 
 func TestPTRResponseFromDHCPLeases(t *testing.T) {
+	const localDomain = "lan"
+
 	s, err := NewServer(DNSCreateParams{
 		DNSFilter:   filtering.New(&filtering.Config{}, nil),
 		DHCPServer:  &testDHCP{},
 		PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
+		LocalDomain: localDomain,
 	})
 	require.NoError(t, err)
 
@@ -1033,14 +1036,13 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
 
 	err = s.Start()
 	require.NoError(t, err)
-
 	t.Cleanup(s.Close)
 
 	addr := s.dnsProxy.Addr(proxy.ProtoUDP)
 	req := createTestMessageWithType("34.12.168.192.in-addr.arpa.", dns.TypePTR)
 
 	resp, err := dns.Exchange(req, addr.String())
-	require.NoError(t, err)
+	require.NoErrorf(t, err, "%s", addr)
 
 	require.Len(t, resp.Answer, 1)
 
@@ -1049,7 +1051,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
 
 	ptr, ok := resp.Answer[0].(*dns.PTR)
 	require.True(t, ok)
-	assert.Equal(t, "myhost.", ptr.Ptr)
+	assert.Equal(t, dns.Fqdn("myhost."+localDomain), ptr.Ptr)
 }
 
 func TestPTRResponseFromHosts(t *testing.T) {

From 3505ce87397372f79bc067f6afa8cead833426a9 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 7 Jul 2022 19:33:32 +0300
Subject: [PATCH 087/143] Pull request: all: use canonical names for hosts file
 runtime clients

Updates #4683.

Squashed commit of the following:

commit daa8fdaee574d4ac2171f6b13c5ce3f3fedd9801
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 7 19:13:29 2022 +0300

    all: use canonical names for hosts file runtime clients
---
 CHANGELOG.md                           |   6 +-
 internal/aghnet/hostscontainer.go      | 113 +++++++++++++-------
 internal/aghnet/hostscontainer_test.go | 137 ++++++++++++++-----------
 internal/aghnet/systemresolvers.go     |   2 +-
 internal/home/clients.go               |  16 +--
 5 files changed, 164 insertions(+), 110 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0490a5f1..d5e36004 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,7 +23,7 @@ and this project adheres to
 ### Added
 
 - Support for Discovery of Designated Resolvers (DDR) according to the [RFC
-  draft][ddr-draft-06] ([#4463]).
+  draft][ddr-draft] ([#4463]).
 - `windows/arm64` support ([#3057]).
 
 ### Deprecated
@@ -32,6 +32,7 @@ and this project adheres to
 
 ### Fixed
 
+- Inconsistent names of runtime clients from hosts files ([#4683]).
 - PTR requests for addresses leased by DHCP will now be resolved into hostnames
   under `dhcp.local_domain_name` ([#4699]).
 - Broken service installation on OpenWrt ([#4677]).
@@ -39,9 +40,10 @@ and this project adheres to
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
 [#4677]: https://github.com/AdguardTeam/AdGuardHome/issues/4677
+[#4683]: https://github.com/AdguardTeam/AdGuardHome/issues/4683
 [#4699]: https://github.com/AdguardTeam/AdGuardHome/issues/4699
 
-[ddr-draft-06]:   https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
+[ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
 
 
 
diff --git a/internal/aghnet/hostscontainer.go b/internal/aghnet/hostscontainer.go
index 65c9d3c4..15875ccc 100644
--- a/internal/aghnet/hostscontainer.go
+++ b/internal/aghnet/hostscontainer.go
@@ -198,7 +198,7 @@ func (hc *HostsContainer) Close() (err error) {
 }
 
 // Upd returns the channel into which the updates are sent.  The receivable
-// map's values are guaranteed to be of type of *stringutil.Set.
+// map's values are guaranteed to be of type of *HostsRecord.
 func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) {
 	return hc.updates
 }
@@ -290,7 +290,7 @@ func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err
 			continue
 		}
 
-		hp.addPairs(ip, hosts)
+		hp.addRecord(ip, hosts)
 	}
 
 	return nil, true, s.Err()
@@ -335,39 +335,66 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
 	return ip, hosts
 }
 
-// addPair puts the pair of ip and host to the rules builder if needed.  For
-// each ip the first member of hosts will become the main one.
-func (hp *hostsParser) addPairs(ip net.IP, hosts []string) {
+// HostsRecord represents a single hosts file record.
+type HostsRecord struct {
+	Aliases   *stringutil.Set
+	Canonical string
+}
+
+// Equal returns true if all fields of rec are equal to field in other or they
+// both are nil.
+func (rec *HostsRecord) Equal(other *HostsRecord) (ok bool) {
+	if rec == nil {
+		return other == nil
+	}
+
+	return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases)
+}
+
+// addRecord puts the record for the IP address to the rules builder if needed.
+// The first host is considered to be the canonical name for the IP address.
+// hosts must have at least one name.
+func (hp *hostsParser) addRecord(ip net.IP, hosts []string) {
+	line := strings.Join(append([]string{ip.String()}, hosts...), " ")
+
+	var rec *HostsRecord
 	v, ok := hp.table.Get(ip)
 	if !ok {
-		// This ip is added at the first time.
-		v = stringutil.NewSet()
-		hp.table.Set(ip, v)
+		rec = &HostsRecord{
+			Aliases: stringutil.NewSet(),
+		}
+
+		rec.Canonical, hosts = hosts[0], hosts[1:]
+		hp.addRules(ip, rec.Canonical, line)
+		hp.table.Set(ip, rec)
+	} else {
+		rec, ok = v.(*HostsRecord)
+		if !ok {
+			log.Error("%s: adding pairs: unexpected type %T", hostsContainerPref, v)
+
+			return
+		}
 	}
 
-	var set *stringutil.Set
-	set, ok = v.(*stringutil.Set)
-	if !ok {
-		log.Debug("%s: adding pairs: unexpected value type %T", hostsContainerPref, v)
-
-		return
-	}
-
-	processed := strings.Join(append([]string{ip.String()}, hosts...), " ")
-	for _, h := range hosts {
-		if set.Has(h) {
+	for _, host := range hosts {
+		if rec.Canonical == host || rec.Aliases.Has(host) {
 			continue
 		}
 
-		set.Add(h)
+		rec.Aliases.Add(host)
 
-		rule, rulePtr := hp.writeRules(h, ip)
-		hp.translations[rule], hp.translations[rulePtr] = processed, processed
-
-		log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, h)
+		hp.addRules(ip, host, line)
 	}
 }
 
+// addRules adds rules and rule translations for the line.
+func (hp *hostsParser) addRules(ip net.IP, host, line string) {
+	rule, rulePtr := hp.writeRules(host, ip)
+	hp.translations[rule], hp.translations[rulePtr] = line, line
+
+	log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, host)
+}
+
 // writeRules writes the actual rule for the qtype and the PTR for the host-ip
 // pair into internal builders.
 func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string) {
@@ -417,6 +444,7 @@ func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string)
 }
 
 // equalSet returns true if the internal hosts table just parsed equals target.
+// target's values must be of type *HostsRecord.
 func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
 	if target == nil {
 		// hp.table shouldn't appear nil since it's initialized on each refresh.
@@ -427,22 +455,35 @@ func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
 		return false
 	}
 
-	hp.table.Range(func(ip net.IP, b interface{}) (cont bool) {
-		// ok is set to true if the target doesn't contain ip or if the
-		// appropriate hosts set isn't equal to the checked one.
-		if a, hasIP := target.Get(ip); !hasIP {
-			ok = true
-		} else if hosts, aok := a.(*stringutil.Set); aok {
-			ok = !hosts.Equal(b.(*stringutil.Set))
+	hp.table.Range(func(ip net.IP, recVal interface{}) (cont bool) {
+		var targetVal interface{}
+		targetVal, ok = target.Get(ip)
+		if !ok {
+			return false
 		}
 
-		// Continue only if maps has no discrepancies.
-		return !ok
+		var rec *HostsRecord
+		rec, ok = recVal.(*HostsRecord)
+		if !ok {
+			log.Error("%s: comparing: unexpected type %T", hostsContainerPref, recVal)
+
+			return false
+		}
+
+		var targetRec *HostsRecord
+		targetRec, ok = targetVal.(*HostsRecord)
+		if !ok {
+			log.Error("%s: comparing: target: unexpected type %T", hostsContainerPref, targetVal)
+
+			return false
+		}
+
+		ok = rec.Equal(targetRec)
+
+		return ok
 	})
 
-	// Return true if every value from the IP map has no discrepancies with the
-	// appropriate one from the target.
-	return !ok
+	return ok
 }
 
 // sendUpd tries to send the parsed data to the ch.
diff --git a/internal/aghnet/hostscontainer_test.go b/internal/aghnet/hostscontainer_test.go
index 42202f43..019c713e 100644
--- a/internal/aghnet/hostscontainer_test.go
+++ b/internal/aghnet/hostscontainer_test.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
 	"github.com/AdguardTeam/golibs/errors"
+	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/AdguardTeam/golibs/stringutil"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/AdguardTeam/urlfilter"
@@ -159,31 +160,47 @@ func TestHostsContainer_refresh(t *testing.T) {
 	require.NoError(t, err)
 	testutil.CleanupAndRequireSuccess(t, hc.Close)
 
-	checkRefresh := func(t *testing.T, wantHosts *stringutil.Set) {
-		upd, ok := <-hc.Upd()
-		require.True(t, ok)
-		require.NotNil(t, upd)
+	checkRefresh := func(t *testing.T, want *HostsRecord) {
+		t.Helper()
+
+		var ok bool
+		var upd *netutil.IPMap
+		select {
+		case upd, ok = <-hc.Upd():
+			require.True(t, ok)
+			require.NotNil(t, upd)
+		case <-time.After(1 * time.Second):
+			t.Fatal("did not receive after 1s")
+		}
 
 		assert.Equal(t, 1, upd.Len())
 
 		v, ok := upd.Get(ip)
 		require.True(t, ok)
 
-		var set *stringutil.Set
-		set, ok = v.(*stringutil.Set)
-		require.True(t, ok)
+		require.IsType(t, (*HostsRecord)(nil), v)
 
-		assert.True(t, set.Equal(wantHosts))
+		rec, _ := v.(*HostsRecord)
+		require.NotNil(t, rec)
+
+		assert.Truef(t, rec.Equal(want), "%+v != %+v", rec, want)
 	}
 
 	t.Run("initial_refresh", func(t *testing.T) {
-		checkRefresh(t, stringutil.NewSet("hostname"))
+		checkRefresh(t, &HostsRecord{
+			Aliases:   stringutil.NewSet(),
+			Canonical: "hostname",
+		})
 	})
 
 	t.Run("second_refresh", func(t *testing.T) {
 		testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)}
 		eventsCh <- event{}
-		checkRefresh(t, stringutil.NewSet("hostname", "alias"))
+
+		checkRefresh(t, &HostsRecord{
+			Aliases:   stringutil.NewSet("alias"),
+			Canonical: "hostname",
+		})
 	})
 
 	t.Run("double_refresh", func(t *testing.T) {
@@ -363,10 +380,15 @@ func TestHostsContainer(t *testing.T) {
 	require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
 
 	testCases := []struct {
-		want []*rules.DNSRewrite
-		name string
 		req  *urlfilter.DNSRequest
+		name string
+		want []*rules.DNSRewrite
 	}{{
+		req: &urlfilter.DNSRequest{
+			Hostname: "simplehost",
+			DNSType:  dns.TypeA,
+		},
+		name: "simple",
 		want: []*rules.DNSRewrite{{
 			RCode:  dns.RcodeSuccess,
 			Value:  net.IPv4(1, 0, 0, 1),
@@ -376,27 +398,12 @@ func TestHostsContainer(t *testing.T) {
 			Value:  net.ParseIP("::1"),
 			RRType: dns.TypeAAAA,
 		}},
-		name: "simple",
-		req: &urlfilter.DNSRequest{
-			Hostname: "simplehost",
-			DNSType:  dns.TypeA,
-		},
 	}, {
-		want: []*rules.DNSRewrite{{
-			RCode:  dns.RcodeSuccess,
-			Value:  net.IPv4(1, 0, 0, 0),
-			RRType: dns.TypeA,
-		}, {
-			RCode:  dns.RcodeSuccess,
-			Value:  net.ParseIP("::"),
-			RRType: dns.TypeAAAA,
-		}},
-		name: "hello_alias",
 		req: &urlfilter.DNSRequest{
 			Hostname: "hello.world",
 			DNSType:  dns.TypeA,
 		},
-	}, {
+		name: "hello_alias",
 		want: []*rules.DNSRewrite{{
 			RCode:  dns.RcodeSuccess,
 			Value:  net.IPv4(1, 0, 0, 0),
@@ -406,26 +413,41 @@ func TestHostsContainer(t *testing.T) {
 			Value:  net.ParseIP("::"),
 			RRType: dns.TypeAAAA,
 		}},
-		name: "other_line_alias",
+	}, {
 		req: &urlfilter.DNSRequest{
 			Hostname: "hello.world.again",
 			DNSType:  dns.TypeA,
 		},
+		name: "other_line_alias",
+		want: []*rules.DNSRewrite{{
+			RCode:  dns.RcodeSuccess,
+			Value:  net.IPv4(1, 0, 0, 0),
+			RRType: dns.TypeA,
+		}, {
+			RCode:  dns.RcodeSuccess,
+			Value:  net.ParseIP("::"),
+			RRType: dns.TypeAAAA,
+		}},
 	}, {
-		want: []*rules.DNSRewrite{},
-		name: "hello_subdomain",
 		req: &urlfilter.DNSRequest{
 			Hostname: "say.hello",
 			DNSType:  dns.TypeA,
 		},
-	}, {
+		name: "hello_subdomain",
 		want: []*rules.DNSRewrite{},
-		name: "hello_alias_subdomain",
+	}, {
 		req: &urlfilter.DNSRequest{
 			Hostname: "say.hello.world",
 			DNSType:  dns.TypeA,
 		},
+		name: "hello_alias_subdomain",
+		want: []*rules.DNSRewrite{},
 	}, {
+		req: &urlfilter.DNSRequest{
+			Hostname: "for.testing",
+			DNSType:  dns.TypeA,
+		},
+		name: "lots_of_aliases",
 		want: []*rules.DNSRewrite{{
 			RCode:  dns.RcodeSuccess,
 			RRType: dns.TypeA,
@@ -435,37 +457,37 @@ func TestHostsContainer(t *testing.T) {
 			RRType: dns.TypeAAAA,
 			Value:  net.ParseIP("::2"),
 		}},
-		name: "lots_of_aliases",
-		req: &urlfilter.DNSRequest{
-			Hostname: "for.testing",
-			DNSType:  dns.TypeA,
-		},
 	}, {
+		req: &urlfilter.DNSRequest{
+			Hostname: "1.0.0.1.in-addr.arpa",
+			DNSType:  dns.TypePTR,
+		},
+		name: "reverse",
 		want: []*rules.DNSRewrite{{
 			RCode:  dns.RcodeSuccess,
 			RRType: dns.TypePTR,
 			Value:  "simplehost.",
 		}},
-		name: "reverse",
-		req: &urlfilter.DNSRequest{
-			Hostname: "1.0.0.1.in-addr.arpa",
-			DNSType:  dns.TypePTR,
-		},
 	}, {
-		want: []*rules.DNSRewrite{},
-		name: "non-existing",
 		req: &urlfilter.DNSRequest{
 			Hostname: "nonexisting",
 			DNSType:  dns.TypeA,
 		},
+		name: "non-existing",
+		want: []*rules.DNSRewrite{},
 	}, {
-		want: nil,
-		name: "bad_type",
 		req: &urlfilter.DNSRequest{
 			Hostname: "1.0.0.1.in-addr.arpa",
 			DNSType:  dns.TypeSRV,
 		},
+		name: "bad_type",
+		want: nil,
 	}, {
+		req: &urlfilter.DNSRequest{
+			Hostname: "domain",
+			DNSType:  dns.TypeA,
+		},
+		name: "issue_4216_4_6",
 		want: []*rules.DNSRewrite{{
 			RCode:  dns.RcodeSuccess,
 			RRType: dns.TypeA,
@@ -475,12 +497,12 @@ func TestHostsContainer(t *testing.T) {
 			RRType: dns.TypeAAAA,
 			Value:  net.ParseIP("::42"),
 		}},
-		name: "issue_4216_4_6",
+	}, {
 		req: &urlfilter.DNSRequest{
-			Hostname: "domain",
+			Hostname: "domain4",
 			DNSType:  dns.TypeA,
 		},
-	}, {
+		name: "issue_4216_4",
 		want: []*rules.DNSRewrite{{
 			RCode:  dns.RcodeSuccess,
 			RRType: dns.TypeA,
@@ -490,12 +512,12 @@ func TestHostsContainer(t *testing.T) {
 			RRType: dns.TypeA,
 			Value:  net.IPv4(1, 3, 5, 7),
 		}},
-		name: "issue_4216_4",
-		req: &urlfilter.DNSRequest{
-			Hostname: "domain4",
-			DNSType:  dns.TypeA,
-		},
 	}, {
+		req: &urlfilter.DNSRequest{
+			Hostname: "domain6",
+			DNSType:  dns.TypeAAAA,
+		},
+		name: "issue_4216_6",
 		want: []*rules.DNSRewrite{{
 			RCode:  dns.RcodeSuccess,
 			RRType: dns.TypeAAAA,
@@ -505,11 +527,6 @@ func TestHostsContainer(t *testing.T) {
 			RRType: dns.TypeAAAA,
 			Value:  net.ParseIP("::31"),
 		}},
-		name: "issue_4216_6",
-		req: &urlfilter.DNSRequest{
-			Hostname: "domain6",
-			DNSType:  dns.TypeAAAA,
-		},
 	}}
 
 	stubWatcher := aghtest.FSWatcher{
diff --git a/internal/aghnet/systemresolvers.go b/internal/aghnet/systemresolvers.go
index 13fbeb32..5ca8e9be 100644
--- a/internal/aghnet/systemresolvers.go
+++ b/internal/aghnet/systemresolvers.go
@@ -19,7 +19,7 @@ type SystemResolvers interface {
 }
 
 // NewSystemResolvers returns a SystemResolvers with the cache refresh rate
-// defined by refreshIvl. It disables auto-resfreshing if refreshIvl is 0.  If
+// defined by refreshIvl. It disables auto-refreshing if refreshIvl is 0.  If
 // nil is passed for hostGenFunc, the default generator will be used.
 func NewSystemResolvers(
 	hostGenFunc HostGenFunc,
diff --git a/internal/home/clients.go b/internal/home/clients.go
index 4ba6b884..74545a67 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -743,8 +743,7 @@ func (clients *clientsContainer) AddHost(ip net.IP, host string, src clientSourc
 
 // addHostLocked adds a new IP-hostname pairing.  For internal use only.
 func (clients *clientsContainer) addHostLocked(ip net.IP, host string, src clientSource) (ok bool) {
-	var rc *RuntimeClient
-	rc, ok = clients.findRuntimeClientLocked(ip)
+	rc, ok := clients.findRuntimeClientLocked(ip)
 	if ok {
 		if rc.Source > src {
 			return false
@@ -799,25 +798,20 @@ func (clients *clientsContainer) addFromHostsFile(hosts *netutil.IPMap) {
 
 	n := 0
 	hosts.Range(func(ip net.IP, v interface{}) (cont bool) {
-		hosts, ok := v.(*stringutil.Set)
+		rec, ok := v.(*aghnet.HostsRecord)
 		if !ok {
 			log.Error("dns: bad type %T in ipToRC for %s", v, ip)
 
 			return true
 		}
 
-		hosts.Range(func(name string) (cont bool) {
-			if clients.addHostLocked(ip, name, ClientSourceHostsFile) {
-				n++
-			}
-
-			return true
-		})
+		clients.addHostLocked(ip, rec.Canonical, ClientSourceHostsFile)
+		n++
 
 		return true
 	})
 
-	log.Debug("clients: added %d client aliases from system hosts-file", n)
+	log.Debug("clients: added %d client aliases from system hosts file", n)
 }
 
 // addFromSystemARP adds the IP-hostname pairings from the output of the arp -a

From 77e5e27d757c6f6110e65f9e99c544614b6c1d1a Mon Sep 17 00:00:00 2001
From: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu, 7 Jul 2022 19:49:47 +0300
Subject: [PATCH 088/143] Pull request: all: updater exe name

Merge in DNS/adguard-home from 4219-updater to master

Squashed commit of the following:

commit f569a5f232330b83c234838a5bff8ae5277f152f
Merge: a90b4fa7 3505ce87
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 22:14:50 2022 +0530

    Merge remote-tracking branch 'origin/master' into 4219-updater

    # Conflicts:
    #	CHANGELOG.md

commit a90b4fa7782c5ec4531d8e305c0d448e84898239
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 21:56:17 2022 +0530

    home: imp code

commit da0f96b976e430fffc531072ef3e2384bc8b1f09
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 21:48:40 2022 +0530

    updater: exe name

commit 246dc9ca3b133cbc93ea59edd272674b87ff8de3
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 19:18:02 2022 +0530

    all: imp docs

commit 042382d170c4d68ff67fe5544a75371337529623
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 18:02:25 2022 +0530

    all: updater exe name

commit a180c4673ead66788969865784348634af1a739e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 17:47:46 2022 +0530

    docs: updater exe name

commit 1a98a6eadbd96add0a488fb8f89fb7d8b0ffb3d0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 17:40:44 2022 +0530

    all: updater exe name

commit 1b13f5d85550dc71b08fd8e5b4258f8414a38759
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 17:14:57 2022 +0530

    all: updater exe name
---
 CHANGELOG.md                     |  3 +++
 internal/home/controlupdate.go   | 14 ++++++--------
 internal/updater/updater.go      | 17 +++++++++++------
 internal/updater/updater_test.go |  4 ++--
 4 files changed, 22 insertions(+), 16 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d5e36004..21046925 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,8 @@ and this project adheres to
 
 ### Fixed
 
+- Updater no longer expects a hardcoded name for  `AdGuardHome` executable
+  ([#4219]).
 - Inconsistent names of runtime clients from hosts files ([#4683]).
 - PTR requests for addresses leased by DHCP will now be resolved into hostnames
   under `dhcp.local_domain_name` ([#4699]).
@@ -39,6 +41,7 @@ and this project adheres to
 
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
+[#4219]: https://github.com/AdguardTeam/AdGuardHome/issues/4219
 [#4677]: https://github.com/AdguardTeam/AdGuardHome/issues/4677
 [#4683]: https://github.com/AdguardTeam/AdGuardHome/issues/4683
 [#4699]: https://github.com/AdguardTeam/AdGuardHome/issues/4699
diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go
index 08ddd455..067ed83d 100644
--- a/internal/home/controlupdate.go
+++ b/internal/home/controlupdate.go
@@ -7,7 +7,6 @@ import (
 	"net/http"
 	"os"
 	"os/exec"
-	"path/filepath"
 	"runtime"
 	"syscall"
 	"time"
@@ -180,11 +179,10 @@ func finishUpdate(ctx context.Context) {
 	cleanup(ctx)
 	cleanupAlways()
 
-	exeName := "AdGuardHome"
-	if runtime.GOOS == "windows" {
-		exeName = "AdGuardHome.exe"
+	curBinName, err := os.Executable()
+	if err != nil {
+		log.Fatalf("executable path request failed: %s", err)
 	}
-	curBinName := filepath.Join(Context.workDir, exeName)
 
 	if runtime.GOOS == "windows" {
 		if Context.runningAsService {
@@ -192,7 +190,7 @@ func finishUpdate(ctx context.Context) {
 			// we can't restart the service via "kardianos/service" package - it kills the process first
 			// we can't start a new instance - Windows doesn't allow it
 			cmd := exec.Command("cmd", "/c", "net stop AdGuardHome & net start AdGuardHome")
-			err := cmd.Start()
+			err = cmd.Start()
 			if err != nil {
 				log.Fatalf("exec.Command() failed: %s", err)
 			}
@@ -204,14 +202,14 @@ func finishUpdate(ctx context.Context) {
 		cmd.Stdin = os.Stdin
 		cmd.Stdout = os.Stdout
 		cmd.Stderr = os.Stderr
-		err := cmd.Start()
+		err = cmd.Start()
 		if err != nil {
 			log.Fatalf("exec.Command() failed: %s", err)
 		}
 		os.Exit(0)
 	} else {
 		log.Info("Restarting: %v", os.Args)
-		err := syscall.Exec(curBinName, os.Args, os.Environ())
+		err = syscall.Exec(curBinName, os.Args, os.Environ())
 		if err != nil {
 			log.Fatalf("syscall.Exec() failed: %s", err)
 		}
diff --git a/internal/updater/updater.go b/internal/updater/updater.go
index d975d977..5077787c 100644
--- a/internal/updater/updater.go
+++ b/internal/updater/updater.go
@@ -111,7 +111,12 @@ func (u *Updater) Update() (err error) {
 	log.Info("updater: updating")
 	defer func() { log.Info("updater: finished; errors: %v", err) }()
 
-	err = u.prepare()
+	execPath, err := os.Executable()
+	if err != nil {
+		return err
+	}
+
+	err = u.prepare(filepath.Base(execPath))
 	if err != nil {
 		return err
 	}
@@ -162,7 +167,8 @@ func (u *Updater) VersionCheckURL() (vcu string) {
 	return u.versionCheckURL
 }
 
-func (u *Updater) prepare() (err error) {
+// prepare fills all necessary fields in Updater object.
+func (u *Updater) prepare(exeName string) (err error) {
 	u.updateDir = filepath.Join(u.workDir, fmt.Sprintf("agh-update-%s", u.newVersion))
 
 	_, pkgNameOnly := filepath.Split(u.packageURL)
@@ -173,13 +179,13 @@ func (u *Updater) prepare() (err error) {
 	u.packageName = filepath.Join(u.updateDir, pkgNameOnly)
 	u.backupDir = filepath.Join(u.workDir, "agh-backup")
 
-	exeName := "AdGuardHome"
+	updateExeName := "AdGuardHome"
 	if u.goos == "windows" {
-		exeName = "AdGuardHome.exe"
+		updateExeName = "AdGuardHome.exe"
 	}
 
 	u.backupExeName = filepath.Join(u.backupDir, exeName)
-	u.updateExeName = filepath.Join(u.updateDir, exeName)
+	u.updateExeName = filepath.Join(u.updateDir, updateExeName)
 
 	log.Debug(
 		"updater: updating from %s to %s using url: %s",
@@ -188,7 +194,6 @@ func (u *Updater) prepare() (err error) {
 		u.packageURL,
 	)
 
-	// TODO(a.garipov): Use os.Args[0] instead?
 	u.currentExeName = filepath.Join(u.workDir, exeName)
 	_, err = os.Stat(u.currentExeName)
 	if err != nil {
diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go
index 771fb6d4..b3268f2f 100644
--- a/internal/updater/updater_test.go
+++ b/internal/updater/updater_test.go
@@ -131,7 +131,7 @@ func TestUpdate(t *testing.T) {
 	u.newVersion = "v0.103.1"
 	u.packageURL = fakeURL.String()
 
-	require.NoError(t, u.prepare())
+	require.NoError(t, u.prepare("AdGuardHome"))
 
 	u.currentExeName = filepath.Join(wd, "AdGuardHome")
 
@@ -209,7 +209,7 @@ func TestUpdateWindows(t *testing.T) {
 	u.newVersion = "v0.103.1"
 	u.packageURL = fakeURL.String()
 
-	require.NoError(t, u.prepare())
+	require.NoError(t, u.prepare("AdGuardHome.exe"))
 
 	u.currentExeName = filepath.Join(wd, "AdGuardHome.exe")
 

From a832987f7c6d7f7ad0339f8a280e24263ef1b661 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Fri, 8 Jul 2022 15:17:47 +0300
Subject: [PATCH 089/143] Pull request: 4698 Gateway IP in DHCP Lease

Closes #4698.

Squashed commit of the following:

commit 6be0caee58926f8cea1e10650fbde0c8d97d0dac
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 8 13:41:50 2022 +0300

    update translation

commit e0370656d05e8463d73ea73568cae81187c6b2e3
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 8 13:40:54 2022 +0300

    client: validate static lease ip

commit 7f4d00f9f3a54dc93ce5d5c45e9c21745f6e39d1
Merge: 2ee79626 77e5e27d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 8 13:20:15 2022 +0300

    Merge branch 'master' into 4698-lease-with-gateway

commit 2ee79626a1b0c7b113dbd22ba4ef6e85ea9913ec
Merge: 471b96b8 3505ce87
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 7 19:34:33 2022 +0300

    Merge branch 'master' into 4698-lease-with-gateway

commit 471b96b81da8920c1e71b7110050154f912677d2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 7 16:07:23 2022 +0300

    dhcpd: imp docs

commit 67dd6c76f7d2df4712a57281e0f40f2ee1a1efa2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 7 15:48:47 2022 +0300

    dhcpd: restrict gateway ip for lease
---
 CHANGELOG.md                                  |   3 +
 client/src/__locales/en.json                  |   1 +
 .../Settings/Dhcp/StaticLeases/Form.js        |   3 +
 .../Settings/Dhcp/StaticLeases/Modal.js       |   3 +
 .../Settings/Dhcp/StaticLeases/index.js       |   3 +
 client/src/components/Settings/Dhcp/index.js  |   1 +
 client/src/helpers/validators.js              |  11 ++
 internal/dhcpd/http.go                        |   1 -
 internal/dhcpd/v4.go                          |  12 +-
 internal/dhcpd/v4_test.go                     | 124 ++++++++++++------
 10 files changed, 120 insertions(+), 42 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21046925..7b8f5d97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,8 @@ and this project adheres to
 
 ### Fixed
 
+- DHCP lease validation incorrectly letting users assign the IP address of the
+  gateway as the address of the lease ([#4698]).
 - Updater no longer expects a hardcoded name for  `AdGuardHome` executable
   ([#4219]).
 - Inconsistent names of runtime clients from hosts files ([#4683]).
@@ -44,6 +46,7 @@ and this project adheres to
 [#4219]: https://github.com/AdguardTeam/AdGuardHome/issues/4219
 [#4677]: https://github.com/AdguardTeam/AdGuardHome/issues/4677
 [#4683]: https://github.com/AdguardTeam/AdGuardHome/issues/4683
+[#4698]: https://github.com/AdguardTeam/AdGuardHome/issues/4698
 [#4699]: https://github.com/AdguardTeam/AdGuardHome/issues/4699
 
 [ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index dd885677..9fc65cac 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Invalid server name",
     "form_error_subnet": "Subnet \"{{cidr}}\" does not contain the IP address \"{{ip}}\"",
     "form_error_positive": "Must be greater than 0",
+    "form_error_gateway_ip": "Lease can't have the IP address of the gateway",
     "out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Must be lower than range start",
     "greater_range_start_error": "Must be greater than range start",
diff --git a/client/src/components/Settings/Dhcp/StaticLeases/Form.js b/client/src/components/Settings/Dhcp/StaticLeases/Form.js
index 625e87e2..0525f6a3 100644
--- a/client/src/components/Settings/Dhcp/StaticLeases/Form.js
+++ b/client/src/components/Settings/Dhcp/StaticLeases/Form.js
@@ -10,6 +10,7 @@ import {
     validateMac,
     validateRequiredValue,
     validateIpv4InCidr,
+    validateIpGateway,
 } from '../../../../helpers/validators';
 import { FORM_NAME } from '../../../../helpers/constants';
 import { toggleLeaseModal } from '../../../../actions';
@@ -57,6 +58,7 @@ const Form = ({
                             validateRequiredValue,
                             validateIpv4,
                             validateIpv4InCidr,
+                            validateIpGateway,
                         ]}
                     />
                 </div>
@@ -101,6 +103,7 @@ Form.propTypes = {
         ip: PropTypes.string.isRequired,
         hostname: PropTypes.string.isRequired,
         cidr: PropTypes.string.isRequired,
+        gatewayIp: PropTypes.string,
     }),
     pristine: PropTypes.bool.isRequired,
     handleSubmit: PropTypes.func.isRequired,
diff --git a/client/src/components/Settings/Dhcp/StaticLeases/Modal.js b/client/src/components/Settings/Dhcp/StaticLeases/Modal.js
index 8ad0f009..0baf487e 100644
--- a/client/src/components/Settings/Dhcp/StaticLeases/Modal.js
+++ b/client/src/components/Settings/Dhcp/StaticLeases/Modal.js
@@ -13,6 +13,7 @@ const Modal = ({
     cidr,
     rangeStart,
     rangeEnd,
+    gatewayIp,
 }) => {
     const dispatch = useDispatch();
 
@@ -42,6 +43,7 @@ const Modal = ({
                         cidr,
                         rangeStart,
                         rangeEnd,
+                        gatewayIp,
                     }}
                     onSubmit={handleSubmit}
                     processingAdding={processingAdding}
@@ -61,6 +63,7 @@ Modal.propTypes = {
     cidr: PropTypes.string.isRequired,
     rangeStart: PropTypes.string,
     rangeEnd: PropTypes.string,
+    gatewayIp: PropTypes.string,
 };
 
 export default withTranslation()(Modal);
diff --git a/client/src/components/Settings/Dhcp/StaticLeases/index.js b/client/src/components/Settings/Dhcp/StaticLeases/index.js
index a63f78cd..2374f044 100644
--- a/client/src/components/Settings/Dhcp/StaticLeases/index.js
+++ b/client/src/components/Settings/Dhcp/StaticLeases/index.js
@@ -24,6 +24,7 @@ const StaticLeases = ({
     cidr,
     rangeStart,
     rangeEnd,
+    gatewayIp,
 }) => {
     const [t] = useTranslation();
     const dispatch = useDispatch();
@@ -106,6 +107,7 @@ const StaticLeases = ({
                 cidr={cidr}
                 rangeStart={rangeStart}
                 rangeEnd={rangeEnd}
+                gatewayIp={gatewayIp}
             />
         </>
     );
@@ -119,6 +121,7 @@ StaticLeases.propTypes = {
     cidr: PropTypes.string.isRequired,
     rangeStart: PropTypes.string,
     rangeEnd: PropTypes.string,
+    gatewayIp: PropTypes.string,
 };
 
 cellWrap.propTypes = {
diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js
index a84e0a93..bd3a45e3 100644
--- a/client/src/components/Settings/Dhcp/index.js
+++ b/client/src/components/Settings/Dhcp/index.js
@@ -278,6 +278,7 @@ const Dhcp = () => {
                             cidr={cidr}
                             rangeStart={dhcp?.values?.v4?.range_start}
                             rangeEnd={dhcp?.values?.v4?.range_end}
+                            gatewayIp={dhcp?.values?.v4?.gateway_ip}
                         />
                         <div className="btn-list mt-2">
                             <button
diff --git a/client/src/helpers/validators.js b/client/src/helpers/validators.js
index 862a2ca4..ed274416 100644
--- a/client/src/helpers/validators.js
+++ b/client/src/helpers/validators.js
@@ -339,3 +339,14 @@ export const validatePasswordLength = (value) => {
     }
     return undefined;
 };
+
+/**
+ * @param value {string}
+ * @returns {Function}
+ */
+export const validateIpGateway = (value, allValues) => {
+    if (value === allValues.gatewayIp) {
+        return i18next.t('form_error_gateway_ip');
+    }
+    return undefined;
+};
diff --git a/internal/dhcpd/http.go b/internal/dhcpd/http.go
index 62a08f66..a8f0c108 100644
--- a/internal/dhcpd/http.go
+++ b/internal/dhcpd/http.go
@@ -498,7 +498,6 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request
 	}
 
 	ip4 := l.IP.To4()
-
 	if ip4 == nil {
 		l.IP = l.IP.To16()
 
diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go
index 4e2a3a25..825ef25c 100644
--- a/internal/dhcpd/v4.go
+++ b/internal/dhcpd/v4.go
@@ -333,12 +333,16 @@ func (s *v4Server) rmLease(lease *Lease) (err error) {
 	return errors.Error("lease not found")
 }
 
-// AddStaticLease adds a static lease.  It is safe for concurrent use.
+// AddStaticLease implements the DHCPServer interface for *v4Server.  It is safe
+// for concurrent use.
 func (s *v4Server) AddStaticLease(l *Lease) (err error) {
 	defer func() { err = errors.Annotate(err, "dhcpv4: adding static lease: %w") }()
 
-	if ip4 := l.IP.To4(); ip4 == nil {
+	ip := l.IP.To4()
+	if ip == nil {
 		return fmt.Errorf("invalid ip %q, only ipv4 is supported", l.IP)
+	} else if gwIP := s.conf.GatewayIP; gwIP.Equal(ip) {
+		return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP)
 	}
 
 	l.Expiry = time.Unix(leaseExpireStatic, 0)
@@ -377,7 +381,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
 		if err != nil {
 			err = fmt.Errorf(
 				"removing dynamic leases for %s (%s): %w",
-				l.IP,
+				ip,
 				l.HWAddr,
 				err,
 			)
@@ -387,7 +391,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
 
 		err = s.addLease(l)
 		if err != nil {
-			err = fmt.Errorf("adding static lease for %s (%s): %w", l.IP, l.HWAddr, err)
+			err = fmt.Errorf("adding static lease for %s (%s): %w", ip, l.HWAddr, err)
 
 			return
 		}
diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go
index 58e5c27a..130782bf 100644
--- a/internal/dhcpd/v4_test.go
+++ b/internal/dhcpd/v4_test.go
@@ -4,6 +4,7 @@
 package dhcpd
 
 import (
+	"fmt"
 	"net"
 	"strings"
 	"testing"
@@ -16,6 +17,13 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+var (
+	DefaultRangeStart = net.IP{192, 168, 10, 100}
+	DefaultRangeEnd   = net.IP{192, 168, 10, 200}
+	DefaultGatewayIP  = net.IP{192, 168, 10, 1}
+	DefaultSubnetMask = net.IP{255, 255, 255, 0}
+)
+
 func notify4(flags uint32) {
 }
 
@@ -24,10 +32,10 @@ func notify4(flags uint32) {
 func defaultV4ServerConf() (conf V4ServerConf) {
 	return V4ServerConf{
 		Enabled:    true,
-		RangeStart: net.IP{192, 168, 10, 100},
-		RangeEnd:   net.IP{192, 168, 10, 200},
-		GatewayIP:  net.IP{192, 168, 10, 1},
-		SubnetMask: net.IP{255, 255, 255, 0},
+		RangeStart: DefaultRangeStart,
+		RangeEnd:   DefaultRangeEnd,
+		GatewayIP:  DefaultGatewayIP,
+		SubnetMask: DefaultSubnetMask,
 		notify:     notify4,
 	}
 }
@@ -44,44 +52,86 @@ func defaultSrv(t *testing.T) (s DHCPServer) {
 	return s
 }
 
-func TestV4_AddRemove_static(t *testing.T) {
+func TestV4Server_AddRemove_static(t *testing.T) {
 	s := defaultSrv(t)
 
 	ls := s.GetLeases(LeasesStatic)
-	assert.Empty(t, ls)
+	require.Empty(t, ls)
 
-	// Add static lease.
-	l := &Lease{
-		Hostname: "static-1.local",
-		HWAddr:   net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
-		IP:       net.IP{192, 168, 10, 150},
+	testCases := []struct {
+		lease      *Lease
+		name       string
+		wantErrMsg string
+	}{{
+		lease: &Lease{
+			Hostname: "success.local",
+			HWAddr:   net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
+			IP:       net.IP{192, 168, 10, 150},
+		},
+		name:       "success",
+		wantErrMsg: "",
+	}, {
+		lease: &Lease{
+			Hostname: "probably-router.local",
+			HWAddr:   net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
+			IP:       DefaultGatewayIP,
+		},
+		name: "with_gateway_ip",
+		wantErrMsg: "dhcpv4: adding static lease: " +
+			"can't assign the gateway IP 192.168.10.1 to the lease",
+	}, {
+		lease: &Lease{
+			Hostname: "ip6.local",
+			HWAddr:   net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
+			IP:       net.ParseIP("ffff::1"),
+		},
+		name: "ipv6",
+		wantErrMsg: `dhcpv4: adding static lease: ` +
+			`invalid ip "ffff::1", only ipv4 is supported`,
+	}, {
+		lease: &Lease{
+			Hostname: "bad-mac.local",
+			HWAddr:   net.HardwareAddr{0xAA, 0xAA},
+			IP:       net.IP{192, 168, 10, 150},
+		},
+		name: "bad_mac",
+		wantErrMsg: `dhcpv4: adding static lease: bad mac address "aa:aa": ` +
+			`bad mac address length 2, allowed: [6 8 20]`,
+	}, {
+		lease: &Lease{
+			Hostname: "bad-lbl-.local",
+			HWAddr:   net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
+			IP:       net.IP{192, 168, 10, 150},
+		},
+		name: "bad_hostname",
+		wantErrMsg: `dhcpv4: adding static lease: validating hostname: ` +
+			`bad domain name "bad-lbl-.local": ` +
+			`bad domain name label "bad-lbl-": bad domain name label rune '-'`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := s.AddStaticLease(tc.lease)
+			testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+			if tc.wantErrMsg != "" {
+				return
+			}
+
+			err = s.RemoveStaticLease(&Lease{
+				IP:     tc.lease.IP,
+				HWAddr: tc.lease.HWAddr,
+			})
+			diffErrMsg := fmt.Sprintf("dhcpv4: lease for ip %s is different: %+v", tc.lease.IP, tc.lease)
+			testutil.AssertErrorMsg(t, diffErrMsg, err)
+
+			// Remove static lease.
+			err = s.RemoveStaticLease(tc.lease)
+			require.NoError(t, err)
+		})
+
+		ls = s.GetLeases(LeasesStatic)
+		require.Emptyf(t, ls, "after %s", tc.name)
 	}
-
-	err := s.AddStaticLease(l)
-	require.NoError(t, err)
-
-	err = s.AddStaticLease(l)
-	assert.Error(t, err)
-
-	ls = s.GetLeases(LeasesStatic)
-	require.Len(t, ls, 1)
-
-	assert.True(t, l.IP.Equal(ls[0].IP))
-	assert.Equal(t, l.HWAddr, ls[0].HWAddr)
-	assert.True(t, ls[0].IsStatic())
-
-	// Try to remove static lease.
-	err = s.RemoveStaticLease(&Lease{
-		IP:     net.IP{192, 168, 10, 110},
-		HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
-	})
-	assert.Error(t, err)
-
-	// Remove static lease.
-	err = s.RemoveStaticLease(l)
-	require.NoError(t, err)
-	ls = s.GetLeases(LeasesStatic)
-	assert.Empty(t, ls)
 }
 
 func TestV4_AddReplace(t *testing.T) {

From bf024fb98591b8effb4c451973b5f222cca4009d Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 11 Jul 2022 17:40:00 +0300
Subject: [PATCH 090/143] Pull request: aghalg: impl json.Marshaler for
 NullBool

Updates #4735.

Squashed commit of the following:

commit 93a0b1dc6b668f7d9fd89d06b8f0f24dcd345356
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 11 17:02:36 2022 +0300

    aghalg: impl json.Marshaler for NullBool
---
 internal/aghalg/nullbool.go      |  8 ++++++
 internal/aghalg/nullbool_test.go | 46 ++++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/internal/aghalg/nullbool.go b/internal/aghalg/nullbool.go
index 3c5633e3..01a8c974 100644
--- a/internal/aghalg/nullbool.go
+++ b/internal/aghalg/nullbool.go
@@ -40,6 +40,14 @@ func BoolToNullBool(cond bool) (nb NullBool) {
 	return NBFalse
 }
 
+// type check
+var _ json.Marshaler = NBNull
+
+// MarshalJSON implements the json.Marshaler interface for NullBool.
+func (nb NullBool) MarshalJSON() (b []byte, err error) {
+	return []byte(nb.String()), nil
+}
+
 // type check
 var _ json.Unmarshaler = (*NullBool)(nil)
 
diff --git a/internal/aghalg/nullbool_test.go b/internal/aghalg/nullbool_test.go
index 0fe7f203..8096f1069 100644
--- a/internal/aghalg/nullbool_test.go
+++ b/internal/aghalg/nullbool_test.go
@@ -10,6 +10,52 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+func TestNullBool_MarshalJSON(t *testing.T) {
+	testCases := []struct {
+		name       string
+		wantErrMsg string
+		want       []byte
+		in         aghalg.NullBool
+	}{{
+		name:       "null",
+		wantErrMsg: "",
+		want:       []byte("null"),
+		in:         aghalg.NBNull,
+	}, {
+		name:       "true",
+		wantErrMsg: "",
+		want:       []byte("true"),
+		in:         aghalg.NBTrue,
+	}, {
+		name:       "false",
+		wantErrMsg: "",
+		want:       []byte("false"),
+		in:         aghalg.NBFalse,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got, err := tc.in.MarshalJSON()
+			testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+
+			assert.Equal(t, tc.want, got)
+		})
+	}
+
+	t.Run("json", func(t *testing.T) {
+		in := &struct {
+			A aghalg.NullBool
+		}{
+			A: aghalg.NBTrue,
+		}
+
+		got, err := json.Marshal(in)
+		require.NoError(t, err)
+
+		assert.Equal(t, []byte(`{"A":true}`), got)
+	})
+}
+
 func TestNullBool_UnmarshalJSON(t *testing.T) {
 	testCases := []struct {
 		name       string

From 1eafb4e7cf64f0027674e59e4a4b5ccc33c44c43 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 11 Jul 2022 18:18:17 +0300
Subject: [PATCH 091/143] Pull request: home: fix exe path finding

Closes #4735.

Squashed commit of the following:

commit 8228e5f82c9d8056d5567a7f1b13b1365346c4d4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 11 17:41:19 2022 +0300

    home: fix exe path finding
---
 internal/home/controlupdate.go | 67 +++++++++++++++++++---------------
 1 file changed, 38 insertions(+), 29 deletions(-)

diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go
index 067ed83d..91164696 100644
--- a/internal/home/controlupdate.go
+++ b/internal/home/controlupdate.go
@@ -117,7 +117,18 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	err := Context.updater.Update()
+	// Retain the current absolute path of the executable, since the updater is
+	// likely to change the position current one to the backup directory.
+	//
+	// See https://github.com/AdguardTeam/AdGuardHome/issues/4735.
+	execPath, err := os.Executable()
+	if err != nil {
+		aghhttp.Error(r, w, http.StatusInternalServerError, "getting path: %s", err)
+
+		return
+	}
+
+	err = Context.updater.Update()
 	if err != nil {
 		aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
 
@@ -129,13 +140,10 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) {
 		f.Flush()
 	}
 
-	// The background context is used because the underlying functions wrap
-	// it with timeout and shut down the server, which handles current
-	// request. It also should be done in a separate goroutine due to the
-	// same reason.
-	go func() {
-		finishUpdate(context.Background())
-	}()
+	// The background context is used because the underlying functions wrap it
+	// with timeout and shut down the server, which handles current request.  It
+	// also should be done in a separate goroutine for the same reason.
+	go finishUpdate(context.Background(), execPath)
 }
 
 // versionResponse is the response for /control/version.json endpoint.
@@ -174,45 +182,46 @@ func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) {
 }
 
 // finishUpdate completes an update procedure.
-func finishUpdate(ctx context.Context) {
-	log.Info("Stopping all tasks")
+func finishUpdate(ctx context.Context, execPath string) {
+	var err error
+
+	log.Info("stopping all tasks")
+
 	cleanup(ctx)
 	cleanupAlways()
 
-	curBinName, err := os.Executable()
-	if err != nil {
-		log.Fatalf("executable path request failed: %s", err)
-	}
-
 	if runtime.GOOS == "windows" {
 		if Context.runningAsService {
-			// Note:
-			// we can't restart the service via "kardianos/service" package - it kills the process first
-			// we can't start a new instance - Windows doesn't allow it
+			// NOTE: We can't restart the service via "kardianos/service"
+			// package, because it kills the process first we can't start a new
+			// instance, because Windows doesn't allow it.
+			//
+			// TODO(a.garipov): Recheck the claim above.
 			cmd := exec.Command("cmd", "/c", "net stop AdGuardHome & net start AdGuardHome")
 			err = cmd.Start()
 			if err != nil {
-				log.Fatalf("exec.Command() failed: %s", err)
+				log.Fatalf("restarting: stopping: %s", err)
 			}
+
 			os.Exit(0)
 		}
 
-		cmd := exec.Command(curBinName, os.Args[1:]...)
-		log.Info("Restarting: %v", cmd.Args)
+		cmd := exec.Command(execPath, os.Args[1:]...)
+		log.Info("restarting: %q %q", execPath, os.Args[1:])
 		cmd.Stdin = os.Stdin
 		cmd.Stdout = os.Stdout
 		cmd.Stderr = os.Stderr
 		err = cmd.Start()
 		if err != nil {
-			log.Fatalf("exec.Command() failed: %s", err)
+			log.Fatalf("restarting:: %s", err)
 		}
+
 		os.Exit(0)
-	} else {
-		log.Info("Restarting: %v", os.Args)
-		err = syscall.Exec(curBinName, os.Args, os.Environ())
-		if err != nil {
-			log.Fatalf("syscall.Exec() failed: %s", err)
-		}
-		// Unreachable code
+	}
+
+	log.Info("restarting: %q %q", execPath, os.Args[1:])
+	err = syscall.Exec(execPath, os.Args, os.Environ())
+	if err != nil {
+		log.Fatalf("restarting: %s", err)
 	}
 }

From bdcd17a41ad95f4732334471b0ca504811dbcc4f Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 12 Jul 2022 17:48:54 +0300
Subject: [PATCH 092/143] Pull request: client: upd i18n

Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit ea24a933626a0cea715d8d04953a55a48df9de15
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 12 17:36:53 2022 +0300

    client: fix si-lk

commit 924b9d8a2b9b6a933799c739ab623c244b02a9a6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 12 17:29:16 2022 +0300

    client: upd i18n
---
 client/src/__locales/be.json    |  1 +
 client/src/__locales/cs.json    |  1 +
 client/src/__locales/de.json    |  1 +
 client/src/__locales/es.json    |  1 +
 client/src/__locales/fi.json    |  3 ++-
 client/src/__locales/fr.json    |  1 +
 client/src/__locales/hr.json    |  1 +
 client/src/__locales/hu.json    |  1 +
 client/src/__locales/it.json    |  1 +
 client/src/__locales/ja.json    |  1 +
 client/src/__locales/ko.json    |  1 +
 client/src/__locales/nl.json    |  3 ++-
 client/src/__locales/pl.json    |  1 +
 client/src/__locales/ro.json    |  1 +
 client/src/__locales/ru.json    | 13 +++++++------
 client/src/__locales/si-lk.json |  4 ++--
 client/src/__locales/sk.json    |  1 +
 client/src/__locales/sl.json    |  1 +
 client/src/__locales/sr-cs.json |  1 +
 client/src/__locales/sv.json    |  1 +
 client/src/__locales/tr.json    |  1 +
 client/src/__locales/uk.json    |  9 +++++----
 client/src/__locales/zh-cn.json |  1 +
 client/src/__locales/zh-tw.json |  1 +
 24 files changed, 37 insertions(+), 14 deletions(-)

diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json
index 8d20bccb..378d20c0 100644
--- a/client/src/__locales/be.json
+++ b/client/src/__locales/be.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Няслушнае імя сервера",
     "form_error_subnet": "Падсетка «{{cidr}}» не ўтрымвае IP-адраса «{{ip}}»",
     "form_error_positive": "Павінна быць больш 0",
+    "form_error_gateway_ip": "Арэнда не можа мець IP-адрас шлюза",
     "out_of_range_error": "Павінна быць па-за дыяпазонам «{{start}}»-«{{end}}»",
     "lower_range_start_error": "Павінна быць менш за пачатак дыяпазону",
     "greater_range_start_error": "Павінна быць больш за пачатак дыяпазону",
diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json
index fd89077d..26a50ba5 100644
--- a/client/src/__locales/cs.json
+++ b/client/src/__locales/cs.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Neplatný název serveru",
     "form_error_subnet": "Podsíť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
     "form_error_positive": "Musí být větší než 0",
+    "form_error_gateway_ip": "Pronájem nemůže mít IP adresu brány",
     "out_of_range_error": "Musí být mimo rozsah \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Musí být menší než začátek rozsahu",
     "greater_range_start_error": "Musí být větší než začátek rozsahu",
diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json
index edb2eb80..29a6ef15 100644
--- a/client/src/__locales/de.json
+++ b/client/src/__locales/de.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Ungültiger Servername",
     "form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“",
     "form_error_positive": "Muss größer als 0 sein",
+    "form_error_gateway_ip": "Lease kann nicht die IP-Adresse des Gateways haben",
     "out_of_range_error": "Muss außerhalb des Bereichs „{{start}}“-„{{end}}“ liegen",
     "lower_range_start_error": "Muss niedriger als der Bereichsbeginn sein",
     "greater_range_start_error": "Muss größer als der Bereichsbeginn sein",
diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json
index 47712ee1..aa65adc3 100644
--- a/client/src/__locales/es.json
+++ b/client/src/__locales/es.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nombre de servidor no válido",
     "form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\"",
     "form_error_positive": "Debe ser mayor que 0",
+    "form_error_gateway_ip": "Asignación no puede tener la dirección IP de la puerta de enlace",
     "out_of_range_error": "Debe estar fuera del rango \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Debe ser inferior que el inicio de rango",
     "greater_range_start_error": "Debe ser mayor que el inicio de rango",
diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json
index fe18c7d2..5ebabde6 100644
--- a/client/src/__locales/fi.json
+++ b/client/src/__locales/fi.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Virheellinen palvelimen nimi",
     "form_error_subnet": "Aliverkko \"{{cidr}}\" ei sisällä IP-osoitetta \"{{ip}}\"",
     "form_error_positive": "Oltava suurempi kuin 0",
+    "form_error_gateway_ip": "Lainalla ei voi olla yhdyskäytävän IP-osoitetta",
     "out_of_range_error": "Oltava alueen \"{{start}}\" - \"{{end}}\" ulkopuolella",
     "lower_range_start_error": "Oltava alueen aloitusarvoa pienempi",
     "greater_range_start_error": "Oltava alueen aloitusarvoa suurempi",
@@ -70,7 +71,7 @@
     "dhcp_error": "AdGuard Home ei voinut tunnistaa, onko verkossa toista aktiivista DHCP-palvelinta",
     "dhcp_static_ip_error": "Jotta DHCP-palvelinta voidaan käyttää, on määritettävä kiinteä IP-osoite. AdGuard Home ei voinut tunnistaa, onko tälle verkkosovittimelle määritetty IP-osoite kiinteä. Määritä kiinteä IP-osoite itse.",
     "dhcp_dynamic_ip_found": "Järjestelmäsi käyttää verkkosovittimelle <0>{{interfaceName}}</0> dynaamista IP-osoitetta. Jotta voit käyttää DHCP-palvelinta, on sovittimelle määritettävä kiinteä IP-osoite. Nykyinen IP-osoitteesi on <0>{{ipAddress}}</0>. Tämä osoite määritetään automaattisesti kiinteäksi, jos painat \"Ota DHCP-palvelin käyttöön\" -painiketta.",
-    "dhcp_lease_added": "Kiinteä laina \"{{key}}\" on lisätty",
+    "dhcp_lease_added": "Kiinteä laina \"{{key}}\" lisättiin",
     "dhcp_lease_deleted": "Kiinteä laina \"{{key}}\" poistettiin",
     "dhcp_new_static_lease": "Uusi kiinteä laina",
     "dhcp_static_leases_not_found": "Kiinteitä DHCP-lainoja ei löytynyt",
diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json
index 9c12c3a9..8b051751 100644
--- a/client/src/__locales/fr.json
+++ b/client/src/__locales/fr.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nom de serveur invalide",
     "form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} »",
     "form_error_positive": "Doit être supérieur à 0",
+    "form_error_gateway_ip": "Le bail ne peut pas avoir d'adresse IP de la passerelle",
     "out_of_range_error": "Doit être hors plage « {{start}} » - « {{end}} »",
     "lower_range_start_error": "Doit être inférieur au début de plage",
     "greater_range_start_error": "Doit être supérieur au début de plage",
diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json
index 4f07548e..ba9bb78c 100644
--- a/client/src/__locales/hr.json
+++ b/client/src/__locales/hr.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nevažeće ime poslužitelja",
     "form_error_subnet": "Podmrežu \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"",
     "form_error_positive": "Mora biti veće od 0",
+    "form_error_gateway_ip": "Najam ne može imati IP adresu pristupnika",
     "out_of_range_error": "Mora biti izvan ranga \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Mora biti niže od početnog ranga",
     "greater_range_start_error": "Mora biti veće od krajnjeg ranga",
diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json
index 2f76426f..de66c91f 100644
--- a/client/src/__locales/hu.json
+++ b/client/src/__locales/hu.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Érvénytelen szervernév",
     "form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet",
     "form_error_positive": "0-nál nagyobbnak kell lennie",
+    "form_error_gateway_ip": "A bérleti szerződés nem tartalmazhatja az átjáró IP-címét",
     "out_of_range_error": "A következő tartományon kívül legyen: \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Kisebb legyen, mint a tartomány kezdete",
     "greater_range_start_error": "Nagyobbnak kell lennie, mint a tartomány kezdete",
diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json
index 1b6d584b..228951c9 100644
--- a/client/src/__locales/it.json
+++ b/client/src/__locales/it.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nome server non valido",
     "form_error_subnet": "Il subnet \"{{cidr}}\" non contiene l'indirizzo IP \"{{ip}}\"",
     "form_error_positive": "Deve essere maggiore di 0",
+    "form_error_gateway_ip": "Il leasing non può avere l'indirizzo IP del gateway",
     "out_of_range_error": "Deve essere fuori intervallo \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Deve essere inferiore dell'intervallo di inizio",
     "greater_range_start_error": "Deve essere maggiore dell'intervallo di inizio",
diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json
index bd11b6d5..168940a4 100644
--- a/client/src/__locales/ja.json
+++ b/client/src/__locales/ja.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "サーバー名が無効です",
     "form_error_subnet": "IPアドレス「{{ip}}」がサブネット「{{cidr}}」に含まれていません",
     "form_error_positive": "0より大きい値でなければなりません",
+    "form_error_gateway_ip": "リースはゲートウェイのIPアドレスになっていることができません",
     "out_of_range_error": "\"{{start}}\"〜\"{{end}}\" の範囲外である必要があります",
     "lower_range_start_error": "範囲開始よりも低い値である必要があります",
     "greater_range_start_error": "範囲開始値より大きい値でなければなりません",
diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json
index 440251d1..9c656c9e 100644
--- a/client/src/__locales/ko.json
+++ b/client/src/__locales/ko.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "유효하지 않은 서버 이름",
     "form_error_subnet": "서브넷 \"{{cidr}}\"에 \"{{ip}}\" IP 주소가 없습니다",
     "form_error_positive": "0보다 커야 합니다",
+    "form_error_gateway_ip": "임대는 게이트웨이의 IP 주소를 가질 수 없습니다",
     "out_of_range_error": "\"{{start}}\"-\"{{end}}\" 범위 밖이어야 합니다",
     "lower_range_start_error": "범위 시작보다 작은 값이어야 합니다",
     "greater_range_start_error": "범위 시작보다 큰 값이어야 합니다",
diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json
index f6266d46..efef1003 100644
--- a/client/src/__locales/nl.json
+++ b/client/src/__locales/nl.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Ongeldige servernaam",
     "form_error_subnet": "Subnet “{{cidr}}” bevat niet het IP-adres “{{ip}}”",
     "form_error_positive": "Moet groter zijn dan 0",
+    "form_error_gateway_ip": "Lease kan niet het IP-adres van de gateway hebben",
     "out_of_range_error": "Moet buiten bereik zijn \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Moet lager zijn dan begin reeks",
     "greater_range_start_error": "Moet groter zijn dan begin reeks",
@@ -408,7 +409,7 @@
     "manual_update": "<a>Volg deze stappen</a> om handmatig bij te werken.",
     "processing_update": "Even geduld, AdGuard Home wordt bijgewerkt",
     "clients_title": "Permanente clients",
-    "clients_desc": "Permanente client-records configureren voor apparaten verboden met AdGuard Home",
+    "clients_desc": "Permanente client-records configureren voor apparaten verbonden met AdGuard Home",
     "settings_global": "Globaal",
     "settings_custom": "Aangepast",
     "table_client": "Gebruiker",
diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json
index 7cd4179a..ae3b3848 100644
--- a/client/src/__locales/pl.json
+++ b/client/src/__locales/pl.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nieprawidłowa nazwa serwera",
     "form_error_subnet": "Podsieć \"{{cidr}}\" nie zawiera adresu IP \"{{ip}}\"",
     "form_error_positive": "Musi być większa niż 0",
+    "form_error_gateway_ip": "Lease nie może mieć adresu IP bramy",
     "out_of_range_error": "Musi być spoza zakresu \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Musi być niższy niż początek zakresu",
     "greater_range_start_error": "Musi być większy niż początek zakresu",
diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json
index 9e6ac0a0..21fccbc5 100644
--- a/client/src/__locales/ro.json
+++ b/client/src/__locales/ro.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nume de server nevalid",
     "form_error_subnet": "Subrețeaua „{{cidr}}” nu conține adresa IP „{{ip}}”",
     "form_error_positive": "Trebuie să fie mai mare de 0",
+    "form_error_gateway_ip": "Locația nu poate avea adresa IP a gateway-ului",
     "out_of_range_error": "Trebuie să fie în afara intervalului „{{start}}”-„{{end}}”",
     "lower_range_start_error": "Trebuie să fie mai mică decât începutul intervalului",
     "greater_range_start_error": "Trebuie să fie mai mare decât începutul intervalului",
diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json
index c96fccae..8c54c24a 100644
--- a/client/src/__locales/ru.json
+++ b/client/src/__locales/ru.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Некорректное имя сервера",
     "form_error_subnet": "Подсеть «{{cidr}}» не содержит IP-адрес «{{ip}}»",
     "form_error_positive": "Должно быть больше 0",
+    "form_error_gateway_ip": "Аренда не может иметь IP-адрес шлюза",
     "out_of_range_error": "Должно быть вне диапазона «{{start}}»-«{{end}}»",
     "lower_range_start_error": "Должно быть меньше начала диапазона",
     "greater_range_start_error": "Должно быть больше начала диапазона",
@@ -66,7 +67,7 @@
     "ip": "IP-адрес",
     "dhcp_table_hostname": "Имя хоста",
     "dhcp_table_expires": "Истекает",
-    "dhcp_warning": "Если вы все равно хотите включить DHCP-сервер, убедитесь, что в сети больше нет активных DHCP-серверов. Иначе это может сломать доступ в сеть для подключённых устройств!",
+    "dhcp_warning": "Если вы всё равно хотите включить DHCP-сервер, убедитесь, что в сети больше нет активных DHCP-серверов. Иначе это может сломать доступ в сеть для подключённых устройств!",
     "dhcp_error": "AdGuard Home не смог определить присутствие других DHCP-серверов в сети",
     "dhcp_static_ip_error": "Чтобы использовать DHCP-сервер, должен быть установлен статический IP-адрес. AdGuard Home не смог определить, использует ли этот сетевой интерфейс статический IP-адрес. Пожалуйста, установите его вручную.",
     "dhcp_dynamic_ip_found": "Ваша система использует динамический IP-адрес для интерфейса <0>{{interfaceName}}</0>. Чтобы использовать DHCP-сервер, необходимо установить статический IP-адрес. Ваш текущий IP-адрес – <0>{{ipAddress}}</0>. Мы автоматически установим его как статический, если вы нажмёте кнопку «Включить DHCP-сервер».",
@@ -163,8 +164,8 @@
     "apply_btn": "Применить",
     "disabled_filtering_toast": "Фильтрация выкл.",
     "enabled_filtering_toast": "Фильтрация вкл.",
-    "disabled_safe_browsing_toast": "Антифишинг отключен",
-    "enabled_safe_browsing_toast": "Антифишинг включен",
+    "disabled_safe_browsing_toast": "Антифишинг отключён",
+    "enabled_safe_browsing_toast": "Антифишинг включён",
     "disabled_parental_toast": "Родительский контроль выкл.",
     "enabled_parental_toast": "Родительский контроль вкл.",
     "disabled_safe_search_toast": "Безопасный поиск выкл.",
@@ -179,7 +180,7 @@
     "edit_table_action": "Редактировать",
     "delete_table_action": "Удалить",
     "elapsed": "Затрачено",
-    "filters_and_hosts_hint": "AdGuard Home распознает базовые правила блокировки и синтаксис файлов hosts.",
+    "filters_and_hosts_hint": "AdGuard Home распознаёт базовые правила блокировки и синтаксис файлов hosts.",
     "no_blocklist_added": "Чёрные списки не добавлены",
     "no_whitelist_added": "Белые списки не добавлены",
     "add_blocklist": "Добавить чёрный список",
@@ -328,10 +329,10 @@
     "install_submit_title": "Поздравляем!",
     "install_submit_desc": "Настройка завершена, AdGuard Home готов к использованию.",
     "install_devices_router": "Роутер",
-    "install_devices_router_desc": "Эта настройка покроет все устройства, подключенные к вашему домашнему роутеру, и вам не нужно будет настраивать каждое вручную.",
+    "install_devices_router_desc": "Эта настройка покроет все устройства, подключённые к вашему домашнему роутеру, и вам не нужно будет настраивать каждое вручную.",
     "install_devices_address": "DNS-сервер AdGuard Home доступен по следующим адресам",
     "install_devices_router_list_1": "Откройте настройки вашего роутера. Обычно вы можете открыть их в вашем браузере, например, http://192.168.0.1/ или http://192.168.1.1/. Вас могут попросить ввести пароль. Если вы не помните его, пароль часто можно сбросить, нажав на кнопку на самом роутере, но помните, что эта процедура может привести к потере всей конфигурации роутера. Если вашему роутеру необходимо приложение для настройки, установите его на свой телефон или ПК и воспользуйтесь им для настройки роутера.",
-    "install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы «DNS» рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделенных на 4 группы от одной до трёх цифр.",
+    "install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы «DNS» рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделённых на 4 группы от одной до трёх цифр.",
     "install_devices_router_list_3": "Введите туда адрес вашего AdGuard Home.",
     "install_devices_router_list_4": "Вы не можете установить собственный DNS-сервер на некоторых типах маршрутизаторов. В этом случае может помочь настройка AdGuard Home в качестве <0>DHCP-сервера</0>. В противном случае вам следует обратиться к руководству по настройке DNS-серверов для вашей конкретной модели маршрутизатора.",
     "install_devices_windows_list_1": "Откройте Панель управления через меню «Пуск» или через поиск Windows.",
diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json
index adfb71f7..2a2c1ab6 100644
--- a/client/src/__locales/si-lk.json
+++ b/client/src/__locales/si-lk.json
@@ -335,7 +335,7 @@
     "encryption_dot": "TLS-හරහා-ව.නා.ප. තොට",
     "encryption_dot_desc": "මෙම තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා TLS-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත.",
     "encryption_doq": "QUIC-හරහා-ව.නා.ප. තොට",
-    "encryption_doq_desc": "මෙම තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම තොට හරහා QUIC-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත. එය පරීක්‍ෂාත්මක වන අතර විශ්වාසදායක නොවිය හැකිය. එසේම, මේ වන විට එයට සහාය දක්වන බොහෝ අනුග්‍රාහක නැත.",
+    "encryption_doq_desc": "මෙම තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම තොට හරහා QUIC-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත.",
     "encryption_certificates": "සහතික",
     "encryption_certificates_input": "ඔබගේ PEM-කේතනය කළ සහතික පිටපත් කර මෙහි අලවන්න.",
     "encryption_status": "තත්වය",
@@ -406,7 +406,7 @@
     "access_disallowed_desc": "CIDR හෝ අ.ජා. කෙ. ලිපින ලැයිස්තුවක් වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අ.ජා. කෙ. ලිපින වලින් ඉල්ලීම් අත්හරිනු ඇත.",
     "access_blocked_title": "නොඉඩ ලත් වසම්",
     "access_settings_saved": "ප්‍රවේශ වීමේ සැකසුම් සාර්ථකව සුරකින ලදි",
-    "updates_checked": "යාවත්කාල සාර්ථකව පරික්‍ෂා කෙරිණි",
+    "updates_checked": "ඇඩ්ගාර්ඩ් හෝම් හි නව අනුවාදයක් තිබේ",
     "updates_version_equal": "ඇඩ්ගාර්ඩ් හෝම් යාවත්කාලීනයි",
     "check_updates_now": "දැන් යාවත්කාල පරීක්‍ෂා කරන්න",
     "dns_privacy": "ව.නා.ප. රහස්‍යතා",
diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json
index 229ebee2..2db7efeb 100644
--- a/client/src/__locales/sk.json
+++ b/client/src/__locales/sk.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Neplatné meno servera",
     "form_error_subnet": "Podsieť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
     "form_error_positive": "Musí byť väčšie ako 0",
+    "form_error_gateway_ip": "Prenájom nemôže mať IP adresu brány",
     "out_of_range_error": "Musí byť mimo rozsahu \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Musí byť nižšie ako začiatok rozsahu",
     "greater_range_start_error": "Musí byť väčšie ako začiatok rozsahu",
diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json
index 7e69c7c0..7cb8a97a 100644
--- a/client/src/__locales/sl.json
+++ b/client/src/__locales/sl.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Neveljavno ime strežnika",
     "form_error_subnet": "Podomrežje \"{{cidr}}\" ne vsebuje naslova IP \"{{ip}}\"",
     "form_error_positive": "Mora biti večja od 0",
+    "form_error_gateway_ip": "Najem ne more imeti naslova IP prehoda",
     "out_of_range_error": "Mora biti izven razpona \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Mora biti manjši od začetka razpona",
     "greater_range_start_error": "Mora biti večji od začetka razpona",
diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json
index bdeaf6aa..d39a9af5 100644
--- a/client/src/__locales/sr-cs.json
+++ b/client/src/__locales/sr-cs.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nevažeće ime servera",
     "form_error_subnet": "Subnet \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"",
     "form_error_positive": "Mora biti veće od 0",
+    "form_error_gateway_ip": "Zakup ne može imati IP adresu mrežnog prolaza",
     "out_of_range_error": "Mora biti izvan opsega \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Mora biti manje od početnog opsega",
     "greater_range_start_error": "Mora biti veće od početnog opsega",
diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json
index e0b7e70e..71b8ae6e 100644
--- a/client/src/__locales/sv.json
+++ b/client/src/__locales/sv.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Ogiltigt servernamn",
     "form_error_subnet": "Subnätet \"{{cidr}}\" innehåller inte IP-adressen \"{{ip}}\"",
     "form_error_positive": "Måste vara större än noll",
+    "form_error_gateway_ip": "Lease kan inte ha IP-adressen för gatewayen",
     "out_of_range_error": "Måste vara utanför intervallet \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Måste vara lägre än starten på intervallet",
     "greater_range_start_error": "Måste vara högre än starten på intervallet",
diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json
index b9f481e5..964406f1 100644
--- a/client/src/__locales/tr.json
+++ b/client/src/__locales/tr.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Geçersiz sunucu adı",
     "form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor",
     "form_error_positive": "0'dan büyük olmalıdır",
+    "form_error_gateway_ip": "Kiralama, ağ geçidinin IP adresine sahip olamaz",
     "out_of_range_error": "\"{{start}}\"-\"{{end}}\" aralığının dışında olmalıdır",
     "lower_range_start_error": "Başlangıç aralığından daha düşük olmalıdır",
     "greater_range_start_error": "Başlangıç aralığından daha büyük olmalıdır",
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index abc37b2e..c78fdde1 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Неправильна назва сервера",
     "form_error_subnet": "Підмережа «{{cidr}}» не містить IP-адресу «{{ip}}»",
     "form_error_positive": "Повинно бути більше за 0",
+    "form_error_gateway_ip": "Оренда не може мати IP-адресу шлюзу",
     "out_of_range_error": "Не повинна бути в діапазоні «{{start}}»−«{{end}}»",
     "lower_range_start_error": "Має бути меншим за початкову адресу",
     "greater_range_start_error": "Має бути більшим за початкову адресу",
@@ -77,7 +78,7 @@
     "dhcp_add_static_lease": "Додати статичну оренду",
     "dhcp_reset_leases": "Скинути всі аренди",
     "dhcp_reset_leases_confirm": "Ви дійсно хочете скинути усі аренди?",
-    "dhcp_reset_leases_success": "Аренди DHCP успішно скинуто",
+    "dhcp_reset_leases_success": "Оренду DHCP успішно скинуто",
     "dhcp_reset": "Ви дійсно хочете скинути DHCP-конфігурацію?",
     "country": "Країна",
     "city": "Місто",
@@ -218,7 +219,7 @@
     "example_upstream_tcp": "звичайний DNS (через TCP);",
     "example_upstream_tcp_hostname": "звичайний DNS (поверх TCP, з назвою вузла);",
     "all_lists_up_to_date_toast": "Всі списки вже оновлені",
-    "updated_upstream_dns_toast": "DNS-сервери оновлено",
+    "updated_upstream_dns_toast": "DNS-сервери успішно збережено",
     "dns_test_ok_toast": "Вказані DNS сервери працюють правильно",
     "dns_test_not_ok_toast": "Сервер «{{key}}»: неможливо використати. Перевірте правильність введення",
     "unblock": "Дозволити",
@@ -245,7 +246,7 @@
     "loading_table_status": "Завантаження...",
     "page_table_footer_text": "Сторінка",
     "rows_table_footer_text": "рядків",
-    "updated_custom_filtering_toast": "Власні правила фільтрування збережено",
+    "updated_custom_filtering_toast": "Власні правила фільтрування успішно збережено",
     "rule_removed_from_custom_filtering_toast": "Правило вилучено з власних правил фільтрування: {{rule}}",
     "rule_added_to_custom_filtering_toast": "Правило додано до власних правил фільтрування: {{rule}}",
     "query_log_response_status": "Стан: {{value}}",
@@ -514,7 +515,7 @@
     "statistics_clear": "Очистити статистику",
     "statistics_clear_confirm": "Ви впевнені, що хочете очистити статистику?",
     "statistics_retention_confirm": "Ви впевнені, що хочете змінити тривалість статистики? Якщо зменшити значення інтервалу, деякі дані будуть втрачені",
-    "statistics_cleared": "Статистика успішно очищена",
+    "statistics_cleared": "Статистику успішно очищено",
     "statistics_enable": "Увімкнути статистику",
     "interval_hours": "{{count}} година",
     "interval_hours_plural": "{{count}} годин(и)",
diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json
index 4d1a8637..b5742ea7 100644
--- a/client/src/__locales/zh-cn.json
+++ b/client/src/__locales/zh-cn.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "无效的服务器名",
     "form_error_subnet": "子网 \"{{cidr}}\" 不包含 IP 地址 \"{{ip}}\"",
     "form_error_positive": "必须大于 0",
+    "form_error_gateway_ip": "租约期限不能有网关的 IP 地址",
     "out_of_range_error": "必定超出了范围 \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "必须小于范围起始值",
     "greater_range_start_error": "必须大于范围起始值",
diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json
index 7ee2ac31..90d85586 100644
--- a/client/src/__locales/zh-tw.json
+++ b/client/src/__locales/zh-tw.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "無效的伺服器名稱",
     "form_error_subnet": "子網路 \"{{cidr}}\" 不包含該 IP 位址 \"{{ip}}\"",
     "form_error_positive": "必須大於 0",
+    "form_error_gateway_ip": "租約不能有閘道的 IP 位址",
     "out_of_range_error": "必須在\"{{start}}\"-\"{{end}}\"範圍之外",
     "lower_range_start_error": "必須低於起始範圍",
     "greater_range_start_error": "必須大於起始範圍",

From 56519548f1a9f706c4d66c91192c2fa74576929f Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 13 Jul 2022 14:43:21 +0300
Subject: [PATCH 093/143] Pull request: all: upd go

Merge in DNS/adguard-home from upd-go to master

Squashed commit of the following:

commit 132358d69178050ca2eacf8a988b4e4cb0e0ef57
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 13 13:29:07 2022 +0300

    all: upd go
---
 CHANGELOG.md              | 18 +++++++++++++-----
 bamboo-specs/release.yaml |  6 +++---
 bamboo-specs/test.yaml    |  2 +-
 3 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b8f5d97..07f33683 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,13 @@ and this project adheres to
 
 ### Security
 
+- Go version was updated to prevent the possibility of exploiting the
+  CVE-2022-1705, CVE-2022-32148, CVE-2022-30631, and other Go vulnerabilities
+  fixed in [Go 1.17.12][go-1.17.12].
+
+  <!--
+      TODO(a.garipov): Use the above format in all similar announcements below.
+  -->
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
 
@@ -49,7 +56,8 @@ and this project adheres to
 [#4698]: https://github.com/AdguardTeam/AdGuardHome/issues/4698
 [#4699]: https://github.com/AdguardTeam/AdGuardHome/issues/4699
 
-[ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
+[ddr-draft]:  https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
+[go-1.17.12]: https://groups.google.com/g/golang-announce/c/nqrv9fbR0zE
 
 
 
@@ -67,7 +75,7 @@ See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
 
 - Go version was updated to prevent the possibility of exploiting the
   [CVE-2022-29526], [CVE-2022-30634], [CVE-2022-30629], [CVE-2022-30580], and
-  [CVE-2022-29804] vulnerabilities.
+  [CVE-2022-29804] Go vulnerabilities.
 - Enforced password strength policy ([#3503]).
 
 ### Added
@@ -222,7 +230,7 @@ See also the [v0.107.6 GitHub milestone][ms-v0.107.6].
 
 - `User-Agent` HTTP header removed from outgoing DNS-over-HTTPS requests.
 - Go version was updated to prevent the possibility of exploiting the
-  [CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] vulnerabilities.
+  [CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] Go vulnerabilities.
 
 ### Added
 
@@ -277,7 +285,7 @@ were resolved.
 ### Security
 
 - Go version was updated to prevent the possibility of exploiting the
-  [CVE-2022-24921] vulnerability.
+  [CVE-2022-24921] Go vulnerability.
 
 [CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921
 
@@ -290,7 +298,7 @@ See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
 ### Security
 
 - Go version was updated to prevent the possibility of exploiting the
-  [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] vulnerabilities.
+  [CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] Go vulnerabilities.
 
 ### Fixed
 
diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index 694430ac..581ce55a 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -7,7 +7,7 @@
 # Make sure to sync any changes with the branch overrides below.
 'variables':
   'channel': 'edge'
-  'dockerGo': 'adguard/golang-ubuntu:4.4'
+  'dockerGo': 'adguard/golang-ubuntu:4.5'
 
 'stages':
 - 'Make release':
@@ -285,7 +285,7 @@
     # need to build a few of these.
     'variables':
       'channel': 'beta'
-      'dockerGo': 'adguard/golang-ubuntu:4.4'
+      'dockerGo': 'adguard/golang-ubuntu:4.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]+':
@@ -300,4 +300,4 @@
     # are the ones that actually get released.
     'variables':
       'channel': 'release'
-      'dockerGo': 'adguard/golang-ubuntu:4.4'
+      'dockerGo': 'adguard/golang-ubuntu:4.5'
diff --git a/bamboo-specs/test.yaml b/bamboo-specs/test.yaml
index cf11aa2e..30818cd4 100644
--- a/bamboo-specs/test.yaml
+++ b/bamboo-specs/test.yaml
@@ -5,7 +5,7 @@
   'key': 'AHBRTSPECS'
   'name': 'AdGuard Home - Build and run tests'
 'variables':
-  'dockerGo': 'adguard/golang-ubuntu:4.4'
+  'dockerGo': 'adguard/golang-ubuntu:4.5'
 
 'stages':
 - 'Tests':

From 84cd528103287dca9636f0450b3995b1506916fd Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 13 Jul 2022 16:39:03 +0300
Subject: [PATCH 094/143] Pull request: all: upd chlog

Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 43ecba8a5892102fbf635b54d50dbefa9fc9d174
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 13 16:29:24 2022 +0300

    all: upd chlog
---
 CHANGELOG.md | 53 ++++++++++++++++++++++++++++++++--------------------
 1 file changed, 33 insertions(+), 20 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 07f33683..51294829 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,13 +17,6 @@ and this project adheres to
 
 ### Security
 
-- Go version was updated to prevent the possibility of exploiting the
-  CVE-2022-1705, CVE-2022-32148, CVE-2022-30631, and other Go vulnerabilities
-  fixed in [Go 1.17.12][go-1.17.12].
-
-  <!--
-      TODO(a.garipov): Use the above format in all similar announcements below.
-  -->
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
 
@@ -37,6 +30,33 @@ and this project adheres to
 
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
 
+[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
+
+[ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
+
+
+
+<!--
+## [v0.107.9] - 2022-08-23 (APPROX.)
+-->
+
+
+
+## [v0.107.8] - 2022-07-13
+
+See also the [v0.107.8 GitHub milestone][ms-v0.107.8].
+
+### Security
+
+- Go version was updated to prevent the possibility of exploiting the
+  CVE-2022-1705, CVE-2022-32148, CVE-2022-30631, and other Go vulnerabilities
+  fixed in [Go 1.17.12][go-1.17.12].
+
+  <!--
+      TODO(a.garipov): Use the above format in all similar announcements below.
+  -->
+
 ### Fixed
 
 - DHCP lease validation incorrectly letting users assign the IP address of the
@@ -48,22 +68,14 @@ and this project adheres to
   under `dhcp.local_domain_name` ([#4699]).
 - Broken service installation on OpenWrt ([#4677]).
 
-[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
-[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
 [#4219]: https://github.com/AdguardTeam/AdGuardHome/issues/4219
 [#4677]: https://github.com/AdguardTeam/AdGuardHome/issues/4677
 [#4683]: https://github.com/AdguardTeam/AdGuardHome/issues/4683
 [#4698]: https://github.com/AdguardTeam/AdGuardHome/issues/4698
 [#4699]: https://github.com/AdguardTeam/AdGuardHome/issues/4699
 
-[ddr-draft]:  https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
-[go-1.17.12]: https://groups.google.com/g/golang-announce/c/nqrv9fbR0zE
-
-
-
-<!--
-## [v0.107.8] - 2022-07-12 (APPROX.)
--->
+[go-1.17.12]:  https://groups.google.com/g/golang-announce/c/nqrv9fbR0zE
+[ms-v0.107.8]: https://github.com/AdguardTeam/AdGuardHome/milestone/44?closed=1
 
 
 
@@ -1034,11 +1046,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
 
 
 <!--
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...HEAD
-[v0.107.8]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...v0.107.8
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...HEAD
+[v0.107.9]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...v0.107.9
 -->
 
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...HEAD
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...HEAD
+[v0.107.8]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...v0.107.8
 [v0.107.7]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...v0.107.7
 [v0.107.6]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...v0.107.6
 [v0.107.5]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5

From 9acb1f364b6e63675a890fe767af508f5f47d19f Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 26 Jul 2022 19:00:43 +0300
Subject: [PATCH 095/143] Pull request: 4782-server-name-label

Updates #4782.

Squashed commit of the following:

commit d350b3853bf722c0f2a8d1fc4a1c28dc384c5ca0
Author: Natalia Sokolova <n.sokolova@adguard.com>
Date:   Tue Jul 26 18:39:38 2022 +0300

    client: imp wording

commit d0785311bfe38fb10477bf8971a46d6c61aecfda
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 26 17:16:41 2022 +0300

    client: imp tls server name label
---
 client/src/__locales/en.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 9fc65cac..58008418 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -363,7 +363,7 @@
     "encryption_config_saved": "Encryption configuration saved",
     "encryption_server": "Server name",
     "encryption_server_enter": "Enter your domain name",
-    "encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate or wildcard certificate. If the field is not set, it will accept TLS connections for any domain.",
+    "encryption_server_desc": "If set, AdGuard Home detects ClientIDs, responds to DDR queries, and performs additional connection validations. If not set, these features are disabled. Must match one of the DNS Names in the certificate.",
     "encryption_redirect": "Redirect to HTTPS automatically",
     "encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
     "encryption_https": "HTTPS port",

From ae43ca06051bf5992aaaba2cd92ff934505ee673 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 27 Jul 2022 17:02:20 +0300
Subject: [PATCH 096/143] Pull request: issue-templates

Merge in DNS/adguard-home from issue-templates to master

Squashed commit of the following:

commit 989253530047a463804e81c8fda82ac268f39adc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 27 16:56:35 2022 +0300

    all: fix issue tmpl schema

commit e69df09ab4b4f713d124dc6eeb1ed34e0f4aaa70
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 27 16:41:14 2022 +0300

    all: rename tmpl files

commit 542306da1ea1bdc09ca328856367c64139a8ec60
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 27 16:37:02 2022 +0300

    all: imp github issue templates
---
 .github/ISSUE_TEMPLATE/Bug_report.md      |  49 ----------
 .github/ISSUE_TEMPLATE/Feature_request.md |  26 ------
 .github/ISSUE_TEMPLATE/bug.yml            | 106 ++++++++++++++++++++++
 .github/ISSUE_TEMPLATE/config.yml         |  17 ++++
 .github/ISSUE_TEMPLATE/feature.yml        |  38 ++++++++
 5 files changed, 161 insertions(+), 75 deletions(-)
 delete mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md
 delete mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md
 create mode 100644 .github/ISSUE_TEMPLATE/bug.yml
 create mode 100644 .github/ISSUE_TEMPLATE/config.yml
 create mode 100644 .github/ISSUE_TEMPLATE/feature.yml

diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md
deleted file mode 100644
index 1cee72e6..00000000
--- a/.github/ISSUE_TEMPLATE/Bug_report.md
+++ /dev/null
@@ -1,49 +0,0 @@
----
-name: Bug report
-about: Create a bug report to help us improve AdGuard Home
----
-
-Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new).
-
-### Prerequisites
-
-Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.**
-
-- [ ] I am running the latest version
-- [ ] I checked the documentation and found no answer
-- [ ] I checked to make sure that this issue has not already been filed
-
-### Issue Details
-
-<!-- Please include all relevant details about the environment you experienced the bug in.  If possible, include the result of running `./AdGuardHome -v --version` from the installation directory. -->
-
-* **Version of AdGuard Home server:**
-  * <!-- (e.g. v0.123.4) -->
-* **How did you install AdGuard Home:**
-  * <!-- (e.g. Built from source, Snapcraft, Docker, GitHub releases, etc.) -->
-* **How did you setup DNS configuration:**
-  * <!-- (System/Router/IoT) -->
-* **If it's a router or IoT, please write device model:**
-  * <!-- (e.g. Raspberry Pi 3 Model B) -->
-* **CPU architecture:**
-  * <!-- (e.g. AMD64, MIPS, etc.) -->
-* **Operating system and version:**
-  * <!-- (e.g. Ubuntu 18.04.1) -->
-
-### Expected Behavior
-<!-- A clear and concise description of what you expected to happen. -->
-
-### Actual Behavior
-<!-- A clear and concise description of what actually happened. -->
-
-### Screenshots
-<!-- If applicable, add screenshots to help explain your problem. -->
-
-<details><summary>Screenshot:</summary>
-
-<!--- drag and drop, upload or paste your screenshot to this area-->
-
-</details>
-
-### Additional Information
-<!-- Add any other context about the problem here. -->
diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md
deleted file mode 100644
index 094531b3..00000000
--- a/.github/ISSUE_TEMPLATE/Feature_request.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-name: Feature request
-about: Suggest a feature request for AdGuard Home
----
-
-Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new).
-
-### Prerequisites
-
-Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.**
-
-- [ ] I am running the latest version
-- [ ] I checked the documentation and found no answer
-- [ ] I checked to make sure that this issue has not already been filed
-
-### Problem Description
-<!-- Is your feature request related to a problem? Please add a clear and concise description of what the problem is. -->
-
-### Proposed Solution
-<!-- Describe the solution you'd like in a clear and concise manner -->
-
-### Alternatives Considered
-<!-- A clear and concise description of any alternative solutions or features you've considered. -->
-
-### Additional Information
-<!-- Add any other context about the problem here. -->
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
new file mode 100644
index 00000000..2d755964
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -0,0 +1,106 @@
+'body':
+  - 'attributes':
+        'description': >
+            Please make sure that the issue is not a duplicate or a question.
+            If it's a duplicate, please react to the original issue with a
+            thumbs up.  If it's a question, please post it to the GitHub
+            Discussions page.
+        'label': 'Prerequisites'
+        'options':
+          - 'label': >
+                I have checked the Wiki and Discussions and found no answer
+            'required': true
+          - 'label': >
+                I have searched other issues and found no duplicates
+            'required': true
+          - 'label': >
+                I want to report a bug and not ask a question
+            'required': true
+    'id': 'prerequisites'
+    'type': 'checkboxes'
+  - 'attributes':
+        'description': 'On which operating system type does the issue occur?'
+        'label': 'Operating system type'
+        'options':
+          - 'FreeBSD'
+          - 'Linux, OpenWrt'
+          - 'Linux, Other (please mention the version in the description)'
+          - 'macOS (aka Darwin)'
+          - 'OpenBSD'
+          - 'Windows'
+          - 'Other (please mention in the description)'
+    'id': 'os'
+    'type': 'dropdown'
+    'validations':
+        'required': true
+  - 'attributes':
+        'description': 'On which CPU architecture does the issue occur?'
+        'label': 'CPU architecture'
+        'options':
+          - 'AMD64'
+          - 'x86'
+          - '64-bit ARM'
+          - 'ARMv5'
+          - 'ARMv6'
+          - 'ARMv7'
+          - '64-bit MIPS'
+          - '64-bit MIPS LE'
+          - '32-bit MIPS'
+          - '32-bit MIPS LE'
+          - '64-bit PowerPC LE'
+          - 'Other (please mention in the description)'
+    'id': 'arch'
+    'type': 'dropdown'
+    'validations':
+        'required': true
+  - 'attributes':
+        'description': 'How did you install AdGuard Home?'
+        'label': 'Installation'
+        'options':
+          - 'GitHub releases or script from README'
+          - 'Docker'
+          - 'Snapcraft'
+          - 'Custom port'
+          - 'Other (please mention in the description)'
+    'id': 'install'
+    'type': 'dropdown'
+    'validations':
+        'required': true
+  - 'attributes':
+        'description': 'How did you setup AdGuard Home?'
+        'label': 'Setup'
+        'options':
+          - 'On one machine'
+          - 'On a router, DHCP is handled by the router'
+          - 'On a router, DHCP is handled by AdGuard Home'
+          - 'Other (please mention in the description)'
+    'id': 'setup'
+    'type': 'dropdown'
+    'validations':
+        'required': true
+  - 'attributes':
+        'description': 'What version of AdGuard Home are you using?'
+        'label': 'AdGuard Home version'
+    'id': 'version'
+    'type': 'input'
+    'validations':
+        'required': true
+  - 'attributes':
+        'description': 'Please describe the bug'
+        'label': 'Description'
+        'value': |
+            ### What did you do?
+
+            ### Expected result
+
+            ### Actual result
+
+            ### Screenshots (if applicable)
+
+            ### Additional information
+    'id': 'description'
+    'type': 'textarea'
+    'validations':
+        'required': true
+'description': 'File a bug report'
+'name': 'Bug'
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..0ef04ab5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,17 @@
+'blank_issues_enabled': false
+'contact_links':
+  - 'about': >
+        Please report filtering issues, for example advertising filters
+        misfiring or safe browsing false positives, using the form on our
+        website
+    'name': 'AdGuard filters issues'
+    'url': 'https://reports.adguard.com/en/new_issue.html'
+  - 'about': >
+        Please use GitHub Discussions for questions
+    'name': 'Q&A Discussions'
+    'url': 'https://github.com/AdguardTeam/AdGuardHome/discussions'
+  - 'about': >
+        Please check our Wiki for configuration file description, frequently
+        asked questions, and other documentation
+    'name': 'Wiki'
+    'url': 'https://github.com/AdguardTeam/AdGuardHome/wiki'
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
new file mode 100644
index 00000000..26f49278
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -0,0 +1,38 @@
+'body':
+  - 'attributes':
+        'description': >
+            Please make sure that the issue is not a duplicate or a question.
+            If it's a duplicate, please react to the original issue with a
+            thumbs up.  If it's a question, please post it to the GitHub
+            Discussions page.
+        'label': 'Prerequisites'
+        'options':
+          - 'label': >
+                I have checked the Wiki and Discussions and found no answer
+            'required': true
+          - 'label': >
+                I have searched other issues and found no duplicates
+            'required': true
+          - 'label': >
+                I want to request a feature or enhancement and not ask a
+                question
+            'required': true
+    'id': 'prerequisites'
+    'type': 'checkboxes'
+  - 'attributes':
+        'description': 'Please describe the request'
+        'label': 'Description'
+        'value': |
+            ### What problem are you trying to solve?
+
+            ### Proposed solution
+
+            ### Alternatives considered
+
+            ### Additional information
+    'id': 'description'
+    'type': 'textarea'
+    'validations':
+        'required': true
+'description': 'Suggest a feature or an enhancement for AdGuard Home'
+'name': 'Feature request or enhancement'

From e58a415d10b0f9656b19e670cd336d7e2ed0463c Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 28 Jul 2022 15:29:03 +0300
Subject: [PATCH 097/143] Pull request: imp-issue-tmpl

Merge in DNS/adguard-home from imp-issue-tmpl to master

Squashed commit of the following:

commit 3941dd135911d850f3ec9b01f55bc45269a7b91c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 28 15:24:26 2022 +0300

    all: fix links in issue tmpls

commit 438375a4666f951fc24ab47e4b0de5a61714973b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 28 15:23:00 2022 +0300

    all: imp issue tmpls
---
 .github/ISSUE_TEMPLATE/bug.yml     | 15 +++++++++------
 .github/ISSUE_TEMPLATE/feature.yml | 13 ++++++++-----
 2 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index 2d755964..a5c40e4f 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -8,7 +8,10 @@
         'label': 'Prerequisites'
         'options':
           - 'label': >
-                I have checked the Wiki and Discussions and found no answer
+                I have checked the
+                [Wiki](https://github.com/AdguardTeam/AdGuardHome/wiki) and
+                [Discussions](https://github.com/AdguardTeam/AdGuardHome/discussions)
+                and found no answer
             'required': true
           - 'label': >
                 I have searched other issues and found no duplicates
@@ -89,15 +92,15 @@
         'description': 'Please describe the bug'
         'label': 'Description'
         'value': |
-            ### What did you do?
+            #### What did you do?
 
-            ### Expected result
+            #### Expected result
 
-            ### Actual result
+            #### Actual result
 
-            ### Screenshots (if applicable)
+            #### Screenshots (if applicable)
 
-            ### Additional information
+            #### Additional information
     'id': 'description'
     'type': 'textarea'
     'validations':
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
index 26f49278..0ad6f5d8 100644
--- a/.github/ISSUE_TEMPLATE/feature.yml
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -8,7 +8,10 @@
         'label': 'Prerequisites'
         'options':
           - 'label': >
-                I have checked the Wiki and Discussions and found no answer
+                I have checked the
+                [Wiki](https://github.com/AdguardTeam/AdGuardHome/wiki) and
+                [Discussions](https://github.com/AdguardTeam/AdGuardHome/discussions)
+                and found no answer
             'required': true
           - 'label': >
                 I have searched other issues and found no duplicates
@@ -23,13 +26,13 @@
         'description': 'Please describe the request'
         'label': 'Description'
         'value': |
-            ### What problem are you trying to solve?
+            #### What problem are you trying to solve?
 
-            ### Proposed solution
+            #### Proposed solution
 
-            ### Alternatives considered
+            #### Alternatives considered
 
-            ### Additional information
+            #### Additional information
     'id': 'description'
     'type': 'textarea'
     'validations':

From 07d48af10c50808c3183b779ba193487cd900192 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 28 Jul 2022 17:28:24 +0300
Subject: [PATCH 098/143] Pull request: 4755-youtube-domain

Updates #4755.

Squashed commit of the following:

commit cb0ab8b26f6f277ef76ee3492c99870cbfc24666
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 28 17:21:02 2022 +0300

    filtering: add another youtube domain
---
 internal/filtering/blocked.go | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index 1d165cf4..48996a6d 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -43,13 +43,14 @@ var serviceRulesArray = []svc{{
 }, {
 	name: "youtube",
 	rules: []string{
-		"||youtube.com^",
-		"||ytimg.com^",
-		"||youtu.be^",
 		"||googlevideo.com^",
-		"||youtubei.googleapis.com^",
-		"||youtube-nocookie.com^",
+		"||wide-youtube.l.google.com^",
+		"||youtu.be^",
 		"||youtube",
+		"||youtube-nocookie.com^",
+		"||youtube.com^",
+		"||youtubei.googleapis.com^",
+		"||ytimg.com^",
 	},
 }, {
 	name:  "twitch",

From 0a5888f27acfba52e8c6d28cf58b93235d875fb2 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 28 Jul 2022 20:18:07 +0300
Subject: [PATCH 099/143] Pull request: upd-domains-and-links

Merge in DNS/adguard-home from upd-domains-and-links to master

Squashed commit of the following:

commit 5e5ff2fec358104995877da689da24749ac470ce
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 28 19:53:19 2022 +0300

    all: upd urls

    Update domains and URLs to make them more resistant to state blocking.
---
 .github/ISSUE_TEMPLATE/config.yml             |  2 +-
 README.md                                     | 77 +++++++++++--------
 bamboo-specs/release.yaml                     |  6 +-
 .../Settings/Dns/Upstream/Examples.js         | 10 +--
 .../components/Settings/Dns/Upstream/Form.js  |  2 +-
 client/src/components/ui/Footer.js            |  2 +-
 client/src/components/ui/Guide/Guide.js       |  4 +-
 client/src/helpers/constants.js               |  2 +-
 internal/dnsforward/config.go                 | 13 +++-
 internal/dnsforward/dnsforward.go             |  5 --
 internal/filtering/safebrowsing.go            |  5 +-
 internal/querylog/qlogfile_test.go            |  2 +-
 internal/updater/updater.go                   |  5 +-
 internal/updater/updater_test.go              | 48 ++++++------
 openapi/v1.yaml                               |  6 +-
 scripts/install.sh                            |  2 +-
 scripts/make/build-release.sh                 |  2 +-
 scripts/querylog/test/querylog.json           |  4 +-
 18 files changed, 108 insertions(+), 89 deletions(-)

diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 0ef04ab5..921e07cc 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -5,7 +5,7 @@
         misfiring or safe browsing false positives, using the form on our
         website
     'name': 'AdGuard filters issues'
-    'url': 'https://reports.adguard.com/en/new_issue.html'
+    'url': 'https://link.adtidy.org/forward.html?action=report&app=home&from=github'
   - 'about': >
         Please use GitHub Discussions for questions
     'name': 'Q&A Discussions'
diff --git a/README.md b/README.md
index 00664365..811e9303 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 &nbsp;
 <p align="center">
-  <img src="https://cdn.adguard.com/public/Adguard/Common/adguard_home.svg" width="300px" alt="AdGuard Home" />
+  <img src="https://cdn.adtidy.org/public/Adguard/Common/adguard_home.svg" width="300px" alt="AdGuard Home" />
 </p>
 <h3 align="center">Privacy protection center for you and your devices</h3>
 <p align="center">
@@ -8,7 +8,7 @@
 </p>
 
 <p align="center">
-    <a href="https://adguard.com/">AdGuard.com</a> |
+    <a href="https://link.adtidy.org/forward.html?action=home&from=readme&app=home">AdGuard.com</a> |
     <a href="https://github.com/AdguardTeam/AdGuardHome/wiki">Wiki</a> |
     <a href="https://reddit.com/r/Adguard">Reddit</a> |
     <a href="https://twitter.com/AdGuard">Twitter</a> |
@@ -23,9 +23,6 @@
     <a href="https://hub.docker.com/r/adguard/adguardhome">
         <img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/adguard/adguardhome.svg?maxAge=604800" />
     </a>
-    <a href="https://hub.docker.com/r/adguard/adguardhome">
-        <img alt="Docker Stars" src="https://img.shields.io/docker/stars/adguard/adguardhome.svg?maxAge=604800" />
-    </a>
     <br />
     <a href="https://github.com/AdguardTeam/AdGuardHome/releases">
         <img src="https://img.shields.io/github/release/AdguardTeam/AdGuardHome/all.svg" alt="Latest release" />
@@ -38,14 +35,19 @@
 <br />
 
 <p align="center">
-    <img src="https://cdn.adguard.com/public/Adguard/Common/adguard_home.gif" width="800" />
+    <img src="https://cdn.adtidy.org/public/Adguard/Common/adguard_home.gif" width="800" />
 </p>
 
 <hr />
 
 AdGuard Home is a network-wide software for blocking ads & tracking. After you set it up, it'll cover ALL your home devices, and you don't need any client-side software for that.
 
-It operates as a DNS server that re-routes tracking domains to a "black hole", thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common code.
+It operates as a DNS server that re-routes tracking domains to a “black hole”,
+thus preventing your devices from connecting to those servers.  It's based on
+software we use for our public
+[AdGuard DNS](https://link.adtidy.org/forward.html?action=dns&from=readme&app=home)
+servers, and both share a lot of code.
+
 
 * [Getting Started](#getting-started)
 * [Comparing AdGuard Home to other solutions](#comparison)
@@ -161,7 +163,14 @@ AdGuard Home provides a lot of features out-of-the-box with no need to install a
 
 It depends.
 
-"DNS sinkholing" is capable of blocking a big percentage of ads, but it lacks flexibility and power of traditional ad blockers. You can get a good impression about the difference between these methods by reading [this article](https://adguard.com/en/blog/adguard-vs-adaway-dns66/). It compares AdGuard for Android (a traditional ad blocker) to hosts-level ad blockers (which are almost identical to DNS-based blockers in their capabilities). This level of protection is enough for some users. 
+“DNS sinkholing” is capable of blocking a big percentage of ads, but it lacks
+flexibility and power of traditional ad blockers.  You can get a good impression
+about the difference between these methods by reading
+[this article](https://link.adtidy.org/forward.html?action=blog_adaway&from=readme&app=home).
+It compares AdGuard for Android (a traditional ad blocker) to hosts-level ad
+blockers (which are almost identical to DNS-based blockers in their
+capabilities).  This level of protection is enough for some users.
+
 
 Additionally, using a DNS-based blocker can help to block ads, tracking and analytics requests on other types of devices, such as SmartTVs, smart speakers or other kinds of IoT devices (on which you can't install traditional ad blockers).
 
@@ -281,28 +290,28 @@ curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/s
 ```
 
  *  Beta channel builds
-     *  Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
-     *  Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
-     *  Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
-     *  Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip)
-     *  macOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip)
-     *  macOS ARM: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_arm64.zip)
-     *  FreeBSD: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz)
-     *  FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz)
+     *  Linux: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
+     *  Linux ARM: [32-bit ARMv6](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
+     *  Linux MIPS: [32-bit MIPS](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
+     *  Windows: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_386.zip)
+     *  macOS: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_386.zip)
+     *  macOS ARM: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_arm64.zip)
+     *  FreeBSD: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz)
+     *  FreeBSD ARM: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz)
      *  OpenBSD: (coming soon)
      *  OpenBSD ARM: (coming soon)
 
  *  Edge channel builds
-     *  Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
-     *  Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
-     *  Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
-     *  Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip)
-     *  macOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip)
-     *  macOS ARM: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_arm64.zip)
-     *  FreeBSD: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_386.tar.gz)
-     *  FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv7.tar.gz)
-     *  OpenBSD: [64-bit (experimental)](https://static.adguard.com/adguardhome/edge/AdGuardHome_openbsd_amd64.tar.gz)
-     *  OpenBSD ARM: [64-bit (experimental)](https://static.adguard.com/adguardhome/edge/AdGuardHome_openbsd_arm64.tar.gz)
+     *  Linux: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
+     *  Linux ARM: [32-bit ARMv6](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
+     *  Linux MIPS: [32-bit MIPS](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
+     *  Windows: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_windows_386.zip)
+     *  macOS: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_darwin_386.zip)
+     *  macOS ARM: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_darwin_arm64.zip)
+     *  FreeBSD: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_386.tar.gz)
+     *  FreeBSD ARM: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_armv7.tar.gz)
+     *  OpenBSD: [64-bit (experimental)](https://static.adtidy.org/adguardhome/edge/AdGuardHome_openbsd_amd64.tar.gz)
+     *  OpenBSD ARM: [64-bit (experimental)](https://static.adtidy.org/adguardhome/edge/AdGuardHome_openbsd_arm64.tar.gz)
 
 
 <a id="reporting-issues"></a>
@@ -313,9 +322,12 @@ If you run into any problem or have a suggestion, head to [this page](https://gi
 <a id="translate"></a>
 ### Help with translations
 
-If you want to help with AdGuard Home translations, please learn more about translating AdGuard products here: https://kb.adguard.com/en/general/adguard-translations
+If you want to help with AdGuard Home translations, please learn more about
+translating AdGuard products
+[in our Knowledge Base](https://link.adtidy.org/forward.html?action=kb_translations&from=readme&app=home).
 
-Here is a link to AdGuard Home project: https://crowdin.com/project/adguard-applications/en#/adguard-home
+Direct link to AdGuard Home project on CrowdIn:
+<https://crowdin.com/project/adguard-applications/en#/adguard-home>.
 
 <a id="help-other"></a>
 ### Other
@@ -364,11 +376,16 @@ This software wouldn't have been possible without:
    * And many more node.js packages.
  * [whotracks.me data](https://github.com/cliqz-oss/whotracks.me)
 
-You might have seen that [CoreDNS](https://coredns.io) was mentioned here before — we've stopped using it in AdGuard Home. While we still use it on our servers for [AdGuard DNS](https://adguard.com/adguard-dns/overview.html) service, it seemed like an overkill for Home as it impeded Home features that we plan to implement.
+You might have seen that [CoreDNS](https://coredns.io) was mentioned here before
+— we've stopped using it in AdGuard Home.
 
 For a full list of all node.js packages in use, please take a look at [client/package.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/package.json) file.
 
 <a id="privacy"></a>
 ## Privacy
 
-Our main idea is that you are the one, who should be in control of your data. So it is only natural, that AdGuard Home does not collect any usage statistics, and does not use any web services unless you configure it to do so. Full policy with every bit that _could in theory be_ sent by AdGuard Home is available [here](https://adguard.com/en/privacy/home.html).
+Our main idea is that you are the one, who should be in control of your data. So
+it is only natural, that AdGuard Home does not collect any usage statistics, and
+does not use any web services unless you configure it to do so. Full policy with
+every bit that _could in theory be_ sent by AdGuard Home is available
+[here](https://link.adtidy.org/forward.html?action=privacy&from=readme&app=home).
diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index 581ce55a..953e3c4c 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -22,11 +22,11 @@
     'jobs':
     - 'Make and publish docker'
 
-- 'Publish to static.adguard.com':
+- 'Publish to static storage':
     'manual': false
     'final': false
     'jobs':
-    - 'Publish to static.adguard.com'
+    - 'Publish to static storage'
 
 - 'Publish to Snapstore':
     'manual': false
@@ -132,7 +132,7 @@
   'requirements':
   - 'adg-docker': 'true'
 
-'Publish to static.adguard.com':
+'Publish to static storage':
   'key': 'PUB'
   'other':
     'clean-working-dir': true
diff --git a/client/src/components/Settings/Dns/Upstream/Examples.js b/client/src/components/Settings/Dns/Upstream/Examples.js
index b4e0ce09..f8010b22 100644
--- a/client/src/components/Settings/Dns/Upstream/Examples.js
+++ b/client/src/components/Settings/Dns/Upstream/Examples.js
@@ -11,16 +11,16 @@ const Examples = (props) => (
                 <code>94.140.14.140</code>: {props.t('example_upstream_regular')}
             </li>
             <li>
-                <code>udp://dns-unfiltered.adguard.com</code>: <Trans>example_upstream_udp</Trans>
+                <code>udp://unfiltered.adguard-dns.com</code>: <Trans>example_upstream_udp</Trans>
             </li>
             <li>
                 <code>tcp://94.140.14.140</code>: <Trans>example_upstream_tcp</Trans>
             </li>
             <li>
-                <code>tcp://dns-unfiltered.adguard.com</code>: <Trans>example_upstream_tcp_hostname</Trans>
+                <code>tcp://unfiltered.adguard-dns.com</code>: <Trans>example_upstream_tcp_hostname</Trans>
             </li>
             <li>
-                <code>tls://dns-unfiltered.adguard.com</code>:
+                <code>tls://unfiltered.adguard-dns.com</code>:
                 <span>
                     <Trans
                         components={[
@@ -39,7 +39,7 @@ const Examples = (props) => (
                 </span>
             </li>
             <li>
-                <code>https://dns-unfiltered.adguard.com/dns-query</code>:
+                <code>https://unfiltered.adguard-dns.com/dns-query</code>:
                 <span>
                     <Trans
                         components={[
@@ -58,7 +58,7 @@ const Examples = (props) => (
                 </span>
             </li>
             <li>
-                <code>quic://dns-unfiltered.adguard.com:784</code>:
+                <code>quic://unfiltered.adguard-dns.com:784</code>:
                 <span>
                     <Trans
                         components={[
diff --git a/client/src/components/Settings/Dns/Upstream/Form.js b/client/src/components/Settings/Dns/Upstream/Form.js
index 63e73b5d..be01b2b7 100644
--- a/client/src/components/Settings/Dns/Upstream/Form.js
+++ b/client/src/components/Settings/Dns/Upstream/Form.js
@@ -149,7 +149,7 @@ const Form = ({
                 {' '}
                 <Trans components={[
                     <a
-                        href="https://kb.adguard.com/general/dns-providers"
+                        href="https://link.adtidy.org/forward.html?action=dns_kb_providers&from=ui&app=home"
                         target="_blank"
                         rel="noopener noreferrer"
                         key="0"
diff --git a/client/src/components/ui/Footer.js b/client/src/components/ui/Footer.js
index 7abe3378..393e16fe 100644
--- a/client/src/components/ui/Footer.js
+++ b/client/src/components/ui/Footer.js
@@ -44,7 +44,7 @@ const Footer = () => {
     const renderCopyright = () => <div className="footer__column">
         <div className="footer__copyright">
             {t('copyright')} &copy; {getYear()}{' '}
-            <a target="_blank" rel="noopener noreferrer" href="https://adguard.com/">AdGuard</a>
+            <a target="_blank" rel="noopener noreferrer" href="https://link.adtidy.org/forward.html?action=home&from=ui&app=home">AdGuard</a>
         </div>
     </div>;
 
diff --git a/client/src/components/ui/Guide/Guide.js b/client/src/components/ui/Guide/Guide.js
index 53cf82a7..64537320 100644
--- a/client/src/components/ui/Guide/Guide.js
+++ b/client/src/components/ui/Guide/Guide.js
@@ -38,7 +38,7 @@ const getDnsPrivacyList = () => [
                 components: [
                     {
                         key: 0,
-                        href: 'https://adguard.com/adguard-android/overview.html',
+                        href: 'https://link.adtidy.org/forward.html?action=android&from=ui&app=home',
                     },
                     <code key="1">text</code>,
                 ],
@@ -63,7 +63,7 @@ const getDnsPrivacyList = () => [
                 components: [
                     {
                         key: 0,
-                        href: 'https://adguard.com/adguard-ios/overview.html',
+                        href: 'https://link.adtidy.org/forward.html?action=ios&from=ui&app=home',
                     },
                     <code key="1">text</code>,
                 ],
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index 197cabba..6598530c 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -60,7 +60,7 @@ export const REPOSITORY = {
 export const CLIENT_ID_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Clients#clientid';
 export const MANUAL_UPDATE_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#manual-update';
 export const PORT_53_FAQ_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#bindinuse';
-export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
+export const PRIVACY_POLICY_LINK = 'https://link.adtidy.org/forward.html?action=privacy&from=ui&app=home';
 export const UPSTREAM_CONFIGURATION_WIKI_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams';
 
 export const FILTERS_RELATIVE_LINK = '#filters';
diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go
index f9234155..eaee9155 100644
--- a/internal/dnsforward/config.go
+++ b/internal/dnsforward/config.go
@@ -278,6 +278,11 @@ func (s *Server) createProxyConfig() (proxy.Config, error) {
 	return proxyConfig, nil
 }
 
+const (
+	defaultSafeBrowsingBlockHost = "standard-block.dns.adguard.com"
+	defaultParentalBlockHost     = "family-block.dns.adguard.com"
+)
+
 // initDefaultSettings initializes default settings if nothing
 // is configured
 func (s *Server) initDefaultSettings() {
@@ -289,12 +294,12 @@ func (s *Server) initDefaultSettings() {
 		s.conf.BootstrapDNS = defaultBootstrap
 	}
 
-	if len(s.conf.ParentalBlockHost) == 0 {
-		s.conf.ParentalBlockHost = parentalBlockHost
+	if s.conf.ParentalBlockHost == "" {
+		s.conf.ParentalBlockHost = defaultParentalBlockHost
 	}
 
-	if len(s.conf.SafeBrowsingBlockHost) == 0 {
-		s.conf.SafeBrowsingBlockHost = safeBrowsingBlockHost
+	if s.conf.SafeBrowsingBlockHost == "" {
+		s.conf.SafeBrowsingBlockHost = defaultSafeBrowsingBlockHost
 	}
 
 	if s.conf.UDPListenAddrs == nil {
diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go
index 9d0e8854..81ac93ed 100644
--- a/internal/dnsforward/dnsforward.go
+++ b/internal/dnsforward/dnsforward.go
@@ -33,11 +33,6 @@ const DefaultTimeout = 10 * time.Second
 // requests between the BeforeRequestHandler stage and the actual processing.
 const defaultClientIDCacheCount = 1024
 
-const (
-	safeBrowsingBlockHost = "standard-block.dns.adguard.com"
-	parentalBlockHost     = "family-block.dns.adguard.com"
-)
-
 var defaultDNS = []string{
 	"https://dns10.quad9.net/dns-query",
 }
diff --git a/internal/filtering/safebrowsing.go b/internal/filtering/safebrowsing.go
index 9de4bcd2..046756ac 100644
--- a/internal/filtering/safebrowsing.go
+++ b/internal/filtering/safebrowsing.go
@@ -24,10 +24,11 @@ import (
 
 // Safe browsing and parental control methods.
 
+// TODO(a.garipov): Make configurable.
 const (
 	dnsTimeout                = 3 * time.Second
-	defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
-	defaultParentalServer     = `https://dns-family.adguard.com/dns-query`
+	defaultSafebrowsingServer = `https://family.adguard-dns.com/dns-query`
+	defaultParentalServer     = `https://family.adguard-dns.com/dns-query`
 	sbTXTSuffix               = `sb.dns.adguard.com.`
 	pcTXTSuffix               = `pc.dns.adguard.com.`
 )
diff --git a/internal/querylog/qlogfile_test.go b/internal/querylog/qlogfile_test.go
index ff1a53b3..3e32420f 100644
--- a/internal/querylog/qlogfile_test.go
+++ b/internal/querylog/qlogfile_test.go
@@ -303,7 +303,7 @@ func NewTestQLogFileData(t *testing.T, data string) (file *QLogFile) {
 func TestQLog_Seek(t *testing.T) {
 	const nl = "\n"
 	const strV = "%s"
-	const recs = `{"T":"` + strV + `","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"}` + nl +
+	const recs = `{"T":"` + strV + `","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://unfiltered.adguard-dns.com:853"}` + nl +
 		`{"T":"` + strV + `"}` + nl +
 		`{"T":"` + strV + `"}` + nl
 	timestamp, _ := time.Parse(time.RFC3339Nano, "2020-08-31T18:44:25.376690873+03:00")
diff --git a/internal/updater/updater.go b/internal/updater/updater.go
index 5077787c..a0672c58 100644
--- a/internal/updater/updater.go
+++ b/internal/updater/updater.go
@@ -82,8 +82,9 @@ type Config struct {
 func NewUpdater(conf *Config) *Updater {
 	u := &url.URL{
 		Scheme: "https",
-		Host:   "static.adguard.com",
-		Path:   path.Join("adguardhome", conf.Channel, "version.json"),
+		// TODO(a.garipov): Make configurable.
+		Host: "static.adtidy.org",
+		Path: path.Join("adguardhome", conf.Channel, "version.json"),
 	}
 	return &Updater{
 		client: conf.Client,
diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go
index b3268f2f..219dc087 100644
--- a/internal/updater/updater_test.go
+++ b/internal/updater/updater_test.go
@@ -45,28 +45,28 @@ func TestUpdateGetVersion(t *testing.T) {
   "announcement": "AdGuard Home v0.103.0-beta.2 is now available!",
   "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases",
   "selfupdate_min_version": "v0.0",
-  "download_windows_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip",
-  "download_windows_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip",
-  "download_darwin_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip",
-  "download_darwin_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip",
-  "download_linux_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz",
-  "download_linux_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz",
-  "download_linux_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz",
-  "download_linux_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz",
-  "download_linux_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz",
-  "download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz",
-  "download_linux_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz",
-  "download_linux_mips": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz",
-  "download_linux_mipsle": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz",
-  "download_linux_mips64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz",
-  "download_linux_mips64le": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz",
-  "download_freebsd_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz",
-  "download_freebsd_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz",
-  "download_freebsd_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz",
-  "download_freebsd_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz",
-  "download_freebsd_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz",
-  "download_freebsd_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz",
-  "download_freebsd_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz"
+  "download_windows_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_amd64.zip",
+  "download_windows_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_386.zip",
+  "download_darwin_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_amd64.zip",
+  "download_darwin_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_386.zip",
+  "download_linux_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz",
+  "download_linux_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_386.tar.gz",
+  "download_linux_arm": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz",
+  "download_linux_armv5": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz",
+  "download_linux_armv6": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz",
+  "download_linux_armv7": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz",
+  "download_linux_arm64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz",
+  "download_linux_mips": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz",
+  "download_linux_mipsle": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz",
+  "download_linux_mips64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz",
+  "download_linux_mips64le": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz",
+  "download_freebsd_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz",
+  "download_freebsd_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz",
+  "download_freebsd_arm": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz",
+  "download_freebsd_armv5": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz",
+  "download_freebsd_armv6": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz",
+  "download_freebsd_armv7": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz",
+  "download_freebsd_arm64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz"
 }`
 
 	l, lport := startHTTPServer(jsonData)
@@ -260,7 +260,7 @@ func TestUpdater_VersionInto_ARM(t *testing.T) {
   "announcement": "AdGuard Home v0.103.0-beta.2 is now available!",
   "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases",
   "selfupdate_min_version": "v0.0",
-  "download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz"
+  "download_linux_armv7": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz"
 }`
 
 	l, lport := startHTTPServer(jsonData)
@@ -297,7 +297,7 @@ func TestUpdater_VersionInto_MIPS(t *testing.T) {
   "announcement": "AdGuard Home v0.103.0-beta.2 is now available!",
   "announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases",
   "selfupdate_min_version": "v0.0",
-  "download_linux_mips_softfloat": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz"
+  "download_linux_mips_softfloat": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz"
 }`
 
 	l, lport := startHTTPServer(jsonData)
diff --git a/openapi/v1.yaml b/openapi/v1.yaml
index a9092c98..ff515692 100644
--- a/openapi/v1.yaml
+++ b/openapi/v1.yaml
@@ -4888,12 +4888,12 @@
 
          *  `94.140.14.140`: plain DNS-over-UDP.
 
-         *  `tls://dns-unfiltered.adguard.com`: encrypted DNS-over-TLS.
+         *  `tls://unfiltered.adguard-dns.com`: encrypted DNS-over-TLS.
 
-         *  `https://dns-unfiltered.adguard.com/dns-query`: encrypted
+         *  `https://unfiltered.adguard-dns.com/dns-query`: encrypted
              DNS-over-HTTPS.
 
-         *  `quic://dns-unfiltered.adguard.com:784`: encrypted DNS-over-QUIC
+         *  `quic://unfiltered.adguard-dns.com:784`: encrypted DNS-over-QUIC
              (experimental).
 
          *  `tcp://94.140.14.140`: plain DNS-over-TCP.
diff --git a/scripts/install.sh b/scripts/install.sh
index 8cad4609..5931fd3c 100644
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -381,7 +381,7 @@ configure() {
 	check_out_dir
 
 	pkg_name="AdGuardHome_${os}_${cpu}.${pkg_ext}"
-	url="https://static.adguard.com/adguardhome/${channel}/${pkg_name}"
+	url="https://static.adtidy.org/adguardhome/${channel}/${pkg_name}"
 	agh_dir="${out_dir}/AdGuardHome"
 	readonly pkg_name url agh_dir
 
diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh
index 2cf95086..072dfe7a 100644
--- a/scripts/make/build-release.sh
+++ b/scripts/make/build-release.sh
@@ -349,7 +349,7 @@ echo "version=$version" > "./${dist}/version.txt"
 
 # Create the version.json file.
 
-version_download_url="https://static.adguard.com/adguardhome/${channel}"
+version_download_url="https://static.adtidy.org/adguardhome/${channel}"
 version_json="./${dist}/version.json"
 readonly version_download_url version_json
 
diff --git a/scripts/querylog/test/querylog.json b/scripts/querylog/test/querylog.json
index a8fa71f4..543917f8 100644
--- a/scripts/querylog/test/querylog.json
+++ b/scripts/querylog/test/querylog.json
@@ -1,5 +1,5 @@
-{"IP":"192.168.0.0","T":"2020-08-31T16:43:37.724457416+03:00","QH":"mtalk.google.com","QT":"A","QC":"IN","CP":"","Answer":"rm+BgAABAAIAAAAABW10YWxrBmdvb2dsZQNjb20AAAEAAcAMAAUAAQAAnwUAEQxtb2JpbGUtZ3RhbGsBbMASwC4AAQABAAAAWQAEjvobvA==","Result":{},"Elapsed":48051030,"Upstream":"tls://dns-unfiltered.adguard.com:853"}
+{"IP":"192.168.0.0","T":"2020-08-31T16:43:37.724457416+03:00","QH":"mtalk.google.com","QT":"A","QC":"IN","CP":"","Answer":"rm+BgAABAAIAAAAABW10YWxrBmdvb2dsZQNjb20AAAEAAcAMAAUAAQAAnwUAEQxtb2JpbGUtZ3RhbGsBbMASwC4AAQABAAAAWQAEjvobvA==","Result":{},"Elapsed":48051030,"Upstream":"tls://unfiltered.adguard-dns.com:853"}
 {"IP":"127.0.0.1","T":"2020-09-09T13:56:35.532956+03:00","QH":"example.org","QT":"AAAA","QC":"IN","CP":"","Answer":"mrOBgAABAAEAAAAAB2V4YW1wbGUDb3JnAAAcAAHADAAcAAEAAKjAABAmBigAAiAAAQJIGJMlyBlG","Result":{},"Elapsed":132164793,"Upstream":"https://dns10.quad9.net:443/dns-query"}
 {"IP":"127.0.0.1","T":"2020-09-09T13:56:54.255453+03:00","QH":"ad.doubleclick.net","QT":"A","QC":"IN","CP":"","Answer":"wqmBgAABAAIAAAAAAmFkC2RvdWJsZWNsaWNrA25ldAAAAQABwAwABQABAACTawAJBGRhcnQBbMAPwDAAAQABAAAA5gAErNkQhg==","Result":{},"Elapsed":48131793,"Upstream":"https://dns10.quad9.net:443/dns-query"}
 {"IP":"127.0.0.1","T":"2020-09-09T13:57:07.495948+03:00","QH":"ad.doubleclick.net","QT":"A","QC":"IN","CP":"","Answer":"JP2BhQABAAAAAAAAAmFkC2RvdWJsZWNsaWNrA25ldAAAAQAB","Result":{"IsFiltered":true,"Reason":3,"Rule":"||ad.doubleclick.net^","FilterID":1},"Elapsed":369806}
-{"IP":"192.168.0.15","T":"2020-01-17T17:39:40.306375885+03:00","QH":"push.apple.com","QT":"TXT","QC":"IN","Answer":"8AWBgAABAAEAAAABBHB1c2gFYXBwbGUDY29tAAAQAAHADAAQAAEAABOsAAkIY291bnQ9NTAAACkFrAAAAAAAQAAMADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","Result":{},"Elapsed":30271893,"Upstream":"https://cloudflare-dns.com:443/dns-query"}
\ No newline at end of file
+{"IP":"192.168.0.15","T":"2020-01-17T17:39:40.306375885+03:00","QH":"push.apple.com","QT":"TXT","QC":"IN","Answer":"8AWBgAABAAEAAAABBHB1c2gFYXBwbGUDY29tAAAQAAHADAAQAAEAABOsAAkIY291bnQ9NTAAACkFrAAAAAAAQAAMADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","Result":{},"Elapsed":30271893,"Upstream":"https://cloudflare-dns.com:443/dns-query"}

From f5959a0dc6f6d2d1ae9c55766097008952038830 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 28 Jul 2022 20:36:21 +0300
Subject: [PATCH 100/143] Pull request: upd-chlog

Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 9637884cebcd1fc2c0d395deba05529e891e3711
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 28 20:29:12 2022 +0300

    all: upd chlog
---
 CHANGELOG.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 51294829..7ae6e50a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,11 @@ and this project adheres to
   draft][ddr-draft] ([#4463]).
 - `windows/arm64` support ([#3057]).
 
+### Changed
+
+- UI and update links have been changed to make them more resistant to DNS
+  blocking.
+
 ### Deprecated
 
 - Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.

From f32da12a86ec21c9a5e41e71b2620a3899d1bcf1 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Fri, 29 Jul 2022 19:27:15 +0300
Subject: [PATCH 101/143] Pull request: 4517 domain specific test

Merge in DNS/adguard-home from 4517-domain-specific-test to master

Updates #4517.

Squashed commit of the following:

commit 03a803f831749a060923ec966592696f99591786
Merge: 8ea24170 f5959a0d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 19:17:28 2022 +0300

    Merge branch 'master' into 4517-domain-specific-test

commit 8ea2417036547996bb2d39b75b0ff31de4fe9b21
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 18:44:26 2022 +0300

    all: log changes, imp docs

commit aa74c8be64f2796a2dfa7166f0155fff5bb395b6
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 18:07:12 2022 +0300

    dnsforward: imp logging

commit 02dccca4e7d766bbfbe0826933e8be70fcd93f58
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 17:24:08 2022 +0300

    all: imp code, docs

commit 3b21067d07b208baf574a34fb06ec808b37c4ee3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 13:34:55 2022 +0300

    client: add warning toast

commit ea2272dc77f87e34dc6aff0af99c7a51a04e3770
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 28 20:11:55 2022 +0300

    dnsforward: imp err msg, docs

commit fd9ee82afef9d93961c30ebafcc7a11d984247b5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 28 19:24:58 2022 +0300

    dnsforward: test doain specific upstreams

commit 9a83ebfa7a73bf4e03eaf1ff4a33f79771159fc7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 28 18:22:49 2022 +0300

    dnsforward: merge some logic
---
 CHANGELOG.md                     |   3 +
 client/src/__locales/en.json     |   1 +
 client/src/actions/index.js      |   6 +-
 internal/dnsforward/http.go      | 156 ++++++++++++++++++++-----------
 internal/dnsforward/http_test.go | 153 +++++++++---------------------
 5 files changed, 151 insertions(+), 168 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ae6e50a..9c95b887 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,8 @@ and this project adheres to
 
 ### Added
 
+- Domain-specific upstream servers test.  Such test fails with an appropriate
+  warning message ([#4517]).
 - Support for Discovery of Designated Resolvers (DDR) according to the [RFC
   draft][ddr-draft] ([#4463]).
 - `windows/arm64` support ([#3057]).
@@ -37,6 +39,7 @@ and this project adheres to
 
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
+[#4517]: https://github.com/AdguardTeam/AdGuardHome/issues/4517
 
 [ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
 
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 58008418..46e75fe6 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Upstream servers successfully saved",
     "dns_test_ok_toast": "Specified DNS servers are working correctly",
     "dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly",
+    "dns_test_warning_toast": "Server \"{{key}}\" does not respond to test requests and may not work properly",
     "unblock": "Unblock",
     "block": "Block",
     "disallow_this_client": "Disallow this client",
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index 8e153224..e1b4e96c 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -314,13 +314,15 @@ export const testUpstream = (
         const testMessages = Object.keys(upstreamResponse)
             .map((key) => {
                 const message = upstreamResponse[key];
-                if (message !== 'OK') {
+                if (message.startsWith('WARNING:')) {
+                    dispatch(addErrorToast({ error: i18next.t('dns_test_warning_toast', { key }) }));
+                } else if (message !== 'OK') {
                     dispatch(addErrorToast({ error: i18next.t('dns_test_not_ok_toast', { key }) }));
                 }
                 return message;
             });
 
-        if (testMessages.every((message) => message === 'OK')) {
+        if (testMessages.every((message) => message === 'OK' || message.startsWith('WARNING:'))) {
             dispatch(addSuccessToast('dns_test_ok_toast'));
         }
 
diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go
index 50ab9643..e62d9f24 100644
--- a/internal/dnsforward/http.go
+++ b/internal/dnsforward/http.go
@@ -363,6 +363,21 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro
 		return nil, nil
 	}
 
+	for _, u := range upstreams {
+		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 nil, err
+		}
+
+		_, err = validateUpstream(ups, domains)
+		if err != nil {
+			return nil, fmt.Errorf("validating upstream %q: %w", u, err)
+		}
+	}
+
 	conf, err = proxy.ParseUpstreamsConfig(
 		upstreams,
 		&upstream.Options{Bootstrap: []string{}, Timeout: DefaultTimeout},
@@ -373,13 +388,6 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro
 		return nil, errors.Error("no default upstreams specified")
 	}
 
-	for _, u := range upstreams {
-		_, err = validateUpstream(u)
-		if err != nil {
-			return nil, err
-		}
-	}
-
 	return conf, nil
 }
 
@@ -449,16 +457,14 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet)
 
 var protocols = []string{"udp://", "tcp://", "tls://", "https://", "sdns://", "quic://"}
 
-func validateUpstream(u string) (useDefault bool, err error) {
-	// Check if the user tries to specify upstream for domain.
-	var isDomainSpec bool
-	u, isDomainSpec, err = separateUpstream(u)
-	if err != nil {
-		return !isDomainSpec, err
-	}
-
+// 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 = !isDomainSpec; u == "#" && isDomainSpec {
+	if useDefault = u == "#" && domains != nil; useDefault {
 		return useDefault, nil
 	}
 
@@ -485,12 +491,14 @@ func validateUpstream(u string) (useDefault bool, err error) {
 	return useDefault, nil
 }
 
-// separateUpstream returns the upstream without the specified domains.
-// isDomainSpec is true when the upstream is domains-specific.
-func separateUpstream(upstreamStr string) (upstream string, isDomainSpec bool, err error) {
+// separateUpstream returns the upstream and the specified domains.  domains is
+// nil when the upstream is not domains-specific.  Otherwise it may also be
+// empty.
+func separateUpstream(upstreamStr string) (ups string, domains []string, err error) {
 	if !strings.HasPrefix(upstreamStr, "[/") {
-		return upstreamStr, false, nil
+		return upstreamStr, nil, nil
 	}
+
 	defer func() { err = errors.Annotate(err, "bad upstream for domain %q: %w", upstreamStr) }()
 
 	parts := strings.Split(upstreamStr[2:], "/]")
@@ -498,40 +506,46 @@ func separateUpstream(upstreamStr string) (upstream string, isDomainSpec bool, e
 	case 2:
 		// Go on.
 	case 1:
-		return "", false, errors.Error("missing separator")
+		return "", nil, errors.Error("missing separator")
 	default:
-		return "", true, errors.Error("duplicated separator")
+		return "", []string{}, errors.Error("duplicated separator")
 	}
 
-	var domains string
-	domains, upstream = parts[0], parts[1]
-	for i, host := range strings.Split(domains, "/") {
+	for i, host := range strings.Split(parts[0], "/") {
 		if host == "" {
 			continue
 		}
 
-		host = strings.TrimPrefix(host, "*.")
-		err = netutil.ValidateDomainName(host)
+		err = netutil.ValidateDomainName(strings.TrimPrefix(host, "*."))
 		if err != nil {
-			return "", true, fmt.Errorf("domain at index %d: %w", i, err)
+			return "", domains, fmt.Errorf("domain at index %d: %w", i, err)
 		}
+
+		domains = append(domains, host)
 	}
 
-	return upstream, true, nil
+	return parts[1], domains, nil
 }
 
-// excFunc is a signature of function to check if upstream exchanges correctly.
-type excFunc func(u upstream.Upstream) (err error)
+// 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:   "google-public-dns-a.google.com.",
+			Name:   testTLD,
 			Qtype:  dns.TypeA,
 			Qclass: dns.ClassINET,
 		}},
@@ -541,12 +555,8 @@ func checkDNSUpstreamExc(u upstream.Upstream) (err error) {
 	reply, err = u.Exchange(req)
 	if err != nil {
 		return fmt.Errorf("couldn't communicate with upstream: %w", err)
-	}
-
-	if len(reply.Answer) != 1 {
-		return fmt.Errorf("wrong response")
-	} else if a, ok := reply.Answer[0].(*dns.A); !ok || !a.A.Equal(net.IP{8, 8, 8, 8}) {
-		return fmt.Errorf("wrong response")
+	} else if len(reply.Answer) != 0 {
+		return errors.Error("wrong response")
 	}
 
 	return nil
@@ -554,14 +564,22 @@ func checkDNSUpstreamExc(u upstream.Upstream) (err error) {
 
 // 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:   "1.0.0.127.in-addr.arpa.",
+			Name:   inAddrArpaTLD,
 			Qtype:  dns.TypePTR,
 			Qclass: dns.ClassINET,
 		}},
@@ -574,46 +592,66 @@ func checkPrivateUpstreamExc(u upstream.Upstream) (err error) {
 	return nil
 }
 
-func checkDNS(input string, bootstrap []string, timeout time.Duration, ef excFunc) (err error) {
-	if IsCommentOrEmpty(input) {
+// 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
+}
+
+// checkDNS checks the upstream server defined by upstreamConfigStr using
+// healthCheck for actually exchange messages.  It uses bootstrap to resolve the
+// upstream's address.
+func checkDNS(
+	upstreamConfigStr string,
+	bootstrap []string,
+	timeout time.Duration,
+	healthCheck healthCheckFunc,
+) (err error) {
+	if IsCommentOrEmpty(upstreamConfigStr) {
 		return nil
 	}
 
 	// Separate upstream from domains list.
-	var useDefault bool
-	if useDefault, err = validateUpstream(input); err != nil {
+	upstreamAddr, domains, err := separateUpstream(upstreamConfigStr)
+	if err != nil {
 		return fmt.Errorf("wrong upstream format: %w", err)
 	}
 
-	// No need to check this DNS server.
-	if !useDefault {
+	useDefault, err := validateUpstream(upstreamAddr, domains)
+	if err != nil {
+		return fmt.Errorf("wrong upstream format: %w", err)
+	} else if useDefault {
 		return nil
 	}
 
-	if input, _, err = separateUpstream(input); err != nil {
-		return fmt.Errorf("wrong upstream format: %w", err)
-	}
-
 	if len(bootstrap) == 0 {
 		bootstrap = defaultBootstrap
 	}
 
-	log.Debug("checking if upstream %s works", input)
+	log.Debug("dnsforward: checking if upstream %q works", upstreamAddr)
 
-	var u upstream.Upstream
-	u, err = upstream.AddressToUpstream(input, &upstream.Options{
+	u, err := upstream.AddressToUpstream(upstreamAddr, &upstream.Options{
 		Bootstrap: bootstrap,
 		Timeout:   timeout,
 	})
 	if err != nil {
-		return fmt.Errorf("failed to choose upstream for %q: %w", input, err)
+		return fmt.Errorf("failed to choose upstream for %q: %w", upstreamAddr, err)
 	}
 
-	if err = ef(u); err != nil {
-		return fmt.Errorf("upstream %q fails to exchange: %w", input, err)
+	if err = healthCheck(u); err != nil {
+		err = fmt.Errorf("upstream %q fails to exchange: %w", upstreamAddr, err)
+		if domains != nil {
+			return domainSpecificTestError{error: err}
+		}
+
+		return err
 	}
 
-	log.Debug("upstream %s is ok", input)
+	log.Debug("dnsforward: upstream %q is ok", upstreamAddr)
 
 	return nil
 }
@@ -636,6 +674,9 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
 		if err != nil {
 			log.Info("%v", err)
 			result[host] = err.Error()
+			if _, ok := err.(domainSpecificTestError); ok {
+				result[host] = fmt.Sprintf("WARNING: %s", result[host])
+			}
 
 			continue
 		}
@@ -651,6 +692,9 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
 			// above, we rewriting the error for it.  These cases should be
 			// handled properly instead.
 			result[host] = err.Error()
+			if _, ok := err.(domainSpecificTestError); ok {
+				result[host] = fmt.Sprintf("WARNING: %s", result[host])
+			}
 
 			continue
 		}
diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go
index f468f7ae..4042c57c 100644
--- a/internal/dnsforward/http_test.go
+++ b/internal/dnsforward/http_test.go
@@ -185,7 +185,8 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
 		wantSet: "",
 	}, {
 		name: "upstream_dns_bad",
-		wantSet: `validating upstream servers: bad ipport address "!!!": ` +
+		wantSet: `validating upstream servers: ` +
+			`validating upstream "!!!": bad ipport address "!!!": ` +
 			`address !!!: missing port in address`,
 	}, {
 		name: "bootstraps_bad",
@@ -256,112 +257,6 @@ func TestIsCommentOrEmpty(t *testing.T) {
 	}
 }
 
-func TestValidateUpstream(t *testing.T) {
-	testCases := []struct {
-		wantDef  assert.BoolAssertionFunc
-		name     string
-		upstream string
-		wantErr  string
-	}{{
-		wantDef:  assert.True,
-		name:     "invalid",
-		upstream: "1.2.3.4.5",
-		wantErr:  `bad ipport address "1.2.3.4.5": address 1.2.3.4.5: missing port in address`,
-	}, {
-		wantDef:  assert.True,
-		name:     "invalid",
-		upstream: "123.3.7m",
-		wantErr:  `bad ipport address "123.3.7m": address 123.3.7m: missing port in address`,
-	}, {
-		wantDef:  assert.True,
-		name:     "invalid",
-		upstream: "htttps://google.com/dns-query",
-		wantErr:  `wrong protocol`,
-	}, {
-		wantDef:  assert.True,
-		name:     "invalid",
-		upstream: "[/host.com]tls://dns.adguard.com",
-		wantErr:  `bad upstream for domain "[/host.com]tls://dns.adguard.com": missing separator`,
-	}, {
-		wantDef:  assert.True,
-		name:     "invalid",
-		upstream: "[host.ru]#",
-		wantErr:  `bad ipport address "[host.ru]#": address [host.ru]#: missing port in address`,
-	}, {
-		wantDef:  assert.True,
-		name:     "valid_default",
-		upstream: "1.1.1.1",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.True,
-		name:     "valid_default",
-		upstream: "tls://1.1.1.1",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.True,
-		name:     "valid_default",
-		upstream: "https://dns.adguard.com/dns-query",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.True,
-		name:     "valid_default",
-		upstream: "sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.True,
-		name:     "default_udp_host",
-		upstream: "udp://dns.google",
-	}, {
-		wantDef:  assert.True,
-		name:     "default_udp_ip",
-		upstream: "udp://8.8.8.8",
-	}, {
-		wantDef:  assert.False,
-		name:     "valid",
-		upstream: "[/host.com/]1.1.1.1",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.False,
-		name:     "valid",
-		upstream: "[//]tls://1.1.1.1",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.False,
-		name:     "valid",
-		upstream: "[/www.host.com/]#",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.False,
-		name:     "valid",
-		upstream: "[/host.com/google.com/]8.8.8.8",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.False,
-		name:     "valid",
-		upstream: "[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.False,
-		name:     "idna",
-		upstream: "[/пример.рф/]8.8.8.8",
-		wantErr:  ``,
-	}, {
-		wantDef:  assert.False,
-		name:     "bad_domain",
-		upstream: "[/!/]8.8.8.8",
-		wantErr: `bad upstream for domain "[/!/]8.8.8.8": domain at index 0: ` +
-			`bad domain name "!": bad domain name label "!": bad domain name label rune '!'`,
-	}}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			defaultUpstream, err := validateUpstream(tc.upstream)
-			testutil.AssertErrorMsg(t, tc.wantErr, err)
-			tc.wantDef(t, defaultUpstream)
-		})
-	}
-}
-
 func TestValidateUpstreams(t *testing.T) {
 	testCases := []struct {
 		name    string
@@ -376,7 +271,7 @@ func TestValidateUpstreams(t *testing.T) {
 		wantErr: ``,
 		set:     []string{"# comment"},
 	}, {
-		name:    "valid_no_default",
+		name:    "no_default",
 		wantErr: `no default upstreams specified`,
 		set: []string{
 			"[/host.com/]1.1.1.1",
@@ -386,7 +281,7 @@ func TestValidateUpstreams(t *testing.T) {
 			"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
 		},
 	}, {
-		name:    "valid_with_default",
+		name:    "with_default",
 		wantErr: ``,
 		set: []string{
 			"[/host.com/]1.1.1.1",
@@ -398,8 +293,46 @@ func TestValidateUpstreams(t *testing.T) {
 		},
 	}, {
 		name:    "invalid",
-		wantErr: `cannot prepare the upstream dhcp://fake.dns ([]): unsupported url scheme: dhcp`,
+		wantErr: `validating upstream "dhcp://fake.dns": wrong protocol`,
 		set:     []string{"dhcp://fake.dns"},
+	}, {
+		name:    "invalid",
+		wantErr: `validating upstream "1.2.3.4.5": bad ipport address "1.2.3.4.5": address 1.2.3.4.5: missing port in address`,
+		set:     []string{"1.2.3.4.5"},
+	}, {
+		name:    "invalid",
+		wantErr: `validating upstream "123.3.7m": bad ipport address "123.3.7m": address 123.3.7m: missing port in address`,
+		set:     []string{"123.3.7m"},
+	}, {
+		name:    "invalid",
+		wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": missing separator`,
+		set:     []string{"[/host.com]tls://dns.adguard.com"},
+	}, {
+		name:    "invalid",
+		wantErr: `validating upstream "[host.ru]#": bad ipport address "[host.ru]#": address [host.ru]#: missing port in address`,
+		set:     []string{"[host.ru]#"},
+	}, {
+		name:    "valid_default",
+		wantErr: ``,
+		set: []string{
+			"1.1.1.1",
+			"tls://1.1.1.1",
+			"https://dns.adguard.com/dns-query",
+			"sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
+			"udp://dns.google",
+			"udp://8.8.8.8",
+			"[/host.com/]1.1.1.1",
+			"[//]tls://1.1.1.1",
+			"[/www.host.com/]#",
+			"[/host.com/google.com/]8.8.8.8",
+			"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
+			"[/пример.рф/]8.8.8.8",
+		},
+	}, {
+		name: "bad_domain",
+		wantErr: `bad upstream for domain "[/!/]8.8.8.8": domain at index 0: ` +
+			`bad domain name "!": bad domain name label "!": bad domain name label rune '!'`,
+		set: []string{"[/!/]8.8.8.8"},
 	}}
 
 	for _, tc := range testCases {

From e0f2c3d170f8c9bf74ad03285614142dbbed4c5a Mon Sep 17 00:00:00 2001
From: NeP <2996023783@qq.com>
Date: Sat, 30 Jul 2022 12:22:24 +0800
Subject: [PATCH 102/143] filtering: add Bilibili service

---
 client/src/components/ui/Icons.js |  4 ++++
 client/src/helpers/constants.js   |  4 ++++
 internal/filtering/blocked.go     | 26 +++++++++++++++++++++++---
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/client/src/components/ui/Icons.js b/client/src/components/ui/Icons.js
index e2186529..47e71f59 100644
--- a/client/src/components/ui/Icons.js
+++ b/client/src/components/ui/Icons.js
@@ -408,6 +408,10 @@ const Icons = () => (
         <symbol id="service_tinder" viewBox="0 0 50 50" fill="currentColor">
             <path d="M25,48C13.225,48,5,39.888,5,28.271c0-6.065,3.922-12.709,9.325-15.797c0.151-0.086,0.322-0.132,0.496-0.132 c0.803,0,1.407,0.547,1.407,1.271c0,1.18,0.456,3.923,1.541,5.738c4.455-1.65,9.074-5.839,7.464-16.308 c-0.008-0.051-0.012-0.102-0.012-0.152c0-0.484,0.217-0.907,0.579-1.132c0.34-0.208,0.764-0.221,1.14-0.034 C31.173,3.808,45,11.892,45,28.407C45,39.394,36.215,48,25,48z M26.052,3.519c0.003,0.001,0.005,0.002,0.008,0.004 C26.057,3.521,26.055,3.52,26.052,3.519z" />
         </symbol>
+
+        <symbol id="service_bilibili" viewBox="-.64 -4.64 2187.5 1004.88" fill="currentColor">
+            <path d="m2030.61,892.82c-9.77,0 -18.55,0 -26.37,-0.98c-16.6,-0.97 -33.2,-1.95 -49.8,-1.95c-10.74,0 -10.74,0 -11.72,-10.74l-15.63,-177.74l-15.62,-147.46l-10.74,-90.82l-9.77,-79.1l-17.58,-123.05c-5.86,-43.94 -12.69,-86.91 -21.48,-130.86c-0.98,-6.83 -0.98,-7.81 6.84,-8.79c30.27,-5.86 61.52,-8.79 92.77,-8.79l10.74,0c4.88,0.98 7.81,3.91 8.79,9.77l3.91,67.38l27.34,364.26l13.67,166.99l8.79,95.71l5.86,76.17zm-1197.27,-780.28l17.58,0c8.79,0 10.74,2.93 10.74,11.72l7.82,118.17l17.58,245.11l10.74,127.93l7.81,98.64l15.63,169.92c0,7.81 -0.98,8.79 -8.79,8.79l-70.32,-2.93c-4.88,0.98 -7.81,-1.95 -7.81,-6.84l-2.93,-34.18c-2.93,-29.29 -5.86,-58.59 -7.81,-88.86l-15.63,-154.3l-16.6,-139.65l-11.72,-98.63l-12.69,-89.85c-5.86,-40.04 -12.7,-81.05 -19.53,-121.09l-4.89,-27.34c-0.97,-4.89 0,-6.84 4.89,-7.82c27.34,-4.88 53.71,-9.76 85.93,-8.79zm982.43,393.56c24.41,0 27.34,0.98 31.25,24.41c4.88,29.3 8.79,58.6 11.72,87.89l10.74,94.73l20.51,201.17c0.97,4.89 0,6.84 -4.89,6.84l-76.17,8.79c-7.81,0.97 -9.77,0 -10.74,-7.81l-43.95,-224.61l-27.34,-149.42l-3.91,-20.51c-0.97,-3.9 0.98,-6.83 4.89,-7.81c30.27,-6.83 59.57,-11.72 87.89,-13.67zm-1110.36,0c26.37,-0.98 29.3,1.95 32.23,26.37c6.84,40.04 11.72,79.1 15.63,119.14l12.69,117.18l7.81,79.11l6.84,63.47c0,8.79 -0.98,10.75 -8.79,11.72l-72.26,6.84c-7.82,0.97 -9.77,0 -10.75,-7.81l-59.57,-306.65l-15.62,-86.91c-0.98,-4.88 0.97,-7.81 5.86,-8.79c30.27,-6.83 58.59,-11.72 85.93,-13.67zm373.05,302.73l0,125c0.98,5.86 -1.95,8.79 -7.81,7.82l-23.44,0c-16.6,0 -33.2,0.97 -49.8,1.95c-8.79,0.98 -9.77,0.98 -10.75,-9.77l-15.62,-175.78l-7.81,-86.91l-11.72,-132.81c-0.98,-10.75 0.98,-12.7 9.76,-13.68c27.35,-2.93 54.69,-2.93 82.04,-1.95l20.5,2.93c7.82,2.93 8.79,3.91 8.79,11.72l2.93,52.73l0.98,58.6c0.98,53.71 1.95,106.44 1.95,160.15zm1108.4,5.86l0,120.12c0,4.88 -1.95,7.81 -6.84,6.84l-35.15,0c-13.67,0 -27.35,0.97 -40.04,1.95c-7.81,0.98 -8.79,0 -9.77,-8.79l-20.5,-228.52l-10.75,-113.28l-3.9,-57.61c-0.98,-7.82 0.97,-9.77 8.79,-9.77c32.22,-3.91 65.43,-4.88 97.65,-0.98c12.7,0.98 14.65,4.89 15.63,17.58l2.93,129.88l1.95,142.58zm-399.41,-516.6c9.76,0 18.55,0.98 25.39,1.95c4.88,0.98 6.83,2.93 7.81,7.82l12.69,135.74c2.93,11.72 0.98,13.67 -10.74,13.67l-33.2,1.95c-6.84,0.98 -9.77,1.96 -9.77,-8.78l-13.67,-110.36l-3.9,-31.25c-0.98,-5.86 0.97,-8.79 6.83,-9.76l18.56,-0.98zm-1106.45,0c7.81,0 15.63,0.98 22.46,1.95c3.91,0.98 5.86,2.93 6.84,7.82l3.9,34.18l9.77,106.44c0.98,7.81 0.98,8.79 -6.84,8.79l-38.08,1.95c-7.81,0.98 -8.79,0 -9.77,-7.81l-8.79,-78.12l-7.81,-65.43c-0.98,-4.89 0.98,-7.82 5.86,-7.82c6.84,-0.97 14.65,-1.95 22.46,-1.95zm389.65,97.66l0,67.38c0.98,10.74 -0.98,10.74 -9.77,10.74c-12.69,0 -24.41,-0.97 -36.13,-1.95c-7.81,-0.98 -8.79,-0.98 -7.81,-8.79l-2.93,-83.01c0,-18.55 0,-37.11 -0.98,-55.66c-0.97,-8.79 0,-9.77 8.79,-9.77c13.67,0 27.34,0.98 41.02,2.93c7.81,0 7.81,1.96 7.81,9.77l0,68.36zm1109.37,0.97l0,67.39c0,8.79 -0.97,9.76 -9.76,9.76l-37.11,-1.95c-5.86,-0.98 -8.79,-3.91 -7.81,-8.79l-2.93,-139.65c0,-7.81 0.97,-8.79 8.79,-8.79c12.69,0 24.41,0.98 36.13,1.96c14.65,0.97 12.69,3.9 12.69,14.64l0,65.43zm-1529.29,52.74c0.97,11.72 0,13.67 -11.72,14.65l-23.44,5.86c-7.81,2.93 -8.79,0.97 -9.76,-5.86l-24.42,-137.7c-2.93,-8.79 -0.98,-10.74 7.81,-11.72l34.18,-5.86c7.82,-0.97 9.77,-0.97 9.77,6.84c2.93,16.6 5.86,33.2 7.81,49.8l9.77,78.13l0,5.86zm1039.06,-133.79c14.65,-2.93 30.27,-4.88 45.9,-6.84c4.88,-0.97 6.83,1.96 7.81,6.84l7.81,53.71c3.91,26.37 6.84,52.73 7.82,79.1l0,7.81c0.97,3.91 -0.98,6.84 -4.89,7.82l-31.25,6.83c-4.88,0.98 -6.83,-0.97 -7.81,-5.86l-25.39,-145.5l0,-3.91zm-693.36,105.47c0,15.62 -0.98,30.27 -1.95,43.94c0,4.89 -1.96,6.84 -6.84,7.82l-30.27,2.93c-4.88,0.97 -6.84,-1.96 -6.84,-6.84c-1.95,-14.65 -2.93,-28.32 -3.9,-42.97c-1.96,-26.37 -3.91,-53.71 -4.89,-81.05l-1.95,-19.53c-0.98,-3.91 0.98,-5.86 4.88,-5.86l40.04,-2.93c6.84,0 8.79,0.97 8.79,8.79l2.93,95.7zm1107.42,-15.63c0.98,18.56 0.98,38.09 0,56.64c0.98,8.79 -0.97,10.75 -9.76,10.75l-27.35,2.93c-4.88,0.97 -7.81,-1.96 -7.81,-6.84c-0.98,-24.41 -2.93,-49.8 -4.88,-74.22l-3.91,-68.36c-0.98,-4.88 0.98,-6.83 4.88,-6.83l39.07,-2.93c6.83,0 7.81,0.97 7.81,8.79c1.95,26.36 2.93,53.71 1.95,80.07zm-1491.21,333.01c15.63,18.56 18.56,39.06 11.72,61.52c-5.86,21.49 -16.6,40.04 -32.23,55.67c-25.39,26.37 -54.68,47.85 -86.91,64.45c-55.66,29.3 -113.28,49.81 -174.81,60.55c-43.94,8.79 -87.89,14.65 -131.83,17.58c-13.67,0.97 -27.34,0.97 -41.02,0.97l-29.29,0c-7.82,0 -9.77,-1.95 -10.75,-9.76l-6.83,-94.73l-18.56,-186.52l-20.5,-177.74l-11.72,-94.72l-12.7,-90.82c-6.83,-49.81 -15.62,-99.61 -24.41,-149.42c-6.84,-40.04 -13.67,-80.08 -22.46,-120.11c-0.98,-4.89 0,-8.79 4.88,-9.77l135.74,-56.64c8.79,-3.91 16.6,-6.84 25.39,-8.79c5.86,-0.98 8.79,0.98 7.81,6.84c0,15.62 0,31.25 -0.97,47.85l-0.98,12.69c-0.97,56.64 -0.97,113.28 0,170.9c0.98,49.81 3.91,100.59 6.84,150.39c4.88,78.13 12.69,156.25 20.51,233.4c0,7.81 0.97,7.81 9.76,6.84c16.6,-2.93 32.23,-3.91 48.83,-3.91c51.76,0 103.51,5.86 153.32,18.55c43.94,10.75 85.94,25.4 127.93,43.95c20.51,9.77 39.06,21.48 56.64,35.16c6.84,4.88 11.72,9.76 16.6,15.62zm1100.59,-8.79c20.51,16.6 27.34,39.06 21.48,65.43c-4.88,21.49 -14.65,40.04 -29.3,56.64c-23.43,26.37 -50.78,46.88 -81.05,63.48c-58.59,32.23 -121.09,54.69 -187.5,66.41c-36.13,6.83 -72.27,12.69 -108.4,15.62c-20.51,1.95 -42.97,2.93 -65.43,1.95l-26.37,0c-5.85,0.98 -8.78,-1.95 -8.78,-7.81c-1.96,-27.34 -3.91,-54.69 -6.84,-82.03l-15.63,-166.99l-16.6,-145.51l-20.5,-164.06c-2.93,-28.32 -6.84,-57.62 -11.72,-85.94l-17.58,-109.38c-7.81,-51.75 -17.58,-103.51 -28.32,-155.27l-0.98,-6.83c-1.95,-4.89 0,-8.79 4.88,-9.77c47.86,-19.53 94.73,-41.02 142.58,-59.57c12.7,-4.88 28.32,-10.74 27.35,0.98c-3.91,36.13 -2.93,72.26 -3.91,107.42c-0.98,29.29 -0.98,58.59 0.98,86.91l0,22.46c0,35.16 0.97,70.32 3.9,104.49c1.96,45.9 4.89,92.78 8.79,138.68l8.79,98.63c0.98,18.55 2.93,36.13 5.86,54.69c0,10.74 1.95,9.76 10.74,8.79c17.58,-2.93 35.16,-3.91 52.74,-3.91c61.52,0.98 121.09,10.74 179.68,27.34c40.04,10.75 78.13,25.39 115.24,44.93c16.6,8.78 31.25,19.53 45.9,32.22zm-1412.11,171.88c14.65,-8.79 40.04,-26.37 75.19,-53.71c35.16,-28.32 56.64,-46.88 65.43,-56.64c-52.73,-23.44 -107.42,-43.95 -164.06,-62.5l23.44,172.85zm1247.07,-105.47c2.93,-2.93 1.95,-4.88 -0.98,-6.84l-23.44,-9.76c-41.01,-17.58 -82.03,-33.21 -124.02,-46.88l-5.86,-1.95c-1.95,-0.98 -3.9,0 -6.83,0.98l23.43,168.94c2.93,0 4.89,-0.98 5.86,-1.95c38.09,-27.35 76.17,-55.67 114.26,-85.94l17.58,-16.6z" />
+        </symbol>
     </svg>
 );
 
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index 6598530c..184e7a24 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -211,6 +211,10 @@ export const SERVICES = [
         id: 'amazon',
         name: 'Amazon',
     },
+    {
+        id: 'bilibili',
+        name: 'Bilibili',
+    },
     {
         id: 'cloudflare',
         name: 'CloudFlare',
diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index 48996a6d..75fafa62 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -216,6 +216,8 @@ var serviceRulesArray = []svc{{
 		"||bytedance.map.fastly.net^",
 		"||douyin.com^",
 		"||tiktokv.com^",
+		"||toutiaovod.com^",
+		"||douyincdn.com^",
 	},
 }, {
 	name:  "vimeo",
@@ -235,16 +237,24 @@ var serviceRulesArray = []svc{{
 		// Block qq.com and subdomains excluding WeChat's domains.
 		"||qq.com^$denyallow=wx.qq.com|weixin.qq.com",
 		"||qqzaixian.com^",
+		"||qq-video.cdn-go.cn^",
+		"||url.cn^",
 	},
 }, {
-	name:  "wechat",
-	rules: []string{"||wechat.com^", "||weixin.qq.com^", "||wx.qq.com^"},
+	name: "wechat",
+	rules: []string{
+		"||wechat.com^",
+		"||wx.qq.com^",
+		"||weixin.qq.com^",
+		"||weixin.qq.com.cn^",
+		"||weixinbridge.com^",
+	},
 }, {
 	name:  "viber",
 	rules: []string{"||viber.com^"},
 }, {
 	name:  "weibo",
-	rules: []string{"||weibo.com^"},
+	rules: []string{"||weibo.com^", "||weibo.cn^"},
 }, {
 	name:  "9gag",
 	rules: []string{"||9cache.com^", "||9gag.com^"},
@@ -284,6 +294,16 @@ var serviceRulesArray = []svc{{
 		"||tinder.com^",
 		"||tindersparks.com^",
 	},
+}, {
+	name: "bilibili",
+	rules: []string{
+		"||bilibili.com^",
+		"||bilivideo.com^",
+		"||biligame.com^",
+		"||biliapi.net^",
+		"||dreamcast.hk^",
+		"||hdslb.com^",
+	},
 }}
 
 // convert array to map

From 41f081d8da037596a13161d5f7f793752eb917cd Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 1 Aug 2022 14:15:50 +0300
Subject: [PATCH 103/143] Pull request: 4517 warning wording

Merge in DNS/adguard-home from 4517-warning-label to master

Updates #4517.

Squashed commit of the following:

commit 4987f63937253da2954cf20c7b99a3b8a0adf112
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 1 13:59:28 2022 +0300

    client: imp wording
---
 client/src/__locales/en.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 46e75fe6..af748038 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -222,7 +222,7 @@
     "updated_upstream_dns_toast": "Upstream servers successfully saved",
     "dns_test_ok_toast": "Specified DNS servers are working correctly",
     "dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly",
-    "dns_test_warning_toast": "Server \"{{key}}\" does not respond to test requests and may not work properly",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" does not respond to test requests and may not work properly",
     "unblock": "Unblock",
     "block": "Block",
     "disallow_this_client": "Disallow this client",

From 053bb72a004df5f0eaaef827ee08c78af312fbf8 Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Tue, 2 Aug 2022 11:51:49 +0300
Subject: [PATCH 104/143] Pull request: 4775 fix query log issue on tablet
 devices

Updates #4775

Squashed commit of the following:

commit 9ad85d2306b68227e11c7b1dd792e3fe6389939d
Merge: 95aa29d6 41f081d8
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Aug 2 11:44:04 2022 +0300

    Merge branch 'master' into 4775-popup

commit 95aa29d68bdf5e9c4e7aa59f42d04328b1872115
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 1 16:21:23 2022 +0300

    client: fix query log issue on tablet devices
---
 client/src/components/Logs/Cells/index.js | 22 ++++++++++---
 client/src/components/Logs/Logs.css       | 40 +++++++++++++++++++----
 client/src/components/Logs/index.js       |  6 ++--
 client/src/helpers/constants.js           |  2 +-
 4 files changed, 55 insertions(+), 15 deletions(-)

diff --git a/client/src/components/Logs/Cells/index.js b/client/src/components/Logs/Cells/index.js
index 273d9495..05c8bf03 100644
--- a/client/src/components/Logs/Cells/index.js
+++ b/client/src/components/Logs/Cells/index.js
@@ -139,11 +139,23 @@ const Row = memo(({
             }
         };
 
-        const blockButton = <button
-                className={classNames('title--border text-center button-action--arrow-option', { 'bg--danger': !isBlocked })}
-                onClick={onToggleBlock}>
-            {t(buttonType)}
-        </button>;
+        const blockButton = (
+            <>
+                <div className="title--border" />
+                <button
+                    type="button"
+                    className={
+                        classNames(
+                            'button-action--arrow-option',
+                            { 'bg--danger': !isBlocked },
+                            { 'bg--green': isFiltered },
+                        )}
+                    onClick={onToggleBlock}
+                >
+                    {t(buttonType)}
+                </button>
+            </>
+        );
 
         const blockForClientButton = <button
                 className='text-center font-weight-bold py-2 button-action--arrow-option'
diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css
index b6410411..d39835e0 100644
--- a/client/src/components/Logs/Logs.css
+++ b/client/src/components/Logs/Logs.css
@@ -102,10 +102,6 @@
     padding: 0.5rem 0.75rem 0.5rem 2rem !important;
 }
 
-.bg--danger {
-    color: var(--danger) !important;
-}
-
 .form-control--search {
     box-shadow: 0 1px 0 #ddd;
     padding: 0 2.5rem;
@@ -230,6 +226,12 @@
     height: 1.6rem;
 }
 
+@media screen and (max-width: 1024px) {
+    .button-action__container {
+        display: none;
+    }
+}
+
 .button-action__container--detailed {
     bottom: 1.3rem;
 }
@@ -310,16 +312,34 @@
     border: 0;
     display: block;
     width: 100%;
-    text-align: left;
+    padding-top: 0.5rem;
+    padding-bottom: 0.5rem;
+    text-align: center;
+    font-weight: 700;
     color: inherit;
+    cursor: pointer;
+}
+
+.button-action--arrow-option:hover,
+.button-action--arrow-option:focus {
+    outline: none;
+}
+
+.button-action--arrow-option:focus-visible {
+    outline: 2px solid #295a9f;
 }
 
 .button-action--arrow-option:disabled {
     display: none;
 }
 
+.tooltip-custom__container .button-action--arrow-option {
+    padding-bottom: 0;
+    text-align: left;
+    font-weight: 400;
+}
+
 .tooltip-custom__container .button-action--arrow-option:not(:disabled):hover {
-    cursor: pointer;
     background: var(--gray-f3);
     overflow: hidden;
 }
@@ -457,3 +477,11 @@
     font-weight: normal;
     margin-bottom: 1rem;
 }
+
+.bg--danger {
+    color: var(--danger);
+}
+
+.bg--green {
+    color: var(--green79);
+}
diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js
index 3e89c3b1..abbc3af7 100644
--- a/client/src/components/Logs/index.js
+++ b/client/src/components/Logs/index.js
@@ -7,7 +7,7 @@ import queryString from 'query-string';
 import classNames from 'classnames';
 import {
     BLOCK_ACTIONS,
-    SMALL_SCREEN_SIZE,
+    MEDIUM_SCREEN_SIZE,
 } from '../../helpers/constants';
 import Loading from '../ui/Loading';
 import Filters from './Filters';
@@ -80,7 +80,7 @@ const Logs = () => {
     const search = search_url_param || filter?.search || '';
     const response_status = response_status_url_param || filter?.response_status || '';
 
-    const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < SMALL_SCREEN_SIZE);
+    const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth <= MEDIUM_SCREEN_SIZE);
     const [detailedDataCurrent, setDetailedDataCurrent] = useState({});
     const [buttonType, setButtonType] = useState(BLOCK_ACTIONS.BLOCK);
     const [isModalOpened, setModalOpened] = useState(false);
@@ -99,7 +99,7 @@ const Logs = () => {
         })();
     }, [response_status, search]);
 
-    const mediaQuery = window.matchMedia(`(max-width: ${SMALL_SCREEN_SIZE}px)`);
+    const mediaQuery = window.matchMedia(`(max-width: ${MEDIUM_SCREEN_SIZE}px)`);
     const mediaQueryHandler = (e) => {
         setIsSmallScreen(e.matches);
         if (e.matches) {
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index 6598530c..5e9e6ce8 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -588,7 +588,7 @@ export const FORM_NAME = {
 };
 
 export const SMALL_SCREEN_SIZE = 767;
-export const MEDIUM_SCREEN_SIZE = 1023;
+export const MEDIUM_SCREEN_SIZE = 1024;
 
 export const SECONDS_IN_DAY = 60 * 60 * 24;
 

From ccf268baf4d8733cb33745293b818efd9f1636f6 Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <ik@adguard.com>
Date: Tue, 2 Aug 2022 11:58:30 +0300
Subject: [PATCH 105/143] Pull request: 4776 add word break for query log
 domains

Updates #4776

Squashed commit of the following:

commit 6f1778fbd11da529ae934ee33c8f1ad227cdfa66
Merge: 753bd44c 053bb72a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Aug 2 11:52:07 2022 +0300

    Merge branch 'master' into 4776-domains

commit 753bd44cbb592903ed996713a79e4dbf073d780b
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 1 16:58:07 2022 +0300

    client: add word break for query log domains
---
 client/src/components/Logs/Cells/DomainCell.js   | 4 ++--
 client/src/components/Logs/Cells/IconTooltip.css | 7 +++++++
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/client/src/components/Logs/Cells/DomainCell.js b/client/src/components/Logs/Cells/DomainCell.js
index 620d6711..ab511890 100644
--- a/client/src/components/Logs/Cells/DomainCell.js
+++ b/client/src/components/Logs/Cells/DomainCell.js
@@ -147,11 +147,11 @@ const DomainCell = ({
             />
             <div className={valueClass}>
                 {unicodeName ? (
-                    <div className="text-truncate" title={unicodeName}>
+                    <div className="text-truncate overflow-break-mobile" title={unicodeName}>
                         {unicodeName}
                     </div>
                 ) : (
-                    <div className="text-truncate" title={domain}>
+                    <div className="text-truncate overflow-break-mobile" title={domain}>
                         {domain}
                     </div>
                 )}
diff --git a/client/src/components/Logs/Cells/IconTooltip.css b/client/src/components/Logs/Cells/IconTooltip.css
index ff3f4926..8a84182a 100644
--- a/client/src/components/Logs/Cells/IconTooltip.css
+++ b/client/src/components/Logs/Cells/IconTooltip.css
@@ -19,6 +19,13 @@
     overflow-wrap: break-word;
 }
 
+@media (max-width: 991.98px) {
+    .overflow-break-mobile {
+        white-space: normal !important;
+        overflow-wrap: break-word;
+    }
+}
+
 .grid {
     display: grid;
     grid-template-columns: repeat(2, min-content);

From da320795165fc1dd861a71c1ded84243b82a4713 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 2 Aug 2022 20:48:14 +0300
Subject: [PATCH 106/143] Pull request: upd-links-etc

Merge in DNS/adguard-home from upd-links-etc to master

Squashed commit of the following:

commit 49856df394f1a2123a27afdb35047d3b1a49860f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 2 20:43:10 2022 +0300

    all: revert cdn link revert

commit 59bbe4bbd300f48674c1a6224a91f9a567d6c79c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 2 20:40:50 2022 +0300

    all: revert static link revert

commit fe2acc4a0d6d5ee31cb8dbb0d0e0984c3cd723db
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 2 18:24:02 2022 +0300

    all: revert links up in README; imp tools
---
 README.md                | 34 ++++++++++++++++++----------------
 internal/tools/go.mod    |  1 -
 internal/tools/go.sum    |  1 -
 internal/tools/tools.go  |  1 -
 scripts/make/go-lint.sh  |  7 +++++--
 scripts/make/go-tools.sh |  1 -
 6 files changed, 23 insertions(+), 22 deletions(-)

diff --git a/README.md b/README.md
index 811e9303..e26cbc0c 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
 </p>
 
 <p align="center">
-    <a href="https://link.adtidy.org/forward.html?action=home&from=readme&app=home">AdGuard.com</a> |
+    <a href="https://adguard.com/">AdGuard.com</a> |
     <a href="https://github.com/AdguardTeam/AdGuardHome/wiki">Wiki</a> |
     <a href="https://reddit.com/r/Adguard">Reddit</a> |
     <a href="https://twitter.com/AdGuard">Twitter</a> |
@@ -44,9 +44,9 @@ AdGuard Home is a network-wide software for blocking ads & tracking. After you s
 
 It operates as a DNS server that re-routes tracking domains to a “black hole”,
 thus preventing your devices from connecting to those servers.  It's based on
-software we use for our public
-[AdGuard DNS](https://link.adtidy.org/forward.html?action=dns&from=readme&app=home)
-servers, and both share a lot of code.
+software we use for our public [AdGuard DNS](https://adguard-dns.io/) servers,
+and both share a lot of code.
+
 
 
 * [Getting Started](#getting-started)
@@ -166,12 +166,13 @@ It depends.
 “DNS sinkholing” is capable of blocking a big percentage of ads, but it lacks
 flexibility and power of traditional ad blockers.  You can get a good impression
 about the difference between these methods by reading
-[this article](https://link.adtidy.org/forward.html?action=blog_adaway&from=readme&app=home).
-It compares AdGuard for Android (a traditional ad blocker) to hosts-level ad
+[this article](https://adguard.com/en/blog/adguard-vs-adaway-dns66/). It
+compares AdGuard for Android (a traditional ad blocker) to hosts-level ad
 blockers (which are almost identical to DNS-based blockers in their
 capabilities).  This level of protection is enough for some users.
 
 
+
 Additionally, using a DNS-based blocker can help to block ads, tracking and analytics requests on other types of devices, such as SmartTVs, smart speakers or other kinds of IoT devices (on which you can't install traditional ad blockers).
 
 **Known limitations**
@@ -324,10 +325,10 @@ If you run into any problem or have a suggestion, head to [this page](https://gi
 
 If you want to help with AdGuard Home translations, please learn more about
 translating AdGuard products
-[in our Knowledge Base](https://link.adtidy.org/forward.html?action=kb_translations&from=readme&app=home).
+[in our Knowledge Base](https://kb.adguard.com/en/general/adguard-translations).
 
-Direct link to AdGuard Home project on CrowdIn:
-<https://crowdin.com/project/adguard-applications/en#/adguard-home>.
+Here is a link to AdGuard Home project:
+<https://crowdin.com/project/adguard-applications/en#/adguard-home>
 
 <a id="help-other"></a>
 ### Other
@@ -376,16 +377,17 @@ This software wouldn't have been possible without:
    * And many more node.js packages.
  * [whotracks.me data](https://github.com/cliqz-oss/whotracks.me)
 
-You might have seen that [CoreDNS](https://coredns.io) was mentioned here before
-— we've stopped using it in AdGuard Home.
+You might have seen that [CoreDNS](https://coredns.io) was mentioned here
+before, but we've stopped using it in AdGuard Home.
 
 For a full list of all node.js packages in use, please take a look at [client/package.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/package.json) file.
 
 <a id="privacy"></a>
 ## Privacy
 
-Our main idea is that you are the one, who should be in control of your data. So
-it is only natural, that AdGuard Home does not collect any usage statistics, and
-does not use any web services unless you configure it to do so. Full policy with
-every bit that _could in theory be_ sent by AdGuard Home is available
-[here](https://link.adtidy.org/forward.html?action=privacy&from=readme&app=home).
+
+Our main idea is that you are the one, who should be in control of your data.
+So it is only natural, that AdGuard Home does not collect any usage statistics,
+and does not use any web services unless you configure it to do so.  Full policy
+with every bit that *could in theory be* sent by AdGuard Home is available
+[here](https://adguard.com/en/privacy/home.html)
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index bb587070..a42140a6 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -9,7 +9,6 @@ require (
 	github.com/kisielk/errcheck v1.6.0
 	github.com/kyoh86/looppointer v0.1.7
 	github.com/securego/gosec/v2 v2.11.0
-	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
 	golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a
 	honnef.co/go/tools v0.3.1
 	mvdan.cc/gofumpt v0.3.1
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index 2f04e4a6..f145dfbf 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -435,7 +435,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
diff --git a/internal/tools/tools.go b/internal/tools/tools.go
index 214301bf..e41fba4a 100644
--- a/internal/tools/tools.go
+++ b/internal/tools/tools.go
@@ -11,7 +11,6 @@ import (
 	_ "github.com/kisielk/errcheck"
 	_ "github.com/kyoh86/looppointer"
 	_ "github.com/securego/gosec/v2/cmd/gosec"
-	_ "golang.org/x/lint/golint"
 	_ "golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness"
 	_ "golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow"
 	_ "honnef.co/go/tools/cmd/staticcheck"
diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh
index df2f297a..853c5fc8 100644
--- a/scripts/make/go-lint.sh
+++ b/scripts/make/go-lint.sh
@@ -154,7 +154,7 @@ underscores() {
 	fi
 }
 
-# TODO(a.garipov): Add an analyser to look for `fallthrough`, `goto`, and `new`?
+# TODO(a.garipov): Add an analyzer to look for `fallthrough`, `goto`, and `new`?
 
 
 
@@ -212,7 +212,10 @@ exit_on_output underscores
 
 exit_on_output gofumpt --extra -e -l .
 
-golint --set_exit_status ./...
+# TODO(a.garipov): golint is deprecated, and seems to cause more and more
+# issues with each release.  Find a suitable replacement.
+#
+#	golint --set_exit_status ./...
 
 "$GO" vet ./...
 
diff --git a/scripts/make/go-tools.sh b/scripts/make/go-tools.sh
index e0ff237d..1830c552 100644
--- a/scripts/make/go-tools.sh
+++ b/scripts/make/go-tools.sh
@@ -42,7 +42,6 @@ env\
 	github.com/kisielk/errcheck\
 	github.com/kyoh86/looppointer/cmd/looppointer\
 	github.com/securego/gosec/v2/cmd/gosec\
-	golang.org/x/lint/golint\
 	golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness\
 	golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow\
 	honnef.co/go/tools/cmd/staticcheck\

From cce0e593c596bd158d6e7b234afefa35d4d1cc48 Mon Sep 17 00:00:00 2001
From: Justin <95468948+jslawler@users.noreply.github.com>
Date: Wed, 3 Aug 2022 05:47:06 +1000
Subject: [PATCH 107/143] Update RFC 9250 link

---
 client/src/components/Settings/Dns/Upstream/Examples.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/src/components/Settings/Dns/Upstream/Examples.js b/client/src/components/Settings/Dns/Upstream/Examples.js
index f8010b22..81d171d3 100644
--- a/client/src/components/Settings/Dns/Upstream/Examples.js
+++ b/client/src/components/Settings/Dns/Upstream/Examples.js
@@ -63,7 +63,7 @@ const Examples = (props) => (
                     <Trans
                         components={[
                             <a
-                                href="https://tools.ietf.org/html/draft-huitema-quic-dnsoquic-07"
+                                href="https://datatracker.ietf.org/doc/html/rfc9250"
                                 target="_blank"
                                 rel="noopener noreferrer"
                                 key="0"

From b59b82474a6ca4c3e28e63a23f881c340d5f73cf Mon Sep 17 00:00:00 2001
From: Justin <95468948+jslawler@users.noreply.github.com>
Date: Wed, 3 Aug 2022 06:21:40 +1000
Subject: [PATCH 108/143] Update Hass.io AdGuard Home integration link

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index e26cbc0c..a7f8b105 100644
--- a/README.md
+++ b/README.md
@@ -116,7 +116,7 @@ If you're running **Linux**, there's a secure and easy way to install AdGuard Ho
 ### API
 
 If you want to integrate with AdGuard Home, you can use our [REST API](https://github.com/AdguardTeam/AdGuardHome/tree/master/openapi).
-Alternatively, you can use this [python client](https://pypi.org/project/adguardhome/), which is used to build the [AdGuard Home Hass.io Add-on](https://community.home-assistant.io/t/community-hass-io-add-on-adguard-home).
+Alternatively, you can use this [python client](https://pypi.org/project/adguardhome/), which is used to build the [AdGuard Home Hass.io Add-on](https://www.home-assistant.io/integrations/adguard/).
 
 <a id="comparison"></a>
 ## Comparing AdGuard Home to other solutions

From 9ed8699c75a3fd49e8a2298dbd20ad0583f85fb4 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 3 Aug 2022 14:36:18 +0300
Subject: [PATCH 109/143] Pull request: upd-go

Merge in DNS/adguard-home from upd-go to master

Squashed commit of the following:

commit 8edfb5cc3466c1e4ee2eacae5157bd93c135a284
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 3 14:25:45 2022 +0300

    all: imp docs; fmt

commit 080b8a85c02afbdaa079c0da47cb7b6311d50fbe
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 2 20:51:20 2022 +0300

    all: upd go, imp generic code
---
 .github/workflows/build.yml            |  2 +-
 .github/workflows/lint.yml             |  2 +-
 CHANGELOG.md                           | 11 +++-
 README.md                              |  2 +-
 bamboo-specs/release.yaml              |  6 +-
 bamboo-specs/test.yaml                 |  2 +-
 go.mod                                 | 39 +++++++-----
 go.sum                                 | 81 +++++++++++-------------
 internal/aghalg/aghalg.go              | 47 +++++---------
 internal/aghhttp/aghhttp.go            |  2 +-
 internal/aghnet/hostscontainer.go      |  4 +-
 internal/dhcpd/conn_unix.go            | 21 ++++---
 internal/dhcpd/conn_unix_test.go       |  6 +-
 internal/dhcpd/v4.go                   |  4 +-
 internal/dhcpd/v4_test.go              |  4 +-
 internal/dnsforward/access.go          |  8 +--
 internal/dnsforward/dns.go             |  2 +-
 internal/dnsforward/dnsforward_test.go |  2 +-
 internal/dnsforward/http.go            | 23 ++-----
 internal/dnsforward/http_test.go       |  2 +-
 internal/filtering/dnsrewrite_test.go  | 16 ++---
 internal/home/clients.go               |  6 +-
 internal/home/clientshttp.go           |  2 +-
 internal/home/config.go                | 47 ++++++--------
 internal/home/controlinstall.go        | 40 ++++++------
 internal/home/home.go                  | 25 ++++----
 internal/home/service_openbsd.go       | 14 ++---
 internal/home/tls.go                   | 54 +++++++++++-----
 internal/home/upgrade.go               | 21 +++----
 internal/home/upgrade_test.go          |  2 +-
 internal/querylog/json.go              |  2 +-
 internal/tools/go.mod                  | 27 ++++----
 internal/tools/go.sum                  | 87 +++++++++++---------------
 internal/v1/websvc/json.go             |  2 +-
 scripts/make/go-lint.sh                |  2 +-
 35 files changed, 302 insertions(+), 315 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index af2dd565..61ed05e2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,7 +1,7 @@
 'name': 'build'
 
 'env':
-  'GO_VERSION': '1.17'
+  'GO_VERSION': '1.18'
   'NODE_VERSION': '14'
 
 'on':
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index c1faeaa9..1842c2dc 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,7 +1,7 @@
 'name': 'lint'
 
 'env':
-  'GO_VERSION': '1.17'
+  'GO_VERSION': '1.18'
 
 'on':
   'push':
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c95b887..c8eb9193 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,10 @@ and this project adheres to
 
 ### Security
 
+- Go version was updated to prevent the possibility of exploiting the
+  CVE-2022-32189 Go vulnerability fixed in [Go 1.18.5][go-1.18.5].  Go 1.17
+  support has also been removed, as it has reached end of life and will not
+  receive security updates.
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
 
@@ -35,13 +39,18 @@ and this project adheres to
 
 ### Deprecated
 
-- Go 1.17 support.  v0.109.0 will require at least Go 1.18 to build.
+- Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
+
+### Removed
+
+- Go 1.17 support, as it has reached end of life.
 
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
 [#4517]: https://github.com/AdguardTeam/AdGuardHome/issues/4517
 
 [ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
+[go-1.18.5]: https://groups.google.com/g/golang-announce/c/YqYYG87xB10
 
 
 
diff --git a/README.md b/README.md
index e26cbc0c..277f2b2d 100644
--- a/README.md
+++ b/README.md
@@ -195,7 +195,7 @@ Run `make init` to prepare the development environment.
 
 You will need this to build AdGuard Home:
 
- * [go](https://golang.org/dl/) v1.17 or later.
+ * [go](https://golang.org/dl/) v1.18 or later.
  * [node.js](https://nodejs.org/en/download/) v10.16.2 or later.
  * [npm](https://www.npmjs.com/) v6.14 or later (temporary requirement, TODO: remove when redesign is finished).
  * [yarn](https://yarnpkg.com/) v1.22.5 or later.
diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index 953e3c4c..7dd9a859 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -7,7 +7,7 @@
 # Make sure to sync any changes with the branch overrides below.
 'variables':
   'channel': 'edge'
-  'dockerGo': 'adguard/golang-ubuntu:4.5'
+  'dockerGo': 'adguard/golang-ubuntu:5.0'
 
 'stages':
 - 'Make release':
@@ -285,7 +285,7 @@
     # need to build a few of these.
     'variables':
       'channel': 'beta'
-      'dockerGo': 'adguard/golang-ubuntu:4.5'
+      'dockerGo': 'adguard/golang-ubuntu:5.0'
 # release-vX.Y.Z branches are the branches from which the actual final release
 # is built.
 - '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -300,4 +300,4 @@
     # are the ones that actually get released.
     'variables':
       'channel': 'release'
-      'dockerGo': 'adguard/golang-ubuntu:4.5'
+      'dockerGo': 'adguard/golang-ubuntu:5.0'
diff --git a/bamboo-specs/test.yaml b/bamboo-specs/test.yaml
index 30818cd4..cd799fda 100644
--- a/bamboo-specs/test.yaml
+++ b/bamboo-specs/test.yaml
@@ -5,7 +5,7 @@
   'key': 'AHBRTSPECS'
   'name': 'AdGuard Home - Build and run tests'
 'variables':
-  'dockerGo': 'adguard/golang-ubuntu:4.5'
+  'dockerGo': 'adguard/golang-ubuntu:5.0'
 
 'stages':
 - 'Tests':
diff --git a/go.mod b/go.mod
index 6a17f351..fc92c668 100644
--- a/go.mod
+++ b/go.mod
@@ -1,41 +1,46 @@
 module github.com/AdguardTeam/AdGuardHome
 
-go 1.17
+go 1.18
 
 require (
 	github.com/AdguardTeam/dnsproxy v0.43.1
-	github.com/AdguardTeam/golibs v0.10.8
+	github.com/AdguardTeam/golibs v0.10.9
 	github.com/AdguardTeam/urlfilter v0.16.0
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/ameshkov/dnscrypt/v2 v2.2.3
 	github.com/digineo/go-ipset/v2 v2.2.1
 	github.com/dimfeld/httptreemux/v5 v5.4.0
 	github.com/fsnotify/fsnotify v1.5.4
-	github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
-	github.com/google/go-cmp v0.5.7
+	github.com/go-ping/ping v1.1.0
+	github.com/google/go-cmp v0.5.8
 	github.com/google/gopacket v1.1.19
 	github.com/google/renameio v1.0.1
 	github.com/google/uuid v1.3.0
-	github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41
+	github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
 	github.com/kardianos/service v1.2.1
-	github.com/lucas-clemente/quic-go v0.27.1
+	github.com/lucas-clemente/quic-go v0.28.1
 	github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
 	github.com/mdlayher/netlink v1.6.0
 	// TODO(a.garipov): This package is deprecated; find a new one or use
 	// our own code for that.
-	github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b
-	github.com/miekg/dns v1.1.49
-	github.com/stretchr/testify v1.7.0
+	github.com/mdlayher/raw v0.1.0 // indirect
+	github.com/miekg/dns v1.1.50
+	github.com/stretchr/testify v1.7.1
 	github.com/ti-mo/netfilter v0.4.0
 	go.etcd.io/bbolt v1.3.6
-	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
-	golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
-	golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
+	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
+	golang.org/x/net v0.0.0-20220728211354-c7608f3a8462
+	golang.org/x/sys v0.0.0-20220731174439-a90be440212d
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v2 v2.4.0
 	howett.net/plist v1.0.0
 )
 
+require (
+	github.com/mdlayher/packet v1.0.0
+	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
+)
+
 require (
 	github.com/BurntSushi/toml v1.1.0 // indirect
 	github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
@@ -47,8 +52,9 @@ require (
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/josharian/native v1.0.0 // indirect
 	github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
-	github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
-	github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
+	github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
+	github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
+	github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
 	github.com/mdlayher/socket v0.2.3 // indirect
 	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/onsi/ginkgo v1.16.5 // indirect
@@ -58,10 +64,9 @@ require (
 	github.com/stretchr/objx v0.1.1 // indirect
 	github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
-	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
+	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/tools v0.1.11-0.20220426200323-dcaea06afc12 // indirect
-	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
+	golang.org/x/tools v0.1.12 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
diff --git a/go.sum b/go.sum
index e9d5920b..2b4cefe8 100644
--- a/go.sum
+++ b/go.sum
@@ -12,8 +12,8 @@ github.com/AdguardTeam/dnsproxy v0.43.1/go.mod h1:JUGTm5dmlll47JltztsT0N//pVJjdg
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
-github.com/AdguardTeam/golibs v0.10.8 h1:diU9gP9qG1qeLbAkzIwfUerpHSqzR6zaBgzvRMR/m6Q=
-github.com/AdguardTeam/golibs v0.10.8/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
+github.com/AdguardTeam/golibs v0.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0=
+github.com/AdguardTeam/golibs v0.10.9/go.mod h1:W+5rznZa1cSNSFt+gPS7f4Wytnr9fOrd5ZYqwadPw14=
 github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
 github.com/AdguardTeam/urlfilter v0.16.0 h1:IO29m+ZyQuuOnPLTzHuXj35V1DZOp1Dcryl576P2syg=
 github.com/AdguardTeam/urlfilter v0.16.0/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
@@ -34,7 +34,6 @@ github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaE
 github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
 github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
-github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
 github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
 github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -58,7 +57,6 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
 github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -66,8 +64,8 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
 github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4=
-github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
+github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
+github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -97,8 +95,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
@@ -118,11 +117,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpg
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
 github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
-github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 h1:Yg3n3AI7GoHnWt7dyjsLPU+TEuZfPAg0OdiA3MJUV6I=
-github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
+github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo=
+github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
 github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
@@ -141,17 +139,19 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lucas-clemente/quic-go v0.27.1 h1:sOw+4kFSVrdWOYmUjufQ9GBVPqZ+tu+jMtXxXNmRJyk=
-github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
+github.com/lucas-clemente/quic-go v0.28.1 h1:Uo0lvVxWg5la9gflIF9lwa39ONq85Xq2D91YNEIslzU=
+github.com/lucas-clemente/quic-go v0.28.1/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0=
 github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
 github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
 github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
 github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
-github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
-github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
-github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y=
-github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
+github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ=
+github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
+github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
+github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
+github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ=
+github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
@@ -164,11 +164,12 @@ github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZ
 github.com/mdlayher/netlink v1.1.2-0.20201013204415-ded538f7f4be/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
 github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
 github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
+github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8=
 github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
 github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
 github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
-github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b h1:MHcTarUMC4sFA7eiyR8IEJ6j2PgmgXR+B9X2IIMjh7A=
-github.com/mdlayher/raw v0.0.0-20211126142749-4eae47f3d54b/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
+github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y=
+github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg=
 github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
 github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
 github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
@@ -176,9 +177,8 @@ github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaU
 github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
 github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
 github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
-github.com/miekg/dns v1.1.44/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
-github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
-github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
+github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
+github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
@@ -248,8 +248,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
 github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
 github.com/ti-mo/netfilter v0.4.0 h1:rTN1nBYULDmMfDeBHZpKuNKX/bWEXQUhe02a/10orzg=
@@ -265,7 +266,6 @@ github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49u
 github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
 go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
 go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
@@ -278,11 +278,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
-golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
+golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -290,7 +290,6 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -325,12 +324,10 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I=
+golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -342,8 +339,9 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -377,7 +375,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -387,14 +384,13 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
-golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
+golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -419,15 +415,12 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
-golang.org/x/tools v0.1.11-0.20220426200323-dcaea06afc12 h1:pODAJF0uBqx6zFa1MYaiTobVo5FzCbnTVUXeO8o71fE=
-golang.org/x/tools v0.1.11-0.20220426200323-dcaea06afc12/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
+golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
-golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
diff --git a/internal/aghalg/aghalg.go b/internal/aghalg/aghalg.go
index 3a9b07db..65e81cbc 100644
--- a/internal/aghalg/aghalg.go
+++ b/internal/aghalg/aghalg.go
@@ -1,32 +1,31 @@
 // Package aghalg contains common generic algorithms and data structures.
 //
-// TODO(a.garipov): Update to use type parameters in Go 1.18.
+// TODO(a.garipov): Move parts of this into golibs.
 package aghalg
 
 import (
 	"fmt"
-	"sort"
+
+	"golang.org/x/exp/constraints"
+	"golang.org/x/exp/slices"
 )
 
-// comparable is an alias for interface{}.  Values passed as arguments of this
-// type alias must be comparable.
-//
-// TODO(a.garipov): Remove in Go 1.18.
-type comparable = interface{}
-
 // UniqChecker allows validating uniqueness of comparable items.
-type UniqChecker map[comparable]int64
+//
+// TODO(a.garipov): The Ordered constraint is only really necessary in Validate.
+// Consider ways of making this constraint comparable instead.
+type UniqChecker[T constraints.Ordered] map[T]int64
 
 // Add adds a value to the validator.  v must not be nil.
-func (uc UniqChecker) Add(elems ...comparable) {
+func (uc UniqChecker[T]) Add(elems ...T) {
 	for _, e := range elems {
 		uc[e]++
 	}
 }
 
 // Merge returns a checker containing data from both uc and other.
-func (uc UniqChecker) Merge(other UniqChecker) (merged UniqChecker) {
-	merged = make(UniqChecker, len(uc)+len(other))
+func (uc UniqChecker[T]) Merge(other UniqChecker[T]) (merged UniqChecker[T]) {
+	merged = make(UniqChecker[T], len(uc)+len(other))
 	for elem, num := range uc {
 		merged[elem] += num
 	}
@@ -39,10 +38,8 @@ func (uc UniqChecker) Merge(other UniqChecker) (merged UniqChecker) {
 }
 
 // Validate returns an error enumerating all elements that aren't unique.
-// isBefore is an optional sorting function to make the error message
-// deterministic.
-func (uc UniqChecker) Validate(isBefore func(a, b comparable) (less bool)) (err error) {
-	var dup []comparable
+func (uc UniqChecker[T]) Validate() (err error) {
+	var dup []T
 	for elem, num := range uc {
 		if num > 1 {
 			dup = append(dup, elem)
@@ -53,23 +50,7 @@ func (uc UniqChecker) Validate(isBefore func(a, b comparable) (less bool)) (err
 		return nil
 	}
 
-	if isBefore != nil {
-		sort.Slice(dup, func(i, j int) (less bool) {
-			return isBefore(dup[i], dup[j])
-		})
-	}
+	slices.Sort(dup)
 
 	return fmt.Errorf("duplicated values: %v", dup)
 }
-
-// IntIsBefore is a helper sort function for UniqChecker.Validate.
-// a and b must be of type int.
-func IntIsBefore(a, b comparable) (less bool) {
-	return a.(int) < b.(int)
-}
-
-// StringIsBefore is a helper sort function for UniqChecker.Validate.
-// a and b must be of type string.
-func StringIsBefore(a, b comparable) (less bool) {
-	return a.(string) < b.(string)
-}
diff --git a/internal/aghhttp/aghhttp.go b/internal/aghhttp/aghhttp.go
index 666342ea..e186f8a3 100644
--- a/internal/aghhttp/aghhttp.go
+++ b/internal/aghhttp/aghhttp.go
@@ -17,7 +17,7 @@ func OK(w http.ResponseWriter) {
 }
 
 // Error writes formatted message to w and also logs it.
-func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
+func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...any) {
 	text := fmt.Sprintf(format, args...)
 	log.Error("%s %s: %s", r.Method, r.URL, text)
 	http.Error(w, text, code)
diff --git a/internal/aghnet/hostscontainer.go b/internal/aghnet/hostscontainer.go
index 15875ccc..ee345905 100644
--- a/internal/aghnet/hostscontainer.go
+++ b/internal/aghnet/hostscontainer.go
@@ -455,8 +455,8 @@ func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
 		return false
 	}
 
-	hp.table.Range(func(ip net.IP, recVal interface{}) (cont bool) {
-		var targetVal interface{}
+	hp.table.Range(func(ip net.IP, recVal any) (cont bool) {
+		var targetVal any
 		targetVal, ok = target.Get(ip)
 		if !ok {
 			return false
diff --git a/internal/dhcpd/conn_unix.go b/internal/dhcpd/conn_unix.go
index 8f8e7ed5..837211af 100644
--- a/internal/dhcpd/conn_unix.go
+++ b/internal/dhcpd/conn_unix.go
@@ -16,16 +16,16 @@ import (
 	"github.com/insomniacslk/dhcp/dhcpv4"
 	"github.com/insomniacslk/dhcp/dhcpv4/server4"
 	"github.com/mdlayher/ethernet"
-	"github.com/mdlayher/raw"
+	"github.com/mdlayher/packet"
 )
 
 // dhcpUnicastAddr is the combination of MAC and IP addresses for responding to
 // the unconfigured host.
 type dhcpUnicastAddr struct {
-	// raw.Addr is embedded here to make *dhcpUcastAddr a net.Addr without
+	// packet.Addr is embedded here to make *dhcpUcastAddr a net.Addr without
 	// actually implementing all methods.  It also contains the client's
 	// hardware address.
-	raw.Addr
+	packet.Addr
 
 	// yiaddr is an IP address just allocated by server for the host.
 	yiaddr net.IP
@@ -49,16 +49,21 @@ type dhcpConn struct {
 }
 
 // newDHCPConn creates the special connection for DHCP server.
-func (s *v4Server) newDHCPConn(ifi *net.Interface) (c net.PacketConn, err error) {
-	// Create the raw connection.
+func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err error) {
 	var ucast net.PacketConn
-	if ucast, err = raw.ListenPacket(ifi, uint16(ethernet.EtherTypeIPv4), nil); err != nil {
+	ucast, err = packet.Listen(
+		iface,
+		packet.Raw,
+		int(ethernet.EtherTypeIPv4),
+		nil,
+	)
+	if err != nil {
 		return nil, fmt.Errorf("creating raw udp connection: %w", err)
 	}
 
 	// Create the UDP connection.
 	var bcast net.PacketConn
-	bcast, err = server4.NewIPv4UDPConn(ifi.Name, &net.UDPAddr{
+	bcast, err = server4.NewIPv4UDPConn(iface.Name, &net.UDPAddr{
 		// TODO(e.burkov):  Listening on zeroes makes the server handle
 		// requests from all the interfaces.  Inspect the ways to
 		// specify the interface-specific listening addresses.
@@ -75,7 +80,7 @@ func (s *v4Server) newDHCPConn(ifi *net.Interface) (c net.PacketConn, err error)
 		udpConn: bcast,
 		bcastIP: s.conf.broadcastIP,
 		rawConn: ucast,
-		srcMAC:  ifi.HardwareAddr,
+		srcMAC:  iface.HardwareAddr,
 		srcIP:   s.conf.dnsIPAddrs[0],
 	}, nil
 }
diff --git a/internal/dhcpd/conn_unix_test.go b/internal/dhcpd/conn_unix_test.go
index cbcaa753..66899af6 100644
--- a/internal/dhcpd/conn_unix_test.go
+++ b/internal/dhcpd/conn_unix_test.go
@@ -11,7 +11,7 @@ import (
 	"github.com/google/gopacket"
 	"github.com/google/gopacket/layers"
 	"github.com/insomniacslk/dhcp/dhcpv4"
-	"github.com/mdlayher/raw"
+	"github.com/mdlayher/packet"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -56,7 +56,7 @@ func TestBuildEtherPkt(t *testing.T) {
 		srcIP:  net.IP{1, 2, 3, 4},
 	}
 	peer := &dhcpUnicastAddr{
-		Addr:   raw.Addr{HardwareAddr: net.HardwareAddr{6, 5, 4, 3, 2, 1}},
+		Addr:   packet.Addr{HardwareAddr: net.HardwareAddr{6, 5, 4, 3, 2, 1}},
 		yiaddr: net.IP{4, 3, 2, 1},
 	}
 	payload := (&dhcpv4.DHCPv4{}).ToBytes()
@@ -102,7 +102,7 @@ func TestBuildEtherPkt(t *testing.T) {
 	t.Run("serializing_error", func(t *testing.T) {
 		// Create a peer with invalid MAC.
 		badPeer := &dhcpUnicastAddr{
-			Addr:   raw.Addr{HardwareAddr: net.HardwareAddr{5, 4, 3, 2, 1}},
+			Addr:   packet.Addr{HardwareAddr: net.HardwareAddr{5, 4, 3, 2, 1}},
 			yiaddr: net.IP{4, 3, 2, 1},
 		}
 
diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go
index 825ef25c..5639837f 100644
--- a/internal/dhcpd/v4.go
+++ b/internal/dhcpd/v4.go
@@ -20,7 +20,7 @@ import (
 	"github.com/go-ping/ping"
 	"github.com/insomniacslk/dhcp/dhcpv4"
 	"github.com/insomniacslk/dhcp/dhcpv4/server4"
-	"github.com/mdlayher/raw"
+	"github.com/mdlayher/packet"
 )
 
 // v4Server is a DHCPv4 server.
@@ -992,7 +992,7 @@ func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DH
 		// Unicast DHCPOFFER and DHCPACK messages to the client's
 		// hardware address and yiaddr.
 		peer = &dhcpUnicastAddr{
-			Addr:   raw.Addr{HardwareAddr: req.ClientHWAddr},
+			Addr:   packet.Addr{HardwareAddr: req.ClientHWAddr},
 			yiaddr: resp.YourIPAddr,
 		}
 	default:
diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go
index 130782bf..be9259d8 100644
--- a/internal/dhcpd/v4_test.go
+++ b/internal/dhcpd/v4_test.go
@@ -12,7 +12,7 @@ import (
 	"github.com/AdguardTeam/golibs/stringutil"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/insomniacslk/dhcp/dhcpv4"
-	"github.com/mdlayher/raw"
+	"github.com/mdlayher/packet"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -554,7 +554,7 @@ func TestV4Server_Send(t *testing.T) {
 		req:  &dhcpv4.DHCPv4{ClientHWAddr: knownMAC},
 		resp: &dhcpv4.DHCPv4{YourIPAddr: knownIP},
 		want: &dhcpUnicastAddr{
-			Addr:   raw.Addr{HardwareAddr: knownMAC},
+			Addr:   packet.Addr{HardwareAddr: knownMAC},
 			yiaddr: knownIP,
 		},
 	}, {
diff --git a/internal/dnsforward/access.go b/internal/dnsforward/access.go
index 7a771946..33c9a978 100644
--- a/internal/dnsforward/access.go
+++ b/internal/dnsforward/access.go
@@ -214,7 +214,7 @@ func validateAccessSet(list *accessListJSON) (err error) {
 	}
 
 	merged := allowed.Merge(disallowed)
-	err = merged.Validate(aghalg.StringIsBefore)
+	err = merged.Validate()
 	if err != nil {
 		return fmt.Errorf("items in allowed and disallowed clients intersect: %w", err)
 	}
@@ -223,13 +223,13 @@ func validateAccessSet(list *accessListJSON) (err error) {
 }
 
 // validateStrUniq returns an informative error if clients are not unique.
-func validateStrUniq(clients []string) (uc aghalg.UniqChecker, err error) {
-	uc = make(aghalg.UniqChecker, len(clients))
+func validateStrUniq(clients []string) (uc aghalg.UniqChecker[string], err error) {
+	uc = make(aghalg.UniqChecker[string], len(clients))
 	for _, c := range clients {
 		uc.Add(c)
 	}
 
-	return uc, uc.Validate(aghalg.StringIsBefore)
+	return uc, uc.Validate()
 }
 
 func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go
index f6008e36..f3c98361 100644
--- a/internal/dnsforward/dns.go
+++ b/internal/dnsforward/dns.go
@@ -506,7 +506,7 @@ func (s *Server) ipToHost(ip net.IP) (host string, ok bool) {
 		return "", false
 	}
 
-	var v interface{}
+	var v any
 	v, ok = s.tableIPToHost.Get(ip)
 	if !ok {
 		return "", false
diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go
index d6cad013..e4d61b48 100644
--- a/internal/dnsforward/dnsforward_test.go
+++ b/internal/dnsforward/dnsforward_test.go
@@ -988,7 +988,7 @@ func TestRewrite(t *testing.T) {
 	}
 }
 
-func publicKey(priv interface{}) interface{} {
+func publicKey(priv any) any {
 	switch k := priv.(type) {
 	case *rsa.PrivateKey:
 		return &k.PublicKey
diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go
index e62d9f24..93121fac 100644
--- a/internal/dnsforward/http.go
+++ b/internal/dnsforward/http.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net"
 	"net/http"
-	"sort"
 	"strings"
 	"time"
 
@@ -17,6 +16,8 @@ import (
 	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/AdguardTeam/golibs/stringutil"
 	"github.com/miekg/dns"
+	"golang.org/x/exp/maps"
+	"golang.org/x/exp/slices"
 )
 
 type dnsConfig struct {
@@ -401,20 +402,6 @@ func ValidateUpstreams(upstreams []string) (err error) {
 	return err
 }
 
-// stringKeysSorted returns the sorted slice of string keys of m.
-//
-// TODO(e.burkov):  Use generics in Go 1.18.  Move into golibs.
-func stringKeysSorted(m map[string][]upstream.Upstream) (sorted []string) {
-	sorted = make([]string, 0, len(m))
-	for s := range m {
-		sorted = append(sorted, s)
-	}
-
-	sort.Strings(sorted)
-
-	return sorted
-}
-
 // 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
@@ -429,9 +416,11 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet)
 		return nil
 	}
 
-	var errs []error
+	keys := maps.Keys(conf.DomainReservedUpstreams)
+	slices.Sort(keys)
 
-	for _, domain := range stringKeysSorted(conf.DomainReservedUpstreams) {
+	var errs []error
+	for _, domain := range keys {
 		var subnet *net.IPNet
 		subnet, err = netutil.SubnetFromReversedAddr(domain)
 		if err != nil {
diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go
index 4042c57c..8863c6c6 100644
--- a/internal/dnsforward/http_test.go
+++ b/internal/dnsforward/http_test.go
@@ -34,7 +34,7 @@ func (fsr *fakeSystemResolvers) Get() (rs []string) {
 	return nil
 }
 
-func loadTestData(t *testing.T, casesFileName string, cases interface{}) {
+func loadTestData(t *testing.T, casesFileName string, cases any) {
 	t.Helper()
 
 	var f *os.File
diff --git a/internal/filtering/dnsrewrite_test.go b/internal/filtering/dnsrewrite_test.go
index 877b46a8..f8415fbf 100644
--- a/internal/filtering/dnsrewrite_test.go
+++ b/internal/filtering/dnsrewrite_test.go
@@ -61,22 +61,22 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
 
 	testCasesA := []struct {
 		name  string
-		want  []interface{}
+		want  []any
 		rcode int
 		dtyp  uint16
 	}{{
 		name:  "a-record",
 		rcode: dns.RcodeSuccess,
-		want:  []interface{}{ipv4p1},
+		want:  []any{ipv4p1},
 		dtyp:  dns.TypeA,
 	}, {
 		name:  "aaaa-record",
-		want:  []interface{}{ipv6p1},
+		want:  []any{ipv6p1},
 		rcode: dns.RcodeSuccess,
 		dtyp:  dns.TypeAAAA,
 	}, {
 		name:  "txt-record",
-		want:  []interface{}{"hello-world"},
+		want:  []any{"hello-world"},
 		rcode: dns.RcodeSuccess,
 		dtyp:  dns.TypeTXT,
 	}, {
@@ -86,22 +86,22 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
 		dtyp:  0,
 	}, {
 		name:  "a-records",
-		want:  []interface{}{ipv4p1, ipv4p2},
+		want:  []any{ipv4p1, ipv4p2},
 		rcode: dns.RcodeSuccess,
 		dtyp:  dns.TypeA,
 	}, {
 		name:  "aaaa-records",
-		want:  []interface{}{ipv6p1, ipv6p2},
+		want:  []any{ipv6p1, ipv6p2},
 		rcode: dns.RcodeSuccess,
 		dtyp:  dns.TypeAAAA,
 	}, {
 		name:  "disable-one",
-		want:  []interface{}{ipv4p2},
+		want:  []any{ipv4p2},
 		rcode: dns.RcodeSuccess,
 		dtyp:  dns.TypeA,
 	}, {
 		name:  "disable-cname",
-		want:  []interface{}{ipv4p1},
+		want:  []any{ipv4p1},
 		rcode: dns.RcodeSuccess,
 		dtyp:  dns.TypeA,
 	}}
diff --git a/internal/home/clients.go b/internal/home/clients.go
index 74545a67..0b9bfcd0 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -493,7 +493,7 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
 // findRuntimeClientLocked finds a runtime client by their IP address.  For
 // internal use only.
 func (clients *clientsContainer) findRuntimeClientLocked(ip net.IP) (rc *RuntimeClient, ok bool) {
-	var v interface{}
+	var v any
 	v, ok = clients.ipToRC.Get(ip)
 	if !ok {
 		return nil, false
@@ -769,7 +769,7 @@ func (clients *clientsContainer) addHostLocked(ip net.IP, host string, src clien
 // rmHostsBySrc removes all entries that match the specified source.
 func (clients *clientsContainer) rmHostsBySrc(src clientSource) {
 	n := 0
-	clients.ipToRC.Range(func(ip net.IP, v interface{}) (cont bool) {
+	clients.ipToRC.Range(func(ip net.IP, v any) (cont bool) {
 		rc, ok := v.(*RuntimeClient)
 		if !ok {
 			log.Error("clients: bad type %T in ipToRC for %s", v, ip)
@@ -797,7 +797,7 @@ func (clients *clientsContainer) addFromHostsFile(hosts *netutil.IPMap) {
 	clients.rmHostsBySrc(ClientSourceHostsFile)
 
 	n := 0
-	hosts.Range(func(ip net.IP, v interface{}) (cont bool) {
+	hosts.Range(func(ip net.IP, v any) (cont bool) {
 		rec, ok := v.(*aghnet.HostsRecord)
 		if !ok {
 			log.Error("dns: bad type %T in ipToRC for %s", v, ip)
diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go
index 1f053343..3bdf95e1 100644
--- a/internal/home/clientshttp.go
+++ b/internal/home/clientshttp.go
@@ -70,7 +70,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
 		data.Clients = append(data.Clients, cj)
 	}
 
-	clients.ipToRC.Range(func(ip net.IP, v interface{}) (cont bool) {
+	clients.ipToRC.Range(func(ip net.IP, v any) (cont bool) {
 		rc, ok := v.(*RuntimeClient)
 		if !ok {
 			log.Error("dns: bad type %T in ipToRC for %s", v, ip)
diff --git a/internal/home/config.go b/internal/home/config.go
index 14f5781e..4df45ed9 100644
--- a/internal/home/config.go
+++ b/internal/home/config.go
@@ -302,27 +302,28 @@ func parseConfig() (err error) {
 		return err
 	}
 
-	uc := aghalg.UniqChecker{}
-	addPorts(
-		uc,
-		tcpPort(config.BindPort),
-		tcpPort(config.BetaBindPort),
-		udpPort(config.DNS.Port),
-	)
+	tcpPorts := aghalg.UniqChecker[tcpPort]{}
+	addPorts(tcpPorts, tcpPort(config.BindPort), tcpPort(config.BetaBindPort))
+
+	udpPorts := aghalg.UniqChecker[udpPort]{}
+	addPorts(udpPorts, udpPort(config.DNS.Port))
 
 	if config.TLS.Enabled {
 		addPorts(
-			uc,
-			// TODO(e.burkov):  Consider adding a udpPort with the same value if
-			// we ever support the HTTP/3 for web admin interface.
+			tcpPorts,
 			tcpPort(config.TLS.PortHTTPS),
 			tcpPort(config.TLS.PortDNSOverTLS),
-			udpPort(config.TLS.PortDNSOverQUIC),
 			tcpPort(config.TLS.PortDNSCrypt),
 		)
+
+		// TODO(e.burkov):  Consider adding a udpPort with the same value when
+		// we add support for HTTP/3 for web admin interface.
+		addPorts(udpPorts, udpPort(config.TLS.PortDNSOverQUIC))
 	}
-	if err = uc.Validate(aghalg.IntIsBefore); err != nil {
-		return fmt.Errorf("validating ports: %w", err)
+	if err = tcpPorts.Validate(); err != nil {
+		return fmt.Errorf("validating tcp ports: %w", err)
+	} else if err = udpPorts.Validate(); err != nil {
+		return fmt.Errorf("validating udp ports: %w", err)
 	}
 
 	if !checkFiltersUpdateIntervalHours(config.DNS.FiltersUpdateIntervalHours) {
@@ -342,23 +343,11 @@ type udpPort int
 // tcpPort is the port number for TCP protocol.
 type tcpPort int
 
-// addPorts is a helper for ports validation.  It skips zero ports.  Each of
-// ports should be either a udpPort or a tcpPort.
-func addPorts(uc aghalg.UniqChecker, ports ...interface{}) {
+// addPorts is a helper for ports validation that skips zero ports.
+func addPorts[T tcpPort | udpPort](uc aghalg.UniqChecker[T], ports ...T) {
 	for _, p := range ports {
-		// Use separate cases for tcpPort and udpPort so that the untyped
-		// constant zero is converted to the appropriate type.
-		switch p := p.(type) {
-		case tcpPort:
-			if p != 0 {
-				uc.Add(p)
-			}
-		case udpPort:
-			if p != 0 {
-				uc.Add(p)
-			}
-		default:
-			// Go on.
+		if p != 0 {
+			uc.Add(p)
 		}
 	}
 }
diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go
index 0435651b..5304b794 100644
--- a/internal/home/controlinstall.go
+++ b/internal/home/controlinstall.go
@@ -105,19 +105,22 @@ type checkConfResp struct {
 
 // validateWeb returns error is the web part if the initial configuration can't
 // be set.
-func (req *checkConfReq) validateWeb(uc aghalg.UniqChecker) (err error) {
+func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err error) {
 	defer func() { err = errors.Annotate(err, "validating ports: %w") }()
 
-	port := req.Web.Port
-	addPorts(uc, tcpPort(config.BetaBindPort), tcpPort(port))
-	if err = uc.Validate(aghalg.IntIsBefore); err != nil {
-		// Avoid duplicating the error into the status of DNS.
-		uc[port] = 1
+	portInt := req.Web.Port
+	port := tcpPort(portInt)
+	addPorts(tcpPorts, tcpPort(config.BetaBindPort), port)
+	if err = tcpPorts.Validate(); err != nil {
+		// Reset the value for the port to 1 to make sure that validateDNS
+		// doesn't throw the same error, unless the same TCP port is set there
+		// as well.
+		tcpPorts[port] = 1
 
 		return err
 	}
 
-	switch port {
+	switch portInt {
 	case 0, config.BindPort:
 		return nil
 	default:
@@ -125,21 +128,18 @@ func (req *checkConfReq) validateWeb(uc aghalg.UniqChecker) (err error) {
 		// unbound after install.
 	}
 
-	return aghnet.CheckPort("tcp", req.Web.IP, port)
+	return aghnet.CheckPort("tcp", req.Web.IP, portInt)
 }
 
 // validateDNS returns error if the DNS part of the initial configuration can't
 // be set.  canAutofix is true if the port can be unbound by AdGuard Home
 // automatically.
-func (req *checkConfReq) validateDNS(uc aghalg.UniqChecker) (canAutofix bool, err error) {
+func (req *checkConfReq) validateDNS(
+	tcpPorts aghalg.UniqChecker[tcpPort],
+) (canAutofix bool, err error) {
 	defer func() { err = errors.Annotate(err, "validating ports: %w") }()
 
 	port := req.DNS.Port
-	addPorts(uc, udpPort(port))
-	if err = uc.Validate(aghalg.IntIsBefore); err != nil {
-		return false, err
-	}
-
 	switch port {
 	case 0:
 		return false, nil
@@ -148,6 +148,11 @@ func (req *checkConfReq) validateDNS(uc aghalg.UniqChecker) (canAutofix bool, er
 		// by AdGuard Home for web interface.
 	default:
 		// Check TCP as well.
+		addPorts(tcpPorts, tcpPort(port))
+		if err = tcpPorts.Validate(); err != nil {
+			return false, err
+		}
+
 		err = aghnet.CheckPort("tcp", req.DNS.IP, port)
 		if err != nil {
 			return false, err
@@ -185,13 +190,12 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request)
 	}
 
 	resp := &checkConfResp{}
-	uc := aghalg.UniqChecker{}
-
-	if err = req.validateWeb(uc); err != nil {
+	tcpPorts := aghalg.UniqChecker[tcpPort]{}
+	if err = req.validateWeb(tcpPorts); err != nil {
 		resp.Web.Status = err.Error()
 	}
 
-	if resp.DNS.CanAutofix, err = req.validateDNS(uc); err != nil {
+	if resp.DNS.CanAutofix, err = req.validateDNS(tcpPorts); err != nil {
 		resp.DNS.Status = err.Error()
 	} else if !req.DNS.IP.IsUnspecified() {
 		resp.StaticIP = handleStaticIP(req.DNS.IP, req.SetStaticIP)
diff --git a/internal/home/home.go b/internal/home/home.go
index 539552d1..15fd4c46 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -298,24 +298,27 @@ func setupConfig(args options) (err error) {
 	Context.clients.Init(config.Clients.Persistent, Context.dhcpServer, Context.etcHosts, arpdb)
 
 	if args.bindPort != 0 {
-		uc := aghalg.UniqChecker{}
-		addPorts(
-			uc,
-			tcpPort(args.bindPort),
-			tcpPort(config.BetaBindPort),
-			udpPort(config.DNS.Port),
-		)
+		tcpPorts := aghalg.UniqChecker[tcpPort]{}
+		addPorts(tcpPorts, tcpPort(args.bindPort), tcpPort(config.BetaBindPort))
+
+		udpPorts := aghalg.UniqChecker[udpPort]{}
+		addPorts(udpPorts, udpPort(config.DNS.Port))
+
 		if config.TLS.Enabled {
 			addPorts(
-				uc,
+				tcpPorts,
 				tcpPort(config.TLS.PortHTTPS),
 				tcpPort(config.TLS.PortDNSOverTLS),
-				udpPort(config.TLS.PortDNSOverQUIC),
 				tcpPort(config.TLS.PortDNSCrypt),
 			)
+
+			addPorts(udpPorts, udpPort(config.TLS.PortDNSOverQUIC))
 		}
-		if err = uc.Validate(aghalg.IntIsBefore); err != nil {
-			return fmt.Errorf("validating ports: %w", err)
+
+		if err = tcpPorts.Validate(); err != nil {
+			return fmt.Errorf("validating tcp ports: %w", err)
+		} else if err = udpPorts.Validate(); err != nil {
+			return fmt.Errorf("validating udp ports: %w", err)
 		}
 
 		config.BindPort = args.bindPort
diff --git a/internal/home/service_openbsd.go b/internal/home/service_openbsd.go
index 8ad0d212..beeabd04 100644
--- a/internal/home/service_openbsd.go
+++ b/internal/home/service_openbsd.go
@@ -160,7 +160,7 @@ rc_cmd $1
 
 // template returns the script template to put into rc.d.
 func (s *openbsdRunComService) template() (t *template.Template) {
-	tf := map[string]interface{}{
+	tf := map[string]any{
 		"args": func(sl []string) string {
 			return `"` + strings.Join(sl, " ") + `"`
 		},
@@ -390,42 +390,42 @@ func newSysLogger(_ string, _ chan<- error) (service.Logger, error) {
 type sysLogger struct{}
 
 // Error implements service.Logger interface for sysLogger.
-func (sysLogger) Error(v ...interface{}) error {
+func (sysLogger) Error(v ...any) error {
 	log.Error(fmt.Sprint(v...))
 
 	return nil
 }
 
 // Warning implements service.Logger interface for sysLogger.
-func (sysLogger) Warning(v ...interface{}) error {
+func (sysLogger) Warning(v ...any) error {
 	log.Info("warning: %s", fmt.Sprint(v...))
 
 	return nil
 }
 
 // Info implements service.Logger interface for sysLogger.
-func (sysLogger) Info(v ...interface{}) error {
+func (sysLogger) Info(v ...any) error {
 	log.Info(fmt.Sprint(v...))
 
 	return nil
 }
 
 // Errorf implements service.Logger interface for sysLogger.
-func (sysLogger) Errorf(format string, a ...interface{}) error {
+func (sysLogger) Errorf(format string, a ...any) error {
 	log.Error(format, a...)
 
 	return nil
 }
 
 // Warningf implements service.Logger interface for sysLogger.
-func (sysLogger) Warningf(format string, a ...interface{}) error {
+func (sysLogger) Warningf(format string, a ...any) error {
 	log.Info("warning: %s", fmt.Sprintf(format, a...))
 
 	return nil
 }
 
 // Infof implements service.Logger interface for sysLogger.
-func (sysLogger) Infof(format string, a ...interface{}) error {
+func (sysLogger) Infof(format string, a ...any) error {
 	log.Info(format, a...)
 
 	return nil
diff --git a/internal/home/tls.go b/internal/home/tls.go
index fe595e98..7240496c 100644
--- a/internal/home/tls.go
+++ b/internal/home/tls.go
@@ -250,21 +250,17 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
 	}
 
 	if setts.Enabled {
-		uc := aghalg.UniqChecker{}
-		addPorts(
-			uc,
+		err = validatePorts(
 			tcpPort(config.BindPort),
 			tcpPort(config.BetaBindPort),
-			udpPort(config.DNS.Port),
 			tcpPort(setts.PortHTTPS),
 			tcpPort(setts.PortDNSOverTLS),
-			udpPort(setts.PortDNSOverQUIC),
 			tcpPort(setts.PortDNSCrypt),
+			udpPort(config.DNS.Port),
+			udpPort(setts.PortDNSOverQUIC),
 		)
-
-		err = uc.Validate(aghalg.IntIsBefore)
 		if err != nil {
-			aghhttp.Error(r, w, http.StatusBadRequest, "validating ports: %s", err)
+			aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
 
 			return
 		}
@@ -343,19 +339,15 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
 	}
 
 	if data.Enabled {
-		uc := aghalg.UniqChecker{}
-		addPorts(
-			uc,
+		err = validatePorts(
 			tcpPort(config.BindPort),
 			tcpPort(config.BetaBindPort),
-			udpPort(config.DNS.Port),
 			tcpPort(data.PortHTTPS),
 			tcpPort(data.PortDNSOverTLS),
-			udpPort(data.PortDNSOverQUIC),
 			tcpPort(data.PortDNSCrypt),
+			udpPort(config.DNS.Port),
+			udpPort(data.PortDNSOverQUIC),
 		)
-
-		err = uc.Validate(aghalg.IntIsBefore)
 		if err != nil {
 			aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
 
@@ -421,6 +413,38 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
+// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
+// DNS protocols.
+func validatePorts(
+	bindPort, betaBindPort, dohPort, dotPort, dnscryptTCPPort tcpPort,
+	dnsPort, doqPort udpPort,
+) (err error) {
+	tcpPorts := aghalg.UniqChecker[tcpPort]{}
+	addPorts(
+		tcpPorts,
+		tcpPort(bindPort),
+		tcpPort(betaBindPort),
+		tcpPort(dohPort),
+		tcpPort(dotPort),
+		tcpPort(dnscryptTCPPort),
+	)
+
+	err = tcpPorts.Validate()
+	if err != nil {
+		return fmt.Errorf("validating tcp ports: %w", err)
+	}
+
+	udpPorts := aghalg.UniqChecker[udpPort]{}
+	addPorts(udpPorts, udpPort(dnsPort), udpPort(doqPort))
+
+	err = udpPorts.Validate()
+	if err != nil {
+		return fmt.Errorf("validating udp ports: %w", err)
+	}
+
+	return nil
+}
+
 func verifyCertChain(data *tlsConfigStatus, certChain, serverName string) error {
 	log.Tracef("TLS: got certificate: %d bytes", len(certChain))
 
diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go
index d9611dc9..85b7e019 100644
--- a/internal/home/upgrade.go
+++ b/internal/home/upgrade.go
@@ -24,10 +24,7 @@ import (
 const currentSchemaVersion = 14
 
 // These aliases are provided for convenience.
-//
-// TODO(e.burkov):  Remove any after updating to Go 1.18.
 type (
-	any  = interface{}
 	yarr = []any
 	yobj = map[any]any
 )
@@ -176,11 +173,11 @@ func upgradeSchema2to3(diskConf yobj) error {
 		return fmt.Errorf("no DNS configuration in config file")
 	}
 
-	// Convert interface{} to yobj
+	// Convert any to yobj
 	newDNSConfig := make(yobj)
 
 	switch v := dnsConfig.(type) {
-	case map[interface{}]interface{}:
+	case map[any]any:
 		for k, v := range v {
 			newDNSConfig[fmt.Sprint(k)] = v
 		}
@@ -216,12 +213,12 @@ func upgradeSchema3to4(diskConf yobj) error {
 	}
 
 	switch arr := clients.(type) {
-	case []interface{}:
+	case []any:
 
 		for i := range arr {
 			switch c := arr[i].(type) {
 
-			case map[interface{}]interface{}:
+			case map[any]any:
 				c["use_global_blocked_services"] = true
 
 			default:
@@ -307,11 +304,11 @@ func upgradeSchema5to6(diskConf yobj) error {
 	}
 
 	switch arr := clients.(type) {
-	case []interface{}:
+	case []any:
 		for i := range arr {
 			switch c := arr[i].(type) {
-			case map[interface{}]interface{}:
-				var ipVal interface{}
+			case map[any]any:
+				var ipVal any
 				ipVal, ok = c["ip"]
 				ids := []string{}
 				if ok {
@@ -326,7 +323,7 @@ func upgradeSchema5to6(diskConf yobj) error {
 					}
 				}
 
-				var macVal interface{}
+				var macVal any
 				macVal, ok = c["mac"]
 				if ok {
 					var mac string
@@ -377,7 +374,7 @@ func upgradeSchema6to7(diskConf yobj) error {
 	}
 
 	switch dhcp := dhcpVal.(type) {
-	case map[interface{}]interface{}:
+	case map[any]any:
 		var str string
 		str, ok = dhcp["gateway_ip"].(string)
 		if !ok {
diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go
index 4c25cba3..6703ab2f 100644
--- a/internal/home/upgrade_test.go
+++ b/internal/home/upgrade_test.go
@@ -500,7 +500,7 @@ func TestUpgradeSchema11to12(t *testing.T) {
 		dnsVal, ok = dns.(yobj)
 		require.True(t, ok)
 
-		var ivl interface{}
+		var ivl any
 		ivl, ok = dnsVal["querylog_interval"]
 		require.True(t, ok)
 
diff --git a/internal/querylog/json.go b/internal/querylog/json.go
index d6adebe4..24acd468 100644
--- a/internal/querylog/json.go
+++ b/internal/querylog/json.go
@@ -17,7 +17,7 @@ import (
 // TODO(a.garipov): Use a proper structured approach here.
 
 // jobject is a JSON object alias.
-type jobject = map[string]interface{}
+type jobject = map[string]any
 
 // entriesToJSON converts query log entries to JSON.
 func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) (res jobject) {
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index a42140a6..19af5cae 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -1,33 +1,32 @@
 module github.com/AdguardTeam/AdGuardHome/internal/tools
 
-go 1.17
+go 1.18
 
 require (
-	github.com/fzipp/gocyclo v0.5.1
+	github.com/fzipp/gocyclo v0.6.0
 	github.com/golangci/misspell v0.3.5
 	github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8
-	github.com/kisielk/errcheck v1.6.0
+	github.com/kisielk/errcheck v1.6.1
 	github.com/kyoh86/looppointer v0.1.7
-	github.com/securego/gosec/v2 v2.11.0
-	golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a
-	honnef.co/go/tools v0.3.1
+	github.com/securego/gosec/v2 v2.12.0
+	golang.org/x/tools v0.1.12
+	honnef.co/go/tools v0.3.3
 	mvdan.cc/gofumpt v0.3.1
-	mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b
+	mvdan.cc/unparam v0.0.0-20220706161116-678bad134442
 )
 
 require (
-	github.com/BurntSushi/toml v1.1.0 // indirect
+	github.com/BurntSushi/toml v1.2.0 // indirect
 	github.com/client9/misspell v0.3.4 // indirect
 	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/google/uuid v1.3.0 // indirect
-	github.com/gookit/color v1.5.0 // indirect
+	github.com/gookit/color v1.5.1 // indirect
 	github.com/kyoh86/nolint v0.0.1 // indirect
 	github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
 	github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
-	golang.org/x/exp/typeparams v0.0.0-20220426173459-3bcf042a4bf5 // indirect
-	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
-	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
-	golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
-	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
+	golang.org/x/exp/typeparams v0.0.0-20220722155223-a9213eeb770e // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
+	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
+	golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 )
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index f145dfbf..f13b3bdf 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -34,9 +34,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
-github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
+github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
@@ -78,7 +77,6 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -94,12 +92,11 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
 github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns=
-github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
-github.com/fzipp/gocyclo v0.5.1 h1:L66amyuYogbxl0j2U+vGqJXusPF2IkduvXLnYD5TFgw=
-github.com/fzipp/gocyclo v0.5.1/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
+github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
+github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -157,7 +154,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -179,8 +175,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw=
-github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
+github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
+github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
 github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
 github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 h1:PVRE9d4AQKmbelZ7emNig1+NT27DUmKZn5qXxfio54U=
 github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
@@ -222,19 +218,17 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
-github.com/kisielk/errcheck v1.6.0 h1:YTDO4pNy7AUN/021p+JGHycQyYNIyMoenM1YDVK6RlY=
-github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/errcheck v1.6.1 h1:cErYo+J4SmEjdXZrVXGwLJCE2sB06s23LpkcyWNrT+s=
+github.com/kisielk/errcheck v1.6.1/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/kyoh86/looppointer v0.1.7 h1:q5sZOhFvmvQ6ZoZxvPB/Mjj2croWX7L49BBuI4XQWCM=
 github.com/kyoh86/looppointer v0.1.7/go.mod h1:l0cRF49N6xDPx8IuBGC/imZo8Yn1BBLJY0vzI+4fepc=
@@ -244,7 +238,7 @@ github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ew
 github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag=
 github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -288,19 +282,18 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
-github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
-github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
 github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
+github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
-github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -324,14 +317,12 @@ github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
-github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
 github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/securego/gosec/v2 v2.11.0 h1:+PDkpzR41OI2jrw1q6AdXZCbsNGNGT7pQjal0H0cArI=
-github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo=
+github.com/securego/gosec/v2 v2.12.0 h1:CQWdW7ATFpvLSohMVsajscfyHJ5rsGmEXmsNcsDNmAg=
+github.com/securego/gosec/v2 v2.12.0/go.mod h1:iTpT+eKTw59bSgklBHlSnH5O2tNygHMDxfvMubA4i7I=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -356,8 +347,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -407,7 +398,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -418,11 +409,9 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo=
 golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
-golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
-golang.org/x/exp/typeparams v0.0.0-20220426173459-3bcf042a4bf5 h1:pKfHvPtBtqS0+V/V9Y0cZQa2h8HJV/qSRJiGgYu+LQA=
-golang.org/x/exp/typeparams v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
+golang.org/x/exp/typeparams v0.0.0-20220722155223-a9213eeb770e h1:7Xs2YCOpMlNqSQSmrrnhlzBXIE/bpMecZplbLePTJvE=
+golang.org/x/exp/typeparams v0.0.0-20220722155223-a9213eeb770e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -445,9 +434,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -487,8 +476,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -504,8 +494,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -557,12 +548,12 @@ 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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
-golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
+golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -619,7 +610,6 @@ golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roY
 golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@@ -627,16 +617,14 @@ golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
 golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
-golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II=
-golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
+golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
-golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -742,8 +730,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 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=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -751,12 +740,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.3.1 h1:1kJlrWJLkaGXgcaeosRXViwviqjI7nkBvU2+sZW0AYc=
-honnef.co/go/tools v0.3.1/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70=
+honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA=
+honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw=
 mvdan.cc/gofumpt v0.3.1 h1:avhhrOmv0IuvQVK7fvwV91oFSGAk5/6Po8GXTzICeu8=
 mvdan.cc/gofumpt v0.3.1/go.mod h1:w3ymliuxvzVx8DAutBnVyDqYb1Niy/yCJt/lk821YCE=
-mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b h1:C8Pi6noat8BcrL9WnSRYeQ63fpkJk3hKVHtF5731kIw=
-mvdan.cc/unparam v0.0.0-20220316160445-06cc5682983b/go.mod h1:WqFWCt8MGPoFSYGsQSiIORRlYVhkJsIk+n2MY6rhNbA=
+mvdan.cc/unparam v0.0.0-20220706161116-678bad134442 h1:seuXWbRB1qPrS3NQnHmFKLJLtskWyueeIzmLXghMGgk=
+mvdan.cc/unparam v0.0.0-20220706161116-678bad134442/go.mod h1:F/Cxw/6mVrNKqrR2YjFf5CaW0Bw4RL8RfbEf4GRggJk=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/internal/v1/websvc/json.go b/internal/v1/websvc/json.go
index beb7f7ec..ef84211b 100644
--- a/internal/v1/websvc/json.go
+++ b/internal/v1/websvc/json.go
@@ -53,7 +53,7 @@ func (t *jsonTime) UnmarshalJSON(b []byte) (err error) {
 
 // writeJSONResponse encodes v into w and logs any errors it encounters.  r is
 // used to get additional information from the request.
-func writeJSONResponse(w io.Writer, r *http.Request, v interface{}) {
+func writeJSONResponse(w io.Writer, r *http.Request, v any) {
 	err := json.NewEncoder(w).Encode(v)
 	if err != nil {
 		log.Error("websvc: writing resp to %s %s: %s", r.Method, r.URL.Path, err)
diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh
index 853c5fc8..b67b8eaf 100644
--- a/scripts/make/go-lint.sh
+++ b/scripts/make/go-lint.sh
@@ -52,7 +52,7 @@ trap not_found EXIT
 go_version="$( "${GO:-go}" version )"
 readonly go_version
 
-go_min_version='go1.17'
+go_min_version='go1.18'
 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.

From 3420becce38e72ef3870f086a86342788ba9a8f8 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 3 Aug 2022 15:32:49 +0300
Subject: [PATCH 110/143] Pull request: upd-i18n

Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 366600a32ecbb163ab43b43145898bbadcfbc2e9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 3 15:09:16 2022 +0300

    client: fix si-lk

commit 2a55ee3846251e53529f4ef6562e5f4939381eae
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 3 15:03:45 2022 +0300

    client: upd i18n
---
 client/src/__locales/be.json    | 53 ++++++++++---------
 client/src/__locales/cs.json    |  3 +-
 client/src/__locales/da.json    |  3 +-
 client/src/__locales/de.json    |  3 +-
 client/src/__locales/es.json    |  3 +-
 client/src/__locales/fi.json    | 17 +++---
 client/src/__locales/fr.json    |  5 +-
 client/src/__locales/hr.json    |  3 +-
 client/src/__locales/hu.json    |  3 +-
 client/src/__locales/id.json    |  4 +-
 client/src/__locales/it.json    |  3 +-
 client/src/__locales/ja.json    |  5 +-
 client/src/__locales/ko.json    |  3 +-
 client/src/__locales/nl.json    |  3 +-
 client/src/__locales/no.json    |  2 +-
 client/src/__locales/pl.json    |  3 +-
 client/src/__locales/pt-br.json |  4 +-
 client/src/__locales/pt-pt.json |  4 +-
 client/src/__locales/ro.json    |  3 +-
 client/src/__locales/ru.json    |  3 +-
 client/src/__locales/si-lk.json | 94 ++++++++++++++++++++++-----------
 client/src/__locales/sk.json    |  3 +-
 client/src/__locales/sl.json    |  3 +-
 client/src/__locales/sr-cs.json |  3 +-
 client/src/__locales/sv.json    |  5 +-
 client/src/__locales/tr.json    | 17 +++---
 client/src/__locales/uk.json    |  3 +-
 client/src/__locales/vi.json    |  4 +-
 client/src/__locales/zh-cn.json |  3 +-
 client/src/__locales/zh-tw.json |  3 +-
 30 files changed, 165 insertions(+), 103 deletions(-)

diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json
index 378d20c0..1f21106e 100644
--- a/client/src/__locales/be.json
+++ b/client/src/__locales/be.json
@@ -71,8 +71,8 @@
     "dhcp_error": "AdGuard Home не можа вызначыць, ці ёсць у сетцы іншы актыўны DHCP-сервер",
     "dhcp_static_ip_error": "Для таго, каб выкарыстоўваць DHCP-сервер, павінен быць усталяваны статычны IP-адрас. Мы не змаглі вызначыць, ці выкарыстоўвае гэты інтэрфейс сеціва статычны IP-адрас. Калі ласка, усталюйце яго ручна.",
     "dhcp_dynamic_ip_found": "Ваша сістэма выкарыстоўвае дынамічны IP-адрас для інтэрфейсу <0>{{interfaceName}}</0>. Каб выкарыстоўваць DHCP-сервер трэба ўсталяваць статычны IP-адрас. Ваш бягучы IP-адрас – <0>{{ipAddress}}</0>. Мы аўтаматычна ўсталюем яго як статычны, калі вы націснеце кнопку Ўключыць DHCP.",
-    "dhcp_lease_added": "Статычная арэнда \"{{key}}\" паспяхова дададзена",
-    "dhcp_lease_deleted": "Статычная арэнда \"{{key}}\" паспяхова выдалена",
+    "dhcp_lease_added": "Статычная арэнда «{{key}}» паспяхова дададзена",
+    "dhcp_lease_deleted": "Статычная арэнда «{{key}}» паспяхова выдалена",
     "dhcp_new_static_lease": "Новая статычная арэнда",
     "dhcp_static_leases_not_found": "Не знойдзена статычных арэнд DHCP",
     "dhcp_add_static_lease": "Дадаць статычную арэнду",
@@ -82,7 +82,7 @@
     "dhcp_reset": "Вы ўпэўнены, што хочаце скінуць налады DHCP?",
     "country": "Краіна",
     "city": "Горад",
-    "delete_confirm": "Вы ўпэўнены, што хочаце выдаліць \"{{key}}\"?",
+    "delete_confirm": "Вы ўпэўнены, што хочаце выдаліць «{{key}}»?",
     "form_enter_hostname": "Увядзіце імя хаста",
     "error_details": "Дэталізацыя памылкі",
     "response_details": "Дэталі адказу",
@@ -115,7 +115,7 @@
     "dns_query": "DNS-запыты",
     "blocked_by": "<0>Заблакавана фільтрамі</0>",
     "stats_malware_phishing": "Заблакаваныя шкодныя і фішынгавыя сайты",
-    "stats_adult": "Заблакаваныя \"дарослыя\" сайты",
+    "stats_adult": "Заблакаваныя «дарослыя» сайты",
     "stats_query_domain": "Часта запытаныя дамены",
     "for_last_24_hours": "за 24 гадзіны",
     "for_last_days": "за апошні {{count}} дзень",
@@ -133,13 +133,13 @@
     "number_of_dns_query_24_hours": "Колькасць DNS-запытаў за 24 гадзіны",
     "number_of_dns_query_blocked_24_hours": "Колькасць DNS-запытаў, заблакаваных фільтрамі і блок-спісамі",
     "number_of_dns_query_blocked_24_hours_by_sec": "Колькасць DNS-запытаў, заблакаваных модулем Антыфішынгу AdGuard",
-    "number_of_dns_query_blocked_24_hours_adult": "Колькасць заблакаваных \"сайтаў для дарослых\"",
+    "number_of_dns_query_blocked_24_hours_adult": "Колькасць заблакаваных «сайтаў для дарослых»",
     "enforced_save_search": "Ужыты бяспечны пошук",
     "number_of_dns_query_to_safe_search": "Колькасць запытаў DNS для пошукавых сістэм, для якіх быў ужыты Бяспечны пошук",
     "average_processing_time": "Сярэдні час апрацоўкі запыту",
     "average_processing_time_hint": "Сярэдні час для апрацоўкі запыту DNS  у мілісекундах",
     "block_domain_use_filters_and_hosts": "Блакаваць дамены з выкарыстаннем фільтраў і файлаў хастоў",
-    "filters_block_toggle_hint": "Вы можаце наладзіць правілы блакавання ў <a> \"Фільтрах\"</a>.",
+    "filters_block_toggle_hint": "Вы можаце наладзіць правілы блакавання ў <a> «Фільтрах»</a>.",
     "use_adguard_browsing_sec": "Выкарыстаць Бяспечную навігацыю AdGuard",
     "use_adguard_browsing_sec_hint": "AdGuard Home праверыць, ці ўлучаны дамен у ўэб-службу бяспекі браўзара. Ён будзе выкарыстоўваць API, каб выканаць праверку: на сервер адсылаецца толькі кароткі прэфікс імя дамена SHA256.",
     "use_adguard_parental": "Ужывайце модуль Бацькоўскага кантролю AdGuard ",
@@ -221,7 +221,8 @@
     "all_lists_up_to_date_toast": "Усе спісы ўжо абноўлены",
     "updated_upstream_dns_toast": "Upstream DNS-серверы абноўлены",
     "dns_test_ok_toast": "Паказаныя серверы DNS працуюць карэктна",
-    "dns_test_not_ok_toast": "Сервер \"{{key}}\": немагчыма выкарыстоўваць, праверце слушнасць напісання",
+    "dns_test_not_ok_toast": "Сервер «{{key}}»: немагчыма выкарыстоўваць, праверце слушнасць напісання",
+    "dns_test_warning_toast": "Upstream «{{key}}» не адказвае на тэставыя запыты і можа не працаваць належным чынам",
     "unblock": "Адблакаваць",
     "block": "Заблакаваць",
     "disallow_this_client": "Забараніць доступ гэтаму кліенту",
@@ -332,15 +333,15 @@
     "install_devices_router_desc": "Такая наладка аўтаматычна пакрые ўсе прылады, што выкарыстоўваюць ваш хатні роўтар, і вам не трэба будзе наладжваць кожнае з іх у асобнасці.",
     "install_devices_address": "DNS-сервер AdGuard Home даступны па наступных адрасах",
     "install_devices_router_list_1": "Адкрыйце налады вашага роўтара. Звычайна вы можаце адкрыць іх у вашым браўзары, напрыклад, http://192.168.0.1/ ці http://192.168.1.1/. Вас могуць папрасіць увесці пароль. Калі вы не помніце яго, пароль часта можна скінуць, націснуўшы на кнопку на самым роўтары. Некаторыя роўтары патрабуюць адмысловага дадатку, які ў гэтым выпадку павінен быць ужо ўсталявана на ваш кампутар ці тэлефон.",
-    "install_devices_router_list_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары \"DNS\" поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.",
+    "install_devices_router_list_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары «DNS» поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.",
     "install_devices_router_list_3": "Увядзіце туды адрас вашага AdGuard Home.",
     "install_devices_router_list_4": "Вы не можаце ўсталяваць уласны DNS-сервер на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе DNS-сервераў для вашай пэўнай мадэлі маршрутызатара.",
-    "install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню \"Пуск\" ці праз пошук Windows.",
-    "install_devices_windows_list_2": "Перайдзіце ў \"Сеціва і інтэрнэт\", а потым у \"Цэнтр кіравання сеціва і агульным доступам\"",
+    "install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню «Пуск» ці праз пошук Windows.",
+    "install_devices_windows_list_2": "Перайдзіце ў «Сеціва і інтэрнэт», а потым у «Цэнтр кіравання сеціва і агульным доступам»",
     "install_devices_windows_list_3": "У левым боку экрана клікніце «Змена параметраў адаптара».",
     "install_devices_windows_list_4": "Пстрыкніце правай кнопкай мышы ваша актыўнае злучэнне і абярыце Уласцівасці.",
-    "install_devices_windows_list_5": "Знайдзіце ў спісе пункт \"IP версіі 4 (TCP/IPv4)\", вылучыце яго і потым ізноў націсніце \"Уласцівасці\".",
-    "install_devices_windows_list_6": "Абярыце \"Выкарыстаць наступныя адрасы DNS-сервераў\" і ўвядзіце адрас AdGuard Home.",
+    "install_devices_windows_list_5": "Знайдзіце ў спісе пункт «IP версіі 4 (TCP/IPv4)», вылучыце яго і потым ізноў націсніце «Уласцівасці».",
+    "install_devices_windows_list_6": "Абярыце «Выкарыстаць наступныя адрасы DNS-сервераў» і ўвядзіце адрас AdGuard Home.",
     "install_devices_macos_list_1": "Клікніце па абразку Apple і перайдзіце ў Сістэмныя налады.",
     "install_devices_macos_list_2": "Клікніце па іконцы Сеціва.",
     "install_devices_macos_list_3": "Абярыце першае падлучэнне ў спісе і націсніце кнопку «Дадаткова».",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Налады шыфравання захаваны",
     "encryption_server": "Імя сервера",
     "encryption_server_enter": "Увядзіце ваша даменавае імя",
-    "encryption_server_desc": "Для выкарыстання HTTPS вам трэба ўвесці імя сервера, якое падыходзіць вашаму SSL-сертыфікату.",
+    "encryption_server_desc": "Калі ўстаноўлена, AdGuard Home вызначае ClientID, адказвае на запыты DDR і выконвае дадатковыя праверкі злучэння. Калі не ўстаноўлена, гэтыя функцыі адключаны. Павінна адпавядаць аднаму з імёнаў DNS у сертыфікаце.",
     "encryption_redirect": "Аўтаматычна перанакіроўваць на HTTPS",
     "encryption_redirect_desc": "Калі ўлучана, AdGuard Home будзе аўтаматычна перанакіроўваць вас з HTTP на HTTPS адрас.",
     "encryption_https": "Порт HTTPS",
@@ -429,11 +430,11 @@
     "form_client_name": "Увядзіце імя кліента",
     "name": "Назва",
     "client_global_settings": "Выкарыстаць глабальныя налады",
-    "client_deleted": "Кліент \"{{key}}\" паспяхова выдалены",
-    "client_added": "Кліент \"{{key}}\" паспяхова дададзены",
-    "client_updated": "Кліент \"{{key}}\" паспяхова абноўлены",
+    "client_deleted": "Кліент «{{key}}» паспяхова выдалены",
+    "client_added": "Кліент «{{key}}» паспяхова дададзены",
+    "client_updated": "Кліент «{{key}}» паспяхова абноўлены",
     "clients_not_found": "Кліентаў не знойдзена",
-    "client_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць кліента \"{{key}}\"?",
+    "client_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць кліента «{{key}}»?",
     "list_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць гэты спіс?",
     "auto_clients_title": "Кліенты (runtime)",
     "auto_clients_desc": "Прылады, якіх няма ў спісе пастаянных кліентаў, якія ўсё яшчэ могуць выкарыстоўваць AdGuard Home",
@@ -467,11 +468,11 @@
     "setup_dns_privacy_other_5": "Вы можаце знайсці яшчэ варыянты <0>тут</0> і <1>тут</1>.",
     "setup_dns_privacy_ioc_mac": "Канфігурацыя для iOS і macOS",
     "setup_dns_notice": "Каб выкарыстоўваць <1>DNS-over-HTTPS</1> ці <1>DNS-over-TLS</1>, вам патрэбна <0>наладзіць шыфраванне</0> у наладах AdGuard Home.",
-    "rewrite_added": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова дададзена",
-    "rewrite_deleted": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова выдалена",
+    "rewrite_added": "Правіла перанакіравання DNS для «{{key}}» паспяхова дададзена",
+    "rewrite_deleted": "Правіла перанакіравання DNS для «{{key}}» паспяхова выдалена",
     "rewrite_add": "Дадаць правіла перанакіравання DNS",
     "rewrite_not_found": "Не знойдзена правілаў перанакіравання DNS",
-    "rewrite_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць правіла перанакіравання DNS для \"{{key}}\"?",
+    "rewrite_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць правіла перанакіравання DNS для «{{key}}»?",
     "rewrite_desc": "Дазваляе лёгка наладзіць карыстацкі DNS-адказ для пэўнага дамена.",
     "rewrite_applied": "Ужыта правіла перанакіравання",
     "rewrite_hosts_applied": "Перапісана па правіле файла hosts",
@@ -552,7 +553,7 @@
     "disable_ipv6_desc": "Калі гэта опцыя ўлучана, усе DNS-запыты адрасоў IPv6 (тып AAAA) будуць ігнаравацца.",
     "fastest_addr": "Найхуткі IP-адрас",
     "fastest_addr_desc": "Апытайце ўсе DNS-серверы і вярніце самы хуткі IP-адрас сярод усіх адказаў. Гэта замарудзіць выкананне DNS-запытаў, бо нам давядзецца чакаць адказаў ад усіх DNS-сервераў, але палепшыць агульную ўзаемасувязь.",
-    "autofix_warning_text": "Пры націску \"Выправіць\" AdGuard Home наладзіць вашу сістэму на выкарыстанне DNS-сервера AdGuard Home.",
+    "autofix_warning_text": "Пры націску «Выправіць» AdGuard Home наладзіць вашу сістэму на выкарыстанне DNS-сервера AdGuard Home.",
     "autofix_warning_list": "Будуць выконвацца наступныя заданні: <0>Дэактываваць сістэмны DNSStubListener</0> <0>Усталяваць адрас сервера DNS на 127.0.0.1</0> <0>Стварыць сімвалічную спасылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Спыніць DNSStubListener (перазагрузіць сістэмную службу)</0>.",
     "autofix_warning_result": "У выніку ўсе DNS-запыты ад вашай сістэмы будуць па змаўчанні апрацоўвацца AdGuard Home.\n",
     "tags_title": "Тэгі",
@@ -572,10 +573,10 @@
     "check_service": "Назва сэрвісу: {{service}}",
     "service_name": "Назва сэрвіса",
     "check_not_found": "Не знойдзена ў вашым спісе фільтраў",
-    "client_confirm_block": "Вы ўпэўнены, што хочаце заблакаваць кліента \"{{ip}}\"?",
-    "client_confirm_unblock": "Вы ўпэўнены, што хочаце адблакаваць кліента \"{{ip}}\"?",
-    "client_blocked": "Кліент \"{{ip}}\" паспяхова заблакаваны",
-    "client_unblocked": "Кліент \"{{ip}}\" паспяхова адблакаваны",
+    "client_confirm_block": "Вы ўпэўнены, што хочаце заблакаваць кліента «{{ip}}»?",
+    "client_confirm_unblock": "Вы ўпэўнены, што хочаце адблакаваць кліента «{{ip}}»?",
+    "client_blocked": "Кліент «{{ip}}» паспяхова заблакаваны",
+    "client_unblocked": "Кліент «{{ip}}» паспяхова адблакаваны",
     "static_ip": "Статычны IP-адрас",
     "static_ip_desc": "AdGuard Home з'яўляецца серверам, таму для карэктнай працы яму патрэбен статычны IP-адрас. У адваротным выпадку, у нейкі момант ваш роўтар можа прысвоіць гэтай прыладзе іншы IP-адрас.",
     "set_static_ip": "Усталяваць статычны IP-адрас",
@@ -624,7 +625,7 @@
     "setup_config_to_enable_dhcp_server": "Наладзіць канфігурацыю для ўключэння DHCP-сервера",
     "original_response": "Першапачатковы адказ",
     "click_to_view_queries": "Націсніце, каб прагледзець запыты",
-    "port_53_faq_link": "Порт 53 часта заняты службамі \"DNSStubListener\" ці \"systemd-resolved\". Азнаёмцеся з <0>інструкцыяй</0> пра тое, як гэта дазволіць.",
+    "port_53_faq_link": "Порт 53 часта заняты службамі «DNSStubListener» ці «systemd-resolved». Азнаёмцеся з <0>інструкцыяй</0> пра тое, як гэта дазволіць.",
     "adg_will_drop_dns_queries": "AdGuard Home скіне ўсе DNS-запыты ад гэтага кліента.",
     "filter_allowlist": "УВАГА: Гэта дзеянне таксама выключыць правіла «{{disallowed_rule}}» са спіса дазволеных кліентаў.",
     "last_rule_in_allowlist": "Няможна заблакаваць гэтага кліента, бо вынятак правіла «{{disallowed_rule}}» АДКЛЮЧЫЦЬ рэжым белага спіса.",
diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json
index 26a50ba5..a01e1c92 100644
--- a/client/src/__locales/cs.json
+++ b/client/src/__locales/cs.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Odchozí servery byly úspěšně uloženy",
     "dns_test_ok_toast": "Specifikované DNS servery pracují správně",
     "dns_test_not_ok_toast": "Server \"{{key}}\": nemohl být použit, zkontrolujte, zda jste ho správně napsali",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" neodpovídá na testovací požadavky a nemusí fungovat správně",
     "unblock": "Odblokovat",
     "block": "Blokovat",
     "disallow_this_client": "Blokovat tohoto klienta",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Konfigurace šifrování byla uložena",
     "encryption_server": "Název serveru",
     "encryption_server_enter": "Zadejte název domény",
-    "encryption_server_desc": "Abyste mohli používat HTTPS, musíte zadat název serveru, který odpovídá vašemu certifikátu SSL nebo zástupnému certifikátu. Pokud není pole nastaveno, bude přijímat připojení TLS pro libovolnou doménu.",
+    "encryption_server_desc": "Pokud je nastaveno, AdGuard Home detekuje ClientID, odpovídá na dotazy DDR a provádí další ověření připojení. Pokud není nastaveno, jsou tyto funkce vypnuty. Musí odpovídat jednomu z názvů DNS v certifikátu.",
     "encryption_redirect": "Automaticky přesměrovat na HTTPS",
     "encryption_redirect_desc": "Pokud je zaškrtnuto, AdGuard Home vás automaticky přesměruje z adres HTTP na HTTPS.",
     "encryption_https": "HTTPS port",
diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json
index 2b21f4e8..7e800e1c 100644
--- a/client/src/__locales/da.json
+++ b/client/src/__locales/da.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Ugyldigt servernavn",
     "form_error_subnet": "Undernet \"{{cidr}}\" indeholder ikke IP-adressen \"{{ip}}\"",
     "form_error_positive": "Skal være større end 0",
+    "form_error_gateway_ip": "Lease kan ikke have gatewayens IP-adresse",
     "out_of_range_error": "Skal være uden for området \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Skal være mindre end starten på området",
     "greater_range_start_error": "Skal være større end starten på ​​området",
@@ -362,7 +363,7 @@
     "encryption_config_saved": "Krypteringsopsætning gemt",
     "encryption_server": "Servernavn",
     "encryption_server_enter": "Angiv dit domænenavn",
-    "encryption_server_desc": "For at kunne bruge HTTPS skal du angive det servernavn, der matcher dit SSL-certifikat eller wildcard-certifikat. Er feltet ikke er opsat, accepterer det TLS-forbindelser til ethvert domæne.",
+    "encryption_server_desc": "Hvis indstillet, registrerer AdGuard Home ClientID'er, svarer på DDR-forespørgsler og udfører yderligere forbindelsesvalideringer. Hvis ikke er indstillet, er disse funktioner deaktiveret. Skal matche et af DNS-navnene i certifikatet.",
     "encryption_redirect": "Omdirigér automatisk til HTTPS",
     "encryption_redirect_desc": "Hvis afkrydset, omdirigerer AdGuard Home dig automatisk fra HTTP- til HTTPS-adresser.",
     "encryption_https": "HTTPS-port",
diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json
index 29a6ef15..a1e49718 100644
--- a/client/src/__locales/de.json
+++ b/client/src/__locales/de.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Upstream-Server erfolgreich gespeichert",
     "dns_test_ok_toast": "Angegebene DNS-Server arbeiten ordnungsgemäß",
     "dns_test_not_ok_toast": "Server „{{key}}“: konnte nicht verwendet werden, bitte überprüfen Sie die korrekte Schreibweise",
+    "dns_test_warning_toast": "Upstream „{{key}}“ reagiert nicht auf Testanfragen und funktioniert möglicherweise nicht fehlerfrei",
     "unblock": "Entsperren",
     "block": "Sperren",
     "disallow_this_client": "Diesen Client sperren",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Verschlüsselungskonfiguration gespeichert",
     "encryption_server": "Servername",
     "encryption_server_enter": "Domain-Namen eingeben",
-    "encryption_server_desc": "Um HTTPS verwenden zu können, müssen Sie den Servernamen eingeben, der zu Ihrem SSL-Zertifikat passt.",
+    "encryption_server_desc": "Wenn diese Option aktiviert ist, erkennt AdGuard Home ClientIDs, antwortet auf DDR-Anfragen und führt zusätzliche Verbindungsüberprüfungen durch. Wenn sie nicht gesetzt ist, sind diese Funktionen deaktiviert. Muss mit einem der DNS-Namen im Zertifikat übereinstimmen.",
     "encryption_redirect": "Automatisch auf HTTPS umleiten",
     "encryption_redirect_desc": "Wenn aktiviert, leitet AdGuard Home Sie automatisch von HTTP- auf HTTPS-Adressen um.",
     "encryption_https": "HTTPS-Port",
diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json
index aa65adc3..56895b58 100644
--- a/client/src/__locales/es.json
+++ b/client/src/__locales/es.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Servidores DNS de subida guardados correctamente",
     "dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
     "dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revisa si lo has escrito correctamente",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" no responde a las peticiones de prueba y es posible que no funcione correctamente",
     "unblock": "Desbloquear",
     "block": "Bloquear",
     "disallow_this_client": "No permitir a este cliente",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Configuración de cifrado guardado",
     "encryption_server": "Nombre del servidor",
     "encryption_server_enter": "Ingresa el nombre del dominio",
-    "encryption_server_desc": "Para utilizar HTTPS, debes ingresar el nombre del servidor que coincida con tu certificado SSL o certificado comodín. Si el campo no está establecido, el servidor aceptará conexiones TLS para cualquier dominio.",
+    "encryption_server_desc": "Si se configura, AdGuard Home detecta los ClientID, responde a las consultas DDR y realiza validaciones de conexión adicionales. Si no se configura, estas funciones están deshabilitadas. Debe coincidir con uno de los nombres DNS del certificado.",
     "encryption_redirect": "Redireccionar a HTTPS automáticamente",
     "encryption_redirect_desc": "Si está marcado, AdGuard Home redireccionará automáticamente de HTTP a las direcciones HTTPS.",
     "encryption_https": "Puerto HTTPS",
diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json
index 5ebabde6..197f74d3 100644
--- a/client/src/__locales/fi.json
+++ b/client/src/__locales/fi.json
@@ -43,7 +43,7 @@
     "form_error_ip6_format": "Virheellinen IPv6-osoite",
     "form_error_ip_format": "Virheellinen IP-osoite",
     "form_error_mac_format": "Virheellinen MAC-osoite",
-    "form_error_client_id_format": "Päätelaitteen ID voi sisältää ainoastaan numeroita, pieniä kirjaimia sekä yhdysviivoja",
+    "form_error_client_id_format": "ClientID-tunniste voi sisältää ainoastaan numeroita, pieniä kirjaimia sekä yhdysviivoja",
     "form_error_server_name": "Virheellinen palvelimen nimi",
     "form_error_subnet": "Aliverkko \"{{cidr}}\" ei sisällä IP-osoitetta \"{{ip}}\"",
     "form_error_positive": "Oltava suurempi kuin 0",
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Ylävirtojen palvelimet tallennettiin",
     "dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
     "dns_test_not_ok_toast": "Palvelin \"{{key}}\": ei voitu käyttää, tarkista sen oikeinkirjoitus",
+    "dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla",
     "unblock": "Salli",
     "block": "Estä",
     "disallow_this_client": "Estä tämä päätelaite",
@@ -278,9 +279,9 @@
     "dns_over_https": "DNS-over-HTTPS",
     "dns_over_tls": "DNS-over-TLS",
     "dns_over_quic": "DNS-over-QUIC",
-    "client_id": "Päätelaitteen ID",
-    "client_id_placeholder": "Syötä päätelaitteen ID",
-    "client_id_desc": "Päätelaitteet voidaan tunnistaa erityisillä ID-tunnisteilla. Lue lisää päätelaitteiden tunnistuksesta <a>täältä</a>.",
+    "client_id": "ClientID",
+    "client_id_placeholder": "Syötä ClientID",
+    "client_id_desc": "Päätelaitteet voidaan tunnistaa erityisillä ClientID-tunnisteilla. Lue lisää päätelaitteiden tunnistuksesta <a>täältä</a>.",
     "download_mobileconfig_doh": "Lataa .mobileconfig-tiedosto DNS-over-HTTPS -käytölle",
     "download_mobileconfig_dot": "Lataa .mobileconfig-tiedosto DNS-over-TLS -käytölle",
     "download_mobileconfig": "Lataa asetustiedosto",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Salausasetukset tallennettiin",
     "encryption_server": "Palvelimen nimi",
     "encryption_server_enter": "Syötä verkkotunnuksesi",
-    "encryption_server_desc": "HTTPS-yhteyden käyttöä varten, on syötettävä SSL- tai jokerivarmennetta vastaava palvelimen nimi. Jos kenttä on tyhjä, sallitaan kaikkien verkkotunnusten TLS-yhteydet.",
+    "encryption_server_desc": "Jos määritetty, AdGuard Home tunnistaa ClientID-tunnisteet, vastaa DDR-kyselyihin ja suorittaa yhteyden lisätarkistuksia. Jos ei määritetty, nämä ominaisuudet eivät ole käytössä. On vastattava yhtä varmenteen DNS-nimistä.",
     "encryption_redirect": "Automaattinen HTTPS-ohjaus",
     "encryption_redirect_desc": "Jos käytössä, AdGuard Home ohjaa HTTP-osoitteet automaattisesti HTTPS-osoitteisiin.",
     "encryption_https": "HTTPS-portti",
@@ -420,7 +421,7 @@
     "client_edit": "Muokkaa päätelaitetta",
     "client_identifier": "Tunniste",
     "ip_address": "IP-osoite",
-    "client_identifier_desc": "Päätelaitteet voidaan tunnistaa IP- tai MAC-osoitteista, CIDR-merkinnöistä tai erityisistä päätelaite ID -tunnisteista (voidaan käyttää DoT/DoH/DoQ yhteydessä). Lue lisää päätelaitteiden tunnistuksesta <0>täältä</0>.",
+    "client_identifier_desc": "Päätelaitteet voidaan tunnistaa IP- tai MAC-osoitteista, CIDR-merkinnöistä tai erityisistä ClientID-tunnisteista (voidaan käyttää DoT/DoH/DoQ yhteydessä). Lue lisää päätelaitteiden tunnistuksesta <0>täältä</0>.",
     "form_enter_ip": "Syötä IP-osoite",
     "form_enter_subnet_ip": "Syötä aliverkossa \"{{cidr}}\" oleva IP-osoite",
     "form_enter_mac": "Syötä MAC-osoite",
@@ -440,9 +441,9 @@
     "access_title": "Käytön asetukset",
     "access_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.",
     "access_allowed_title": "Sallitut päätelaitteet",
-    "access_allowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai <a>päätelaite ID</a> -tunnisteista. Jos listalla on kohteita, hyväksyy AdGuard Home pyyntöjä vain näiltä päätelaitteilta.",
+    "access_allowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai <a>ClientID</a>-tunnisteista. Jos listalla on kohteita, hyväksyy AdGuard Home pyyntöjä vain näiltä päätelaitteilta.",
     "access_disallowed_title": "Kielletyt päätelaitteet",
-    "access_disallowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai <a>päätelaite ID</a> -tunnisteista. Jos listalla on kohteita, hylkää AdGuard Home näiden päätelaitteiden pyynnöt. Tätä kenttää ei huomioida, jos sallittuja päätelaitteita on määritetty.",
+    "access_disallowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai <a>ClientID</a>-tunnisteista. Jos listalla on kohteita, hylkää AdGuard Home näiden päätelaitteiden pyynnöt. Tätä kenttää ei huomioida, jos sallittuja päätelaitteita on määritetty.",
     "access_blocked_title": "Kielletyt verkkotunnukset",
     "access_blocked_desc": "Ei pidä sekoittaa suodattimiin. AdGuard Home hylkää näiden verkkotunnusten DNS-pyynnöt, eivätkä nämä pyynnöt näy edes pyyntöhistoriassa. Tähän voidaan syöttää tarkkoja verkkotunnuksia, jokerimerkkejä tai URL-suodatussääntöjä, kuten \"example.org\", \"*.example.org\" tai \"||example.org^\".",
     "access_settings_saved": "Käytön asetukset tallennettiin",
diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json
index 8b051751..fe891203 100644
--- a/client/src/__locales/fr.json
+++ b/client/src/__locales/fr.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Serveurs en amont enregistrés",
     "dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement",
     "dns_test_not_ok_toast": "Impossible d'utiliser le serveur « {{key}} »: veuillez vérifier si le nom saisi est bien correct",
+    "dns_test_warning_toast": "L'amont « {{key}} » ne répond pas aux demandes de test et peut ne pas fonctionner correctement.",
     "unblock": "Débloquer",
     "block": "Bloquer",
     "disallow_this_client": "Interdire ce client",
@@ -337,7 +338,7 @@
     "install_devices_router_list_4": "Vous ne pouvez pas définir un serveur DNS personnalisé sur certains types de routeurs. Dans ce cas, la configuration de AdGuard Home en tant que <0>serveur DHCP</0> peut aider. Sinon, vous devez rechercher le manuel sur la façon de personnaliser les serveurs DNS pour votre modèle de routeur particulier.",
     "install_devices_windows_list_1": "Ouvrez votre Panneau de configuration depuis le menu Démarrer ou la recherche Windows.",
     "install_devices_windows_list_2": "Allez dans la catégorie Réseau et Internet et ensuite dans le Centre Réseau et Partage.",
-    "install_devices_windows_list_3": "Dans le panneau de gauche, cliquez sur \"Modifier les paramètres de l'adaptateur\".",
+    "install_devices_windows_list_3": "Dans le panneau de gauche, cliquez sur « Modifier les paramètres de l'adaptateur ».",
     "install_devices_windows_list_4": "Cliquez avec le bouton droit de la souris sur votre connexion active et sélectionnez Propriétés.",
     "install_devices_windows_list_5": "Recherchez « Protocole Internet Version 4 (TCP/IPv4) » (soit, pour IPv6, « Protocole Internet Version 6 (TCP/IPv6) ») dans la liste, sélectionnez-la puis cliquez à nouveau sur Propriétés.",
     "install_devices_windows_list_6": "Sélectionnez « Utiliser l’adresse de serveur DNS suivante » et saisissez votre adresse de serveur AdGuard Home.",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Configuration de chiffrement enregistrée",
     "encryption_server": "Nom du serveur",
     "encryption_server_enter": "Entrez votre nom de domaine",
-    "encryption_server_desc": "Pour utiliser HTTPS, vous devez saisir le nom du serveur qui correspond à votre certificat SSL ou wildcard. Si le champ n'est pas configuré, les connexions TLS pour tous les domaines seront acceptées.",
+    "encryption_server_desc": "Si cette option est définie, AdGuard Home détecte les ClientID, répond aux requêtes DDR et effectue des validations de connexion supplémentaires. Si elle n'est pas définie, ces fonctions sont désactivées. Doit correspondre à l'un des noms DNS du certificat.",
     "encryption_redirect": "Redirection automatiquement vers HTTPS",
     "encryption_redirect_desc": "Si coché, AdGuard Home vous redirigera automatiquement d'adresses HTTP vers HTTPS.",
     "encryption_https": "Port HTTPS",
diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json
index ba9bb78c..7b9d5453 100644
--- a/client/src/__locales/hr.json
+++ b/client/src/__locales/hr.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Uzvodni poslužitelji uspješno su spremljeni",
     "dns_test_ok_toast": "Odabrani DNS poslužitelji su trenutno aktivni",
     "dns_test_not_ok_toast": "\"{{key}}\" poslužitelja: ne može se upotrijebiti, provjerite jeste li to ispravno napisali",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" ne odgovara na zahtjeve za testiranje i možda neće raditi ispravno",
     "unblock": "Odblokiraj",
     "block": "Blokiraj",
     "disallow_this_client": "Onemogući ovog klijenta",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Konfiguracija šifriranja spremljena",
     "encryption_server": "Naziv poslužitelja",
     "encryption_server_enter": "Unesite naziv domene",
-    "encryption_server_desc": "Da biste koristili HTTPS, morate unijeti ime poslužitelja koje odgovara vašem SSL certifikatu ili wildcard certifikatu. Ako polje nije postavljeno, prihvatit će TLS veze za bilo koju domenu.",
+    "encryption_server_desc": "Ako je postavljeno, AdGuard Home otkriva ClientID-ove, odgovara na DDR upite i provodi dodatne provjere valjanosti veze. Ako nije postavljeno, ove značajke su onemogućene. Mora odgovarati jednom od DNS naziva u certifikatu.",
     "encryption_redirect": "Automatski preusmjeri na HTTPS",
     "encryption_redirect_desc": "Ako je omogućeno, AdGuard Home će vas automatski preusmjeravati s HTTP na HTTPS adrese.",
     "encryption_https": "HTTPS port",
diff --git a/client/src/__locales/hu.json b/client/src/__locales/hu.json
index de66c91f..ffdbf7ce 100644
--- a/client/src/__locales/hu.json
+++ b/client/src/__locales/hu.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Upstream szerverek sikeresen mentve",
     "dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek",
     "dns_test_not_ok_toast": "Szerver \"{{key}}\": nem használható, ellenőrizze, hogy helyesen írta-e be",
+    "dns_test_warning_toast": "A \"{{key}}\" feltöltés nem válaszol a tesztkérelmekre, és lehet, hogy nem működik megfelelően",
     "unblock": "Feloldás",
     "block": "Blokkolás",
     "disallow_this_client": "Tiltás ennek a kliensnek",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Titkosítási beállítások mentve",
     "encryption_server": "Szerver neve",
     "encryption_server_enter": "Adja meg az Ön domain címét",
-    "encryption_server_desc": "A HTTPS használatához meg kell adnia a szerver nevét, amely megegyezik az SSL tanúsítvánnyal vagy a helyettesítő tanúsítvánnyal. Ha a mező nincs beállítva, akkor bármely tartományhoz elfogadja a TLS kapcsolatokat.",
+    "encryption_server_desc": "Ha be van állítva, az AdGuard Home észleli az ClientID-ket, válaszol a DDR-lekérdezésekre, és további kapcsolatellenőrzéseket végez. Ha nincs beállítva, ezek a funkciók le vannak tiltva. Meg kell egyeznie a tanúsítványban szereplő DNS-nevek egyikével.",
     "encryption_redirect": "Automatikus átirányítás HTTPS kapcsolatra",
     "encryption_redirect_desc": "Ha be van jelölve, az AdGuard Home automatikusan átirányítja a HTTP kapcsolatokat a biztonságos HTTPS protokollra.",
     "encryption_https": "HTTPS port",
diff --git a/client/src/__locales/id.json b/client/src/__locales/id.json
index 4f655806..0b055f31 100644
--- a/client/src/__locales/id.json
+++ b/client/src/__locales/id.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nama server tidak valid",
     "form_error_subnet": "Subnet \"{{cidr}}\" tidak berisi alamat IP \"{{ip}}\"",
     "form_error_positive": "Harus lebih dari 0",
+    "form_error_gateway_ip": "Sewa tidak dapat memiliki alamat IP gateway",
     "out_of_range_error": "Harus di luar rentang \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Harus lebih rendah dari rentang awal",
     "greater_range_start_error": "Harus lebih besar dari rentang awal",
@@ -221,6 +222,7 @@
     "updated_upstream_dns_toast": "Server upstream berhasil disimpan",
     "dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar",
     "dns_test_not_ok_toast": "Server \"{{key}}\": tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" tidak menanggapi permintaan pengujian dan mungkin tidak berfungsi dengan baik",
     "unblock": "Buka Blokir",
     "block": "Blok",
     "disallow_this_client": "Cabut ijin untuk klien ini",
@@ -362,7 +364,7 @@
     "encryption_config_saved": "Pengaturan enkripsi telah tersimpan",
     "encryption_server": "Nama server",
     "encryption_server_enter": "Masukkan nama domain anda",
-    "encryption_server_desc": "Untuk menggunakan HTTPS, Anda harus memasukkan nama server yang cocok dengan sertifikat SSL Anda. Bila ruas tak ditata, akan menerima koneksi TLS bagi domain manapun.",
+    "encryption_server_desc": "Jika disetel, AdGuard Home mendeteksi ClientID, merespons kueri DDR, dan melakukan validasi koneksi tambahan. Jika tidak disetel, fitur-fitur ini dinonaktifkan. Harus cocok dengan salah satu Nama DNS dalam sertifikat.",
     "encryption_redirect": "Alihkan ke HTTPS secara otomatis",
     "encryption_redirect_desc": "Jika dicentang, AdGuard Home akan secara otomatis mengarahkan anda dari HTTP ke alamat HTTPS.",
     "encryption_https": "Port HTTPS",
diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json
index 228951c9..9a4ed74b 100644
--- a/client/src/__locales/it.json
+++ b/client/src/__locales/it.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "I server upstream sono stati salvati correttamente",
     "dns_test_ok_toast": "I server DNS specificati funzionano correttamente",
     "dns_test_not_ok_toast": "Server \"{{key}}\": non può essere utilizzato, assicurati di averlo digitato correttamente",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" non risponde alle richieste di test e potrebbe non funzionare correttamente",
     "unblock": "Sblocca",
     "block": "Blocca",
     "disallow_this_client": "Blocca questo client",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Configurazione crittografia salvata",
     "encryption_server": "Nome server",
     "encryption_server_enter": "Inserisci il tuo nome di dominio",
-    "encryption_server_desc": "Per utilizzare HTTPS, è necessario immettere il nome del server che corrisponde al certificato SSL o al certificato wildcard. Se il campo risulterà vuoto, accetterà connessioni TLS per qualsiasi dominio.",
+    "encryption_server_desc": "Se impostato, AdGuard Home rileva i ClientID, risponde alle query DDR ed esegue ulteriori convalide della connessione. Se non sono impostate, queste funzioni sono disabilitate. Deve corrispondere a uno dei nomi DNS nel certificato.",
     "encryption_redirect": "Reindirizza automaticamente a HTTPS",
     "encryption_redirect_desc": "Se selezionato, AdGuard Home ti reindirizzerà automaticamente da indirizzi HTTP a HTTPS.",
     "encryption_https": "Porta HTTPS",
diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json
index 168940a4..fed72b6d 100644
--- a/client/src/__locales/ja.json
+++ b/client/src/__locales/ja.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "上流DNSサーバを保存しました。",
     "dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています",
     "dns_test_not_ok_toast": "サーバ \"{{key}}\": 使用できませんでした。正しく入力されているかどうかを確認してください",
+    "dns_test_warning_toast": "アップストリーム\"{{key}}\"はテストリクエストに応答せず、正しく動作しない可能性があります。",
     "unblock": "ブロック解除",
     "block": "ブロック",
     "disallow_this_client": "このクライアントを拒否する",
@@ -361,9 +362,9 @@
     "encryption_title": "暗号化",
     "encryption_desc": "DNSと管理者ウェブインターフェースの両方に対する暗号化(HTTPS/QUIC/TLS)サポート。",
     "encryption_config_saved": "暗号化構成が保存されました。",
-    "encryption_server": "サーバ名",
+    "encryption_server": "サーバー名",
     "encryption_server_enter": "ドメイン名を入力してください",
-    "encryption_server_desc": "HTTPSを使用するには、SSL証明書またはワイルドカード証明書と一致するサーバー名を入力する必要があります。このフィールドが設定されていない場合は、任意のドメインのTLS接続を受け入れます。",
+    "encryption_server_desc": "こちらでサーバー名を設定すると、AdGuard HomeはClientIDを検出し、DDRクエリに応答し、追加の接続検証を実行します。設定されていない場合、これらの機能は無効になります。※証明書のDNS名のいずれかに一致する必要があります。",
     "encryption_redirect": "HTTPSに自動的にリダイレクト",
     "encryption_redirect_desc": "チェックすると、AdGuard Homeは自動的にHTTPからHTTPSアドレスへリダイレクトします。",
     "encryption_https": "HTTPS ポート",
diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json
index 9c656c9e..62080721 100644
--- a/client/src/__locales/ko.json
+++ b/client/src/__locales/ko.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "업스트림 서버가 성공적으로 저장되었습니다",
     "dns_test_ok_toast": "특정 DNS 서버들은 정상적으로 동작 중입니다",
     "dns_test_not_ok_toast": "서버 \"{{key}}\": 사용할 수 없습니다, 제대로 작성했는지 확인하세요.",
+    "dns_test_warning_toast": "업스트림 '{{key}}'이(가) 테스트 요청에 응답하지 않으며 제대로 작동하지 않을 수 있습니다",
     "unblock": "차단 해제",
     "block": "차단",
     "disallow_this_client": "클라이언트 거부",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "암호화 구성이 저장되었습니다",
     "encryption_server": "서버 이름",
     "encryption_server_enter": "도메인 이름을 입력하세요.",
-    "encryption_server_desc": "HTTPS를 사용하려면 SSL 인증서와 일치하는 서버 이름을 입력해야 합니다.",
+    "encryption_server_desc": "설정된 경우 AdGuard Home은 ClientID를 감지하고 DDR 쿼리에 응답하고 추가 연결 유효성 검사를 수행합니다. 설정하지 않으면 이러한 기능이 비활성화됩니다. 인증서의 DNS 이름 중 하나와 일치해야 합니다.",
     "encryption_redirect": "HTTPS로 자동 리디렉션",
     "encryption_redirect_desc": "상자를 체크하면 AdGuard Home 자동으로 사용자를 HTTP에서 HTTPS 주소로 리디렉션합니다.",
     "encryption_https": "HTTP 포트",
diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json
index efef1003..408dc561 100644
--- a/client/src/__locales/nl.json
+++ b/client/src/__locales/nl.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Upstream-servers succesvol opgeslagen",
     "dns_test_ok_toast": "Opgegeven DNS-servers werken correct",
     "dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of je het correct hebt geschreven",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" reageert niet op testverzoeken en werkt mogelijk niet goed",
     "unblock": "Deblokkeren",
     "block": "Blokkeren",
     "disallow_this_client": "Toepassing/systeem niet toelaten",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Versleuteling configuratie opgeslagen",
     "encryption_server": "Server naam",
     "encryption_server_enter": "Voer domein naam in",
-    "encryption_server_desc": "Om HTTPS te gebruiken, moet je de servernaam invoeren die overeenkomt met je SSL-certificaat of jokerteken-certificaat. Als het veld niet is ingesteld, accepteert het TLS-verbindingen voor elk domein.",
+    "encryption_server_desc": "Indien ingesteld, detecteert AdGuard Home Client-ID's, reageert op DDR-zoekopdrachten en voert aanvullende verbindingsvalidaties uit. Indien niet ingesteld, zijn deze functies uitgeschakeld. Moet overeenkomen met een van de DNS-namen in het certificaat.",
     "encryption_redirect": "Herleid automatisch naar HTTPS",
     "encryption_redirect_desc": "Indien ingeschakeld, zal AdGuard Home je automatisch herleiden van HTTP naar HTTPS.",
     "encryption_https": "HTTPS poort",
diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json
index 665df717..7ff4664f 100644
--- a/client/src/__locales/no.json
+++ b/client/src/__locales/no.json
@@ -347,7 +347,7 @@
     "encryption_config_saved": "Krypteringsoppsettet ble lagret",
     "encryption_server": "Tjenerens navn",
     "encryption_server_enter": "Skriv inn domenenavnet ditt",
-    "encryption_server_desc": "For å kunne bruke HTTPS, må du skrive inn tjenernavnet som samsvarer med ditt SSL-sertifikat eller jokertegnsertifikat. Hvis feltet er tomt, vil den akseptere TLS-tilkoblinger til ethvert domene.",
+    "encryption_server_desc": "Hvis angitt, oppdager AdGuard Home klient-IDer, svarer på DDR-spørringer og utfører ytterligere tilkoblingsvalideringer. Hvis ikke angitt, er disse funksjonene deaktivert. Må samsvare med ett av DNS-navnene i sertifikatet.",
     "encryption_redirect": "Automatisk omdiriger til HTTPS",
     "encryption_redirect_desc": "Dersom dette er valgt, vil AdGuard Home automatisk omdirigere deg fra HTTP til HTTPS-adresser.",
     "encryption_https": "HTTPS-port",
diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json
index ae3b3848..65be442a 100644
--- a/client/src/__locales/pl.json
+++ b/client/src/__locales/pl.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane",
     "dns_test_ok_toast": "Określone serwery DNS działają poprawnie",
     "dns_test_not_ok_toast": "Serwer \"{{key}}\": nie można go użyć, sprawdź, czy napisałeś go poprawnie",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" nie odpowiada na zapytania testowe i może nie działać prawidłowo",
     "unblock": "Odblokuj",
     "block": "Zablokuj",
     "disallow_this_client": "Odrzuć tego klienta",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Konfiguracja szyfrowania została zapisana",
     "encryption_server": "Nazwa serwera",
     "encryption_server_enter": "Wpisz swoją nazwę domeny",
-    "encryption_server_desc": "Aby korzystać z protokołu HTTPS, musisz wprowadzić nazwę serwera, która jest zgodna z certyfikatem SSL lub certyfikatem typu wildcard. Jeśli pole nie jest ustawione, będzie akceptować połączenia TLS dla dowolnej domeny.",
+    "encryption_server_desc": "Jeśli jest ustawiony, AdGuard Home wykrywa ClientID, odpowiada na zapytania DDR i wykonuje dodatkowe walidacje połączeń. Jeśli nie jest ustawiony, funkcje te są wyłączone. Musi odpowiadać jednej z nazw DNS w certyfikacie.",
     "encryption_redirect": "Przekieruj automatycznie do HTTPS",
     "encryption_redirect_desc": "Jeśli zaznaczone, AdGuard Home automatycznie przekieruje Cię z adresów HTTP na HTTPS.",
     "encryption_https": "Port HTTPS",
diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json
index 27a30fa1..dce24636 100644
--- a/client/src/__locales/pt-br.json
+++ b/client/src/__locales/pt-br.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nome de servidor inválido",
     "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
     "form_error_positive": "Deve ser maior que 0",
+    "form_error_gateway_ip": "A concessão não pode ter o endereço IP do gateway",
     "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Deve ser inferior ao início do intervalo",
     "greater_range_start_error": "Deve ser maior que o início do intervalo",
@@ -221,6 +222,7 @@
     "updated_upstream_dns_toast": "Servidores DNS primário salvos com sucesso",
     "dns_test_ok_toast": "Os servidores DNS especificados estão funcionando corretamente",
     "dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se você escreveu corretamente",
+    "dns_test_warning_toast": "Servidor DNS primário \"{{key}}\" não responde aos Solicitações de teste e pode não funcionar corretamente",
     "unblock": "Desbloquear",
     "block": "Bloquear",
     "disallow_this_client": "Não permitir este cliente",
@@ -362,7 +364,7 @@
     "encryption_config_saved": "Configuração de criptografia salva",
     "encryption_server": "Nome do servidor",
     "encryption_server_enter": "Digite seu nome de domínio",
-    "encryption_server_desc": "Para usar HTTPS, você precisa inserir o nome do servidor que corresponda ao seu certificado SSL ou certificado curinga. Se o campo não estiver definido, ele aceitará conexões TLS para qualquer domínio.",
+    "encryption_server_desc": "Se definido, AdGuard Home detecta ClientIDs, responde a consultas DDR, e executa validações de ligações adicionais. Se não estiver definido, estas características são desactivadas. Devem corresponder a um dos Nomes DNS no certificado.",
     "encryption_redirect": "Redirecionar automaticamente para HTTPS",
     "encryption_redirect_desc": "Se marcado, o AdGuard Home irá redirecionar automaticamente os endereços HTTP para HTTPS.",
     "encryption_https": "Porta HTTPS",
diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json
index 7d1d6e7f..6003952e 100644
--- a/client/src/__locales/pt-pt.json
+++ b/client/src/__locales/pt-pt.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Nome de servidor inválido",
     "form_error_subnet": "A sub-rede \"{{cidr}}\" não contém o endereço IP \"{{ip}}\"",
     "form_error_positive": "Deve ser maior que 0",
+    "form_error_gateway_ip": "A concessão não pode ter o endereço IP do gateway",
     "out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Deve ser inferior ao início do intervalo",
     "greater_range_start_error": "Deve ser maior que o início do intervalo",
@@ -221,6 +222,7 @@
     "updated_upstream_dns_toast": "Servidores DNS primário guardados com sucesso",
     "dns_test_ok_toast": "Os servidores DNS especificados estão a funcionar corretamente",
     "dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se o escreveu corretamente",
+    "dns_test_warning_toast": "Servidor DNS primário \"{{key}}\" não responde aos solicitações de teste e pode não funcionar corretamente",
     "unblock": "Desbloquear",
     "block": "Bloquear",
     "disallow_this_client": "Não permitir este cliente",
@@ -362,7 +364,7 @@
     "encryption_config_saved": "Definição de criptografia guardada",
     "encryption_server": "Nome do servidor",
     "encryption_server_enter": "Insira o seu nome de domínio",
-    "encryption_server_desc": "Para usar HTTPS, você precisa inserir o nome do servidor que corresponda ao seu certificado SSL ou certificado curinga. Se o campo não estiver definido, ele aceitará conexões TLS para qualquer domínio.",
+    "encryption_server_desc": "Se definido, AdGuard Home detecta ClientIDs, responde a consultas DDR, e executa validações de ligações adicionais. Se não estiver definido, estas características são desactivadas. Devem corresponder a um dos Nomes DNS no certificado.",
     "encryption_redirect": "Redirecionar automaticamente para HTTPS",
     "encryption_redirect_desc": "Se marcado, o AdGuard Home irá redirecionar automaticamente os endereços HTTP para HTTPS.",
     "encryption_https": "Porta HTTPS",
diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json
index 21fccbc5..e0bf4fd0 100644
--- a/client/src/__locales/ro.json
+++ b/client/src/__locales/ro.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Serverele din amonte au fost salvate cu succes",
     "dns_test_ok_toast": "Serverele DNS specificate funcționează corect",
     "dns_test_not_ok_toast": "Serverul \"{{key}}\": nu a putut fi utilizat, verificați dacă l-ați scris corect",
+    "dns_test_warning_toast": "„{{key}}” în amonte nu răspunde la solicitările de testare și s-ar putea să nu funcționeze corect",
     "unblock": "Deblocați",
     "block": "Blocați",
     "disallow_this_client": "Nu permiteți acest client",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Configurația de criptare salvată",
     "encryption_server": "Nume de server",
     "encryption_server_enter": "Introduceți numele domeniului",
-    "encryption_server_desc": "Pentru a utiliza HTTPS, trebuie să introduceți numele serverului care se potrivește cu certificatul SSL sau certificatul wildcard al dvs. În cazul în care câmpul nu este setat, va accepta conexiuni TLS pentru orice domeniu.",
+    "encryption_server_desc": "Dacă este setat, AdGuard Home detectează ID-urile de client, răspunde la interogările DDR și efectuează validări suplimentare ale conexiunii. Dacă nu este setat, aceste caracteristici sunt dezactivate. Trebuie să corespundă cu unul dintre numele DNS din certificat.",
     "encryption_redirect": "Redirecționați automat la HTTPS",
     "encryption_redirect_desc": "Dacă este bifat, AdGuard Home vă va redirecționa automat de la adrese HTTP la HTTPS.",
     "encryption_https": "Port HTTPS",
diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json
index 8c54c24a..6a1b6d66 100644
--- a/client/src/__locales/ru.json
+++ b/client/src/__locales/ru.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "DNS-серверы успешно обновлены",
     "dns_test_ok_toast": "Указанные серверы DNS работают корректно",
     "dns_test_not_ok_toast": "Сервер «{{key}}»: невозможно использовать, проверьте правильность написания",
+    "dns_test_warning_toast": "Upstream «{{key}}» не отвечает на тестовые запросы и может работать некорректно",
     "unblock": "Разблокировать",
     "block": "Заблокировать",
     "disallow_this_client": "Запретить доступ клиенту",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Настройки шифрования сохранены",
     "encryption_server": "Имя сервера",
     "encryption_server_enter": "Введите ваше доменное имя",
-    "encryption_server_desc": "Для использования HTTPS вам необходимо ввести имя сервера, которое подходит вашему SSL-сертификату или сертификату с поддержкой поддоменов. Если это поле не задано, сервер будет принимать TLS-соединения для любого домена.",
+    "encryption_server_desc": "Если задано, AdGuard Home распознаёт ClientID, отвечает на DDR-запросы, и дополнительно проверяет соединения. Если не задано, этот функционал отключён. Должно соответствовать одному из параметров DNS Names в сертификате.",
     "encryption_redirect": "Автоматически перенаправлять на HTTPS",
     "encryption_redirect_desc": "Если включено, AdGuard Home будет автоматически перенаправлять вас с HTTP на HTTPS адрес.",
     "encryption_https": "Порт HTTPS",
diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json
index 2a2c1ab6..17802771 100644
--- a/client/src/__locales/si-lk.json
+++ b/client/src/__locales/si-lk.json
@@ -5,10 +5,11 @@
     "load_balancing": "ධාරිතාව තුලනය",
     "local_ptr_title": "පෞද්ගලික ප්‍රතිවර්ත ව.නා.ප. සේවාදායක",
     "local_ptr_desc": "ස්ථානීය PTR විමසුම් සඳහා ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන ව.නා.ප. සේවාදායක. මෙම සේවාදායක පුද්ගලික අ.ජා.කෙ. ලිපින සහිත අනුග්‍රාහකවල සත්කාරක නාම විසඳීමට භාවිතා කරයි, උදාහරණයක් ලෙස ප්‍රතිවර්ත ව.නා.ප. භාවිතයෙන් \"192.168.12.34\". නැති නම්, ඇඩ්ගාර්ඩ් හෝම් හි ලිපින සඳහා හැරුනු විට ඔබගේ මෙහෙයුම් පද්ධතියේ පෙරනිමි ව.නා.ප. විසදුම්වල ලිපින භාවිතා කරයි.",
-    "local_ptr_default_resolver": "පෙරනිමි ලෙස, ඇඩ්ගාර්ඩ් හෝම් පහත ප්‍රතිවර්තත ව.නා.ප. විසඳුම් භාවිතා කරයි: {{ip}}.",
-    "local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්‍රතිවර්ත ව.නා.ප. විසඳුම් නිශ්චය කරගත නොහැකි විය.",
+    "local_ptr_default_resolver": "පෙරනිමි ලෙස, ඇඩ්ගාර්ඩ් හෝම් පහත ප්‍රතිවර්තත ව.නා.ප. පිළිවිසඳු භාවිතා කරයි: {{ip}}.",
+    "local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු නිශ්චය කරගත නොහැකි විය.",
     "local_ptr_placeholder": "පේළියකට එක් සේවාදායක ලිපිනය බැගින් යොදන්න",
     "resolve_clients_title": "අනුග්‍රාහකවල අ.ජා.කෙ. ලිපින ප්‍රතිවර්ත විසඳීම සබල කරන්න",
+    "use_private_ptr_resolvers_title": "පෞද්. ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතය",
     "check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක සඳහා පරීක්‍ෂා කරන්න",
     "save_config": "වින්‍යාසය සුරකින්න",
     "enabled_dhcp": "ග.ධා.වි.කෙ. සේවාදායකය සබල කෙරිණි",
@@ -16,9 +17,14 @@
     "unavailable_dhcp": "ග.ධා.වි.කෙ. නැත",
     "unavailable_dhcp_desc": "ඇඩ්ගාර්ඩ් හෝම් හට ඔබගේ මෙහෙයුම් පද්ධතියේ ග.ධා.වි.කෙ. සේවාදායකයක් ධාවනය කිරීමට නොහැකිය",
     "dhcp_title": "ග.ධා.වි.කෙ. සේවාදායකය (පර්යේෂණාත්මක!)",
-    "dhcp_description": "ඔබගේ මාර්ගකාරකය ග.ධා.වි.කෙ. (DHCP) සැකසුම් ලබා නොදෙන්නේ නම්, ඔබට ඇඩ්ගාර්ඩ් හි ඇති ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කළ හැකිය.",
+    "dhcp_description": "ඔබගේ මාර්ගකාරකය ග.ධා.වි.කෙ. (DHCP) සැකසුම් ලබා නොදෙන්නේ නම්, ඔබට ඇඩ්ගාර්ඩ් හි තිළෑලි ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කළ හැකිය.",
     "dhcp_enable": "ග.ධා.වි.කෙ. සේවාදායකය සබල කරන්න",
     "dhcp_disable": "ග.ධා.වි.කෙ. සේවාදායකය අබල කරන්න",
+    "dhcp_not_found": "ඇඩ්ගාර්ඩ් හෝම් සඳහා ජාලයෙහි කිසිදු ක්‍රියාත්මක ග.ධා.වි.කෙ. සේවාදායකයක් හමු නොවූ නිසා තිළෑලි සේවාදායකය සබල කිරීම ආරක්‍ෂිත වේ. කෙසේ වෙතත්, ස්වයංක්‍රීය ඒෂණය ඉතා නිවැරදි නොවිය හැකි බැවින් ඔබ එය අතින් නැවත පරීක්‍ෂා කළ යුතුය.",
+    "dhcp_found": "ක්‍රියාත්මක ග.ධා.වි.කෙ සේවාදායකයක් ජාලය තුළ හමු විය. තිළෑලි ග.ධා.වි.කෙ සේවාදායකය සබල කිරීම ආරක්‍ෂිත නොවේ.",
+    "dhcp_leases": "ග.ධා.වි.කෙ. කල්පැවරීම",
+    "dhcp_static_leases": "ස්ථිර ග.ධා.වි.කෙ. කල්පැවරීම",
+    "dhcp_leases_not_found": "ග.ධා.වි.කෙ. කල්පැවරීම් නැත",
     "dhcp_config_saved": "ග.ධා.වි.කෙ. වින්‍යාසය සාර්ථකව සුරකින ලදි",
     "dhcp_ipv4_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 4 සැකසුම්",
     "dhcp_ipv6_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 6 සැකසුම්",
@@ -31,6 +37,7 @@
     "form_error_mac_format": "මා.ප්‍ර.පා. ලිපිනය වලංගු නොවේ",
     "form_error_client_id_format": "අනුග්‍රාහකයේ හැඳු. වලංගු නොවේ",
     "form_error_server_name": "සේවාදායකයේ නම වලංගු නොවේ",
+    "form_error_subnet": "\"{{cidr}}\" අනුජාලය හි \"{{ip}}\" අ.ජා.කෙ. ලිපිනය අඩංගු නොවේ",
     "form_error_positive": "0 ට වඩා වැඩි විය යුතුය",
     "out_of_range_error": "\"{{start}}\"-\"{{end}}\" පරාසයෙන් පිට විය යුතුය",
     "lower_range_start_error": "පරාසය ආරම්භයට වඩා අඩු විය යුතුය",
@@ -40,6 +47,8 @@
     "dhcp_form_range_title": "අ.ජා. කෙ. (IP) ලිපින පරාසය",
     "dhcp_form_range_start": "පරාසය ආරම්භය",
     "dhcp_form_range_end": "පරාසය අවසානය",
+    "dhcp_form_lease_title": "ග.ධා.වි.කෙ. කල්පැවරීම (තත්. වලින්)",
+    "dhcp_form_lease_input": "කල්පැවරීමේ පරාසය",
     "dhcp_interface_select": "ග.ධා.වි.කෙ. අතුරුමුහුණත තෝරන්න",
     "dhcp_hardware_address": "දෘඩාංග ලිපිනය",
     "dhcp_ip_addresses": "අ.ජා.කෙ. (IP) ලිපින",
@@ -50,6 +59,14 @@
     "dhcp_error": "ජාලයේ තවත් ක්‍රියාත්මක ග.ධා.වි.කෙ. සේවාදායකයක් තිබේද යන්න නිශ්චය කළ නොහැකි විය",
     "dhcp_static_ip_error": "ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් සැකසිය යුතුය. මෙම ජාල අතුරුමුහුණත ස්ථිතික අ.ජා. කෙ. ලිපිනයක් භාවිතයෙන් වින්‍යාසගත කර තිබේද යන්න තීරණය කිරීමට ඇඩ්ගාර්ඩ් හෝම් අසමත් විය. කරුණාකර ස්ථිතික අ.ජා. කෙ. ලිපිනයක් අතින් සකසන්න.",
     "dhcp_dynamic_ip_found": "ඔබගේ පද්ධතිය <0>{{interfaceName}}</0> අතුරු මුහුණත සඳහා ගතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපින වින්‍යාසය භාවිතා කරයි. ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අ.ජා. කෙ. ලිපිනයක් සැකසිය යුතුය. ඔබගේ වර්තමාන අ.ජා. කෙ. ලිපිනය <0>{{ipAddress}}</0> වේ. ඔබ \"ග.ධා.වි.කෙ. සබල කරන්න\" බොත්තම එබුවහොත් ඇඩ්ගාර්ඩ් හෝම් ස්වයංක්‍රීයව මෙම අ.ජා. කෙ. ලිපිනය ස්ථිතික ලෙස සකසනු ඇත.",
+    "dhcp_lease_added": "\"{{key}}\" ස්ථිර කල්පැවරීම එකතු කෙරිණි",
+    "dhcp_lease_deleted": "\"{{key}}\" ස්ථිර කල්පැවරීම මකා දැමිණි",
+    "dhcp_new_static_lease": "නව ස්ථිර කල්පැවරීම",
+    "dhcp_static_leases_not_found": "ග.ධා.වි.කෙ. ස්ථිර කල්පැවරීම් නැත",
+    "dhcp_add_static_lease": "ස්ථිර කල්පැවරීමක් යොදන්න",
+    "dhcp_reset_leases": "කල්පැවරීම් යළි සකසන්න",
+    "dhcp_reset_leases_confirm": "සියළු කල්පැවරීම් යළි සැකසීමට වුවමනා ද?",
+    "dhcp_reset_leases_success": "ග.ධා.වි.කෙ. කල්පැවරීම් යළි සැකසිණි",
     "dhcp_reset": "ග.ධා.වි.කෙ. වින්‍යාසය යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
     "country": "රට",
     "city": "නගරය",
@@ -112,6 +129,7 @@
     "block_domain_use_filters_and_hosts": "පෙරහන් සහ ධාරක ගොනු භාවිතා කරමින් වසම් අවහිර කරන්න",
     "filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
     "use_adguard_browsing_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව භාවිතා කරන්න",
+    "use_adguard_browsing_sec_hint": "ඇඩ්ගාර්ඩ් හෝම් විසින් පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මගින් වසම අවහිර කර ඇත්දැයි පරීක්‍ෂා කරයි. එය සිදු කිරීමට රහස්‍යතා-හිතකාමී බැලීමේ යෙ.ක්‍ර.මු. භාවිතා කෙරේ: වසමේ කෙටි උපසර්ගයක SHA256 පූරකයක් පමණක් සේවාදායකය වෙත යවනු ලැබේ.",
     "use_adguard_parental": "ඇඩ්ගාර්ඩ් දෙමාපිය පාලන වියමන සේවාව භාවිතා කරන්න",
     "use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.",
     "enforce_safe_search": "ආරක්‍ෂිත සෙවුම භාවිතා කරන්න",
@@ -179,13 +197,13 @@
     "example_comment_hash": "# එසේම අදහස් දැක්වීමක්.",
     "example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැළපෙන වසම් වෙත ප්‍රවේශය අවහිර කරයි.",
     "example_upstream_regular": "සාමාන්‍ය ව.නා.ප. (UDP හරහා);",
-    "example_upstream_udp": "සාමාන්‍ය ව.නා.ප. (UDP, සත්කාරක නම හරහා);",
-    "example_upstream_dot": "සංකේතිත <0>DNS-over-TLS</0>",
-    "example_upstream_doh": "සංකේතිත <0>DNS-over-HTTPS</0>",
-    "example_upstream_doq": "සංකේතිත <0>DNS-over-QUIC</0>;",
-    "example_upstream_sdns": "<1>DNSCrypt</1> හෝ <2>HTTPS-හරහා-ව.නා.ප.</2> විසඳුම් සඳහා <0>ව.නා.ප. මුද්දර</0>;",
+    "example_upstream_udp": "සාමාන්‍ය ව.නා.ප. (UDP, සත්කාරක-නම හරහා);",
+    "example_upstream_dot": "සංකේතිත <0>TLS-මගින්-ව.නා.ප.</0>;",
+    "example_upstream_doh": "සංකේතිත <0>HTTPS-මගින්-ව.නා.ප.</0>;",
+    "example_upstream_doq": "සංකේතිත <0>QUIC-මගින්-ව.නා.ප.</0>;",
+    "example_upstream_sdns": "<1>DNSCrypt</1> හෝ <2>HTTPS-මගින්-ව.නා.ප.</2> පිළිවිසඳු සඳහා <0>ව.නා.ප. මුද්දර</0>;",
     "example_upstream_tcp": "සාමාන්‍ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා);",
-    "example_upstream_tcp_hostname": "සාමාන්‍ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක නම හරහා);",
+    "example_upstream_tcp_hostname": "සාමාන්‍ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක-නම හරහා);",
     "all_lists_up_to_date_toast": "සියළුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි",
     "dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායක නිවැරදිව ක්‍රියා කරයි",
     "dns_test_not_ok_toast": "\"{{key}}\" සේවාදායක(ය): භාවිතා කිරීමට නොහැකි විය, ඔබ එය නිවැරදිව ලියා ඇතිදැයි පරීක්‍ෂා කරන්න",
@@ -242,14 +260,14 @@
     "blocking_ipv4": "අ.ජා.කෙ.4 අවහිර කිරීම",
     "blocking_ipv6": "අ.ජා.කෙ.6 අවහිර කිරීම",
     "dnscrypt": "DNSCrypt",
-    "dns_over_https": "HTTPS-හරහා-ව.නා.ප.",
-    "dns_over_tls": "TLS-හරහා-ව.නා.ප.",
-    "dns_over_quic": "QUIC-හරහා-ව.නා.ප.",
+    "dns_over_https": "HTTPS-මගින්-ව.නා.ප.",
+    "dns_over_tls": "TLS-මගින්-ව.නා.ප.",
+    "dns_over_quic": "QUIC-මගින්-ව.නා.ප.",
     "client_id": "අනුග්‍රාහකයේ හැඳු.",
     "client_id_placeholder": "අනුග්‍රාහකයක හැඳු. යොදන්න",
     "client_id_desc": "අනුග්‍රාහක හැඳු. මගින් අනුග්‍රාහක හඳුනාගත හැකිය. කෙසේදැයි <a>මෙතැනින්</a> දැන ගන්න.",
-    "download_mobileconfig_doh": "HTTPS-හරහා-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න",
-    "download_mobileconfig_dot": "TLS-හරහා-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න",
+    "download_mobileconfig_doh": "HTTPS-මගින්-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න",
+    "download_mobileconfig_dot": "TLS-මගින්-ව.නා.ප. සඳහා .ජංගමවින්‍යාසය බාගන්න",
     "download_mobileconfig": "වින්‍යාසගත ගොනුව බාගන්න",
     "plain_dns": "සරල ව.නා.ප.",
     "form_enter_rate_limit": "අනුපාත සීමාව ඇතුල් කරන්න",
@@ -295,12 +313,13 @@
     "install_submit_title": "සුභ පැතුම්!",
     "install_submit_desc": "පිහිටුවීමේ ක්‍රියා පටිපාටිය අවසන් වී ඇති අතර ඔබ දැන් ඇඩ්ගාර්ඩ් හෝම් භාවිතය ආරම්භ කිරීමට සූදානම්ය.",
     "install_devices_router": "මාර්ගකාරකය",
-    "install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධ සියළුම උපාංග ස්වයංක්‍රීයව ආවරණය කරන අතර ඔබට ඒ සෑම එකක්ම අතින් වින්‍යාසගත කිරීමට අවශ්‍ය නොවේ.",
+    "install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධිත සියළුම උපාංග ස්වයංක්‍රීයව ආවරණය කරන අතර ඔබට ඒ සෑම එකක්ම අතින් වින්‍යාසගත කිරීමට අවශ්‍ය නොවේ.",
     "install_devices_address": "ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකය පහත ලිපිනයන්ට සවන් දෙමින් පවතී",
     "install_devices_router_list_1": "ඔබගේ මාර්ගකාරකය සඳහා වූ මනාපයන් විවෘත කරන්න. සාමාන්‍යයෙන්, එය ඔබගේ අතිරික්සුවෙන් ඒ.ස.නි.(URL) ක් හරහා (http://192.168.0.1/ හෝ http://192.168.1.1/ වැනි) ප්‍රවේශ විය හැකිය. මුරපදය ඇතුල් කිරීමට සිදු විය හැකි නමුත් එය මතක නැතිනම් බොහෝ විට මාර්ගකාරකයේ බොත්තමක් එබීමෙන් මුරපදය නැවත සැකසිය හැකිය. නමුත් මෙම ක්‍රියා පටිපාටිය තෝරා ගන්නේ නම්, බොහෝ විට ඔබගේ මාර්ගකාරකයේ සමස්ථ වින්‍යාසය අහිමි වනු ඇති බව මතක තබා ගන්න.එය පිහිටුවීමට ඔබගේ මාර්ගකාරකයට යෙදුමක් ඇවැසි නම්, කරුණාකර එය ඔබගේ පරිගණකයේ හෝ දුරකථනයේ ස්ථාපනය කර මාර්ගකාරකයේ සැකසුම් වෙත ප්‍රවේශ වීමට භාවිතා කරන්න.",
     "install_devices_router_list_2": "ග.ධා.වි.කෙ. (DHCP)/ ව.නා.ප. (DNS) සැකසුම් සොයා ගන්න. අංක කට්ටල දෙකකට හෝ තුනකට ඉඩ දෙන ක්ෂේත්‍රයක් අසල ඇති ව.නා.ප. අකුරු බලන්න, සෑම එකක්ම ඉලක්කම් එකේ සිට තුන දක්වා කාණ්ඩ හතරකට බෙදා ඇත.",
     "install_devices_router_list_3": "ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින එහි ඇතුල් කරන්න.",
     "install_devices_router_list_4": "සමහර වර්ගයේ මාර්ගකාරක වල අභිරුචි ව.නා.ප. සේවාදායකයක් සැකසීමට නොහැකිය. මෙම අවස්ථාවේදී ඇඩ්ගාර්ඩ් හෝම් <0>ග.ධා.වි.කෙ. සේවාදායකයක්</0> ලෙස පිහිටුවන්නේ නම් එය උපකාර වනු ඇත. එසේ නැතිනම්, ඔබගේ විශේෂිත මාර්ගකාරකය සඳහා වූ ව.නා.ප. සේවාදායක රිසිකරණය කරන්නේ කෙසේද යන්න පිළිබඳ අත්පොත පරීක්‍ෂා කළ යුතුය.",
+    "install_devices_windows_list_1": "පාලන වට්ටෝරුව හෝ වින්ඩෝස් සෙවුම හරහා පාලන මඬල අරින්න.",
     "install_devices_windows_list_2": "ජාල සහ අන්තර්ජාල ප්‍රවර්ගයට ගොස් පසුව ජාල සහ බෙදාගැනීමේ මධ්‍යස්ථානය වෙත යන්න.",
     "install_devices_windows_list_3": "වම් තීරුවෙහි \"උපයුක්තක‌‌‌යෙහි සැකසුම් වෙනස් කිරීම\" ඔබන්න.",
     "install_devices_windows_list_4": "ඔබගේ ක්‍රියාකාරී සම්බන්ධතාවය මත දකුණු-ක්ලික් කර ගුණාංග තෝරන්න.",
@@ -310,8 +329,8 @@
     "install_devices_macos_list_2": "ජාලය මත ඔබන්න.",
     "install_devices_macos_list_3": "ඔබගේ ලැයිස්තුවේ පළමු සම්බන්ධතාවය තෝරා වැඩිදුර යන්න ඔබන්න.",
     "install_devices_macos_list_4": "ව.නා.ප. (DNS) තීරුව තෝරා ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින ඇතුල් කරන්න.",
-    "install_devices_android_list_1": "ඇන්ඩ්‍රොයිඩ් මෙනුවෙහි මුල් තිරයෙන්, සැකසීම් මත තට්ටු කරන්න.",
-    "install_devices_android_list_2": "මෙනුවේ වයි-ෆයි මත තට්ටු කරන්න. පවතින සියලුම ජාල ලැයිස්තුගත කර ඇති තිරය පෙන්වනු ඇත (ජංගම සම්බන්ධතාවය සඳහා අභිරුචි ව.නා.ප. සැකසිය නොහැක).",
+    "install_devices_android_list_1": "ඇන්ඩ්‍රොයිඩ් මුල් තිරයෙන්, සැකසුම් මත තට්ටු කරන්න.",
+    "install_devices_android_list_2": "වට්ටෝරුවෙහි වයි-ෆයි මත තට්ටු කරන්න. පවතින සියළුම ජාල ලේඛන ගතවී තිබෙන තිරය පෙන්වනු ඇත (ජංගම සම්බන්ධතාවය සඳහා අභිරුචි ව.නා.ප. සැකසීමට නොහැකිය).",
     "install_devices_android_list_3": "සම්බන්ධිත ජාලය මත දිගු වේලාවක් ඔබන්න, ඉන්පසුව ජාලය වෙනස් කිරීම මත තට්ටු කරන්න.",
     "install_devices_android_list_4": "ඔබට සමහර උපාංගවල සියළු සැකසුම් බැලීමට \"වැඩිදුර\" සඳහා වූ කොටුව සලකුණු කිරීමට අවශ්‍ය විය හැකිය. එමෙන්ම ඔබගේ ඇන්ඩ්‍රොයිඩ් ව.නා.ප. (DNS) සැකසුම් වෙනස් කිරීමට අ.ජා.කෙ. (IP) සැකසුම්, ග.ධා.වි.කෙ. (DHCP) සිට ස්ථිතික වෙත මාරු කළ යුතුය.",
     "install_devices_android_list_5": "ව.නා.ප. 1 සහ ව.නා.ප. 2 පිහිටුවීම් අගයන් ඔබගේ ඇඩ්ගාර්ඩ් හෝම් සේවාදායක ලිපින වලට වෙනස් කරන්න.",
@@ -328,22 +347,24 @@
     "encryption_config_saved": "සංකේතන වින්‍යාසය සුරකින ලදි",
     "encryption_server": "සේවාදායක‌‌‌‌යේ නම",
     "encryption_server_enter": "ඔබගේ වසම් නාමය ඇතුල් කරන්න",
+    "encryption_server_desc": "සැකසා ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් අනුග්‍රාහක හැඳුනුම් හඳුනා ගැනෙයි, සෘ.ද.ඉ. (DDR) විමසුම්වලට ප්‍රතිචාර දක්වයි, සහ අතිරේක සම්බන්ධතා වලංගුකරණය සිදු කරයි. නොඑසේ නම්, මෙම විශේෂාංග අබලව ඇත. සහතිකයේ තිබෙන ව.නා.ප. නම් වලින් එකකට ගැළපිය යුතුය.",
     "encryption_redirect": "ස්වයංක්‍රීයව HTTPS වෙත හරවා යවන්න",
     "encryption_redirect_desc": "සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් ඔබව ස්වයංක්‍රීයව HTTP සිට HTTPS ලිපින වෙත හරවා යවනු ඇත.",
     "encryption_https": "HTTPS තොට",
-    "encryption_https_desc": "HTTPS තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ප්‍රවේශ විය හැකි අතර එය '/dns-query' ස්ථානයේ HTTPS-හරහා-ව.නා.ප. ද ලබා දෙනු ඇත.",
-    "encryption_dot": "TLS-හරහා-ව.නා.ප. තොට",
-    "encryption_dot_desc": "මෙම තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා TLS-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත.",
-    "encryption_doq": "QUIC-හරහා-ව.නා.ප. තොට",
-    "encryption_doq_desc": "මෙම තොට වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම තොට හරහා QUIC-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත.",
+    "encryption_https_desc": "HTTPS තොට වින්‍යාසගත නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ප්‍රවේශ විය හැකි අතර එය '/dns-query' ස්ථානයේ HTTPS-මගින්-ව.නා.ප. ද ලබා දෙනු ඇත.",
+    "encryption_dot": "TLS-මගින්-ව.නා.ප. තොට",
+    "encryption_dot_desc": "මෙම තොට වින්‍යාසගත නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා TLS-මගින්-ව.නා.ප. සේවාදායකයක් ධාවනය කෙරේ.",
+    "encryption_doq": "QUIC-මගින්-ව.නා.ප. තොට",
+    "encryption_doq_desc": "මෙම තොට වින්‍යාසගත නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම තොට හරහා QUIC-මගින්-ව.නා.ප. සේවාදායකයක් ධාවනය කෙරේ.",
     "encryption_certificates": "සහතික",
+    "encryption_certificates_desc": "සංකේතනය භාවිතයට, ඔබගේ වසම සඳහා වලංගු SSL සහතික දාමයක් සැපයිය යුතුය. <0>{{link}}</0> වෙතින් නොමිලේ සහතිකයක් ලබා ගැනීමට හැකිය හෝ විශ්වාසදායක සහතික අධිකාරියකින් මිලදී ගන්න.",
     "encryption_certificates_input": "ඔබගේ PEM-කේතනය කළ සහතික පිටපත් කර මෙහි අලවන්න.",
     "encryption_status": "තත්වය",
     "encryption_expire": "කල් ඉකුත් වීම",
     "encryption_key": "පුද්ගලික යතුර",
     "encryption_key_input": "ඔබගේ සහතිකය සඳහා PEM-කේතනය කළ පුද්ගලික යතුර පිටපත් කර මෙහි අලවන්න.",
-    "encryption_enable": "සංකේතනය සබල කරන්න (HTTPS, DNS-over-HTTPS සහ DNS-over-TLS)",
-    "encryption_enable_desc": "සංකේතනය සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ක්‍රියා කරනු ඇති අතර ව.නා.ප. සේවාදායකය DNS-over-HTTPS සහ DNS-over-TLS හරහා ලැබෙන ඉල්ලීම් සඳහා සවන් දෙනු ඇත.",
+    "encryption_enable": "සංකේතනය සබල කරන්න (HTTPS, HTTPS-මගින්-ව.නා.ප. සහ TLS-මගින්-ව.නා.ප.)",
+    "encryption_enable_desc": "සංකේතනය සබල කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් පරිපාලක අතුරුමුහුණත HTTPS හරහා ක්‍රියා කරනු ඇති අතර ව.නා.ප. සේවාදායකය HTTPS-මගින්-ව.නා.ප. සහ TLS-මගින්-ව.නා.ප. හරහා ලැබෙන ඉල්ලීම් සඳහා සවන් දෙනු ඇත.",
     "encryption_chain_valid": "සහතික දාමය වලංගු ය",
     "encryption_chain_invalid": "සහතික දාමය වලංගු නොවේ",
     "encryption_key_valid": "මෙය වලංගු {{type}} පුද්ගලික යතුරකි",
@@ -383,6 +404,7 @@
     "client_edit": "අනුග්‍රාහකය සංස්කරණය",
     "client_identifier": "හඳුන්වනය",
     "ip_address": "අ.ජා.කෙ. ලිපිනය",
+    "client_identifier_desc": "අ.ජා.කෙ. (IP) ලිපින, අන.ජා. (CIDR), මා.ප්‍ර.පා. (MAC) ලිපින හෝ අනුග්‍රාහක හැඳුනුමක් (DoT/DoH/DoQ සඳහා භාවිතා කළ හැකිය) මගින් අනුග්‍රාහක හඳුනාගත හැකිය. අනුග්‍රාහක හඳුනා ගන්නේ කෙසේද යන්න පිළිබඳව <0>මෙතැනින්</0> තව දැනගන්න.",
     "form_enter_ip": "අ.ජා.කෙ. (IP) ඇතුල් කරන්න",
     "form_enter_subnet_ip": "\"{{cidr}}\" අනුජාලයෙහි අ.ජා.කෙ. ලිපිනයක් යොදන්න.",
     "form_enter_mac": "මා.ප්‍ර.පා. (MAC) යොදන්න",
@@ -401,25 +423,32 @@
     "access_title": "ප්‍රවේශවීමට සැකසුම්",
     "access_desc": "මෙහිදී ඔබට ඇඩ්ගාර්ඩ් හෝම් ව.නා.ප. සේවාදායකයට ප්‍රවේශ වී‌‌‌‌මේ නීති වින්‍යාසගත කළ හැකිය",
     "access_allowed_title": "ඉඩ ලත් අනුග්‍රාහකයින්",
-    "access_allowed_desc": "CIDR හෝ අ.ජා. කෙ. ලිපින ලැයිස්තුවක් වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අ.ජා. කෙ. ලිපින වලින් පමණක් ඉල්ලීම් පිළිගනු ඇත.",
+    "access_allowed_desc": "අන.ජා.(CIDR), අ.ජා.කෙ. ලිපින හෝ <a>අනුග්‍රාහක හැඳු.</a> ලේඛනයකි. මෙහි නිවේශිත ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අනුග්‍රාහක වලින් පමණක් ඉල්ලීම් පිළිගනු ඇත.",
     "access_disallowed_title": "නොඉඩ ලත් අනුග්‍රාහකයින්",
-    "access_disallowed_desc": "CIDR හෝ අ.ජා. කෙ. ලිපින ලැයිස්තුවක් වින්‍යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අ.ජා. කෙ. ලිපින වලින් ඉල්ලීම් අත්හරිනු ඇත.",
+    "access_disallowed_desc": "අන.ජා.(CIDR), අ.ජා.කෙ. ලිපින හෝ <a>අනුග්‍රාහක හැඳු.</a> ලේඛනයකි. මෙහි නිවේශිත ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අනුග්‍රාහක වලින් ඉල්ලීම් අත්හරිනු ඇත. ඉඩ ලත් අනුග්‍රාහකවල නිවේශිත තිබේ නම්, මෙම ක්‍ෂේත්‍රය නොසලකා හරිනු ඇත.",
     "access_blocked_title": "නොඉඩ ලත් වසම්",
     "access_settings_saved": "ප්‍රවේශ වීමේ සැකසුම් සාර්ථකව සුරකින ලදි",
     "updates_checked": "ඇඩ්ගාර්ඩ් හෝම් හි නව අනුවාදයක් තිබේ",
     "updates_version_equal": "ඇඩ්ගාර්ඩ් හෝම් යාවත්කාලීනයි",
     "check_updates_now": "දැන් යාවත්කාල පරීක්‍ෂා කරන්න",
     "dns_privacy": "ව.නා.ප. රහස්‍යතා",
+    "setup_dns_privacy_1": "<0>TLS-මගින්-ව.නා.ප.</0> සඳහා <1>{{address}}</1>.",
+    "setup_dns_privacy_2": "<0>HTTPS-මගින්-ව.නා.ප.</0> සඳහා <1>{{address}}</1>.",
     "setup_dns_privacy_3": "<0>මෙහි ඔබට භාවිතා කළ හැකි මෘදුකාංග ලැයිස්තුවක් ඇත.</0>",
-    "setup_dns_privacy_android_2": "<1>HTTPS-හරහා-ව.නා.ප.</1> සහ <1>TLS-හරහා-ව.නා.ප.</1> සඳහා <0>ඇන්ඩ්‍රොයිඩ් සඳහා ඇඩ්ගාර්ඩ්</0> සහාය දක්වයි.",
+    "setup_dns_privacy_android_1": "TLS-මගින්-ව.නා.ප සහාය සමගම ඇන්ඩ්‍රොයිඩ් 9 පැමිණේ. එය වින්‍යාස කිරීමට, සැකසුම් → ජාලය හා අන්තර්ජාලය → වැඩිදුර → පෞද්. ව.නා.ප. වෙත ගොස් එහි ඔබගේ වසමේ නම යොදන්න.",
+    "setup_dns_privacy_android_2": "<1>HTTPS-මගින්-ව.නා.ප.</1> හා <1>TLS-මගින්-ව.නා.ප.</1> සඳහා <0>ඇන්ඩ්‍රොයිඩ් සඳහා ඇඩ්ගාර්ඩ්</0> සහාය දක්වයි.",
+    "setup_dns_privacy_android_3": "<0>ඉන්ට්‍රා</0> විසින් <1>HTTPS-මගින්-ව.නා.ප</1> සහාය ඇන්ඩ්‍රොයිඩ් සඳහා එකතු කරයි.",
+    "setup_dns_privacy_ios_2": "<1>HTTPS-මගින්-ව.නා.ප.</1> හා <1>TLS-මගින්-ව.නා.ප.</1> සඳහා <0>අයිඕඑස් සඳහා ඇඩ්ගාර්ඩ්</0> සහාය දක්වයි.",
     "setup_dns_privacy_other_title": "වෙනත් ක්‍රියාවට නැංවූ දෑ",
-    "setup_dns_privacy_other_2": "<0>ඩීඑන්එස්ප්‍රොක්සි</0> දන්නා සියලුම ආරක්‍ෂිත ව.නා.ප. කෙටුම්පත් සඳහා සහාය දක්වයි.",
-    "setup_dns_privacy_other_4": "<1>DNS-over-HTTPS</1> සඳහා <0>මොසිල්ලා ෆයර්ෆොක්ස්</0> සහාය දක්වයි.",
+    "setup_dns_privacy_other_1": "ඇඩ්ගාර්ඩ් හෝම් මෘදුකාංගයට ඕනෑම වේදිකාවක ආරක්‍ෂිත ව.නා.ප. අනුග්‍රාහකයක් ලෙස ක්‍රියාත්මක වීමට ද හැකිය.",
+    "setup_dns_privacy_other_2": "<0>ව.නා.ප. ප්‍රතියුක්තය</0> දන්නා සියළුම ආරක්‍ෂිත ව.නා.ප. කෙටුම්පත් සඳහා සහාය දක්වයි.",
+    "setup_dns_privacy_other_3": "<1>HTTPS-මගින්-ව.නා.ප.</1> සඳහා <0>dnscrypt-ප්‍රතියුක්තය</0> සහාය දක්වයි.",
+    "setup_dns_privacy_other_4": "<1>HTTPS-මගින්-ව.නා.ප.</1> සඳහා <0>මොසිල්ලා ෆයර්ෆොක්ස්</0> සහාය දක්වයි.",
     "setup_dns_privacy_other_5": "<0>මෙහි</0> සහ <1>මෙහි</1> තවත් ක්‍රියාවට නැංවූ දෑ ඔබට හමුවනු ඇත.",
     "setup_dns_privacy_ioc_mac": "අයිඕඑස් සහ මැක්ඕඑස් වින්‍යාසය",
-    "setup_dns_notice": "ඔබට <1>DNS-over-HTTPS</1> හෝ <1>DNS-over-TLS</1> භාවිතයට ඇඩ්ගාර්ඩ් හෝම් සැකසුම් තුළ <0>සංකේතනය වින්‍යාසගත</0> කිරීමට ඇවැසිය.",
+    "setup_dns_notice": "ඔබට <1>HTTPS-මගින්-ව.නා.ප.</1> හෝ <1>DNS-මගින්-ව.නා.ප.</1> භාවිතයට ඇඩ්ගාර්ඩ් හෝම් සැකසුම් තුළ <0>සංකේතනය වින්‍යාසගත</0> කළ යුතුය.",
     "rewrite_added": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම සාර්ථකව එකතු කෙරිණි",
-    "rewrite_deleted": "\"{{key}}\" සඳහා ව. නා. ප. නැවත ලිවීම සාර්ථකව ඉවත් කෙරිණි",
+    "rewrite_deleted": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කෙරිණි",
     "rewrite_add": "ව.නා.ප. නැවත ලිවීමක් එකතු කරන්න",
     "rewrite_not_found": "ව.නා.ප. නැවත ලිවීම් හමු නොවිණි",
     "rewrite_confirm_delete": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?",
@@ -462,7 +491,7 @@
     "statistics_retention": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම",
     "statistics_retention_desc": "ඔබ කාල පරතරය අඩු කළහොත් සමහර දත්ත නැති වනු ඇත",
     "statistics_clear": "සංඛ්‍යාලේඛන හිස් කරන්න",
-    "statistics_clear_confirm": "සංඛ්‍යාලේඛන ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
+    "statistics_clear_confirm": "සංඛ්‍යාලේඛන ඉවත් කිරීමට වුවමනා ද?",
     "statistics_retention_confirm": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
     "statistics_cleared": "සංඛ්‍යාලේඛන සාර්ථකව ඉවත් කෙරිණි",
     "statistics_enable": "සංඛ්‍යාලේඛන සබල කරන්න",
@@ -531,6 +560,7 @@
     "list_updated": "ලැයිස්තු {{count}} ක් යාවත්කාල කෙරිණි",
     "list_updated_plural": "ලැයිස්තු {{count}} ක් යාවත්කාල කෙරිණි",
     "dnssec_enable": "DNSSEC සබල කරන්න",
+    "validated_with_dnssec": "DNSSEC සමඟ වලංගු කෙරිණි",
     "all_queries": "සියළුම විමසුම්",
     "show_blocked_responses": "අවහිර කර ඇත",
     "show_whitelisted_responses": "ඉඩ දී ඇත",
diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json
index 2db7efeb..b4fa2955 100644
--- a/client/src/__locales/sk.json
+++ b/client/src/__locales/sk.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Upstream servery boli úspešne uložené",
     "dns_test_ok_toast": "Špecifikované DNS servery pracujú korektne",
     "dns_test_not_ok_toast": "Server \"{{key}}\": nemohol byť použitý, skontrolujte, či ste ho správne napísali",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" neodpovedá na testovacie dopyty a nemusí fungovať správne",
     "unblock": "Odblokovať",
     "block": "Blokovať",
     "disallow_this_client": "Zablokovať tohto klienta",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Konfigurácia šifrovania uložená",
     "encryption_server": "Meno servera",
     "encryption_server_enter": "Zadajte meno Vašej domény",
-    "encryption_server_desc": "Ak chcete používať protokol HTTPS, musíte zadať názov servera, ktorý zodpovedá Vášmu certifikátu SSL alebo certifikátu so zástupnými znakmi. Ak pole nie je nastavené, bude akceptovať TLS pripojenia pre ľubovoľnú doménu.",
+    "encryption_server_desc": "Ak je nastavené, AdGuard Home zisťuje ClientID, odpovedá na dotazy DDR a vykonáva ďalšie overenia pripojenia. Ak nie je nastavená, tieto funkcie sú vypnuté. Musí sa zhodovať s jedným z názvov DNS v certifikáte.",
     "encryption_redirect": "Automaticky presmerovať na HTTPS",
     "encryption_redirect_desc": "Ak je táto možnosť začiarknutá, služba AdGuard Home Vás automaticky presmeruje z adresy HTTP na adresy HTTPS.",
     "encryption_https": "HTTPS port",
diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json
index 7cb8a97a..1e89cc66 100644
--- a/client/src/__locales/sl.json
+++ b/client/src/__locales/sl.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Gorvodni trežniki so uspešno shranjeni",
     "dns_test_ok_toast": "Navedeni strežniki DNS delujejo pravilno",
     "dns_test_not_ok_toast": "Ni mogoče uporabiti: strežnika \"{{key}}\". Preverite, ali ste ga pravilno napisali",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" se ne odziva na testne zahteve in morda ne deluje pravilno",
     "unblock": "Omogoči",
     "block": "Onemogoči",
     "disallow_this_client": "Onemogoči tega odjemalca",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Nastavitve šifriranja so shranjene",
     "encryption_server": "Ime strežnika",
     "encryption_server_enter": "Vnesite ime vaše domene",
-    "encryption_server_desc": "Za uporabo HTTPS morate vnesti ime strežnika, ki se ujema z vašim digitalnim certifikatom SSL.\n",
+    "encryption_server_desc": "Če je nastavljeno, AdGuard Home zazna ClientID-je, odgovori na poizvedbe DDR in izvede dodatna preverjanja povezave. Če ni nastavljeno, so te funkcije onemogočene. Ujemati se mora z enim od imen DNS v potrdilu.",
     "encryption_redirect": "Samodejno preusmeri na HTTPS",
     "encryption_redirect_desc": "Če je označeno, vas bo AdGuard Home samodejno preusmeril iz naslovov HTTP na naslove HTTPS.",
     "encryption_https": "Vrata HTTPS",
diff --git a/client/src/__locales/sr-cs.json b/client/src/__locales/sr-cs.json
index d39a9af5..c42b57ad 100644
--- a/client/src/__locales/sr-cs.json
+++ b/client/src/__locales/sr-cs.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Upstream serveri su uspešno sačuvani",
     "dns_test_ok_toast": "Dati DNS serveri rade ispravno",
     "dns_test_not_ok_toast": "Server \"{{key}}\": se ne može koristiti. Proverite da li ste ga ispravno uneli",
+    "dns_test_warning_toast": "Apstrim \"{{key}}\" ne odgovara na zahteve za testiranje i možda neće raditi kako treba",
     "unblock": "Odblokiraj",
     "block": "Blokiraj",
     "disallow_this_client": "Zabrani ovaj klijent",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Konfiguracija šifrovanja je sačuvana",
     "encryption_server": "Ime servera",
     "encryption_server_enter": "Unesite vaše ime domena",
-    "encryption_server_desc": "Da biste koristili HTTPS, potrebno je da unesete ime servera koje se podudara sa SSL certifikatom ili džoker certifikatom. Ako polje nije postavljeno, prihvatiće TLS veze za bilo koji domen.",
+    "encryption_server_desc": "Ako je podešen, AdGuard Home otkriva ID-ove klijenta, odgovara na DDR upite i izvršava dodatne provere valjanosti veze. Ako se ne postave, ove funkcije su onemogućene. Mora se podudarati sa DNS imenima u certifikatu.",
     "encryption_redirect": "Automatski preusmeri na HTTPS",
     "encryption_redirect_desc": "Ako je označeno, AdGuard Home će vas automatski preusmeravati sa HTTP na HTTPS adrese.",
     "encryption_https": "HTTPS port",
diff --git a/client/src/__locales/sv.json b/client/src/__locales/sv.json
index 71b8ae6e..5fc3a4cc 100644
--- a/client/src/__locales/sv.json
+++ b/client/src/__locales/sv.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Sparade uppströms dns-servrar",
     "dns_test_ok_toast": "Angivna DNS servrar fungerar korrekt",
     "dns_test_not_ok_toast": "Server \"{{key}}\": kunde inte användas. Var snäll och kolla att du skrivit in rätt",
+    "dns_test_warning_toast": "Uppströms \"{{key}}\" svarar inte på testförfrågningar och kanske inte fungerar korrekt",
     "unblock": "Avblockera",
     "block": "Blockera",
     "disallow_this_client": "Tillåt inte den här klienten",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Krypteringsinställningar sparade",
     "encryption_server": "Servernamn",
     "encryption_server_enter": "Skriv in ditt domännamn",
-    "encryption_server_desc": "För att kunna använda HTTPS måste du ange servernamnet som matchar ditt SSL-certifikat eller jokerteckencertifikat. Om fältet inte är inställt kommer det att acceptera TLS-anslutningar för alla domäner.",
+    "encryption_server_desc": "För att använda HTTPS behöver du skriva in servernamnet som stämmer överens med ditt SSL-certifikat.",
     "encryption_redirect": "Omdirigera till HTTPS automatiskt",
     "encryption_redirect_desc": "Om bockad kommer AdGuard Home automatiskt att omdirigera dig från HTTP till HTTPS-adresser.",
     "encryption_https": "HTTPS-port",
@@ -613,7 +614,7 @@
     "ttl_cache_validation": "Minsta cache TTL-värde måste vara mindre än eller lika med maxvärdet",
     "cache_optimistic": "Optimistisk cachning",
     "cache_optimistic_desc": "Få AdGuard Home att svara från cachen även när posterna har gått ut och försök även uppdatera dem.",
-    "filter_category_general": "General",
+    "filter_category_general": "Allmänt",
     "filter_category_security": "säkerhet",
     "filter_category_regional": "Regional",
     "filter_category_other": "Övrigt",
diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json
index 964406f1..e4415e33 100644
--- a/client/src/__locales/tr.json
+++ b/client/src/__locales/tr.json
@@ -109,7 +109,7 @@
     "privacy_policy": "Gizlilik Politikası",
     "enable_protection": "Korumayı etkinleştir",
     "enabled_protection": "Koruma etkileştirildi",
-    "disable_protection": "Korumayı durdur",
+    "disable_protection": "Korumayı devre dışı bırak",
     "disabled_protection": "Koruma durduruldu",
     "refresh_statics": "İstatistikleri yenile",
     "dns_query": "DNS Sorguları",
@@ -171,7 +171,7 @@
     "disabled_safe_search_toast": "Güvenli Arama devre dışı bırakıldı",
     "enabled_save_search_toast": "Güvenli Arama etkinleştirildi",
     "enabled_table_header": "Etkin",
-    "name_table_header": "İsim",
+    "name_table_header": "Ad",
     "list_url_table_header": "Liste URL'si",
     "rules_count_table_header": "Kural sayısı",
     "last_time_updated_table_header": "Son güncelleme zamanı",
@@ -186,7 +186,7 @@
     "add_blocklist": "Engel listesi ekle",
     "add_allowlist": "İzin listesi ekle",
     "cancel_btn": "İptal",
-    "enter_name_hint": "İsim girin",
+    "enter_name_hint": "Ad girin",
     "enter_url_or_path_hint": "Listenin URL adresini veya dosya yolunu girin",
     "check_updates_btn": "Güncellemeleri denetle",
     "new_blocklist": "Yeni engel listesi",
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Üst sunucular başarıyla kaydedildi",
     "dns_test_ok_toast": "Belirtilen DNS sunucuları düzgün çalışıyor",
     "dns_test_not_ok_toast": "Sunucu \"{{key}}\": kullanılamıyor, lütfen doğru yazdığınızdan emin olun",
+    "dns_test_warning_toast": "Üst kaynak \"{{key}}\", test isteklerine yanıt vermiyor ve düzgün çalışmayabilir",
     "unblock": "Engeli kaldır",
     "block": "Engelle",
     "disallow_this_client": "Bu istemciye izin verme",
@@ -239,7 +240,7 @@
     "empty_response_status": "Boş",
     "show_all_filter_type": "Tümünü göster",
     "show_filtered_type": "Filtrelenenleri göster",
-    "no_logs_found": "Günlük kaydı bulunamadı",
+    "no_logs_found": "Günlük bulunamadı",
     "refresh_btn": "Yenile",
     "previous_btn": "Önceki",
     "next_btn": "Sonraki",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Şifreleme yapılandırması kaydedildi",
     "encryption_server": "Sunucu adı",
     "encryption_server_enter": "Alan adınızı girin",
-    "encryption_server_desc": "HTTPS kullanmak için SSL sertifikanızla veya joker sertifikanızla eşleşen sunucu adını girmeniz gerekir. Bu alan ayarlanmazsa, herhangi bir alan adının TLS bağlantılarını kabul eder.",
+    "encryption_server_desc": "Ayarlanırsa, AdGuard Home ClientID'leri algılar, DDR sorgularına yanıt verir ve ek bağlantı doğrulamaları gerçekleştirir. Ayarlanmazsa, bu özellikler devre dışı bırakılır. Sertifikadaki DNS Adlarından biriyle eşleşmelidir.",
     "encryption_redirect": "Otomatik olarak HTTPS'e yönlendir",
     "encryption_redirect_desc": "Etkinleştirirseniz, AdGuard Home sizi HTTP adresi yerine HTTPS adresine yönlendirir.",
     "encryption_https": "HTTPS bağlantı noktası",
@@ -396,7 +397,7 @@
     "form_error_equal": "Aynı olmamalıdır",
     "form_error_password": "Parolalar uyuşmuyor",
     "reset_settings": "Ayarları sıfırla",
-    "update_announcement": "AdGuard Home {{version}} sürümü mevcut! Daha fazla bilgi için <0>buraya tıklayın.</0>",
+    "update_announcement": "AdGuard Home {{version}} sürümü artık mevcut! Daha fazla bilgi için <0>buraya tıklayın</0>.",
     "setup_guide": "Kurulum Rehberi",
     "dns_addresses": "DNS adresleri",
     "dns_start": "DNS sunucusu başlatılıyor",
@@ -413,7 +414,7 @@
     "settings_global": "Genel",
     "settings_custom": "Özel",
     "table_client": "İstemci",
-    "table_name": "İsim",
+    "table_name": "Ad",
     "save_btn": "Kaydet",
     "client_add": "İstemci Ekle",
     "client_new": "Yeni İstemci",
@@ -427,7 +428,7 @@
     "form_enter_id": "Tanımlayıcı girin",
     "form_add_id": "Tanımlayıcı ekle",
     "form_client_name": "İstemci ismi girin",
-    "name": "İsim",
+    "name": "Ad",
     "client_global_settings": "Genel ayarları kullan",
     "client_deleted": "\"{{key}}\" istemcisi başarıyla silindi",
     "client_added": "\"{{key}}\" istemcisi başarıyla eklendi",
diff --git a/client/src/__locales/uk.json b/client/src/__locales/uk.json
index c78fdde1..d89c1da4 100644
--- a/client/src/__locales/uk.json
+++ b/client/src/__locales/uk.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "DNS-сервери успішно збережено",
     "dns_test_ok_toast": "Вказані DNS сервери працюють правильно",
     "dns_test_not_ok_toast": "Сервер «{{key}}»: неможливо використати. Перевірте правильність введення",
+    "dns_test_warning_toast": "Upstream «{{key}}» не відповідає на тестові запити та може працювати не правильно",
     "unblock": "Дозволити",
     "block": "Заборонити",
     "disallow_this_client": "Заборонити цього клієнта",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "Конфігурацію шифрування збережено",
     "encryption_server": "Назва сервера",
     "encryption_server_enter": "Введіть ваше доменне ім'я",
-    "encryption_server_desc": "Для використання HTTPS вам потрібно ввести назву сервера, який відповідає вашому SSL-сертифікату або сертифікату з підтримкою піддоменів. Якщо значення не вказано, то сервер буде приймати TLS-з'єднання для будь-якого домену.",
+    "encryption_server_desc": "Якщо встановлено, AdGuard Home розпізнає ClientID, відповідає на DDR-запити та додатково перевіряє з'єднання. Якщо не встановлено, то цей функціонал вимкнено. Мусить відповідати одному з параметрів DNS Names в сертифікаті.",
     "encryption_redirect": "Автоматично перенаправляти на HTTPS",
     "encryption_redirect_desc": "Якщо встановлено, AdGuard Home автоматично перенаправить вас з HTTP на адреси HTTPS.",
     "encryption_https": "Порт HTTPS",
diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json
index a8aab1ad..3000a29a 100644
--- a/client/src/__locales/vi.json
+++ b/client/src/__locales/vi.json
@@ -47,6 +47,7 @@
     "form_error_server_name": "Tên máy chủ không hợp lệ",
     "form_error_subnet": "Mạng con \"{{cidr}}\" không chứa địa chỉ IP \"{{ip}}\"",
     "form_error_positive": "Phải lớn hơn 0",
+    "form_error_gateway_ip": "Cho thuê không thể có địa chỉ IP của cổng",
     "out_of_range_error": "Phải nằm ngoài phạm vi \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Phải thấp hơn khởi động phạm vi",
     "greater_range_start_error": "Phải lớn hơn khoảng bắt đầu",
@@ -221,6 +222,7 @@
     "updated_upstream_dns_toast": "Các máy chủ thượng nguồn đã được lưu thành công",
     "dns_test_ok_toast": "Máy chủ DNS có thể sử dụng",
     "dns_test_not_ok_toast": "Máy chủ \"{{key}}\"': không thể sử dụng, vui lòng kiểm tra lại",
+    "dns_test_warning_toast": "Ngược lại \"{{key}}\" không phản hồi các yêu cầu kiểm tra và có thể không hoạt động bình thường",
     "unblock": "Bỏ chặn",
     "block": "Chặn",
     "disallow_this_client": "Không cho phép client này",
@@ -362,7 +364,7 @@
     "encryption_config_saved": "Đã lưu cấu hình mã hóa",
     "encryption_server": "Tên máy chủ",
     "encryption_server_enter": "Nhập tên miền của bạn",
-    "encryption_server_desc": "Để sử dụng HTTPS, bạn cần nhập tên máy chủ phù hợp với chứng chỉ SSL của bạn. Nếu trường này bị bỏ trống, nó sẽ chấp nhận kết nối TLS với tất cả tên miền.",
+    "encryption_server_desc": "Nếu được đặt, AdGuard Home sẽ phát hiện ClientID, phản hồi các truy vấn DDR và thực hiện xác thực kết nối bổ sung. Nếu không được đặt, các tính năng này sẽ bị vô hiệu hóa. Phải khớp với một trong các Tên DNS trong chứng chỉ.",
     "encryption_redirect": "Tự động chuyển hướng đến HTTPS",
     "encryption_redirect_desc": "Nếu được chọn, AdGuard Home sẽ tự động chuyển hướng bạn từ địa chỉ HTTP sang địa chỉ HTTPS.",
     "encryption_https": "Cổng HTTPS",
diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json
index b5742ea7..7c777c1d 100644
--- a/client/src/__locales/zh-cn.json
+++ b/client/src/__locales/zh-cn.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "上游服务器保存成功",
     "dns_test_ok_toast": "指定的 DNS 服务器现已正常运行",
     "dns_test_not_ok_toast": "服务器 \"{{key}}\":无法使用,请检查你输入的是否正确",
+    "dns_test_warning_toast": "上游 “{{key}}” 不响应测试请求,可能无法正常工作",
     "unblock": "放行",
     "block": "拦截",
     "disallow_this_client": "不允许这个客户端",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "加密配置已保存",
     "encryption_server": "服务器名称",
     "encryption_server_enter": "输入您的域名",
-    "encryption_server_desc": "为了使用 HTTPS,请您输入与 SSL 证书或通配证书相匹配的服务器名称。如此字段未设置,服务器将要为所有域名接受 TLS 连接。",
+    "encryption_server_desc": "设置后,AdGuard Home 检测客户端标识号,对 DDR 查询作出反应,以及进一步检查连接。在没有设置的情况下,该功能被禁用。必须与证书里的一个 DNS 名称相匹配。",
     "encryption_redirect": "HTTPS 自动重定向",
     "encryption_redirect_desc": "如果勾选此选项,AdGuard Home 将自动将您从 HTTP 重定向到 HTTPS 地址。",
     "encryption_https": "HTTPS 端口",
diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json
index 90d85586..3f4cdef2 100644
--- a/client/src/__locales/zh-tw.json
+++ b/client/src/__locales/zh-tw.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "上游的伺服器被成功地儲存",
     "dns_test_ok_toast": "已明確指定的 DNS 伺服器正在正確地運作",
     "dns_test_not_ok_toast": "伺服器 \"{{key}}\":無法被使用,請檢查您已正確地填寫它",
+    "dns_test_warning_toast": "上游 “{{key}}” 不回應測試請求,可能無法正常工作",
     "unblock": "解除封鎖",
     "block": "封鎖",
     "disallow_this_client": "不允許此用戶端",
@@ -363,7 +364,7 @@
     "encryption_config_saved": "加密配置被儲存",
     "encryption_server": "伺服器名稱",
     "encryption_server_enter": "輸入您的域名",
-    "encryption_server_desc": "為了使用 HTTPS,您需要輸入與您的安全通訊端層(SSL)憑證或萬用字元憑證相符的伺服器名稱。如果此欄位未被設定,它將接受向任何網域的傳輸層安全性協定(TLS)連線。",
+    "encryption_server_desc": "如果被設定,AdGuard Home 檢測用戶端 IDs,回覆 DDR 查詢,並執行額外的連線驗證。如果未被設定,這些功能被禁用。必須與在該憑證裡的 DNS 名稱其中之一相符。",
     "encryption_redirect": "自動地重新導向到 HTTPS",
     "encryption_redirect_desc": "如果被勾選,AdGuard Home 將自動地重新導向您從 HTTP 到 HTTPS 位址。",
     "encryption_https": "HTTPS 連接埠",

From eb8e8166c85b6505505770e2f950c2677e888470 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 3 Aug 2022 17:32:27 +0300
Subject: [PATCH 111/143] Pull request: udp-chlog

Merge in DNS/adguard-home from udp-chlog to master

Squashed commit of the following:

commit 1c8dbff75c8377c352f6fedd18699f151a087f2b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 3 17:19:18 2022 +0300

    all: upd chlog
---
 CHANGELOG.md | 63 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 43 insertions(+), 20 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8eb9193..2cc43b12 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,19 +17,45 @@ and this project adheres to
 
 ### Security
 
-- Go version was updated to prevent the possibility of exploiting the
-  CVE-2022-32189 Go vulnerability fixed in [Go 1.18.5][go-1.18.5].  Go 1.17
-  support has also been removed, as it has reached end of life and will not
-  receive security updates.
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
 
 ### Added
 
-- Domain-specific upstream servers test.  Such test fails with an appropriate
-  warning message ([#4517]).
 - Support for Discovery of Designated Resolvers (DDR) according to the [RFC
   draft][ddr-draft] ([#4463]).
+
+### Deprecated
+
+- Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
+
+[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+
+[ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
+
+
+
+<!--
+## [v0.107.10] - 2022-09-06 (APPROX.)
+-->
+
+
+
+## [v0.107.9] - 2022-08-03
+
+See also the [v0.107.9 GitHub milestone][ms-v0.107.9].
+
+### Security
+
+- Go version was updated to prevent the possibility of exploiting the
+  CVE-2022-32189 Go vulnerability fixed in [Go 1.18.5][go-1.18.5].  Go 1.17
+  support has also been removed, as it has reached end of life and will not
+  receive security updates.
+
+### Added
+
+- Domain-specific upstream servers test.  If such test fails, a warning message
+  is shown ([#4517]).
 - `windows/arm64` support ([#3057]).
 
 ### Changed
@@ -37,26 +63,22 @@ and this project adheres to
 - UI and update links have been changed to make them more resistant to DNS
   blocking.
 
-### Deprecated
+### Fixed
 
-- Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
+- Several UI issues ([#4775], [#4776], [#4782]).
 
 ### Removed
 
 - Go 1.17 support, as it has reached end of life.
 
-[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
 [#4517]: https://github.com/AdguardTeam/AdGuardHome/issues/4517
+[#4775]: https://github.com/AdguardTeam/AdGuardHome/issues/4775
+[#4776]: https://github.com/AdguardTeam/AdGuardHome/issues/4776
+[#4782]: https://github.com/AdguardTeam/AdGuardHome/issues/4782
 
-[ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
-[go-1.18.5]: https://groups.google.com/g/golang-announce/c/YqYYG87xB10
-
-
-
-<!--
-## [v0.107.9] - 2022-08-23 (APPROX.)
--->
+[go-1.18.5]:   https://groups.google.com/g/golang-announce/c/YqYYG87xB10
+[ms-v0.107.9]: https://github.com/AdguardTeam/AdGuardHome/milestone/45?closed=1
 
 
 
@@ -1063,11 +1085,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
 
 
 <!--
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...HEAD
-[v0.107.9]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...v0.107.9
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...HEAD
+[v0.107.9]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...v0.107.10
 -->
 
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...HEAD
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...HEAD
+[v0.107.9]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...v0.107.9
 [v0.107.8]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...v0.107.8
 [v0.107.7]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...v0.107.7
 [v0.107.6]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...v0.107.6

From 8a3d5f046c8b54017c55f0f35fdfa9f44a05794b Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 3 Aug 2022 20:36:20 +0300
Subject: [PATCH 112/143] Pull request: 4670-invalid-arg-cap-check

Updates #4670.

Squashed commit of the following:

commit 9c32739eb92ef57c78a4dc3ec3c0f280aebf7182
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 3 20:04:54 2022 +0300

    aghnet: imp port check for older linuxes
---
 CHANGELOG.md                 |  6 ++++++
 internal/aghnet/net_linux.go | 15 +++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2cc43b12..8183fd68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,7 +29,13 @@ and this project adheres to
 
 - Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
 
+### Fixed
+
+- `invalid argument` errors during update checks on older Linux kernels
+  ([#4670]).
+
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+[#4670]: https://github.com/AdguardTeam/AdGuardHome/issues/4670
 
 [ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
 
diff --git a/internal/aghnet/net_linux.go b/internal/aghnet/net_linux.go
index 148abe1f..4be8835c 100644
--- a/internal/aghnet/net_linux.go
+++ b/internal/aghnet/net_linux.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
 	"github.com/AdguardTeam/golibs/errors"
+	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/golibs/stringutil"
 	"github.com/google/renameio/maybe"
 	"golang.org/x/sys/unix"
@@ -22,17 +23,27 @@ import (
 const dhcpcdConf = "etc/dhcpcd.conf"
 
 func canBindPrivilegedPorts() (can bool, err error) {
-	cnbs, err := unix.PrctlRetInt(
+	res, err := unix.PrctlRetInt(
 		unix.PR_CAP_AMBIENT,
 		unix.PR_CAP_AMBIENT_IS_SET,
 		unix.CAP_NET_BIND_SERVICE,
 		0,
 		0,
 	)
+	if err != nil {
+		if errors.Is(err, unix.EINVAL) {
+			// Older versions of Linux kernel do not support this.  Print a
+			// warning and check admin rights.
+			log.Info("warning: cannot check capability cap_net_bind_service: %s", err)
+		} else {
+			return false, err
+		}
+	}
+
 	// Don't check the error because it's always nil on Linux.
 	adm, _ := aghos.HaveAdminRights()
 
-	return cnbs == 1 || adm, err
+	return res == 1 || adm, nil
 }
 
 // dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to

From 4c6377c5cb79c80f409e6b0ad8c33b8d91fc61be Mon Sep 17 00:00:00 2001
From: NeP <2996023783@qq.com>
Date: Thu, 4 Aug 2022 19:45:10 +0800
Subject: [PATCH 113/143] filtering: add Bilibili and Weibo domains

---
 internal/filtering/blocked.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index 75fafa62..89531a22 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -254,7 +254,7 @@ var serviceRulesArray = []svc{{
 	rules: []string{"||viber.com^"},
 }, {
 	name:  "weibo",
-	rules: []string{"||weibo.com^", "||weibo.cn^"},
+	rules: []string{"||weibo.com^", "||weibo.cn^", "||weibocdn.com^"},
 }, {
 	name:  "9gag",
 	rules: []string{"||9cache.com^", "||9gag.com^"},
@@ -299,6 +299,7 @@ var serviceRulesArray = []svc{{
 	rules: []string{
 		"||bilibili.com^",
 		"||bilivideo.com^",
+		"||bilivideo.cn^",
 		"||biligame.com^",
 		"||biliapi.net^",
 		"||dreamcast.hk^",

From 4293cf5945a9331fd8ce1eb33a88865c6b8713cc Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Thu, 4 Aug 2022 19:05:28 +0300
Subject: [PATCH 114/143] Pull request: 4358 fix stats

Merge in DNS/adguard-home from 4358-fix-stats to master

Updates #4358.
Updates #4342.

Squashed commit of the following:

commit 5683cb304688ea639e5ba7f219a7bf12370211a4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 18:20:54 2022 +0300

    stats: rm races test

commit 63dd67650ed64eaf9685b955a4fdf3c0067a7f8c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 17:13:36 2022 +0300

    stats: try to imp test

commit 59a0f249fc00566872db62e362c87bc0c201b333
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 16:38:57 2022 +0300

    stats: fix nil ptr deref

commit 7fc3ff18a34a1d0e0fec3ca83a33f499ac752572
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 7 16:02:51 2022 +0300

    stats: fix races finally, imp tests

commit c63f5f4e7929819fe79b3a1e392f6b91cd630846
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 00:56:49 2022 +0300

    aghhttp: add register func

commit 61adc7f0e95279c1b7f4a0c0af5ab387ee461411
Merge: edbdb2d4 9b3adac1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 00:36:01 2022 +0300

    Merge branch 'master' into 4358-fix-stats

commit edbdb2d4c6a06dcbf8107a28c4c3a61ba394e907
Merge: a91e4d7a a481ff4c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 21:00:42 2022 +0300

    Merge branch 'master' into 4358-fix-stats

commit a91e4d7af13591eeef45cb7980d1ebc1650a5cb7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 18:46:19 2022 +0300

    stats: imp code, docs

commit c5f3814c5c1a734ca8ff6726cc9ffc1177a055cf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 18:16:13 2022 +0300

    all: log changes

commit 5e6caafc771dddc4c6be07c34658de359106fbe5
Merge: 091ba756 eb8e8166
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 18:09:10 2022 +0300

    Merge branch 'master' into 4358-fix-stats

commit 091ba75618d3689b9c04f05431283417c8cc52f9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 18:07:39 2022 +0300

    stats: imp docs, code

commit f2b2de77ce5f0448d6df9232a614a3710f1e2e8a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 2 17:09:30 2022 +0300

    all: refactor stats & add mutexes

commit b3f11c455ceaa3738ec20eefc46f866ff36ed046
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 27 15:30:09 2022 +0300

    WIP
---
 CHANGELOG.md                      |   6 +
 internal/aghhttp/aghhttp.go       |   6 +
 internal/dhcpd/dhcpd.go           |   4 +-
 internal/dnsforward/config.go     |   4 +-
 internal/dnsforward/dnsforward.go |   4 +-
 internal/dnsforward/stats_test.go |   2 +-
 internal/filtering/filtering.go   |   4 +-
 internal/home/clients.go          |  29 ++
 internal/home/clientshttp.go      |  37 +--
 internal/home/control.go          |   2 +-
 internal/home/home.go             |   2 +-
 internal/querylog/querylog.go     |   4 +-
 internal/stats/http.go            |  53 ++--
 internal/stats/stats.go           |  77 +++--
 internal/stats/stats_test.go      |   4 +-
 internal/stats/unit.go            | 502 ++++++++++++++++++------------
 16 files changed, 440 insertions(+), 300 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8183fd68..ce333385 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,10 @@ and this project adheres to
 - Support for Discovery of Designated Resolvers (DDR) according to the [RFC
   draft][ddr-draft] ([#4463]).
 
+### Fixed
+
+- Data races and concurrent map access in statistics module ([#4358], [#4342]).
+
 ### Deprecated
 
 - Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
@@ -35,6 +39,8 @@ and this project adheres to
   ([#4670]).
 
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+[#4342]: https://github.com/AdguardTeam/AdGuardHome/issues/4342
+[#4358]: https://github.com/AdguardTeam/AdGuardHome/issues/4358
 [#4670]: https://github.com/AdguardTeam/AdGuardHome/issues/4670
 
 [ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
diff --git a/internal/aghhttp/aghhttp.go b/internal/aghhttp/aghhttp.go
index e186f8a3..57a1c868 100644
--- a/internal/aghhttp/aghhttp.go
+++ b/internal/aghhttp/aghhttp.go
@@ -9,6 +9,12 @@ import (
 	"github.com/AdguardTeam/golibs/log"
 )
 
+// RegisterFunc is the function that sets the handler to handle the URL for the
+// method.
+//
+// TODO(e.burkov, a.garipov):  Get rid of it.
+type RegisterFunc func(method, url string, handler http.HandlerFunc)
+
 // OK responds with word OK.
 func OK(w http.ResponseWriter) {
 	if _, err := io.WriteString(w, "OK\n"); err != nil {
diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go
index 55c56c18..a085e656 100644
--- a/internal/dhcpd/dhcpd.go
+++ b/internal/dhcpd/dhcpd.go
@@ -5,11 +5,11 @@ import (
 	"encoding/json"
 	"fmt"
 	"net"
-	"net/http"
 	"path/filepath"
 	"runtime"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/golibs/netutil"
 )
@@ -126,7 +126,7 @@ type ServerConfig struct {
 	ConfigModified func() `yaml:"-"`
 
 	// Register an HTTP handler
-	HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
+	HTTPRegister aghhttp.RegisterFunc `yaml:"-"`
 
 	Enabled       bool   `yaml:"enabled"`
 	InterfaceName string `yaml:"interface_name"`
diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go
index eaee9155..d5e918c3 100644
--- a/internal/dnsforward/config.go
+++ b/internal/dnsforward/config.go
@@ -5,12 +5,12 @@ import (
 	"crypto/x509"
 	"fmt"
 	"net"
-	"net/http"
 	"os"
 	"sort"
 	"strings"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
 	"github.com/AdguardTeam/AdGuardHome/internal/filtering"
 	"github.com/AdguardTeam/dnsproxy/proxy"
@@ -193,7 +193,7 @@ type ServerConfig struct {
 	ConfigModified func()
 
 	// Register an HTTP handler
-	HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request))
+	HTTPRegister aghhttp.RegisterFunc
 
 	// ResolveClients signals if the RDNS should resolve clients' addresses.
 	ResolveClients bool
diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go
index 81ac93ed..ca479fc4 100644
--- a/internal/dnsforward/dnsforward.go
+++ b/internal/dnsforward/dnsforward.go
@@ -61,7 +61,7 @@ type Server struct {
 	dnsFilter  *filtering.DNSFilter  // DNS filter instance
 	dhcpServer dhcpd.ServerInterface // DHCP server instance (optional)
 	queryLog   querylog.QueryLog     // Query log instance
-	stats      stats.Stats
+	stats      stats.Interface
 	access     *accessCtx
 
 	// localDomainSuffix is the suffix used to detect internal hosts.  It
@@ -107,7 +107,7 @@ const defaultLocalDomainSuffix = "lan"
 // DNSCreateParams are parameters to create a new server.
 type DNSCreateParams struct {
 	DNSFilter   *filtering.DNSFilter
-	Stats       stats.Stats
+	Stats       stats.Interface
 	QueryLog    querylog.QueryLog
 	DHCPServer  dhcpd.ServerInterface
 	PrivateNets netutil.SubnetSet
diff --git a/internal/dnsforward/stats_test.go b/internal/dnsforward/stats_test.go
index fdaa3678..d991be12 100644
--- a/internal/dnsforward/stats_test.go
+++ b/internal/dnsforward/stats_test.go
@@ -34,7 +34,7 @@ func (l *testQueryLog) Add(p *querylog.AddParams) {
 type testStats struct {
 	// Stats is embedded here simply to make testStats a stats.Stats without
 	// actually implementing all methods.
-	stats.Stats
+	stats.Interface
 
 	lastEntry stats.Entry
 }
diff --git a/internal/filtering/filtering.go b/internal/filtering/filtering.go
index 8af49b0c..4a3e6b28 100644
--- a/internal/filtering/filtering.go
+++ b/internal/filtering/filtering.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"io/fs"
 	"net"
-	"net/http"
 	"os"
 	"runtime"
 	"runtime/debug"
@@ -14,6 +13,7 @@ import (
 	"sync"
 	"sync/atomic"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/dnsproxy/upstream"
 	"github.com/AdguardTeam/golibs/cache"
@@ -94,7 +94,7 @@ type Config struct {
 	ConfigModified func() `yaml:"-"`
 
 	// Register an HTTP handler
-	HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
+	HTTPRegister aghhttp.RegisterFunc `yaml:"-"`
 
 	// CustomResolver is the resolver used by DNSFilter.
 	CustomResolver Resolver `yaml:"-"`
diff --git a/internal/home/clients.go b/internal/home/clients.go
index 0b9bfcd0..e50b7904 100644
--- a/internal/home/clients.go
+++ b/internal/home/clients.go
@@ -2,6 +2,7 @@ package home
 
 import (
 	"bytes"
+	"encoding"
 	"fmt"
 	"net"
 	"sort"
@@ -60,6 +61,33 @@ const (
 	ClientSourceHostsFile
 )
 
+var _ fmt.Stringer = clientSource(0)
+
+// String returns a human-readable name of cs.
+func (cs clientSource) String() (s string) {
+	switch cs {
+	case ClientSourceWHOIS:
+		return "WHOIS"
+	case ClientSourceARP:
+		return "ARP"
+	case ClientSourceRDNS:
+		return "rDNS"
+	case ClientSourceDHCP:
+		return "DHCP"
+	case ClientSourceHostsFile:
+		return "etc/hosts"
+	default:
+		return ""
+	}
+}
+
+var _ encoding.TextMarshaler = clientSource(0)
+
+// MarshalText implements encoding.TextMarshaler for the clientSource.
+func (cs clientSource) MarshalText() (text []byte, err error) {
+	return []byte(cs.String()), nil
+}
+
 // clientSourceConf is used to configure where the runtime clients will be
 // obtained from.
 type clientSourcesConf struct {
@@ -397,6 +425,7 @@ func (clients *clientsContainer) Find(id string) (c *Client, ok bool) {
 	c.Tags = stringutil.CloneSlice(c.Tags)
 	c.BlockedServices = stringutil.CloneSlice(c.BlockedServices)
 	c.Upstreams = stringutil.CloneSlice(c.Upstreams)
+
 	return c, true
 }
 
diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go
index 3bdf95e1..5f10ccbe 100644
--- a/internal/home/clientshttp.go
+++ b/internal/home/clientshttp.go
@@ -47,9 +47,9 @@ type clientJSON struct {
 type runtimeClientJSON struct {
 	WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info"`
 
-	Name   string `json:"name"`
-	Source string `json:"source"`
-	IP     net.IP `json:"ip"`
+	Name   string       `json:"name"`
+	Source clientSource `json:"source"`
+	IP     net.IP       `json:"ip"`
 }
 
 type clientListJSON struct {
@@ -81,20 +81,9 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
 		cj := runtimeClientJSON{
 			WHOISInfo: rc.WHOISInfo,
 
-			Name: rc.Host,
-			IP:   ip,
-		}
-
-		cj.Source = "etc/hosts"
-		switch rc.Source {
-		case ClientSourceDHCP:
-			cj.Source = "DHCP"
-		case ClientSourceRDNS:
-			cj.Source = "rDNS"
-		case ClientSourceARP:
-			cj.Source = "ARP"
-		case ClientSourceWHOIS:
-			cj.Source = "WHOIS"
+			Name:   rc.Host,
+			Source: rc.Source,
+			IP:     ip,
 		}
 
 		data.RuntimeClients = append(data.RuntimeClients, cj)
@@ -107,13 +96,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
 	w.Header().Set("Content-Type", "application/json")
 	e := json.NewEncoder(w).Encode(data)
 	if e != nil {
-		aghhttp.Error(
-			r,
-			w,
-			http.StatusInternalServerError,
-			"Failed to encode to json: %v",
-			e,
-		)
+		aghhttp.Error(r, w, http.StatusInternalServerError, "failed to encode to json: %v", e)
 
 		return
 	}
@@ -279,9 +262,9 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
 func (clients *clientsContainer) findRuntime(ip net.IP, idStr string) (cj *clientJSON) {
 	rc, ok := clients.FindRuntimeClient(ip)
 	if !ok {
-		// It is still possible that the IP used to be in the runtime
-		// clients list, but then the server was reloaded.  So, check
-		// the DNS server's blocked IP list.
+		// It is still possible that the IP used to be in the runtime clients
+		// list, but then the server was reloaded.  So, check the DNS server's
+		// blocked IP list.
 		//
 		// See https://github.com/AdguardTeam/AdGuardHome/issues/2428.
 		disallowed, rule := clients.dnsServer.IsBlockedClient(ip, idStr)
diff --git a/internal/home/control.go b/internal/home/control.go
index 8234e00e..54d1652a 100644
--- a/internal/home/control.go
+++ b/internal/home/control.go
@@ -189,7 +189,7 @@ func registerControlHandlers() {
 	RegisterAuthHandlers()
 }
 
-func httpRegister(method, url string, handler func(http.ResponseWriter, *http.Request)) {
+func httpRegister(method, url string, handler http.HandlerFunc) {
 	if method == "" {
 		// "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method
 		Context.mux.HandleFunc(url, postInstall(handler))
diff --git a/internal/home/home.go b/internal/home/home.go
index 15fd4c46..0fc16c09 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -47,7 +47,7 @@ type homeContext struct {
 	// --
 
 	clients    clientsContainer     // per-client-settings module
-	stats      stats.Stats          // statistics module
+	stats      stats.Interface      // statistics module
 	queryLog   querylog.QueryLog    // query log module
 	dnsServer  *dnsforward.Server   // DNS module
 	rdns       *RDNS                // rDNS module
diff --git a/internal/querylog/querylog.go b/internal/querylog/querylog.go
index a854c2c4..2d8e397f 100644
--- a/internal/querylog/querylog.go
+++ b/internal/querylog/querylog.go
@@ -2,10 +2,10 @@ package querylog
 
 import (
 	"net"
-	"net/http"
 	"path/filepath"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/AdGuardHome/internal/filtering"
 	"github.com/AdguardTeam/golibs/errors"
@@ -38,7 +38,7 @@ type Config struct {
 	ConfigModified func()
 
 	// HTTPRegister registers an HTTP handler.
-	HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request))
+	HTTPRegister aghhttp.RegisterFunc
 
 	// FindClient returns client information by their IDs.
 	FindClient func(ids []string) (c *Client, err error)
diff --git a/internal/stats/http.go b/internal/stats/http.go
index e2f00039..033dd3bb 100644
--- a/internal/stats/http.go
+++ b/internal/stats/http.go
@@ -39,34 +39,21 @@ type statsResponse struct {
 }
 
 // handleStats is a handler for getting statistics.
-func (s *statsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
+func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
 	start := time.Now()
 
 	var resp statsResponse
-	if s.conf.limit == 0 {
-		resp = statsResponse{
-			TimeUnits: "days",
+	var ok bool
+	resp, ok = s.getData()
 
-			TopBlocked: []topAddrs{},
-			TopClients: []topAddrs{},
-			TopQueried: []topAddrs{},
+	log.Debug("stats: prepared data in %v", time.Since(start))
 
-			BlockedFiltering:     []uint64{},
-			DNSQueries:           []uint64{},
-			ReplacedParental:     []uint64{},
-			ReplacedSafebrowsing: []uint64{},
-		}
-	} else {
-		var ok bool
-		resp, ok = s.getData()
+	if !ok {
+		// Don't bring the message to the lower case since it's a part of UI
+		// text for the moment.
+		aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't get statistics data")
 
-		log.Debug("stats: prepared data in %v", time.Since(start))
-
-		if !ok {
-			aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't get statistics data")
-
-			return
-		}
+		return
 	}
 
 	w.Header().Set("Content-Type", "application/json")
@@ -84,9 +71,9 @@ type config struct {
 }
 
 // Get configuration
-func (s *statsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
+func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
 	resp := config{}
-	resp.IntervalDays = s.conf.limit / 24
+	resp.IntervalDays = s.limitHours / 24
 
 	data, err := json.Marshal(resp)
 	if err != nil {
@@ -102,7 +89,7 @@ func (s *statsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
 }
 
 // Set configuration
-func (s *statsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
+func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
 	reqData := config{}
 	err := json.NewDecoder(r.Body).Decode(&reqData)
 	if err != nil {
@@ -118,22 +105,22 @@ func (s *statsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
 	}
 
 	s.setLimit(int(reqData.IntervalDays))
-	s.conf.ConfigModified()
+	s.configModified()
 }
 
 // Reset data
-func (s *statsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) {
+func (s *StatsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) {
 	s.clear()
 }
 
 // Register web handlers
-func (s *statsCtx) initWeb() {
-	if s.conf.HTTPRegister == nil {
+func (s *StatsCtx) initWeb() {
+	if s.httpRegister == nil {
 		return
 	}
 
-	s.conf.HTTPRegister(http.MethodGet, "/control/stats", s.handleStats)
-	s.conf.HTTPRegister(http.MethodPost, "/control/stats_reset", s.handleStatsReset)
-	s.conf.HTTPRegister(http.MethodPost, "/control/stats_config", s.handleStatsConfig)
-	s.conf.HTTPRegister(http.MethodGet, "/control/stats_info", s.handleStatsInfo)
+	s.httpRegister(http.MethodGet, "/control/stats", s.handleStats)
+	s.httpRegister(http.MethodPost, "/control/stats_reset", s.handleStatsReset)
+	s.httpRegister(http.MethodPost, "/control/stats_config", s.handleStatsConfig)
+	s.httpRegister(http.MethodGet, "/control/stats_info", s.handleStatsInfo)
 }
diff --git a/internal/stats/stats.go b/internal/stats/stats.go
index 2944a163..04a933d4 100644
--- a/internal/stats/stats.go
+++ b/internal/stats/stats.go
@@ -4,75 +4,85 @@ package stats
 
 import (
 	"net"
-	"net/http"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 )
 
-type unitIDCallback func() uint32
+// UnitIDGenFunc is the signature of a function that generates a unique ID for
+// the statistics unit.
+type UnitIDGenFunc func() (id uint32)
 
-// DiskConfig - configuration settings that are stored on disk
+// DiskConfig is the configuration structure that is stored in file.
 type DiskConfig struct {
-	Interval uint32 `yaml:"statistics_interval"` // time interval for statistics (in days)
+	// Interval is the number of days for which the statistics are collected
+	// before flushing to the database.
+	Interval uint32 `yaml:"statistics_interval"`
 }
 
-// Config - module configuration
+// Config is the configuration structure for the statistics collecting.
 type Config struct {
-	Filename  string         // database file name
-	LimitDays uint32         // time limit (in days)
-	UnitID    unitIDCallback // user function to get the current unit ID.  If nil, the current time hour is used.
+	// UnitID is the function to generate the identifier for current unit.  If
+	// nil, the default function is used, see newUnitID.
+	UnitID UnitIDGenFunc
 
-	// Called when the configuration is changed by HTTP request
+	// ConfigModified will be called each time the configuration changed via web
+	// interface.
 	ConfigModified func()
 
-	// Register an HTTP handler
-	HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request))
+	// HTTPRegister is the function that registers handlers for the stats
+	// endpoints.
+	HTTPRegister aghhttp.RegisterFunc
 
-	limit uint32 // maximum time we need to keep data for (in hours)
+	// Filename is the name of the database file.
+	Filename string
+
+	// LimitDays is the maximum number of days to collect statistics into the
+	// current unit.
+	LimitDays uint32
 }
 
-// New - create object
-func New(conf Config) (Stats, error) {
-	return createObject(conf)
-}
-
-// Stats - main interface
-type Stats interface {
+// Interface is the statistics interface to be used by other packages.
+type Interface interface {
+	// Start begins the statistics collecting.
 	Start()
 
-	// Close object.
-	// This function is not thread safe
-	//  (can't be called in parallel with any other function of this interface).
+	// Close stops the statistics collecting.
 	Close()
 
-	// Update counters
+	// Update collects the incoming statistics data.
 	Update(e Entry)
 
-	// Get IP addresses of the clients with the most number of requests
+	// GetTopClientIP returns at most limit IP addresses corresponding to the
+	// clients with the most number of requests.
 	GetTopClientsIP(limit uint) []net.IP
 
-	// WriteDiskConfig - write configuration
+	// WriteDiskConfig puts the Interface's configuration to the dc.
 	WriteDiskConfig(dc *DiskConfig)
 }
 
-// TimeUnit - time unit
+// TimeUnit is the unit of measuring time while aggregating the statistics.
 type TimeUnit int
 
-// Supported time units
+// Supported TimeUnit values.
 const (
 	Hours TimeUnit = iota
 	Days
 )
 
-// Result of DNS request processing
+// Result is the resulting code of processing the DNS request.
 type Result int
 
-// Supported result values
+// Supported Result values.
+//
+// TODO(e.burkov):  Think about better naming.
 const (
 	RNotFiltered Result = iota + 1
 	RFiltered
 	RSafeBrowsing
 	RSafeSearch
 	RParental
-	rLast
+
+	resultLast = RParental + 1
 )
 
 // Entry is a statistics data entry.
@@ -82,7 +92,12 @@ type Entry struct {
 	// TODO(a.garipov): Make this a {net.IP, string} enum?
 	Client string
 
+	// Domain is the domain name requested.
 	Domain string
+
+	// Result is the result of processing the request.
 	Result Result
-	Time   uint32 // processing time (msec)
+
+	// Time is the duration of the request processing in milliseconds.
+	Time uint32
 }
diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go
index 70b71db8..0cffd2e3 100644
--- a/internal/stats/stats_test.go
+++ b/internal/stats/stats_test.go
@@ -37,7 +37,7 @@ func TestStats(t *testing.T) {
 		LimitDays: 1,
 	}
 
-	s, err := createObject(conf)
+	s, err := New(conf)
 	require.NoError(t, err)
 	testutil.CleanupAndRequireSuccess(t, func() (err error) {
 		s.clear()
@@ -110,7 +110,7 @@ func TestLargeNumbers(t *testing.T) {
 		LimitDays: 1,
 		UnitID:    newID,
 	}
-	s, err := createObject(conf)
+	s, err := New(conf)
 	require.NoError(t, err)
 	testutil.CleanupAndRequireSuccess(t, func() (err error) {
 		s.Close()
diff --git a/internal/stats/unit.go b/internal/stats/unit.go
index 35d47a51..6d32a6d1 100644
--- a/internal/stats/unit.go
+++ b/internal/stats/unit.go
@@ -9,11 +9,13 @@ import (
 	"os"
 	"sort"
 	"sync"
+	"sync/atomic"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
-	bolt "go.etcd.io/bbolt"
+	"go.etcd.io/bbolt"
 )
 
 // TODO(a.garipov): Rewrite all of this.  Add proper error handling and
@@ -24,47 +26,130 @@ const (
 	maxClients = 100 // max number of top clients to store in file or return via Get()
 )
 
-// statsCtx - global context
-type statsCtx struct {
-	// mu protects unit.
-	mu *sync.Mutex
-	// current is the actual statistics collection result.
-	current *unit
+// StatsCtx collects the statistics and flushes it to the database.  Its default
+// flushing interval is one hour.
+//
+// TODO(e.burkov):  Use atomic.Pointer for accessing curr and db in go1.19.
+type StatsCtx struct {
+	// currMu protects the current unit.
+	currMu *sync.Mutex
+	// curr is the actual statistics collection result.
+	curr *unit
 
-	db   *bolt.DB
-	conf *Config
+	// dbMu protects db.
+	dbMu *sync.Mutex
+	// db is the opened statistics database, if any.
+	db *bbolt.DB
+
+	// unitIDGen is the function that generates an identifier for the current
+	// unit.  It's here for only testing purposes.
+	unitIDGen UnitIDGenFunc
+
+	// httpRegister is used to set HTTP handlers.
+	httpRegister aghhttp.RegisterFunc
+
+	// configModified is called whenever the configuration is modified via web
+	// interface.
+	configModified func()
+
+	// filename is the name of database file.
+	filename string
+
+	// limitHours is the maximum number of hours to collect statistics into the
+	// current unit.
+	limitHours uint32
 }
 
-// data for 1 time unit
+// unit collects the statistics data for a specific period of time.
 type unit struct {
-	id uint32 // unit ID.  Default: absolute hour since Jan 1, 1970
+	// mu protects all the fields of a unit.
+	mu *sync.RWMutex
 
-	nTotal  uint64   // total requests
-	nResult []uint64 // number of requests per one result
-	timeSum uint64   // sum of processing time of all requests (usec)
+	// id is the unique unit's identifier.  It's set to an absolute hour number
+	// since the beginning of UNIX time by the default ID generating function.
+	id uint32
 
-	// top:
-	domains        map[string]uint64 // number of requests per domain
-	blockedDomains map[string]uint64 // number of blocked requests per domain
-	clients        map[string]uint64 // number of requests per client
+	// nTotal stores the total number of requests.
+	nTotal uint64
+	// nResult stores the number of requests grouped by it's result.
+	nResult []uint64
+	// timeSum stores the sum of processing time in milliseconds of each request
+	// written by the unit.
+	timeSum uint64
+
+	// domains stores the number of requests for each domain.
+	domains map[string]uint64
+	// blockedDomains stores the number of requests for each domain that has
+	// been blocked.
+	blockedDomains map[string]uint64
+	// clients stores the number of requests from each client.
+	clients map[string]uint64
 }
 
-// name-count pair
+// ongoing returns the current unit.  It's safe for concurrent use.
+//
+// Note that the unit itself should be locked before accessing.
+func (s *StatsCtx) ongoing() (u *unit) {
+	s.currMu.Lock()
+	defer s.currMu.Unlock()
+
+	return s.curr
+}
+
+// swapCurrent swaps the current unit with another and returns it.  It's safe
+// for concurrent use.
+func (s *StatsCtx) swapCurrent(with *unit) (old *unit) {
+	s.currMu.Lock()
+	defer s.currMu.Unlock()
+
+	old, s.curr = s.curr, with
+
+	return old
+}
+
+// database returns the database if it's opened.  It's safe for concurrent use.
+func (s *StatsCtx) database() (db *bbolt.DB) {
+	s.dbMu.Lock()
+	defer s.dbMu.Unlock()
+
+	return s.db
+}
+
+// swapDatabase swaps the database with another one and returns it.  It's safe
+// for concurrent use.
+func (s *StatsCtx) swapDatabase(with *bbolt.DB) (old *bbolt.DB) {
+	s.dbMu.Lock()
+	defer s.dbMu.Unlock()
+
+	old, s.db = s.db, with
+
+	return old
+}
+
+// countPair is a single name-number pair for deserializing statistics data into
+// the database.
 type countPair struct {
 	Name  string
 	Count uint64
 }
 
-// structure for storing data in file
+// unitDB is the structure for deserializing statistics data into the database.
 type unitDB struct {
-	NTotal  uint64
+	// NTotal is the total number of requests.
+	NTotal uint64
+	// NResult is the number of requests by the result's kind.
 	NResult []uint64
 
-	Domains        []countPair
+	// Domains is the number of requests for each domain name.
+	Domains []countPair
+	// BlockedDomains is the number of requests blocked for each domain name.
 	BlockedDomains []countPair
-	Clients        []countPair
+	// Clients is the number of requests from each client.
+	Clients []countPair
 
-	TimeAvg uint32 // usec
+	// TimeAvg is the average of processing times in milliseconds of all the
+	// requests in the unit.
+	TimeAvg uint32
 }
 
 // withRecovered turns the value recovered from panic if any into an error and
@@ -86,34 +171,40 @@ func withRecovered(orig *error) {
 	*orig = errors.WithDeferred(*orig, err)
 }
 
-// createObject creates s from conf and properly initializes it.
-func createObject(conf Config) (s *statsCtx, err error) {
+// isEnabled is a helper that check if the statistics collecting is enabled.
+func (s *StatsCtx) isEnabled() (ok bool) {
+	return atomic.LoadUint32(&s.limitHours) != 0
+}
+
+// New creates s from conf and properly initializes it.  Don't use s before
+// calling it's Start method.
+func New(conf Config) (s *StatsCtx, err error) {
 	defer withRecovered(&err)
 
-	s = &statsCtx{
-		mu: &sync.Mutex{},
+	s = &StatsCtx{
+		currMu:         &sync.Mutex{},
+		dbMu:           &sync.Mutex{},
+		filename:       conf.Filename,
+		configModified: conf.ConfigModified,
+		httpRegister:   conf.HTTPRegister,
 	}
-	if !checkInterval(conf.LimitDays) {
-		conf.LimitDays = 1
+	if s.limitHours = conf.LimitDays * 24; !checkInterval(conf.LimitDays) {
+		s.limitHours = 24
+	}
+	if s.unitIDGen = newUnitID; conf.UnitID != nil {
+		s.unitIDGen = conf.UnitID
 	}
 
-	s.conf = &Config{}
-	*s.conf = conf
-	s.conf.limit = conf.LimitDays * 24
-	if conf.UnitID == nil {
-		s.conf.UnitID = newUnitID
+	if err = s.dbOpen(); err != nil {
+		return nil, fmt.Errorf("opening database: %w", err)
 	}
 
-	if !s.dbOpen() {
-		return nil, fmt.Errorf("open database")
-	}
-
-	id := s.conf.UnitID()
-	tx := s.beginTxn(true)
+	id := s.unitIDGen()
+	tx := beginTxn(s.db, true)
 	var udb *unitDB
 	if tx != nil {
 		log.Tracef("Deleting old units...")
-		firstID := id - s.conf.limit - 1
+		firstID := id - s.limitHours - 1
 		unitDel := 0
 
 		err = tx.ForEach(newBucketWalker(tx, &unitDel, firstID))
@@ -133,12 +224,11 @@ func createObject(conf Config) (s *statsCtx, err error) {
 		}
 	}
 
-	u := unit{}
-	s.initUnit(&u, id)
-	if udb != nil {
-		deserialize(&u, udb)
-	}
-	s.current = &u
+	u := newUnit(id)
+	// This use of deserialize is safe since the accessed unit has just been
+	// created.
+	u.deserialize(udb)
+	s.curr = u
 
 	log.Debug("stats: initialized")
 
@@ -153,11 +243,11 @@ const errStop errors.Error = "stop iteration"
 // integer that unitDelPtr points to is incremented for every successful
 // deletion.  If the bucket isn't deleted, f returns errStop.
 func newBucketWalker(
-	tx *bolt.Tx,
+	tx *bbolt.Tx,
 	unitDelPtr *int,
 	firstID uint32,
-) (f func(name []byte, b *bolt.Bucket) (err error)) {
-	return func(name []byte, _ *bolt.Bucket) (err error) {
+) (f func(name []byte, b *bbolt.Bucket) (err error)) {
+	return func(name []byte, _ *bbolt.Bucket) (err error) {
 		nameID, ok := unitNameToID(name)
 		if !ok || nameID < firstID {
 			err = tx.DeleteBucket(name)
@@ -178,80 +268,92 @@ func newBucketWalker(
 	}
 }
 
-func (s *statsCtx) Start() {
+// Start makes s process the incoming data.
+func (s *StatsCtx) Start() {
 	s.initWeb()
 	go s.periodicFlush()
 }
 
-func checkInterval(days uint32) bool {
+// checkInterval returns true if days is valid to be used as statistics
+// retention interval.  The valid values are 0, 1, 7, 30 and 90.
+func checkInterval(days uint32) (ok bool) {
 	return days == 0 || days == 1 || days == 7 || days == 30 || days == 90
 }
 
-func (s *statsCtx) dbOpen() bool {
-	var err error
+// dbOpen returns an error if the database can't be opened from the specified
+// file.  It's safe for concurrent use.
+func (s *StatsCtx) dbOpen() (err error) {
 	log.Tracef("db.Open...")
-	s.db, err = bolt.Open(s.conf.Filename, 0o644, nil)
+
+	s.dbMu.Lock()
+	defer s.dbMu.Unlock()
+
+	s.db, err = bbolt.Open(s.filename, 0o644, nil)
 	if err != nil {
-		log.Error("stats: open DB: %s: %s", s.conf.Filename, err)
+		log.Error("stats: open DB: %s: %s", s.filename, err)
 		if err.Error() == "invalid argument" {
 			log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#limitations")
 		}
-		return false
+
+		return err
 	}
+
 	log.Tracef("db.Open")
-	return true
+
+	return nil
 }
 
-// Atomically swap the currently active unit with a new value
-// Return old value
-func (s *statsCtx) swapUnit(new *unit) (u *unit) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
+// newUnitID is the default UnitIDGenFunc that generates the unique id hourly.
+func newUnitID() (id uint32) {
+	const secsInHour = int64(time.Hour / time.Second)
 
-	u = s.current
-	s.current = new
-
-	return u
+	return uint32(time.Now().Unix() / secsInHour)
 }
 
-// Get unit ID for the current hour
-func newUnitID() uint32 {
-	return uint32(time.Now().Unix() / (60 * 60))
+// newUnit allocates the new *unit.
+func newUnit(id uint32) (u *unit) {
+	return &unit{
+		mu:             &sync.RWMutex{},
+		id:             id,
+		nResult:        make([]uint64, resultLast),
+		domains:        make(map[string]uint64),
+		blockedDomains: make(map[string]uint64),
+		clients:        make(map[string]uint64),
+	}
 }
 
-// Initialize a unit
-func (s *statsCtx) initUnit(u *unit, id uint32) {
-	u.id = id
-	u.nResult = make([]uint64, rLast)
-	u.domains = make(map[string]uint64)
-	u.blockedDomains = make(map[string]uint64)
-	u.clients = make(map[string]uint64)
-}
-
-// Open a DB transaction
-func (s *statsCtx) beginTxn(wr bool) *bolt.Tx {
-	db := s.db
+// beginTxn opens a new database transaction.  If writable is true, the
+// transaction will be opened for writing, and for reading otherwise.  It
+// returns nil if the transaction can't be created.
+func beginTxn(db *bbolt.DB, writable bool) (tx *bbolt.Tx) {
 	if db == nil {
 		return nil
 	}
 
-	log.Tracef("db.Begin...")
-	tx, err := db.Begin(wr)
+	log.Tracef("opening a database transaction")
+
+	tx, err := db.Begin(writable)
 	if err != nil {
-		log.Error("db.Begin: %s", err)
+		log.Error("stats: opening a transaction: %s", err)
+
 		return nil
 	}
-	log.Tracef("db.Begin")
+
+	log.Tracef("transaction has been opened")
+
 	return tx
 }
 
-func (s *statsCtx) commitTxn(tx *bolt.Tx) {
+// commitTxn applies the changes made in tx to the database.
+func (s *StatsCtx) commitTxn(tx *bbolt.Tx) {
 	err := tx.Commit()
 	if err != nil {
-		log.Debug("tx.Commit: %s", err)
+		log.Error("stats: committing a transaction: %s", err)
+
 		return
 	}
-	log.Tracef("tx.Commit")
+
+	log.Tracef("transaction has been committed")
 }
 
 // bucketNameLen is the length of a bucket, a 64-bit unsigned integer.
@@ -262,10 +364,10 @@ const bucketNameLen = 8
 
 // idToUnitName converts a numerical ID into a database unit name.
 func idToUnitName(id uint32) (name []byte) {
-	name = make([]byte, bucketNameLen)
-	binary.BigEndian.PutUint64(name, uint64(id))
+	n := [bucketNameLen]byte{}
+	binary.BigEndian.PutUint64(n[:], uint64(id))
 
-	return name
+	return n[:]
 }
 
 // unitNameToID converts a database unit name into a numerical ID.  ok is false
@@ -278,13 +380,6 @@ func unitNameToID(name []byte) (id uint32, ok bool) {
 	return uint32(binary.BigEndian.Uint64(name)), true
 }
 
-func (s *statsCtx) ongoing() (u *unit) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
-	return s.current
-}
-
 // Flush the current unit to DB and delete an old unit when a new hour is started
 // If a unit must be flushed:
 // . lock DB
@@ -293,34 +388,29 @@ func (s *statsCtx) ongoing() (u *unit) {
 // . write the unit to DB
 // . remove the stale unit from DB
 // . unlock DB
-func (s *statsCtx) periodicFlush() {
-	for {
-		ptr := s.ongoing()
-		if ptr == nil {
-			break
-		}
-
-		id := s.conf.UnitID()
-		if ptr.id == id || s.conf.limit == 0 {
+func (s *StatsCtx) periodicFlush() {
+	for ptr := s.ongoing(); ptr != nil; ptr = s.ongoing() {
+		id := s.unitIDGen()
+		// Access the unit's ID with atomic to avoid locking the whole unit.
+		if !s.isEnabled() || atomic.LoadUint32(&ptr.id) == id {
 			time.Sleep(time.Second)
 
 			continue
 		}
 
-		tx := s.beginTxn(true)
+		tx := beginTxn(s.database(), true)
 
-		nu := unit{}
-		s.initUnit(&nu, id)
-		u := s.swapUnit(&nu)
-		udb := serialize(u)
+		nu := newUnit(id)
+		u := s.swapCurrent(nu)
+		udb := u.serialize()
 
 		if tx == nil {
 			continue
 		}
 
-		ok1 := s.flushUnitToDB(tx, u.id, udb)
-		ok2 := s.deleteUnit(tx, id-s.conf.limit)
-		if ok1 || ok2 {
+		flushOK := flushUnitToDB(tx, u.id, udb)
+		delOK := s.deleteUnit(tx, id-atomic.LoadUint32(&s.limitHours))
+		if flushOK || delOK {
 			s.commitTxn(tx)
 		} else {
 			_ = tx.Rollback()
@@ -330,8 +420,8 @@ func (s *statsCtx) periodicFlush() {
 	log.Tracef("periodicFlush() exited")
 }
 
-// Delete unit's data from file
-func (s *statsCtx) deleteUnit(tx *bolt.Tx, id uint32) bool {
+// deleteUnit removes the unit by it's id from the database the tx belongs to.
+func (s *StatsCtx) deleteUnit(tx *bbolt.Tx, id uint32) bool {
 	err := tx.DeleteBucket(idToUnitName(id))
 	if err != nil {
 		log.Tracef("stats: bolt DeleteBucket: %s", err)
@@ -347,10 +437,7 @@ func (s *statsCtx) deleteUnit(tx *bolt.Tx, id uint32) bool {
 func convertMapToSlice(m map[string]uint64, max int) []countPair {
 	a := []countPair{}
 	for k, v := range m {
-		pair := countPair{}
-		pair.Name = k
-		pair.Count = v
-		a = append(a, pair)
+		a = append(a, countPair{Name: k, Count: v})
 	}
 	less := func(i, j int) bool {
 		return a[j].Count < a[i].Count
@@ -370,41 +457,46 @@ func convertSliceToMap(a []countPair) map[string]uint64 {
 	return m
 }
 
-func serialize(u *unit) *unitDB {
-	udb := unitDB{}
-	udb.NTotal = u.nTotal
-
-	udb.NResult = append(udb.NResult, u.nResult...)
+// serialize converts u to the *unitDB.  It's safe for concurrent use.
+func (u *unit) serialize() (udb *unitDB) {
+	u.mu.RLock()
+	defer u.mu.RUnlock()
 
+	var timeAvg uint32 = 0
 	if u.nTotal != 0 {
-		udb.TimeAvg = uint32(u.timeSum / u.nTotal)
+		timeAvg = uint32(u.timeSum / u.nTotal)
 	}
 
-	udb.Domains = convertMapToSlice(u.domains, maxDomains)
-	udb.BlockedDomains = convertMapToSlice(u.blockedDomains, maxDomains)
-	udb.Clients = convertMapToSlice(u.clients, maxClients)
-
-	return &udb
+	return &unitDB{
+		NTotal:         u.nTotal,
+		NResult:        append([]uint64{}, u.nResult...),
+		Domains:        convertMapToSlice(u.domains, maxDomains),
+		BlockedDomains: convertMapToSlice(u.blockedDomains, maxDomains),
+		Clients:        convertMapToSlice(u.clients, maxClients),
+		TimeAvg:        timeAvg,
+	}
 }
 
-func deserialize(u *unit, udb *unitDB) {
+// deserealize assigns the appropriate values from udb to u.  u must not be nil.
+// It's safe for concurrent use.
+func (u *unit) deserialize(udb *unitDB) {
+	if udb == nil {
+		return
+	}
+
+	u.mu.Lock()
+	defer u.mu.Unlock()
+
 	u.nTotal = udb.NTotal
-
-	n := len(udb.NResult)
-	if n < len(u.nResult) {
-		n = len(u.nResult) // n = min(len(udb.NResult), len(u.nResult))
-	}
-	for i := 1; i < n; i++ {
-		u.nResult[i] = udb.NResult[i]
-	}
-
+	u.nResult = make([]uint64, resultLast)
+	copy(u.nResult, udb.NResult)
 	u.domains = convertSliceToMap(udb.Domains)
 	u.blockedDomains = convertSliceToMap(udb.BlockedDomains)
 	u.clients = convertSliceToMap(udb.Clients)
-	u.timeSum = uint64(udb.TimeAvg) * u.nTotal
+	u.timeSum = uint64(udb.TimeAvg) * udb.NTotal
 }
 
-func (s *statsCtx) flushUnitToDB(tx *bolt.Tx, id uint32, udb *unitDB) bool {
+func flushUnitToDB(tx *bbolt.Tx, id uint32, udb *unitDB) bool {
 	log.Tracef("Flushing unit %d", id)
 
 	bkt, err := tx.CreateBucketIfNotExists(idToUnitName(id))
@@ -430,7 +522,7 @@ func (s *statsCtx) flushUnitToDB(tx *bolt.Tx, id uint32, udb *unitDB) bool {
 	return true
 }
 
-func (s *statsCtx) loadUnitFromDB(tx *bolt.Tx, id uint32) *unitDB {
+func (s *StatsCtx) loadUnitFromDB(tx *bbolt.Tx, id uint32) *unitDB {
 	bkt := tx.Bucket(idToUnitName(id))
 	if bkt == nil {
 		return nil
@@ -451,44 +543,44 @@ func (s *statsCtx) loadUnitFromDB(tx *bolt.Tx, id uint32) *unitDB {
 	return &udb
 }
 
-func convertTopSlice(a []countPair) []map[string]uint64 {
-	m := []map[string]uint64{}
+func convertTopSlice(a []countPair) (m []map[string]uint64) {
+	m = make([]map[string]uint64, 0, len(a))
 	for _, it := range a {
-		ent := map[string]uint64{}
-		ent[it.Name] = it.Count
-		m = append(m, ent)
+		m = append(m, map[string]uint64{it.Name: it.Count})
 	}
+
 	return m
 }
 
-func (s *statsCtx) setLimit(limitDays int) {
-	s.conf.limit = uint32(limitDays) * 24
+func (s *StatsCtx) setLimit(limitDays int) {
+	atomic.StoreUint32(&s.limitHours, uint32(24*limitDays))
 	if limitDays == 0 {
 		s.clear()
 	}
 
-	log.Debug("stats: set limit: %d", limitDays)
+	log.Debug("stats: set limit: %d days", limitDays)
 }
 
-func (s *statsCtx) WriteDiskConfig(dc *DiskConfig) {
-	dc.Interval = s.conf.limit / 24
+func (s *StatsCtx) WriteDiskConfig(dc *DiskConfig) {
+	dc.Interval = atomic.LoadUint32(&s.limitHours) / 24
 }
 
-func (s *statsCtx) Close() {
-	u := s.swapUnit(nil)
-	udb := serialize(u)
-	tx := s.beginTxn(true)
-	if tx != nil {
-		if s.flushUnitToDB(tx, u.id, udb) {
+func (s *StatsCtx) Close() {
+	u := s.swapCurrent(nil)
+
+	db := s.database()
+	if tx := beginTxn(db, true); tx != nil {
+		udb := u.serialize()
+		if flushUnitToDB(tx, u.id, udb) {
 			s.commitTxn(tx)
 		} else {
 			_ = tx.Rollback()
 		}
 	}
 
-	if s.db != nil {
+	if db != nil {
 		log.Tracef("db.Close...")
-		_ = s.db.Close()
+		_ = db.Close()
 		log.Tracef("db.Close")
 	}
 
@@ -496,11 +588,11 @@ func (s *statsCtx) Close() {
 }
 
 // Reset counters and clear database
-func (s *statsCtx) clear() {
-	tx := s.beginTxn(true)
+func (s *StatsCtx) clear() {
+	db := s.database()
+	tx := beginTxn(db, true)
 	if tx != nil {
-		db := s.db
-		s.db = nil
+		_ = s.swapDatabase(nil)
 		_ = tx.Rollback()
 		// the active transactions can continue using database,
 		//  but no new transactions will be opened
@@ -509,11 +601,10 @@ func (s *statsCtx) clear() {
 		// all active transactions are now closed
 	}
 
-	u := unit{}
-	s.initUnit(&u, s.conf.UnitID())
-	_ = s.swapUnit(&u)
+	u := newUnit(s.unitIDGen())
+	_ = s.swapCurrent(u)
 
-	err := os.Remove(s.conf.Filename)
+	err := os.Remove(s.filename)
 	if err != nil {
 		log.Error("os.Remove: %s", err)
 	}
@@ -523,13 +614,13 @@ func (s *statsCtx) clear() {
 	log.Debug("stats: cleared")
 }
 
-func (s *statsCtx) Update(e Entry) {
-	if s.conf.limit == 0 {
+func (s *StatsCtx) Update(e Entry) {
+	if !s.isEnabled() {
 		return
 	}
 
 	if e.Result == 0 ||
-		e.Result >= rLast ||
+		e.Result >= resultLast ||
 		e.Domain == "" ||
 		e.Client == "" {
 		return
@@ -540,13 +631,15 @@ func (s *statsCtx) Update(e Entry) {
 		clientID = ip.String()
 	}
 
-	s.mu.Lock()
-	defer s.mu.Unlock()
+	u := s.ongoing()
+	if u == nil {
+		return
+	}
 
-	u := s.current
+	u.mu.Lock()
+	defer u.mu.Unlock()
 
 	u.nResult[e.Result]++
-
 	if e.Result == RNotFiltered {
 		u.domains[e.Domain]++
 	} else {
@@ -558,14 +651,19 @@ func (s *statsCtx) Update(e Entry) {
 	u.nTotal++
 }
 
-func (s *statsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) {
-	tx := s.beginTxn(false)
+func (s *StatsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) {
+	tx := beginTxn(s.database(), false)
 	if tx == nil {
 		return nil, 0
 	}
 
 	cur := s.ongoing()
-	curID := cur.id
+	var curID uint32
+	if cur != nil {
+		curID = atomic.LoadUint32(&cur.id)
+	} else {
+		curID = s.unitIDGen()
+	}
 
 	// Per-hour units.
 	units := []*unitDB{}
@@ -574,14 +672,16 @@ func (s *statsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) {
 		u := s.loadUnitFromDB(tx, i)
 		if u == nil {
 			u = &unitDB{}
-			u.NResult = make([]uint64, rLast)
+			u.NResult = make([]uint64, resultLast)
 		}
 		units = append(units, u)
 	}
 
 	_ = tx.Rollback()
 
-	units = append(units, serialize(cur))
+	if cur != nil {
+		units = append(units, cur.serialize())
+	}
 
 	if len(units) != int(limit) {
 		log.Fatalf("len(units) != limit: %d %d", len(units), limit)
@@ -628,13 +728,13 @@ func statsCollector(units []*unitDB, firstID uint32, timeUnit TimeUnit, ng numsG
 // pairsGetter is a signature for topsCollector argument.
 type pairsGetter func(u *unitDB) (pairs []countPair)
 
-// topsCollector collects statistics about highest values fro the given *unitDB
+// topsCollector collects statistics about highest values from the given *unitDB
 // slice using pg to retrieve data.
 func topsCollector(units []*unitDB, max int, pg pairsGetter) []map[string]uint64 {
 	m := map[string]uint64{}
 	for _, u := range units {
-		for _, it := range pg(u) {
-			m[it.Name] += it.Count
+		for _, cp := range pg(u) {
+			m[cp.Name] += cp.Count
 		}
 	}
 	a2 := convertMapToSlice(m, max)
@@ -668,8 +768,22 @@ func topsCollector(units []*unitDB, max int, pg pairsGetter) []map[string]uint64
   * parental-blocked
   These values are just the sum of data for all units.
 */
-func (s *statsCtx) getData() (statsResponse, bool) {
-	limit := s.conf.limit
+func (s *StatsCtx) getData() (statsResponse, bool) {
+	limit := atomic.LoadUint32(&s.limitHours)
+	if limit == 0 {
+		return statsResponse{
+			TimeUnits: "days",
+
+			TopBlocked: []topAddrs{},
+			TopClients: []topAddrs{},
+			TopQueried: []topAddrs{},
+
+			BlockedFiltering:     []uint64{},
+			DNSQueries:           []uint64{},
+			ReplacedParental:     []uint64{},
+			ReplacedSafebrowsing: []uint64{},
+		}, true
+	}
 
 	timeUnit := Hours
 	if limit/24 > 7 {
@@ -698,7 +812,7 @@ func (s *statsCtx) getData() (statsResponse, bool) {
 
 	// Total counters:
 	sum := unitDB{
-		NResult: make([]uint64, rLast),
+		NResult: make([]uint64, resultLast),
 	}
 	timeN := 0
 	for _, u := range units {
@@ -731,12 +845,12 @@ func (s *statsCtx) getData() (statsResponse, bool) {
 	return data, true
 }
 
-func (s *statsCtx) GetTopClientsIP(maxCount uint) []net.IP {
-	if s.conf.limit == 0 {
+func (s *StatsCtx) GetTopClientsIP(maxCount uint) []net.IP {
+	if !s.isEnabled() {
 		return nil
 	}
 
-	units, _ := s.loadUnits(s.conf.limit)
+	units, _ := s.loadUnits(atomic.LoadUint32(&s.limitHours))
 	if units == nil {
 		return nil
 	}

From 70f85fca210aee4d2f43f26bba9743a300d19e1c Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 8 Aug 2022 15:50:54 +0300
Subject: [PATCH 115/143] Pull request: upd-yaml

Merge in DNS/adguard-home from upd-yaml to master

Squashed commit of the following:

commit f0c3a1896e7eba73b1c8a02533637cdabc89909b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 8 15:28:02 2022 +0300

    home: restore indent lvl

commit b52c124d2e786e8575c58e75efa7d2cd2b70b67f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 8 15:06:41 2022 +0300

    all: upd tools, yaml mod
---
 go.mod                        | 10 ++---
 go.sum                        |  4 +-
 internal/home/config.go       | 72 +++++++++++++++++++++++------------
 internal/home/dns.go          |  2 +-
 internal/home/home.go         | 40 +++++++++----------
 internal/home/upgrade.go      | 15 +++++---
 internal/home/upgrade_test.go |  2 +-
 internal/tools/go.mod         |  4 +-
 internal/tools/go.sum         |  8 ++--
 9 files changed, 91 insertions(+), 66 deletions(-)

diff --git a/go.mod b/go.mod
index fc92c668..43f2847c 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@ require (
 	github.com/lucas-clemente/quic-go v0.28.1
 	github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
 	github.com/mdlayher/netlink v1.6.0
+	github.com/mdlayher/packet v1.0.0
 	// TODO(a.garipov): This package is deprecated; find a new one or use
 	// our own code for that.
 	github.com/mdlayher/raw v0.1.0 // indirect
@@ -29,18 +30,14 @@ require (
 	github.com/ti-mo/netfilter v0.4.0
 	go.etcd.io/bbolt v1.3.6
 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
+	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
 	golang.org/x/net v0.0.0-20220728211354-c7608f3a8462
 	golang.org/x/sys v0.0.0-20220731174439-a90be440212d
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
-	gopkg.in/yaml.v2 v2.4.0
+	gopkg.in/yaml.v3 v3.0.1
 	howett.net/plist v1.0.0
 )
 
-require (
-	github.com/mdlayher/packet v1.0.0
-	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
-)
-
 require (
 	github.com/BurntSushi/toml v1.1.0 // indirect
 	github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
@@ -68,5 +65,4 @@ require (
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/tools v0.1.12 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
diff --git a/go.sum b/go.sum
index 2b4cefe8..afd170d2 100644
--- a/go.sum
+++ b/go.sum
@@ -465,8 +465,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
 grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/home/config.go b/internal/home/config.go
index 4df45ed9..5bd9b98b 100644
--- a/internal/home/config.go
+++ b/internal/home/config.go
@@ -1,6 +1,7 @@
 package home
 
 import (
+	"bytes"
 	"fmt"
 	"net"
 	"os"
@@ -19,7 +20,7 @@ import (
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/golibs/timeutil"
 	"github.com/google/renameio/maybe"
-	yaml "gopkg.in/yaml.v2"
+	yaml "gopkg.in/yaml.v3"
 )
 
 const (
@@ -27,15 +28,36 @@ const (
 	filterDir = "filters" // cache location for downloaded filters, it's under DataDir
 )
 
-// logSettings
+// logSettings are the logging settings part of the configuration file.
+//
+// TODO(a.garipov): Put them into a separate object.
 type logSettings struct {
-	LogCompress   bool   `yaml:"log_compress"`    // Compress determines if the rotated log files should be compressed using gzip (default: false)
-	LogLocalTime  bool   `yaml:"log_localtime"`   // If the time used for formatting the timestamps in is the computer's local time (default: false [UTC])
-	LogMaxBackups int    `yaml:"log_max_backups"` // Maximum number of old log files to retain (MaxAge may still cause them to get deleted)
-	LogMaxSize    int    `yaml:"log_max_size"`    // Maximum size in megabytes of the log file before it gets rotated (default 100 MB)
-	LogMaxAge     int    `yaml:"log_max_age"`     // MaxAge is the maximum number of days to retain old log files
-	LogFile       string `yaml:"log_file"`        // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
-	Verbose       bool   `yaml:"verbose"`         // If true, verbose logging is enabled
+	// File is the path to the log file.  If empty, logs are written to stdout.
+	// If "syslog", logs are written to syslog.
+	File string `yaml:"log_file"`
+
+	// MaxBackups is the maximum number of old log files to retain.
+	//
+	// NOTE: MaxAge may still cause them to get deleted.
+	MaxBackups int `yaml:"log_max_backups"`
+
+	// MaxSize is the maximum size of the log file before it gets rotated, in
+	// megabytes.  The default value is 100 MB.
+	MaxSize int `yaml:"log_max_size"`
+
+	// MaxAge is the maximum duration for retaining old log files, in days.
+	MaxAge int `yaml:"log_max_age"`
+
+	// Compress determines, if the rotated log files should be compressed using
+	// gzip.
+	Compress bool `yaml:"log_compress"`
+
+	// LocalTime determines, if the time used for formatting the timestamps in
+	// is the computer's local time.
+	LocalTime bool `yaml:"log_localtime"`
+
+	// Verbose determines, if verbose (aka debug) logging is enabled.
+	Verbose bool `yaml:"verbose"`
 }
 
 // osConfig contains OS-related configuration.
@@ -223,11 +245,11 @@ var config = &configuration{
 		},
 	},
 	logSettings: logSettings{
-		LogCompress:   false,
-		LogLocalTime:  false,
-		LogMaxBackups: 0,
-		LogMaxSize:    100,
-		LogMaxAge:     3,
+		Compress:   false,
+		LocalTime:  false,
+		MaxBackups: 0,
+		MaxSize:    100,
+		MaxAge:     3,
 	},
 	OSConfig:      &osConfig{},
 	SchemaVersion: currentSchemaVersion,
@@ -366,13 +388,14 @@ func readConfigFile() (fileData []byte, err error) {
 }
 
 // Saves configuration to the YAML file and also saves the user filter contents to a file
-func (c *configuration) write() error {
+func (c *configuration) write() (err error) {
 	c.Lock()
 	defer c.Unlock()
 
 	if Context.auth != nil {
 		config.Users = Context.auth.GetUsers()
 	}
+
 	if Context.tls != nil {
 		tlsConf := tlsConfigSettings{}
 		Context.tls.WriteDiskConfig(&tlsConf)
@@ -418,19 +441,20 @@ func (c *configuration) write() error {
 	config.Clients.Persistent = Context.clients.forConfig()
 
 	configFile := config.getConfigFilename()
-	log.Debug("Writing YAML file: %s", configFile)
-	yamlText, err := yaml.Marshal(&config)
-	if err != nil {
-		log.Error("Couldn't generate YAML file: %s", err)
+	log.Debug("writing config file %q", configFile)
 
-		return err
+	buf := &bytes.Buffer{}
+	enc := yaml.NewEncoder(buf)
+	enc.SetIndent(2)
+
+	err = enc.Encode(config)
+	if err != nil {
+		return fmt.Errorf("generating config file: %w", err)
 	}
 
-	err = maybe.WriteFile(configFile, yamlText, 0o644)
+	err = maybe.WriteFile(configFile, buf.Bytes(), 0o644)
 	if err != nil {
-		log.Error("Couldn't save YAML config: %s", err)
-
-		return err
+		return fmt.Errorf("writing config file: %w", err)
 	}
 
 	return nil
diff --git a/internal/home/dns.go b/internal/home/dns.go
index d51a6dd2..52837826 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -17,7 +17,7 @@ import (
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/ameshkov/dnscrypt/v2"
-	yaml "gopkg.in/yaml.v2"
+	yaml "gopkg.in/yaml.v3"
 )
 
 // Default ports.
diff --git a/internal/home/home.go b/internal/home/home.go
index 0fc16c09..29cb6d5d 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -602,17 +602,17 @@ func configureLogger(args options) {
 		ls.Verbose = true
 	}
 	if args.logFile != "" {
-		ls.LogFile = args.logFile
-	} else if config.LogFile != "" {
-		ls.LogFile = config.LogFile
+		ls.File = args.logFile
+	} else if config.File != "" {
+		ls.File = config.File
 	}
 
 	// Handle default log settings overrides
-	ls.LogCompress = config.LogCompress
-	ls.LogLocalTime = config.LogLocalTime
-	ls.LogMaxBackups = config.LogMaxBackups
-	ls.LogMaxSize = config.LogMaxSize
-	ls.LogMaxAge = config.LogMaxAge
+	ls.Compress = config.Compress
+	ls.LocalTime = config.LocalTime
+	ls.MaxBackups = config.MaxBackups
+	ls.MaxSize = config.MaxSize
+	ls.MaxAge = config.MaxAge
 
 	// log.SetLevel(log.INFO) - default
 	if ls.Verbose {
@@ -623,27 +623,27 @@ func configureLogger(args options) {
 	// happen pretty quickly.
 	log.SetFlags(log.LstdFlags | log.Lmicroseconds)
 
-	if args.runningAsService && ls.LogFile == "" && runtime.GOOS == "windows" {
+	if args.runningAsService && ls.File == "" && runtime.GOOS == "windows" {
 		// When running as a Windows service, use eventlog by default if nothing
 		// else is configured.  Otherwise, we'll simply lose the log output.
-		ls.LogFile = configSyslog
+		ls.File = configSyslog
 	}
 
 	// logs are written to stdout (default)
-	if ls.LogFile == "" {
+	if ls.File == "" {
 		return
 	}
 
-	if ls.LogFile == configSyslog {
+	if ls.File == configSyslog {
 		// Use syslog where it is possible and eventlog on Windows
 		err := aghos.ConfigureSyslog(serviceName)
 		if err != nil {
 			log.Fatalf("cannot initialize syslog: %s", err)
 		}
 	} else {
-		logFilePath := filepath.Join(Context.workDir, ls.LogFile)
-		if filepath.IsAbs(ls.LogFile) {
-			logFilePath = ls.LogFile
+		logFilePath := filepath.Join(Context.workDir, ls.File)
+		if filepath.IsAbs(ls.File) {
+			logFilePath = ls.File
 		}
 
 		_, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
@@ -653,11 +653,11 @@ func configureLogger(args options) {
 
 		log.SetOutput(&lumberjack.Logger{
 			Filename:   logFilePath,
-			Compress:   ls.LogCompress, // disabled by default
-			LocalTime:  ls.LogLocalTime,
-			MaxBackups: ls.LogMaxBackups,
-			MaxSize:    ls.LogMaxSize, // megabytes
-			MaxAge:     ls.LogMaxAge,  // days
+			Compress:   ls.Compress, // disabled by default
+			LocalTime:  ls.LocalTime,
+			MaxBackups: ls.MaxBackups,
+			MaxSize:    ls.MaxSize, // megabytes
+			MaxAge:     ls.MaxAge,  // days
 		})
 	}
 }
diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go
index 85b7e019..15970e7b 100644
--- a/internal/home/upgrade.go
+++ b/internal/home/upgrade.go
@@ -1,6 +1,7 @@
 package home
 
 import (
+	"bytes"
 	"fmt"
 	"net/url"
 	"os"
@@ -17,7 +18,7 @@ import (
 	"github.com/AdguardTeam/golibs/timeutil"
 	"github.com/google/renameio/maybe"
 	"golang.org/x/crypto/bcrypt"
-	yaml "gopkg.in/yaml.v2"
+	yaml "gopkg.in/yaml.v3"
 )
 
 // currentSchemaVersion is the current schema version.
@@ -104,16 +105,20 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
 		return fmt.Errorf("unknown configuration schema version %d", oldVersion)
 	}
 
-	body, err := yaml.Marshal(diskConf)
+	buf := &bytes.Buffer{}
+	enc := yaml.NewEncoder(buf)
+	enc.SetIndent(2)
+
+	err = enc.Encode(diskConf)
 	if err != nil {
 		return fmt.Errorf("generating new config: %w", err)
 	}
 
-	config.fileData = body
+	config.fileData = buf.Bytes()
 	confFile := config.getConfigFilename()
-	err = maybe.WriteFile(confFile, body, 0o644)
+	err = maybe.WriteFile(confFile, config.fileData, 0o644)
 	if err != nil {
-		return fmt.Errorf("saving new config: %w", err)
+		return fmt.Errorf("writing new config: %w", err)
 	}
 
 	return nil
diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go
index 6703ab2f..a5267032 100644
--- a/internal/home/upgrade_test.go
+++ b/internal/home/upgrade_test.go
@@ -190,7 +190,7 @@ func testDiskConf(schemaVersion int) (diskConf yobj) {
 	return diskConf
 }
 
-// testDNSConf creates a DNS config for test the way gopkg.in/yaml.v2 would
+// testDNSConf creates a DNS config for test the way gopkg.in/yaml.v3 would
 // unmarshal it.  In YAML, keys aren't guaranteed to always only be strings.
 func testDNSConf(schemaVersion int) (dnsConf yobj) {
 	dnsConf = yobj{
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index 19af5cae..74969289 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/fzipp/gocyclo v0.6.0
 	github.com/golangci/misspell v0.3.5
 	github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8
-	github.com/kisielk/errcheck v1.6.1
+	github.com/kisielk/errcheck v1.6.2
 	github.com/kyoh86/looppointer v0.1.7
 	github.com/securego/gosec/v2 v2.12.0
 	golang.org/x/tools v0.1.12
@@ -27,6 +27,6 @@ require (
 	golang.org/x/exp/typeparams v0.0.0-20220722155223-a9213eeb770e // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
 	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
-	golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect
+	golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 )
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index f13b3bdf..144cd0eb 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -218,8 +218,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
-github.com/kisielk/errcheck v1.6.1 h1:cErYo+J4SmEjdXZrVXGwLJCE2sB06s23LpkcyWNrT+s=
-github.com/kisielk/errcheck v1.6.1/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
+github.com/kisielk/errcheck v1.6.2 h1:uGQ9xI8/pgc9iOoCe7kWQgRE6SBTrCGmTSf0LrEtY7c=
+github.com/kisielk/errcheck v1.6.2/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -549,8 +549,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME=
+golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

From 50565bed3b5bcd934781940d398571a4ecb9b593 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 10 Aug 2022 13:39:28 +0300
Subject: [PATCH 116/143] Pull request: upd-websvc

Merge in DNS/adguard-home from upd-websvc to master

Squashed commit of the following:

commit 30d6a2dc5083efd91479bcbe20f03c37baddbf94
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 9 18:55:42 2022 +0300

    all: upd openapi, websvc
---
 internal/aghnet/net.go            |  11 ++-
 internal/aghnet/net_test.go       |   4 +-
 internal/home/controlinstall.go   |   2 +-
 internal/v1/cmd/cmd.go            |  12 +--
 internal/v1/websvc/websvc.go      |   6 +-
 internal/v1/websvc/websvc_test.go |  10 +--
 openapi/v1.yaml                   | 121 ++++++++++++++++++++++++++++--
 7 files changed, 135 insertions(+), 31 deletions(-)

diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go
index 268380bd..2de9c630 100644
--- a/internal/aghnet/net.go
+++ b/internal/aghnet/net.go
@@ -154,10 +154,13 @@ func GetValidNetInterfacesForWeb() (netIfaces []*NetInterface, err error) {
 	return netIfaces, nil
 }
 
-// GetInterfaceByIP returns the name of interface containing provided ip.
+// InterfaceByIP returns the name of the interface bound to ip.
 //
-// TODO(e.burkov):  See TODO on GetValidInterfacesForWeb.
-func GetInterfaceByIP(ip net.IP) string {
+// TODO(a.garipov, e.burkov): This function is technically incorrect, since one
+// IP address can be shared by multiple interfaces in some configurations.
+//
+// TODO(e.burkov):  See TODO on GetValidNetInterfacesForWeb.
+func InterfaceByIP(ip net.IP) (ifaceName string) {
 	ifaces, err := GetValidNetInterfacesForWeb()
 	if err != nil {
 		return ""
@@ -177,7 +180,7 @@ func GetInterfaceByIP(ip net.IP) string {
 // GetSubnet returns pointer to net.IPNet for the specified interface or nil if
 // the search fails.
 //
-// TODO(e.burkov):  See TODO on GetValidInterfacesForWeb.
+// TODO(e.burkov):  See TODO on GetValidNetInterfacesForWeb.
 func GetSubnet(ifaceName string) *net.IPNet {
 	netIfaces, err := GetValidNetInterfacesForWeb()
 	if err != nil {
diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go
index 40d395ba..d4ee59ee 100644
--- a/internal/aghnet/net_test.go
+++ b/internal/aghnet/net_test.go
@@ -132,7 +132,7 @@ func TestGatewayIP(t *testing.T) {
 	}
 }
 
-func TestGetInterfaceByIP(t *testing.T) {
+func TestInterfaceByIP(t *testing.T) {
 	ifaces, err := GetValidNetInterfacesForWeb()
 	require.NoError(t, err)
 	require.NotEmpty(t, ifaces)
@@ -142,7 +142,7 @@ func TestGetInterfaceByIP(t *testing.T) {
 			require.NotEmpty(t, iface.Addresses)
 
 			for _, ip := range iface.Addresses {
-				ifaceName := GetInterfaceByIP(ip)
+				ifaceName := InterfaceByIP(ip)
 				require.Equal(t, iface.Name, ifaceName)
 			}
 		})
diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go
index 5304b794..c46f3459 100644
--- a/internal/home/controlinstall.go
+++ b/internal/home/controlinstall.go
@@ -216,7 +216,7 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request)
 func handleStaticIP(ip net.IP, set bool) staticIPJSON {
 	resp := staticIPJSON{}
 
-	interfaceName := aghnet.GetInterfaceByIP(ip)
+	interfaceName := aghnet.InterfaceByIP(ip)
 	resp.Static = "no"
 
 	if len(interfaceName) == 0 {
diff --git a/internal/v1/cmd/cmd.go b/internal/v1/cmd/cmd.go
index 1f1cc64e..2f61509b 100644
--- a/internal/v1/cmd/cmd.go
+++ b/internal/v1/cmd/cmd.go
@@ -8,12 +8,11 @@ import (
 	"context"
 	"io/fs"
 	"math/rand"
-	"net"
+	"net/netip"
 	"time"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/v1/websvc"
 	"github.com/AdguardTeam/golibs/log"
-	"github.com/AdguardTeam/golibs/netutil"
 )
 
 // Main is the entry point of application.
@@ -32,12 +31,9 @@ func Main(clientBuildFS fs.FS) {
 
 	// TODO(a.garipov): Make configurable.
 	web := websvc.New(&websvc.Config{
-		Addresses: []*netutil.IPPort{{
-			IP:   net.IP{127, 0, 0, 1},
-			Port: 3001,
-		}},
-		Start:   start,
-		Timeout: 60 * time.Second,
+		Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:3001")},
+		Start:     start,
+		Timeout:   60 * time.Second,
 	})
 
 	err := web.Start()
diff --git a/internal/v1/websvc/websvc.go b/internal/v1/websvc/websvc.go
index 9af22a15..1a9a6a09 100644
--- a/internal/v1/websvc/websvc.go
+++ b/internal/v1/websvc/websvc.go
@@ -10,13 +10,13 @@ import (
 	"io"
 	"net"
 	"net/http"
+	"net/netip"
 	"sync"
 	"time"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/v1/agh"
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
-	"github.com/AdguardTeam/golibs/netutil"
 	httptreemux "github.com/dimfeld/httptreemux/v5"
 )
 
@@ -27,11 +27,11 @@ type Config struct {
 	TLS *tls.Config
 
 	// Addresses are the addresses on which to serve the plain HTTP API.
-	Addresses []*netutil.IPPort
+	Addresses []netip.AddrPort
 
 	// SecureAddresses are the addresses on which to serve the HTTPS API.  If
 	// SecureAddresses is not empty, TLS must not be nil.
-	SecureAddresses []*netutil.IPPort
+	SecureAddresses []netip.AddrPort
 
 	// Start is the time of start of AdGuard Home.
 	Start time.Time
diff --git a/internal/v1/websvc/websvc_test.go b/internal/v1/websvc/websvc_test.go
index 459ffd14..de4a9f5d 100644
--- a/internal/v1/websvc/websvc_test.go
+++ b/internal/v1/websvc/websvc_test.go
@@ -3,14 +3,13 @@ package websvc_test
 import (
 	"context"
 	"io"
-	"net"
 	"net/http"
+	"net/netip"
 	"net/url"
 	"testing"
 	"time"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/v1/websvc"
-	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -30,11 +29,8 @@ func newTestServer(t testing.TB) (svc *websvc.Service, addr string) {
 	t.Helper()
 
 	c := &websvc.Config{
-		TLS: nil,
-		Addresses: []*netutil.IPPort{{
-			IP:   net.IP{127, 0, 0, 1},
-			Port: 0,
-		}},
+		TLS:             nil,
+		Addresses:       []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")},
 		SecureAddresses: nil,
 		Timeout:         testTimeout,
 		Start:           testStart,
diff --git a/openapi/v1.yaml b/openapi/v1.yaml
index ff515692..bdf49383 100644
--- a/openapi/v1.yaml
+++ b/openapi/v1.yaml
@@ -144,6 +144,13 @@
   '/health-check':
     'get':
       'operationId': 'HealthCheck'
+      'responses':
+        '200':
+          'description': >
+            An OK response.
+          'content':
+            'text/plain':
+              'example': 'OK'
       'servers':
         - 'url': '/'
       'summary': 'Check if the server is up.'
@@ -874,6 +881,26 @@
       'tags':
       - 'settings'
 
+  '/settings/http':
+    'patch':
+      'operationId': 'PatchV1SettingsHttp'
+      'requestBody':
+        '$ref': '#/components/requestBodies/PatchV1SettingsHttpReq'
+      'responses':
+        '200':
+          '$ref': '#/components/responses/PatchV1SettingsHttpResp'
+        '400':
+          '$ref': '#/components/responses/BadRequestResp'
+        '401':
+          '$ref': '#/components/responses/UnauthorizedResp'
+        '422':
+          '$ref': '#/components/responses/UnprocessableEntityResp'
+        '500':
+          '$ref': '#/components/responses/InternalServerErrorResp'
+      'summary': 'Update web interface settings.'
+      'tags':
+      - 'settings'
+
   '/settings/log':
     'patch':
       'operationId': 'PatchV1SettingsLog'
@@ -1209,6 +1236,13 @@
             '$ref': '#/components/schemas/PatchV1SettingsDnsReq'
       'required': true
 
+    'PatchV1SettingsHttpReq':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsHttpReq'
+      'required': true
+
     'PatchV1SettingsLogReq':
       'content':
         'application/json':
@@ -1604,6 +1638,14 @@
       'description': >
         A successful response to a `PATCH /api/v1/settings/dns` request.
 
+    'PatchV1SettingsHttpResp':
+      'content':
+        'application/json':
+          'schema':
+            '$ref': '#/components/schemas/PatchV1SettingsHttpResp'
+      'description': >
+        A successful response to a `PATCH /api/v1/settings/http` request.
+
     'PatchV1SettingsLogResp':
       'content':
         'application/json':
@@ -2229,6 +2271,9 @@
       - 'description': >
           DNS server settings.
         'example':
+          'addresses':
+          - '127.0.0.1:53'
+          - '192.168.1.1:53'
           'blocking_mode': 'default'
           'bootstrap_servers':
           - '9.9.9.10'
@@ -2244,7 +2289,9 @@
           'upstream_servers':
           - '1.1.1.1'
           - '8.8.8.8'
+          'upstream_timeout': '1s'
         'required':
+        - 'addresses'
         - 'blocking_mode'
         - 'bootstrap_servers'
         - 'cache_size'
@@ -2256,6 +2303,7 @@
         - 'rate_limit'
         - 'upstream_mode'
         - 'upstream_servers'
+        - 'upstream_timeout'
 
     'DnsSettingsPatch':
       'description': >
@@ -2265,6 +2313,13 @@
         'upstream_servers':
         - '1.1.1.1'
       'properties':
+        'addresses':
+          'description': >
+            Addresses on which to serve plain DNS, in ip:port format.  Empty
+            array disables plain DNS.
+          'items':
+            'type': 'string'
+          'type': 'array'
         'blocking_ipv4':
           'description': >
             IPv4 address to respond with when `blocking_mode` is `custom_ip`.
@@ -2340,6 +2395,10 @@
           'items':
             '$ref': '#/components/schemas/UpstreamServerAddr'
           'type': 'array'
+        'upstream_timeout':
+          'description': >
+            Upstream request timeout, as a human readable duration.
+          'type': 'string'
       'type': 'object'
 
     'DnsType':
@@ -3038,6 +3097,8 @@
           '$ref': '#/components/schemas/DhcpSettings'
         'dns':
           '$ref': '#/components/schemas/DnsSettings'
+        'http':
+          '$ref': '#/components/schemas/HttpSettings'
         'log':
           '$ref': '#/components/schemas/LogSettings'
         'protection':
@@ -3049,6 +3110,7 @@
       'required':
       - 'dhcp'
       - 'dns'
+      - 'http'
       - 'log'
       - 'protection'
       - 'stats'
@@ -3432,6 +3494,53 @@
       - 'version'
       'type': 'object'
 
+    'HttpSettings':
+      'allOf':
+      - '$ref': '#/components/schemas/HttpSettingsPatch'
+      - 'description': >
+          HTTP interface server settings.
+
+          **TODO(a.garipov): Finish, split from TLS settings.**
+        'example':
+          'addresses':
+          - '127.0.0.1:80'
+          - '192.168.1.1:80'
+          'secure_addresses':
+          - '127.0.0.1:443'
+          - '192.168.1.1:443'
+          'force_https': true
+        'required':
+        - 'addresses'
+        - 'secure_addresses'
+        - 'force_https'
+
+    'HttpSettingsPatch':
+      'description': >
+        HTTP server settings update object.
+      'example':
+        'force_https': false
+      'properties':
+        'addresses':
+          'description': >
+            Addresses on which to serve the plain-HTTP web interface and API, in
+            ip:port format.  Empty array disables the web interface over plain
+            HTTP.
+          'items':
+            'type': 'string'
+          'type': 'array'
+        'force_https':
+          'description': >
+            If `true`, enabled the HTTP-to-HTTPS redirect.
+          'type': 'boolean'
+        'secure_addresses':
+          'description': >
+            Addresses on which to serve the HTTPS web interface and API, in
+            ip:port format.  Empty array disables the web interface over HTTPS.
+          'items':
+            'type': 'string'
+          'type': 'array'
+      'type': 'object'
+
     'InternalServerErrorResp':
       'example':
         'code': 'RNT000'
@@ -3725,6 +3834,12 @@
     'PatchV1SettingsDnsResp':
       '$ref': '#/components/schemas/DnsSettings'
 
+    'PatchV1SettingsHttpReq':
+      '$ref': '#/components/schemas/HttpSettingsPatch'
+
+    'PatchV1SettingsHttpResp':
+      '$ref': '#/components/schemas/HttpSettings'
+
     'PatchV1SettingsLogReq':
       '$ref': '#/components/schemas/LogSettingsPatch'
 
@@ -4750,7 +4865,6 @@
         'example':
           'certificate_path': '/etc/ssl/example.com.cert'
           'enabled': true
-          'force_https': true
           'port_dns_over_quic': 784
           'port_dns_over_tls': 853
           'port_https': 443
@@ -4758,7 +4872,6 @@
           'server_name': 'dns.example.com'
         'required':
         - 'enabled'
-        - 'force_https'
         - 'port_dns_over_quic'
         - 'port_dns_over_tls'
         - 'port_https'
@@ -4793,10 +4906,6 @@
             over HTTPS, and the DNS server will listen requests over
             DNS-over-TLS and other protocols.
           'type': 'boolean'
-        'force_https':
-          'description': >
-            If `true`, enabled the HTTP-to-HTTPS redirect.
-          'type': 'boolean'
         'port_dns_over_quic':
           'default': 784
           'description': >

From 14fd995ae9a4a9f8e4a56f2d0a33aadc7bc42035 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 10 Aug 2022 21:03:13 +0300
Subject: [PATCH 117/143] Pull request: add-ar-i18n

Merge in DNS/adguard-home from add-ar-i18n to master

Squashed commit of the following:

commit 6ef7c70bceb6f6ebabd81011154022a75fc91bd3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 10 20:55:39 2022 +0300

    client: add ar locale
---
 .twosky.json                 |   5 +-
 CHANGELOG.md                 |   3 +-
 client/src/__locales/ar.json | 634 +++++++++++++++++++++++++++++++++++
 client/src/i18n.js           |   2 +
 internal/home/i18n.go        |   5 +-
 5 files changed, 646 insertions(+), 3 deletions(-)
 create mode 100644 client/src/__locales/ar.json

diff --git a/.twosky.json b/.twosky.json
index 244df6a9..e7721ca6 100644
--- a/.twosky.json
+++ b/.twosky.json
@@ -2,8 +2,11 @@
   {
     "project_id": "home",
     "base_locale": "en",
-    "localizable_files": ["client/src/__locales/en.json"],
+    "localizable_files": [
+      "client/src/__locales/en.json"
+    ],
     "languages": {
+      "ar": "العربية",
       "be": "Беларуская",
       "bg": "Български",
       "cs": "Český",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce333385..00b2820d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ and this project adheres to
 
 ### Added
 
+- Arabic localization.
 - Support for Discovery of Designated Resolvers (DDR) according to the [RFC
   draft][ddr-draft] ([#4463]).
 
@@ -462,7 +463,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
 
 - Upstream server information for responses from cache ([#3772]).  Note that old
   log entries concerning cached responses won't include that information.
-- Finnish and Ukrainian translations.
+- Finnish and Ukrainian localizations.
 - Setting the timeout for IP address pinging in the "Fastest IP address" mode
   through the new `fastest_timeout` field in the configuration file ([#1992]).
 - Static IP address detection on FreeBSD ([#3289]).
diff --git a/client/src/__locales/ar.json b/client/src/__locales/ar.json
new file mode 100644
index 00000000..2384ecaf
--- /dev/null
+++ b/client/src/__locales/ar.json
@@ -0,0 +1,634 @@
+{
+    "client_settings": "الإعدادات",
+    "example_upstream_reserved": "يمكنك تحديد <0> DNS upstream لنطاق معين (نطاقات) </0>",
+    "example_upstream_comment": "يمكنك تحديد تعليق",
+    "upstream_parallel": "استخدام الاستعلامات المتوازية لتسريع الحل عن طريق الاستعلام في وقت واحد عن جميع خوادم المنبع",
+    "parallel_requests": "طلبات موازية",
+    "load_balancing": "توزيع الحمل",
+    "load_balancing_desc": "الاستعلام عن خادم واحد في كل مرة سيستخدم AdGuard  الرئيسية الخوارزمية العشوائية الموزونة لاختيار الخادم بحيث يتم استخدام أسرع خادم في كثير من الأحيان",
+    "bootstrap_dns": "خوادم Bootstrap DNS",
+    "bootstrap_dns_desc": "يتم استخدام خوادم Bootstrap DNS لحل عناوين IP الخاصة بمحللات DoH / DoT التي تحددها على هيئة تدفقات.",
+    "local_ptr_title": "خوادم DNS العكسية الخاصة",
+    "local_ptr_desc": "خوادم DNS التي يستخدمها AdGuard Home لاستعلامات PTR المحلية. تُستخدم هذه الخوادم لحل أسماء المضيفين للعملاء بعناوين IP خاصة ، على سبيل المثال \"192.168.12.34\" ، باستخدام DNS العكسي. في حالة عدم التعيين ، يستخدم AdGuard Home عناوين محللات DNS الافتراضية لنظام التشغيل الخاص بك باستثناء عناوين AdGuard Home نفسها.",
+    "local_ptr_default_resolver": "بشكل افتراضي ، يستخدم AdGuard Home محللات DNS العكسية التالية: {{ip}}.",
+    "local_ptr_no_default_resolver": "لم يتمكن AdGuard Home من تحديد محللات DNS العكسية المناسبة لهذا النظام.",
+    "local_ptr_placeholder": "أدخل عنوان خادم واحد لكل سطر",
+    "resolve_clients_title": "تفعيل التحليل العكسي لعناوين IP للعملاء",
+    "resolve_clients_desc": "حل عكسيًا لعناوين IP للعملاء في أسماء مضيفيهم عن طريق إرسال استعلامات PTR إلى أدوات الحل المقابلة (خوادم DNS الخاصة للعملاء المحليين ، والخوادم الأولية للعملاء الذين لديهم عناوين IP عامة).",
+    "use_private_ptr_resolvers_title": "استخدم محللات DNS العكسية الخاصة",
+    "use_private_ptr_resolvers_desc": "قم بإجراء عمليات بحث DNS عكسية عن العناوين التي يتم تقديمها محليًا باستخدام هذه الخوادم الأولية. في حالة التعطيل ، يستجيب AdGuard Home مع NXDOMAIN لجميع طلبات PTR هذه باستثناء العملاء المعروفين من DHCP و / etc / hosts وما إلى ذلك.",
+    "check_dhcp_servers": "تحقق من خوادم DHCP",
+    "save_config": "حفظ الإعدادات",
+    "enabled_dhcp": "خادم DHCP مفعل",
+    "disabled_dhcp": "خادم DHCP غير مفعل",
+    "unavailable_dhcp": "DHCP غير متوفر",
+    "unavailable_dhcp_desc": "لا يمكن لـ AdGuard Home تشغيل خادم DHCP على نظام التشغيل الخاص بك",
+    "dhcp_title": "خادم DHCP (تجريبي!)",
+    "dhcp_description": "إذا كان جهاز الراوتر الخاص بك لا يوفر إعدادات DHCP ، يمكنك استخدام خادم DHCP المدمج في AdGuard.",
+    "dhcp_enable": "فعل خادم DHCP",
+    "dhcp_disable": "عطل خادم DHCP",
+    "dhcp_not_found": "من الآمن تمكين خادم DHCP المدمج - لم نعثر على أي خوادم DHCP نشطة على الشبكة. ومع ذلك ، نشجعك على إعادة التحقق يدويًا لأن اختبارنا التلقائي في الوقت الحالي لا يوفر ضمانًا بنسبة 100٪.",
+    "dhcp_found": "تم العثور على خادم DHCP نشط على الشبكة. وبالتالي لا ينصح بتفعيل خادم DHCP المدمج.",
+    "dhcp_leases": "عقود إيجار DHCP",
+    "dhcp_static_leases": "إيجارات DHCP الثابتة",
+    "dhcp_leases_not_found": "لم يتم العثور على عقود إيجار DHCP",
+    "dhcp_config_saved": "الإعدادات محفوظة لخادم DHCP",
+    "dhcp_ipv4_settings": "DHCP IPv4 إعدادات",
+    "dhcp_ipv6_settings": "DHCP IPv6 إعدادات",
+    "form_error_required": "الحقل مطلوب",
+    "form_error_ip4_format": "عنوان IPv4 غير صالح",
+    "form_error_ip4_range_start_format": "عناوين البداية لـIPv4 غير صالحة للنطاق",
+    "form_error_ip4_range_end_format": "عناوين IPv4 غير صالحة لنطاق النهاية",
+    "form_error_ip4_gateway_format": "عنوان IPv4 غير صالح للبوابة",
+    "form_error_ip6_format": "عنوان IPv6 غير صالح",
+    "form_error_ip_format": "عنوان IP غير صحيح",
+    "form_error_mac_format": "عنوان MAC غير صالح",
+    "form_error_client_id_format": "يجب أن يحتوي معرف العميل على الأرقام والأحرف الصغيرة والواصلات فقط",
+    "form_error_server_name": "اسم الخادم غير صالح",
+    "form_error_subnet": "لا تحتوي الشبكة الفرعية \"{{cidr}}\" على عنوان IP \"{{ip}}\"",
+    "form_error_positive": "يجب أن يكون أكبر من 0",
+    "out_of_range_error": "يجب أن يكون خارج النطاق \"{{start}}\" - \"{{end}}\"",
+    "lower_range_start_error": "يجب أن يكون أقل من نطاق البداية",
+    "greater_range_start_error": "يجب أن يكون أكبر من نطاق البداية",
+    "greater_range_end_error": "يجب أن يكون أكبر من نطاق النهاية",
+    "subnet_error": "يجب أن تكون العناوين في شبكة فرعية واحدة",
+    "gateway_or_subnet_invalid": "قناع الشبكة الفرعية غير صالح",
+    "dhcp_form_gateway_input": "IP البوابة",
+    "dhcp_form_subnet_input": "قناع الشبكة الفرعية",
+    "dhcp_form_range_title": "مجموعة عناوين IP",
+    "dhcp_form_range_start": "نطاق البداية",
+    "dhcp_form_range_end": "نطاق النهاية",
+    "dhcp_form_lease_title": "مدة تأجير DHCP (بالثواني)",
+    "dhcp_form_lease_input": "مدة الإيجار",
+    "dhcp_interface_select": "حدد واجهة DHCP",
+    "dhcp_hardware_address": "عناوين الأجهزة",
+    "dhcp_ip_addresses": "عناوين الـIP",
+    "ip": "IP",
+    "dhcp_table_hostname": "اسم المضيف",
+    "dhcp_table_expires": "يتنهي في",
+    "dhcp_warning": "إذا كنت تريد تمكين خادم DHCP على أي حال ، فتأكد من عدم وجود خادم DHCP نشط آخر في شبكتك. خلاف ذلك ، يمكن أن يعطل خدمة الإنترنت للأجهزة المتصلة!",
+    "dhcp_error": "لم نتمكن من تحديد ما إذا كان هناك خادم DHCP آخر في الشبكة.",
+    "dhcp_static_ip_error": "من أجل استخدام خادم DHCP ، يجب تعيين عنوان IP ثابت. فشلنا في تحديد ما إذا تم تكوين واجهة الشبكة هذه باستخدام عنوان IP ثابت. يرجى تعيين عنوان IP ثابت يدويًا.",
+    "dhcp_dynamic_ip_found": "يستخدم نظامك عنوان IP الديناميكي للواجهة <0>{{interfaceName}}</0>. من أجل استعمال خادم DHCP ، يجب تعيين عنوان IP ثابت. عنوان IP الحالي الخاص بك هو <0>{{ipAddress}}</0>. إذا ضغطت على زر تفعيل DHCP سنقوم تلقائيًا بتعيين عنوان الIP هذا على أنه ثابت.",
+    "dhcp_lease_added": "تمت أضافة مدة الايجار \"{{key}}\" بنجاح",
+    "dhcp_lease_deleted": "تمت ازالة مدة الايجار \"{{key}}\" بنجاح",
+    "dhcp_new_static_lease": "عقد إيجار ثابت جديد",
+    "dhcp_static_leases_not_found": "لم يتم العثور على عقود إيجار ثابتة DHCP",
+    "dhcp_add_static_lease": "إضافة عقد إيجار ثابت",
+    "dhcp_reset_leases": "إعادة تعيين كافة عقود الإيجار",
+    "dhcp_reset_leases_confirm": "هل أنت متأكد أنك تريد إعادة تعيين كافة عقود الإيجار؟",
+    "dhcp_reset_leases_success": "إعادة تعيين تأجير DHCP بنجاح",
+    "dhcp_reset": "هل أنت متأكد من أنك تريد إعادة تعيين تكوين DHCP؟",
+    "country": "الدولة",
+    "city": "المدينة",
+    "delete_confirm": "هل أنت متأكد من أنك تريد حذف \"{{key}}\"؟",
+    "form_enter_hostname": "أدخل اسم الhostname",
+    "error_details": "مزيد من التفاصيل حول الخطأ",
+    "response_details": "تفاصيل الاستجابة",
+    "request_details": "تفاصيل الطلب",
+    "client_details": "تفاصيل العميل",
+    "details": "التفاصيل",
+    "back": "رجوع",
+    "dashboard": "لوحة القيادة",
+    "settings": "الإعدادات",
+    "filters": "الفلاتر",
+    "filter": "فلتر",
+    "query_log": "سجل الQuery",
+    "compact": "المدمج",
+    "nothing_found": "لم يتم العثور علي شيء...",
+    "faq": "أسئلة مكررة",
+    "version": "الإصدار",
+    "address": "العناوين",
+    "protocol": "البروتوكول",
+    "on": "ON",
+    "off": "OFF",
+    "copyright": "حقوق النشر",
+    "homepage": "الصفحة الرئيسية",
+    "report_an_issue": "الإبلاغ عن مشكلة",
+    "privacy_policy": "سياسة الخصوصية",
+    "enable_protection": "تفعيل الحماية",
+    "enabled_protection": "الحماية مفعلة",
+    "disable_protection": "تعطيل الحماية",
+    "disabled_protection": "الحماية غير مفعلة",
+    "refresh_statics": "تحيين الإحصائيات",
+    "dns_query": "DNS Queries",
+    "blocked_by": "<0>تم حظره بواسطة الفلاتر</0>",
+    "stats_malware_phishing": "حسر البرامج الضارة / والتصيّد",
+    "stats_adult": "حظر مواقع الويب الخاصة بالبالغين",
+    "stats_query_domain": "اعلى النطاقات التي تم الاستعلام عنها",
+    "for_last_24_hours": "لأخر 24 ساعة",
+    "for_last_days": "لآخر {{value}} يوم",
+    "for_last_days_plural": "لآخر {{count}} ايام",
+    "stats_disabled": "تم تعطيل الإحصائيات. يمكنك تشغيله من <0> صفحة الإعدادات </0>.",
+    "stats_disabled_short": "تم تعطيل الإحصائيات",
+    "no_domains_found": "لم يتم العثور على النطاق",
+    "requests_count": "عدد الطلبات",
+    "top_blocked_domains": "اعلى الدومينات المحظورة",
+    "top_clients": "كبار العملاء",
+    "no_clients_found": "لم يتم العثور على عملاء",
+    "general_statistics": "الإحصاءات العامة",
+    "number_of_dns_query_days": "عدد استعلامات DNS التي تمت معالجتها لآخر {{count}} يوم",
+    "number_of_dns_query_days_plural": "عدد استعلامات DNS التي تمت معالجتها لآخر {{count}} أيام",
+    "number_of_dns_query_24_hours": "عدد استعلامات DNS التي تمت معالجتها لآخر 24 ساعة",
+    "number_of_dns_query_blocked_24_hours": "عدد طلبات DNS المحظورة بواسطة فلاتر adblock وقوائم حظر المضيفين",
+    "number_of_dns_query_blocked_24_hours_by_sec": "عدد طلبات DNS التي تم حظرها من قبل وحدة أمان التصفح AdGuard",
+    "number_of_dns_query_blocked_24_hours_adult": "عدد من المواقع (الإباحية) للبالغين تم حجبها",
+    "enforced_save_search": "فرض البحث الآمن",
+    "number_of_dns_query_to_safe_search": "عدد طلبات DNS لمحركات البحث التي تم فرض البحث الآمن عنها",
+    "average_processing_time": "متوسط وقت المعالجة",
+    "average_processing_time_hint": "متوسط الوقت بالمللي ثانية عند معالجة طلب DNS",
+    "block_domain_use_filters_and_hosts": "حظر النطاقات باستخدام عوامل التصفية وملفات المضيفين",
+    "filters_block_toggle_hint": "يمكنك إعداد قواعد حظر في <a>المرشحات</a> اعدادات.",
+    "use_adguard_browsing_sec": "استخدم خدمة الويب الأمنية لتصفح AdGuard",
+    "use_adguard_browsing_sec_hint": "سيتحقق AdGuard Home مما إذا كان النطاق محظورًا بواسطة خدمة الويب الخاصة بأمان التصفح. سيستخدم واجهة برمجة تطبيقات بحث صديقة للخصوصية لإجراء الفحص: يتم إرسال بادئة قصيرة فقط من تجزئة اسم المجال SHA256 إلى الخادم.",
+    "use_adguard_parental": "استخدام خدمة AdGuard للرقابة الأبوية على الويب",
+    "use_adguard_parental_hint": "سيتحقق AdGuard Home مما إذا كان النطاق يحتوي على محتوى للبالغين. إنه يستخدم نفس واجهة برمجة التطبيقات الصديقة للخصوصية مثل خدمة الويب الأمنية للتصفح.",
+    "enforce_safe_search": "استخدم البحث الآمن",
+    "enforce_save_search_hint": "سيفرض AdGuard Home البحث الآمن في محركات البحث التالية: Google وYouTube وBing وDuckDuckGo وYandex وPixabay.",
+    "no_servers_specified": "لم يتم تحديد خوادم",
+    "general_settings": "الإعدادات العامة",
+    "dns_settings": "إعدادات الـ DNS",
+    "dns_blocklists": "قوائم حظر DNS",
+    "dns_allowlists": "قوائم السماح لـ DNS",
+    "dns_blocklists_desc": "سيقوم AdGuard Home بحظر النطاقات المطابقة لقوائم الحظر",
+    "dns_allowlists_desc": "سيتم السماح بالنطاقات من قوائم DNS المسموحة حتى لو كانت في أي من قوائم الحظر",
+    "custom_filtering_rules": "قواعد التصفية المخصصة",
+    "encryption_settings": "إعدادات التعمية",
+    "dhcp_settings": "إعدادات DHCP",
+    "upstream_dns": "خادم DNS لـ Upstream",
+    "upstream_dns_help": "أدخل عنوان خادم واحد في كل سطر. <a>تعرف على المزيد</a> حول تكوين خوادم DNS الأولية.",
+    "upstream_dns_configured_in_file": "تم اعداده في {{path}}",
+    "test_upstream_btn": "اختبار upstream",
+    "upstreams": "Upstreams",
+    "apply_btn": "تطبيق",
+    "disabled_filtering_toast": "تم تعطيل الفلترة",
+    "enabled_filtering_toast": "تم تمكين الفلترة",
+    "disabled_safe_browsing_toast": "تم تعطيل التصفح الآمن",
+    "enabled_safe_browsing_toast": "تم تمكين التصفح الآمن",
+    "disabled_parental_toast": "تعطيل الرقابة الأبوية",
+    "enabled_parental_toast": "تفعيل الرقابة الأبوية",
+    "disabled_safe_search_toast": "تعطيل البحث الآمن",
+    "enabled_save_search_toast": "تفعيل البحث الآمن",
+    "enabled_table_header": "تمكين",
+    "name_table_header": "الاسم",
+    "list_url_table_header": "قائمة الروابط",
+    "rules_count_table_header": "عدد القواعد",
+    "last_time_updated_table_header": "آخر تحديث",
+    "actions_table_header": "الإجراءات",
+    "request_table_header": "طلب",
+    "edit_table_action": "تحرير",
+    "delete_table_action": "حذف",
+    "elapsed": "المنقضي",
+    "filters_and_hosts_hint": "يفهم AdGuard Home قواعد حظر الإعلانات الاساسية وملفات الهوست.",
+    "no_blocklist_added": "لم يتم إضافة قوائم الحظر",
+    "no_whitelist_added": "لم تتم إضافة قوائم السماح",
+    "add_blocklist": "إضافة قائمة الحظر",
+    "add_allowlist": "إضافة قائمة السماح",
+    "cancel_btn": "إلغاء",
+    "enter_name_hint": "أدخل الاسم",
+    "enter_url_or_path_hint": "إدخال عنوان URL أو مسار مطلق للقائمة",
+    "check_updates_btn": "تحقق من وجود تحديثات",
+    "new_blocklist": "قائمة حظر جديدة",
+    "new_allowlist": "قائمة السماح الجديدة",
+    "edit_blocklist": "تحرير قائمة الحظر",
+    "edit_allowlist": "تحرير قائمة السماح",
+    "choose_blocklist": "اختر قوائم الحظر",
+    "choose_allowlist": "اختر قوائم السماح",
+    "enter_valid_blocklist": "إدخال عنوان URL صالح إلى قائمة الحظر",
+    "enter_valid_allowlist": "أدخل عنوان URL صالحًا لقائمة السماح",
+    "form_error_url_format": "تنسيق رابط غير صالح",
+    "form_error_url_or_path_format": "عنوان URL أو المسار المطلق للقائمة غير صالح",
+    "custom_filter_rules": "قواعد التصفية المخصصة",
+    "custom_filter_rules_hint": "أدخل قاعدة واحدة على السطر يمكنك استخدام قواعد adblock أو بناء جملة ملفات المضيفين",
+    "system_host_files": "ملفات الهوست للنظام",
+    "examples_title": "أمثلة",
+    "example_meaning_filter_block": "منع الوصول إلى نطاق example.org وجميع نطاقاته الفرعية",
+    "example_meaning_filter_whitelist": "إلغاء حظر الوصول إلى نطاق example.org وجميع نطاقاته الفرعية",
+    "example_meaning_host_block": "الرد ب 127.0.0.1 على example.org (ولكن ليس لنطاقاته الفرعية);",
+    "example_comment": "! ها هو التعليق.",
+    "example_comment_meaning": "فقط تعليق;",
+    "example_comment_hash": "# تعليق أيضًا",
+    "example_regex_meaning": "منع الوصول إلى النطاقات المطابقة للتعبير العادي المحدد.",
+    "example_upstream_regular": "regular DNS (over UDP);",
+    "example_upstream_udp": "regular DNS (over UDP, hostname);",
+    "example_upstream_dot": "مشفر<0>DNS-over-TLS</0>;",
+    "example_upstream_doh": "مشفر <0>DNS-over-HTTPS</0>;",
+    "example_upstream_doq": "encrypted <0>DNS-over-QUIC</0>;",
+    "example_upstream_sdns": "<0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers;",
+    "example_upstream_tcp": "regular DNS (over TCP);",
+    "example_upstream_tcp_hostname": "regular DNS (over TCP, hostname);",
+    "all_lists_up_to_date_toast": "جميع القوائم محدثة بالفعل",
+    "updated_upstream_dns_toast": "تم حفظ خوادم Upstream بنجاح",
+    "dns_test_ok_toast": "تعمل خوادم DNS المحددة بشكل صحيح",
+    "dns_test_not_ok_toast": "خادم \"{{key}}\": لا يمكن استخدامه يرجى التحقق من كتابته بشكل صحيح",
+    "unblock": "إلغاء الحظر",
+    "block": "حظر",
+    "disallow_this_client": "منع هذا العميل",
+    "allow_this_client": "السماح لهذا العميل",
+    "block_for_this_client_only": "احجب هذا العميل فقط",
+    "unblock_for_this_client_only": "إلغاء حجب هذا العميل فقط",
+    "time_table_header": "وقت",
+    "date": "التاريخ",
+    "domain_name_table_header": "اسم النطاق",
+    "domain_or_client": "الدومين أو العميل",
+    "type_table_header": "النوع",
+    "response_table_header": "استجابة",
+    "response_code": "كود الاستجابة",
+    "client_table_header": "عميل",
+    "empty_response_status": "فارغ",
+    "show_all_filter_type": "إظهار الكل",
+    "show_filtered_type": "إظهار ماتمت تصفيته",
+    "no_logs_found": "لم يتم العثور على سجلات",
+    "refresh_btn": "تحديث",
+    "previous_btn": "السابق",
+    "next_btn": "التالي",
+    "loading_table_status": "جار التحميل...",
+    "page_table_footer_text": "الصفحة",
+    "rows_table_footer_text": "صفوف",
+    "updated_custom_filtering_toast": "تحديث قواعد الفلترة المخصصة",
+    "rule_removed_from_custom_filtering_toast": "تم إزالة قاعدة من قواعد الفلترة المخصصة: {{rule}}",
+    "rule_added_to_custom_filtering_toast": "تم إضافة إلى قواعد الفلترة المخصصة: {{rule}}",
+    "query_log_response_status": "الحالات: {{value}}",
+    "query_log_filtered": "تم الفلترة بواسطة {{filter}}",
+    "query_log_confirm_clear": "هل أنت متأكد من أنك تريد محو كامل سجل التصفية؟",
+    "query_log_cleared": "تم مسح سجل الاستعلام بنجاح",
+    "query_log_updated": "تم تحديث سجل الاستعلام بنجاح",
+    "query_log_clear": "مسح سجلات الاستعلام",
+    "query_log_retention": "الاحتفاظ بسجلات الاستعلام",
+    "query_log_enable": "تمكين السجل",
+    "query_log_configuration": "تكوين السجلات",
+    "query_log_disabled": "سجل الاستعلام معطل ويمكن تهيئته من<0>الاعدادات</0>",
+    "query_log_strict_search": "استخدم علامات الاقتباس المزدوجة للبحث الدقيق",
+    "query_log_retention_confirm": "هل أنت متأكد من أنك تريد تغيير الاحتفاظ بسجل الاستعلام؟ إذا قمت بتقليل قيمة الفاصل الزمني سيتم فقدان بعض البيانات",
+    "anonymize_client_ip": "إخفاء عنوان IP العميل",
+    "anonymize_client_ip_desc": "لا تقم بحفظ كامل عنوان IP العميل في السجلات والإحصائيات",
+    "dns_config": "إعداد خادم DNS",
+    "dns_cache_config": "ضبط الملفات المؤقتة لـ DNS",
+    "dns_cache_config_desc": "هنا تستطيع ضبط اعدادات الـ DNS وملفاته",
+    "blocking_mode": "وضع الحجب",
+    "default": "إفتراضي",
+    "nxdomain": "NXDOMAIN",
+    "refused": "مرفوض",
+    "null_ip": "عنوان IP فارغ",
+    "custom_ip": "عنوان IP مخصص",
+    "blocking_ipv4": "حجب عنوان IPv4",
+    "blocking_ipv6": "حجب عنوان IPv6",
+    "dnscrypt": "DNSCrypt",
+    "dns_over_https": "DNS-over-HTTPS",
+    "dns_over_tls": "DNS-over-TLS",
+    "dns_over_quic": "DNS-over-QUIC",
+    "client_id": "عنوان العميل الشخصي",
+    "client_id_placeholder": "ادخل عنوان العميل الشخصي",
+    "client_id_desc": "يمكن تحديد هوية العميل. اعرف المزيد عن كيفية تحديد هوية العملاء <a> هنا</a>.",
+    "download_mobileconfig_doh": "حمّل .mobileconfig for DNS-over-HTTPS",
+    "download_mobileconfig_dot": "حمل .mobileconfig for DNS-over-TLS",
+    "download_mobileconfig": "حمّل ملف الإعدادات",
+    "plain_dns": "عنوان DNS العادي",
+    "form_enter_rate_limit": "ادخل حد التقييم",
+    "rate_limit": "حدود التقييم",
+    "edns_enable": "فعل EDNS client subnet",
+    "edns_cs_desc": "أضف EDNS الشبكة الفرعية للعميل (ECS) إلى الطلبات الأولية وقم بتسجيل القيم المرسلة من قبل العملاء في سجل الاستعلام.",
+    "rate_limit_desc": "عدد الطلبات في الثانية المسموح بها لكل عميل. جعله على 0 يعني عدم وجود حد.",
+    "blocking_ipv4_desc": "سيتم إرجاع عنوان IP لطلب محظور",
+    "blocking_ipv6_desc": "سيتم إرجاع عنوان IP لطلب AAAA محظور",
+    "blocking_mode_default": "الافتراضي: الرد بعنوان IP صفري (0.0.0.0 لـ A ؛ :: لـ AAAA) عند حظره بواسطة قاعدة نمط Adblock ؛ الرد بعنوان IP المحدد في القاعدة عند حظره بواسطة / etc / hosts-style rule",
+    "blocking_mode_refused": "مرفوض: رد برمز مرفوض",
+    "blocking_mode_nxdomain": "NXDOMAIN: الرد باستخدام رمز NXDOMAIN",
+    "blocking_mode_null_ip": "IP Null: الاستجابة بعنوان IP صفري (0.0.0.0 لـ A ؛ :: لـ AAAA)",
+    "blocking_mode_custom_ip": "استجابة IP مخصصة بعنوان IP تم تعيينه يدويًا",
+    "upstream_dns_client_desc": "إذا احتفظت بهذا الحقل فارغًا ، فسيستخدم AdGuard Home الخوادم التي تم تكوينها في<0>DNS إعدادات</0>.",
+    "tracker_source": "مصدر المتعقب",
+    "source_label": "المصدر",
+    "found_in_known_domain_db": "تم العثور عليه في قاعدة بيانات دومينات معروفة.",
+    "category_label": "الفئة",
+    "rule_label": "قواعد",
+    "list_label": "قائمه",
+    "unknown_filter": "فلتر غير معروف {{filterId}}",
+    "known_tracker": "متعقب معروف",
+    "install_welcome_title": "مرحبًا بك في AdGuard Home!",
+    "install_welcome_desc": "AdGuard Home هو إعلان ومتتبع على مستوى الشبكة يمنع خادم DNS. الغرض منه هو السماح لك بالتحكم في شبكتك بأكملها وجميع أجهزتك، ولا يتطلب استخدام برنامج من جانب العميل.",
+    "install_settings_title": "واجهة ويب المسؤول",
+    "install_settings_listen": "واجهة الاستماع",
+    "install_settings_port": "المنفذ",
+    "install_settings_interface_link": "ستكون واجهة الويب الخاصة بمسؤول AdGuard Home متاحة على العناوين التالية:",
+    "form_error_port": "أدخل رقم منفذ صالح",
+    "install_settings_dns": "خادم DNS",
+    "install_settings_dns_desc": "ستحتاج إلى ضبط أجهزتك أو جهاز التوجيه الخاص بك لاستخدام خادم DNS على العناوين التالية:",
+    "install_settings_all_interfaces": "جميع الواجهات",
+    "install_auth_title": "المصادقة",
+    "install_auth_desc": "يجب إعداد مصادقة كلمة المرور لواجهة ويب مسؤول AdGuard Home. في حال كان AdGuard Home لا يمكن الوصول إليه إلا في شبكتك المحلية ، فلا يزال من المهم حمايته من الوصول غير المقيد.",
+    "install_auth_username": "اسم المستخدم",
+    "install_auth_password": "الكلمة السرية",
+    "install_auth_confirm": "تاكيد كلمه المرور",
+    "install_auth_username_enter": "أدخل اسم المستخدم",
+    "install_auth_password_enter": "أدخل كلمة المرور",
+    "install_step": "خطوة",
+    "install_devices_title": "قم بإعداد أجهزتك",
+    "install_devices_desc": "لبدء استخدام AdGuard Home، تحتاج إلى إعداد أجهزتك لاستخدامها.",
+    "install_submit_title": "تهانينا!",
+    "install_submit_desc": "انتهى إجراء الإعداد وأنت على استعداد لبدء استخدام AdGuard Home",
+    "install_devices_router": "راوتر",
+    "install_devices_router_desc": "يغطي هذا الإعداد تلقائيا جميع الأجهزة المتصلة بجهاز التوجيه المنزلي، دون الحاجة إلى تكوين كل منها يدويا.",
+    "install_devices_address": "يستمع خادم AdGuard Home DNS إلى العناوين التالية",
+    "install_devices_router_list_1": "افتح تفضيلات جهاز التوجيه الخاص بك. عادة، يمكنك الوصول إليه من متصفحك عبر عنوان URL، مثل http://192.168.0.1/ أو http://192.168.1.1/. قد يطلب منك إدخال كلمة مرور. إذا كنت لا تتذكر ذلك، يمكنك في كثير من الأحيان إعادة تعيين كلمة المرور عن طريق الضغط على زر في جهاز التوجيه نفسه، ولكن كن على علم بأنه إذا تم اختيار هذا الإجراء، فمن المحتمل أن تفقد إعدادات جهاز التوجيه بأكمله. إذا كان جهاز التوجيه الخاص بك يتطلب تطبيقا لإعداده، فيرجى تثبيت التطبيق على هاتفك أو الكمبيوتر الشخصي واستخدامه للوصول إلى إعدادات جهاز التوجيه.",
+    "install_devices_router_list_2": "ابحث عن إعدادات DHCP / DNS. ابحث عن أحرف DNS بجوار الحقل الذي يسمح بمجموعتين أو ثلاث مجموعات من الأرقام ، كل واحدة مقسمة إلى أربع مجموعات من واحد إلى ثلاثة أرقام.",
+    "install_devices_router_list_3": "أدخل عناوين خادم AdGuard Home هناك.",
+    "install_devices_router_list_4": "في بعض أنواع أجهزة التوجيه ، لا يمكن إعداد خادم DNS مخصص. في هذه الحالة ، قد يساعد إعداد AdGuard Home باعتباره <0>خادم DHCP</0>. بخلاف ذلك ، يجب عليك التحقق من دليل جهاز التوجيه حول كيفية تخصيص خوادم DNS على طراز جهاز التوجيه المحدد الخاص بك.",
+    "install_devices_windows_list_1": "افتح لوحة التحكم من خلال قائمة ابدأ أو بحث Windows.",
+    "install_devices_windows_list_2": "انتقل إلى فئة الشبكة والإنترنت ثم إلى مركز الشبكة والمشاركة.",
+    "install_devices_windows_list_3": "على الجانب الأيسر من الشاشة ، ابحث عن \"تغيير إعدادات المحول\" وانقر عليها.",
+    "install_devices_windows_list_4": "حدد اتصالك النشط ، وانقر فوقه بزر الماوس الأيمن واختر خصائص.",
+    "install_devices_windows_list_5": "ابحث عن \"Internet Protocol Version 4 (TCP / IPv4)\" (أو ، لـ IPv6 ، \"Internet Protocol Version 6 (TCP / IPv6)\") في القائمة ، حدده ثم انقر فوق خصائص مرة أخرى.",
+    "install_devices_windows_list_6": "اختر \"استخدام عناوين خادم DNS التالية\" وأدخل عناوين خادم AdGuard Home.",
+    "install_devices_macos_list_1": "انقر فوق أيقونة Apple وانتقل إلى تفضيلات النظام.",
+    "install_devices_macos_list_2": "اضغط على الشبكة.",
+    "install_devices_macos_list_3": "حدد الاتصال الأول في قائمتك وانقر فوق خيارات متقدمة.",
+    "install_devices_macos_list_4": "حدد علامة التبويب DNS وأدخل عناوين خادم AdGuard Home.",
+    "install_devices_android_list_1": "من الشاشة الرئيسية لقائمة Android ، انقر فوق الإعدادات.",
+    "install_devices_android_list_2": "اضغط على Wi-Fi في القائمة. ستظهر الشاشة التي تسرد جميع الشبكات المتاحة (من المستحيل تعيين DNS مخصص لاتصال المحمول).",
+    "install_devices_android_list_3": "اضغط لفترة طويلة على الشبكة التي تتصل بها ثم اضغط على تعديل الشبكة",
+    "install_devices_android_list_4": "في بعض الأجهزة قد تحتاج إلى تحديد المربع المتقدم لرؤية المزيد من الإعدادات لضبط إعدادات DNS لنظام اندرويد ستحتاج إلى تبديل إعدادات IP من DHCP إلى ثابت.",
+    "install_devices_android_list_5": "قم بتغيير قيم DNS 1 و DNS 2 المعينة لعناوين خادم AdGuard Home",
+    "install_devices_ios_list_1": "من الشاشة الرئيسية انقر فوق الإعدادات",
+    "install_devices_ios_list_2": "اختر Wi-Fi في القائمة اليسرى (من المستحيل ضبط الـ DNS لشبكات الجوال).",
+    "install_devices_ios_list_3": "اضغط على اسم الشبكة النشطة حاليًا.",
+    "install_devices_ios_list_4": "في حقل DNS ، أدخل عناوين خادم AdGuard Home.",
+    "get_started": "أبدأ",
+    "next": "التالي",
+    "open_dashboard": "افتح لوحة التحكم",
+    "install_saved": "تم الحفظ بنجاح",
+    "encryption_title": "التعمية",
+    "encryption_desc": "دعم التشفير (HTTPS / TLS) لكل من DNS وواجهة ويب المسؤول",
+    "encryption_config_saved": "تم حفظ اعدادات التشفير",
+    "encryption_server": "اسم الخادم",
+    "encryption_server_enter": "ادخل عنوان النطاق الخاص بك",
+    "encryption_redirect": "إعادة التوجيه إلى HTTPS تلقائيًا",
+    "encryption_redirect_desc": "إذا تم تحديده ، فسيقوم AdGuard Home بإعادة توجيهك تلقائيًا من عناوين HTTP إلى عناوين HTTPS.",
+    "encryption_https": "منفذ HTTPS",
+    "encryption_https_desc": "إذا تم تكوين منفذ HTTPS ، فسيتم الوصول إلى واجهة مشرف AdGuard Home عبر HTTPS ، وستوفر أيضًا DNS-over-HTTPS على موقع '/dns-query'.",
+    "encryption_dot": "منفذ DNS-over-TLS",
+    "encryption_dot_desc": "إذا تم ضبط هذا المنفذ ، فسيقوم AdGuard Home بتشغيل خادم DNS-over-TLS على هذا المنفذ.",
+    "encryption_doq": "DNS-over-QUIC port",
+    "encryption_doq_desc": "إذا تم ضبط هذا المنفذ، فسيقوم AdGuard Home بتشغيل خادم DNS-over-QUIC على هذا المنفذ.",
+    "encryption_certificates": "الشهادات",
+    "encryption_certificates_desc": "من أجل استخدام التشفير ، تحتاج إلى تقديم سلسلة شهادات SSL صالحة لنطاقك. يمكنك الحصول على شهادة مجانية على <0>{{link}}</0> أو يمكنك شرائها من أحد المراجع المصدقة الموثوقة.",
+    "encryption_certificates_input": "انسخ / الصق الشهادات المشفرة PEM هنا.",
+    "encryption_status": "الحالة",
+    "encryption_expire": "يتنهي في",
+    "encryption_key": "مفتاح خاص",
+    "encryption_key_input": "انسخ / الصق مفتاحك الخاص المشفر بـ PEM لشهادتك هنا",
+    "encryption_enable": "تمكين التشفير (HTTPS و DNS-over-HTTPS و DNS-over-TLS)",
+    "encryption_enable_desc": "إذا تم تمكين التشفير فستعمل واجهة مسؤول AdGuard Home عبر HTTPS وسيستمع خادم DNS للطلبات عبر DNS-over-HTTPS و DNS-over-TLS.",
+    "encryption_chain_valid": "سلسلة الشهادات صالحة",
+    "encryption_chain_invalid": "سلسلة الشهادات غير صالحة",
+    "encryption_key_valid": "هذا مفتاح خاص {{type}} صالح",
+    "encryption_key_invalid": "هذا مفتاح خاص {{type}} غير صالح",
+    "encryption_subject": "الموضوع",
+    "encryption_issuer": "المصدر",
+    "encryption_hostnames": "اسم المستضيف",
+    "encryption_reset": "هل أنت متأكد أنك تريد إعادة تعيين إعدادات التشفير؟",
+    "topline_expiring_certificate": "شهادة SSL الخاصة بك على وشك الانتهاء. قم بتحديث <0>إعدادات التشفير</0>.",
+    "topline_expired_certificate": "انتهت صلاحية شهادة SSL الخاصة بك. قم بتحديث <0>إعدادات التشفير</0>.",
+    "form_error_port_range": "أدخل رقم المنفذ في النطاق 80-65535",
+    "form_error_port_unsafe": "منفذ غير آمن",
+    "form_error_equal": "يجب ألا تكون متساوية",
+    "form_error_password": "كلمة السر غير مطابقة",
+    "reset_settings": "إعادة ضبط الإعدادات",
+    "update_announcement": "AdGuard Home {{version}} متوفر الآن! <0>انقر هنا</0> لمزيد من المعلومات.",
+    "setup_guide": "دليل الإعداد",
+    "dns_addresses": "عناوين DNS",
+    "dns_start": "خادم DNS قيد التشغيل",
+    "dns_status_error": "خطأ في التحقق من حالة خادم الـ DNS",
+    "down": "تحت",
+    "fix": "يصلح",
+    "dns_providers": "فيما يلي قائمة <0> بموفري DNS المعروفين </0> للاختيار من بينها.",
+    "update_now": "تحديث الآن",
+    "update_failed": "فشل التحديث التلقائي. الرجاء <a> اتباع هذه الخطوات </a> للتحديث يدويًا.",
+    "manual_update": "الرجاء <a> اتباع هذه الخطوات </a> للتحديث يدويًا.",
+    "processing_update": "يُرجى الانتظار ، يتم تحديث صفحة AdGuard الرئيسية",
+    "clients_title": "العملاء الدائمين",
+    "clients_desc": "قم بضبط سجلات العميل الدائمة للأجهزة المتصلة بـ AdGuard Home",
+    "settings_global": "عالمي",
+    "settings_custom": "مخصص",
+    "table_client": "العميل",
+    "table_name": "الاسم",
+    "save_btn": "حفظ",
+    "client_add": "إضافة عميل",
+    "client_new": "عميل جديد",
+    "client_edit": "تعديل العميل",
+    "client_identifier": "المعّرف",
+    "ip_address": "عنوان IP",
+    "client_identifier_desc": "يمكن التعرف على العملاء من خلال عنوان IP أو CIDR أو عنوان MAC أو ClientID (يمكن استخدامه في DoT / DoH / DoQ). تعرف على المزيد حول كيفية تحديد العملاء <0> هنا </0>.",
+    "form_enter_ip": "ادخل عنوان IP",
+    "form_enter_subnet_ip": "أدخل عنوان IP في الشبكة الفرعية \"{{cidr}}\"",
+    "form_enter_mac": "ادخل MAC",
+    "form_enter_id": "ادخل المعّرف",
+    "form_add_id": "أضافة معّرف",
+    "form_client_name": "ادخل اسم العميل",
+    "name": "اسم",
+    "client_global_settings": "استخدم إعدادات عالمية",
+    "client_deleted": "تم حذف العميل \"{{key}}\" بنجاح",
+    "client_added": "تم اضافة العميل \"{{key}}\" بنجاح",
+    "client_updated": "تم تحديث العميل \"{{key}}\" بنجاح",
+    "clients_not_found": "لم يتم العثور على عملاء",
+    "client_confirm_delete": "هل أنت متأكد من أنك تريد حذف العميل \"{{key}}\"?",
+    "list_confirm_delete": "هل أنت متأكد أنك تريد حذف هذه القائمة؟",
+    "auto_clients_title": "Runtime clients",
+    "auto_clients_desc": "الأجهزة غير المدرجة في قائمة العملاء الدائمين الذين قد لا يزالون يستخدمون AdGuard Home",
+    "access_title": "إعدادات الوصول",
+    "access_desc": "هنا يمكنك ضبط قواعد الوصول لخادم AdGuard Home DNS",
+    "access_allowed_title": "العملاء المسموحين",
+    "access_allowed_desc": "قائمة CIDRs أو عناوين IP أو <a> ClientIDs </a>. إذا كانت هذه القائمة تحتوي على إدخالات ، فسيقبل AdGuard Home الطلبات من هؤلاء العملاء فقط.",
+    "access_disallowed_title": "العملاء غير المسموحين",
+    "access_disallowed_desc": "قائمة CIDRs أو عناوين IP أو <a> ClientIDs </a>. إذا كانت هذه القائمة تحتوي على إدخالات ، فسيقوم AdGuard Home بإسقاط الطلبات من هؤلاء العملاء. يتم تجاهل هذا الحقل إذا كانت هناك إدخالات في العملاء المسموح لهم.",
+    "access_blocked_title": "النطاقات غير المسموح بها",
+    "access_blocked_desc": "لا ينبغي الخلط بينه وبين المرشحات. يسقط AdGuard Home استعلامات DNS المطابقة لهذه المجالات ، ولا تظهر هذه الاستعلامات حتى في سجل الاستعلام. يمكنك تحديد أسماء النطاقات الدقيقة أو أحرف البدل أو قواعد تصفية عناوين URL ، على سبيل المثال \"example.org\" أو \"*.example.org\" أو \"|| example.org ^\" في المقابل.",
+    "access_settings_saved": "تم حفظ إعدادات الوصول بنجاح",
+    "updates_checked": "يتوفر إصدار جديد من AdGuard Home",
+    "updates_version_equal": "AdGuard Home محدث",
+    "check_updates_now": "تحقق من وجود تحديثات الآن",
+    "dns_privacy": "خصوصية DNS",
+    "setup_dns_privacy_1": "<0> DNS-over-TLS: </0> استخدم سلسلة <1> {{address}} </1>.",
+    "setup_dns_privacy_2": "<0> DNS-over-HTTPS: </0> استخدم سلسلة <1> {{address}} </1>.",
+    "setup_dns_privacy_3": "<0> فيما يلي قائمة بالبرامج التي يمكنك استخدامها. </0>",
+    "setup_dns_privacy_4": "على جهاز iOS 14 أو macOS Big Sur ، يمكنك تنزيل ملف \".mobileconfig\" خاص يضيف خوادم <highlight> DNS-over-HTTPS </highlight> أو <highlight> DNS-over-TLS </highlight> إلى إعدادات DNS.",
+    "setup_dns_privacy_android_1": "يدعم Android 9 DNS-over-TLS أصلاً. لتكوينه ، انتقل إلى الإعدادات → الشبكة والإنترنت → متقدم → DNS الخاص وأدخل اسم المجال الخاص بك هناك.",
+    "setup_dns_privacy_android_2": "<0> AdGuard لنظام Android </0> يدعم <1> DNS-over-HTTPS </1> و <1> DNS-over-TLS </1>.",
+    "setup_dns_privacy_android_3": "<0> Intra </0> يضيف دعم <1> DNS-over-HTTPS </1> إلى Android.",
+    "setup_dns_privacy_ios_1": "<0> DNSCloak </0> يدعم <1> DNS-over-HTTPS </1> ، ولكن من أجل تكوينه لاستخدام الخادم الخاص بك ، ستحتاج إلى إنشاء <2> DNS Stamp </2> لذلك.",
+    "setup_dns_privacy_ios_2": "<0> AdGuard لنظام iOS </0> يدعم إعداد <1> DNS-over-HTTPS </1> و <1> DNS-over-TLS </1> الإعداد.",
+    "setup_dns_privacy_other_title": "تطبيقات أخرى",
+    "setup_dns_privacy_other_1": "يمكن أن يكون AdGuard Home نفسه عميل DNS آمنًا على أي نظام أساسي.",
+    "setup_dns_privacy_other_2": "يدعم <0> dnsproxy </0> جميع بروتوكولات DNS الآمنة المعروفة.",
+    "setup_dns_privacy_other_3": "<0> dnscrypt-proxy </0> يدعم <1> DNS-over-HTTPS </1>.",
+    "setup_dns_privacy_other_4": "يدعم <0> Mozilla Firefox </0> <1> DNS-over-HTTPS </1>.",
+    "setup_dns_privacy_other_5": "ستجد المزيد من التطبيقات <0> هنا </0> و <1> هنا </1>.",
+    "setup_dns_privacy_ioc_mac": "اعدادات iOS و macOS",
+    "setup_dns_notice": "من أجل استخدام <0> DNS-over-HTTPS </0> أو <1> DNS-over-TLS </1> ، تحتاج إلى <1> تكوين التشفير </1> في إعدادات AdGuard Home.",
+    "rewrite_added": "تمت إضافة إعادة كتابة DNS لـ \"{{key}}\" بنجاح",
+    "rewrite_deleted": "تم حذف إعادة كتابة DNS لـ \"{{key}}\" بنجاح",
+    "rewrite_add": "إضافة إعادة كتابة DNS",
+    "rewrite_not_found": "لم يتم العثور على إعادة كتابة DNS",
+    "rewrite_confirm_delete": "هل أنت متأكد من أنك تريد حذف إعادة كتابة DNS لـ \"{{key}}\"؟",
+    "rewrite_desc": "يسمح بتكوين استجابة DNS المخصصة بسهولة لاسم نطاق معين.",
+    "rewrite_applied": "يتم تطبيق قاعدة إعادة الكتابة",
+    "rewrite_hosts_applied": "أعيد كتابتها بواسطة قاعدة ملف المضيفين",
+    "dns_rewrites": "إعادة كتابة DNS",
+    "form_domain": "أدخل اسم النطاق أو حرف البدل",
+    "form_answer": "أدخل عنوان IP أو اسم النطاق",
+    "form_error_domain_format": "تنسيق النطاق غير صالح",
+    "form_error_answer_format": "تنسيق إجابة غير صالح",
+    "configure": "ضبط",
+    "main_settings": "الاعدادات الرئيسية",
+    "block_services": "حظر خدمات معينة",
+    "blocked_services": "الخوادم المحجوبة",
+    "blocked_services_desc": "يسمح بحجب المواقع والخدمات الشعبية بسرعة.",
+    "blocked_services_saved": "تم حفظ الخوادم المحجوبة بنجاح",
+    "blocked_services_global": "استخدام خدمات الحظر العالمية",
+    "blocked_service": "الخدمات المحجوبة",
+    "block_all": "حجب الكل",
+    "unblock_all": "إلغاء حجب الكل",
+    "encryption_certificate_path": "مسار الشهادة",
+    "encryption_private_key_path": "مسار المفتاح الخاص",
+    "encryption_certificates_source_path": "قم بتعيين مسار ملف الشهادات",
+    "encryption_certificates_source_content": "الصق محتويات الشهادات",
+    "encryption_key_source_path": "قم بتعيين ملف مفتاح خاص",
+    "encryption_key_source_content": "الصق محتويات المفتاح الخاص",
+    "stats_params": "ضبط الاحصائيات",
+    "config_successfully_saved": "تم حفظ الاعدادات بنجاح",
+    "interval_6_hour": "ساعات6",
+    "interval_24_hour": "24 ساعة",
+    "interval_days": "{{count}} يوم",
+    "interval_days_plural": "{{count}} الأيام",
+    "domain": "النطاق",
+    "ecs": "ECS",
+    "punycode": "Punycode",
+    "answer": "الإجابة",
+    "filter_added_successfully": "تم إضافة القائمة بنجاح",
+    "filter_removed_successfully": "تم ازالته من القائمة بنجاح",
+    "filter_updated": "تم تحديث القائمة بنجاح",
+    "statistics_configuration": "ضبط الاحصائيات",
+    "statistics_retention": "الاحتفاظ بالإحصاءات",
+    "statistics_retention_desc": "إذا قمت بتقليل قيمة الفاصل الزمني ، فستفقد بعض البيانات",
+    "statistics_clear": "إعادة تعيين الإحصائيات",
+    "statistics_clear_confirm": "هل أنت متأكد من أنك تريد مسح الإحصاءات؟",
+    "statistics_retention_confirm": "هل أنت متأكد أنك تريد تغيير الاحتفاظ بالإحصاءات؟ إذا قمت بتقليل قيمة الفاصل الزمني ، فستفقد بعض البيانات",
+    "statistics_cleared": "تم مسح الإحصائيات بنجاح",
+    "statistics_enable": "تفعيل الاحصائيات",
+    "interval_hours": "{{count}} ساعة",
+    "interval_hours_plural": "{{count}} ساعات",
+    "filters_configuration": "اضبط الفلاتر",
+    "filters_enable": "تفعيل الفلاتر",
+    "filters_interval": "الفاصل الزمني لتحديث الفلاتر",
+    "disabled": "معطلة",
+    "username_label": "اسم المستخدم",
+    "username_placeholder": "ادخل اسم المستخدم",
+    "password_label": "كلمة المرور",
+    "password_placeholder": "ادخل كلمة المرور",
+    "sign_in": "تسجيل الدخول",
+    "sign_out": "تسجيل الخروج",
+    "forgot_password": "نسيت كلمة المرور؟",
+    "forgot_password_desc": "يرجى اتباع <0> هذه الخطوات </0> لإنشاء كلمة مرور جديدة لحساب المستخدم الخاص بك.",
+    "location": "الموقع",
+    "orgname": "اسم المنظمة",
+    "netname": "اسم الشبكة",
+    "network": "الشبكة",
+    "descr": "الوصف",
+    "whois": "WHOIS",
+    "filtering_rules_learn_more": "<0> اعرف المزيد </0> حول إنشاء قوائم المضيفين الخاصة بك.",
+    "blocked_by_response": "حظر بواسطة CNAME or IP in response",
+    "blocked_by_cname_or_ip": "حظر بواسطة CNAME or IP",
+    "try_again": "حاول مرة أخرى",
+    "domain_desc": "أدخل اسم النطاق أو حرف البدل الذي تريد إعادة كتابته.",
+    "example_rewrite_domain": "أعد كتابة الردود لاسم النطاق هذا فقط.",
+    "example_rewrite_wildcard": "أعد كتابة الردود لجميع النطاقات الفرعية <0> example.org </0>.",
+    "rewrite_ip_address": "عنوان IP: استخدم عنوان IP هذا في استجابة A أو AAAA",
+    "rewrite_domain_name": "اسم النطاق: أضف سجل CNAME",
+    "rewrite_A": "<0> A </0>: قيمة خاصة ، احتفظ بسجلات <0> A </0> من upstream",
+    "rewrite_AAAA": "<0> AAAA </0>: قيمة خاصة ، احتفظ بسجلات <0> AAAA </0> من upstream",
+    "disable_ipv6": "قم بتعطيل تحليل عناوين IPv6",
+    "disable_ipv6_desc": "قم بإسقاط جميع استعلامات DNS لعناوين IPv6 (اكتب AAAA).",
+    "fastest_addr": "أسرع عنوان IP",
+    "fastest_addr_desc": "استعلم عن جميع خوادم DNS وأعد عنوان IP الأسرع بين جميع الاستجابات. يؤدي هذا إلى إبطاء استعلامات DNS حيث يتعين على AdGuard Home انتظار الاستجابات من جميع خوادم DNS ، ولكنه يحسن الاتصال الكلي.",
+    "autofix_warning_text": "إذا قمت بالنقر فوق \"إصلاح\" ، فسيقوم AdGuard Home بتهيئة نظامك لاستخدام خادم AdGuard Home DNS.",
+    "autofix_warning_list": "سيقوم بتنفيذ هذه المهام: <0> إلغاء تنشيط نظام DNSStubListener </0> <0> تعيين عنوان خادم DNS إلى 127.0.0.1 </0> <0> استبدال هدف الارتباط الرمزي لـ /etc/resolv.conf بـ / run / systemd /resolve/resolv.conf </0> <0> إيقاف DNSStubListener (إعادة تحميل خدمة حل نظام d) </0>",
+    "autofix_warning_result": "نتيجة لذلك ، ستتم معالجة جميع طلبات DNS من نظامك بواسطة AdGuard Home افتراضيًا.",
+    "tags_title": "وسوم",
+    "tags_desc": "يمكنك تحديد العلامات التي تتوافق مع العميل. قم بتضمين العلامات في قواعد التصفية لتطبيقها بدقة أكبر. <0> معرفة المزيد </0>.",
+    "form_select_tags": "حدد علامات العميل",
+    "check_title": "تحقق من الفلترة",
+    "check_desc": "تحقق مما إذا تم فلترة اسم المضيف.",
+    "check": "تحقق",
+    "form_enter_host": "ادخل اسم المضيف",
+    "filtered_custom_rules": "تمت تصفيتها حسب قواعد التصفية المخصصة",
+    "choose_from_list": "اختر من القائمة",
+    "add_custom_list": "أضف قائمة مخصصة",
+    "host_whitelisted": "المضيف مسموح به",
+    "check_ip": "عناوين الـ IP: {{ip}}",
+    "check_cname": "CNAME: {{cname}}",
+    "check_reason": "سبب: {{reason}}",
+    "check_service": "أسم الخدمة: {{service}}",
+    "service_name": "أسم الخدمة",
+    "check_not_found": "غير موجود في قوائم التصفية الخاصة بك",
+    "client_confirm_block": "هل أنت متأكد من أنك تريد منع العميل \"{{ip}}\"؟",
+    "client_confirm_unblock": "هل تريد بالتأكيد إلغاء حظر العميل \"{{ip}}\"؟",
+    "client_blocked": "تم حظر العميل \"{{ip}}\" بنجاح",
+    "client_unblocked": "تم إلغاء حظر العميل \"{{ip}}\" بنجاح",
+    "static_ip": "عنوان IP ثابت",
+    "static_ip_desc": "AdGuard Home هو خادم لذلك يحتاج إلى عنوان IP ثابت ليعمل بشكل صحيح. خلاف ذلك ، في مرحلة ما ، قد يقوم جهاز التوجيه الخاص بك بتعيين عنوان IP مختلف لهذا الجهاز.",
+    "set_static_ip": "قم بتعيين عنوان IP ثابت",
+    "install_static_ok": "أخبار جيدة! تم ضبط عنوان IP الثابت بالفعل",
+    "install_static_error": "لا يمكن لـ AdGuard Home تكوينه تلقائيًا لواجهة الشبكة هذه. الرجاء البحث عن تعليمات حول كيفية القيام بذلك يدويًا.",
+    "install_static_configure": "اكتشف AdGuard Home استخدام عنوان IP الديناميكي <0> {{ip}} </0>. هل تريد تعيينه كعنوان ثابت؟",
+    "confirm_static_ip": "سيقوم AdGuard Home بتهيئة {{ip}} ليكون عنوان IP الثابت الخاص بك. هل تريد المتابعة؟",
+    "list_updated": "قائمة {{count}} محدثة",
+    "list_updated_plural": "قوائم {{count}} محدثة",
+    "dnssec_enable": "تفعيل DNSSEC",
+    "dnssec_enable_desc": "قم بتعيين علامة DNSSEC في استعلامات DNS الواردة وتحقق من النتيجة (مطلوب محلل يدعم DNSSEC).",
+    "validated_with_dnssec": "تم التحقق من صحتها باستخدام DNSSEC",
+    "all_queries": "كافة الاستفسارات",
+    "show_blocked_responses": "حظر",
+    "show_whitelisted_responses": "القائمة البيضاء",
+    "show_processed_responses": "المعالجة",
+    "blocked_safebrowsing": "محظور بواسطة التصفح الآمن",
+    "blocked_adult_websites": "محظور بواسطة الرقابة الأبوية",
+    "blocked_threats": "التهديدات المحظورة",
+    "allowed": "القائمة البيضاء",
+    "filtered": "تمت الفلترة",
+    "rewritten": "أعيدت كتابته",
+    "safe_search": "البحث الأمن",
+    "blocklist": "قائمة الحظر",
+    "milliseconds_abbreviation": "ms",
+    "cache_size": "حجم ذاكرة التخزين المؤقت",
+    "cache_size_desc": "حجم ذاكرة التخزين المؤقت لنظام أسماء النطاقات (بالبايت).",
+    "cache_ttl_min_override": "تجاوز الحد الأدنى من مدة البقاء TTL",
+    "cache_ttl_max_override": "تجاوز الحد الاقصى من مدة البقاء TTL",
+    "enter_cache_size": "أدخل حجم ذاكرة التخزين المؤقت (بايت)",
+    "enter_cache_ttl_min_override": "أدخل الحد الأدنى من مدة البقاء (بالثواني)",
+    "enter_cache_ttl_max_override": "أدخل الحد الاقصى من مدة البقاء (بالثواني)",
+    "cache_ttl_min_override_desc": "قم بتمديد قيم فترة البقاء القصيرة (بالثواني) المستلمة من الخادم الرئيسي عند تخزين استجابات DNS مؤقتًا.",
+    "cache_ttl_max_override_desc": "قم بتعيين الحد الأقصى لقيمة الوقت للعيش (بالثواني) للإدخالات في ذاكرة التخزين المؤقت لنظام أسماء النطاقات.",
+    "ttl_cache_validation": "يجب أن يكون الحد الأدنى لتجاوز TTL لذاكرة التخزين المؤقت أقل من أو يساوي الحد الأقصى",
+    "cache_optimistic": "متفائل التخزين المؤقت",
+    "cache_optimistic_desc": "اجعل AdGuard Home يستجيب من ذاكرة التخزين المؤقت حتى عندما تنتهي صلاحية الإدخالات وحاول أيضًا تحديثها.",
+    "filter_category_general": "General",
+    "filter_category_security": "الامان",
+    "filter_category_regional": "إقليمي",
+    "filter_category_other": "أخرى",
+    "filter_category_general_desc": "القوائم التي تمنع التتبع والإعلان على معظم الأجهزة",
+    "filter_category_security_desc": "القوائم المصممة خصيصًا لحظر النطاقات الخبيثة والتصيد الاحتيالي والخداع",
+    "filter_category_regional_desc": "القوائم التي تركز على الإعلانات الإقليمية وخوادم التتبع",
+    "filter_category_other_desc": "قوائم حظر أخرى",
+    "setup_config_to_enable_dhcp_server": "أضبط الاعدادات لتمكين خادم DHCP",
+    "original_response": "الرد الأصلي",
+    "click_to_view_queries": "انقر لعرض الـ queries",
+    "port_53_faq_link": "غالبًا ما يتم احتلال المنفذ 53 بواسطة خدمات \"DNSStubListener\" أو \"حل النظام\". يرجى قراءة <0> هذه التعليمات </0> حول كيفية حل هذه المشكلة.",
+    "adg_will_drop_dns_queries": "سيقوم AdGuard Home بإسقاط جميع استعلامات DNS من هذا العميل.",
+    "filter_allowlist": "تحذير: سيؤدي هذا الإجراء أيضًا إلى استبعاد القاعدة \"{{disallowed_rule}}\" من قائمة العملاء المسموح لهم.",
+    "last_rule_in_allowlist": "لا يمكن منع هذا العميل لأن استبعاد القاعدة \"{{disallowed_rule}}\" سيؤدي إلى تعطيل قائمة \"العملاء المسموح لهم\".",
+    "use_saved_key": "استخدم المفتاح المحفوظ مسبقًا",
+    "parental_control": "الرقابة الابويه",
+    "safe_browsing": "تصفح آمن",
+    "served_from_cache": "{{value}} <i>(يتم تقديمه من ذاكرة التخزين المؤقت)</i>",
+    "form_error_password_length": "يجب أن تتكون كلمة المرور من {{value}} من الأحرف على الأقل"
+}
diff --git a/client/src/i18n.js b/client/src/i18n.js
index 8f0e46ea..7008459f 100644
--- a/client/src/i18n.js
+++ b/client/src/i18n.js
@@ -4,6 +4,7 @@ import langDetect from 'i18next-browser-languagedetector';
 
 import { LANGUAGES, BASE_LOCALE } from './helpers/twosky';
 
+import ar from './__locales/ar.json';
 import be from './__locales/be.json';
 import bg from './__locales/bg.json';
 import cs from './__locales/cs.json';
@@ -42,6 +43,7 @@ import zhTW from './__locales/zh-tw.json';
 import { setHtmlLangAttr } from './helpers/helpers';
 
 const resources = {
+    ar: { translation: ar },
     be: { translation: be },
     bg: { translation: bg },
     cs: { translation: cs },
diff --git a/internal/home/i18n.go b/internal/home/i18n.go
index 51f6a2ac..d58dfcf3 100644
--- a/internal/home/i18n.go
+++ b/internal/home/i18n.go
@@ -13,6 +13,7 @@ import (
 
 // TODO(a.garipov): Get rid of a global or generate from .twosky.json.
 var allowedLanguages = stringutil.NewSet(
+	"ar",
 	"be",
 	"bg",
 	"cs",
@@ -50,7 +51,7 @@ var allowedLanguages = stringutil.NewSet(
 	"zh-tw",
 )
 
-func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) {
+func handleI18nCurrentLanguage(w http.ResponseWriter, _ *http.Request) {
 	w.Header().Set("Content-Type", "text/plain")
 	log.Printf("config.Language is %s", config.Language)
 	_, err := fmt.Fprintf(w, "%s\n", config.Language)
@@ -58,6 +59,7 @@ func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) {
 		msg := fmt.Sprintf("Unable to write response json: %s", err)
 		log.Println(msg)
 		http.Error(w, msg, http.StatusInternalServerError)
+
 		return
 	}
 }
@@ -69,6 +71,7 @@ func handleI18nChangeLanguage(w http.ResponseWriter, r *http.Request) {
 		msg := fmt.Sprintf("failed to read request body: %s", err)
 		log.Println(msg)
 		http.Error(w, msg, http.StatusBadRequest)
+
 		return
 	}
 

From f58265ec98dbdcfee030cc77d89e5d02b02da389 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 15 Aug 2022 18:31:32 +0300
Subject: [PATCH 118/143] Pull request: 4836-revert-dhcp-upd

Updates #4836.

Squashed commit of the following:

commit 6fe1721d44be1c23e524d477e28b5f7cc5dd2dc6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 17:48:41 2022 +0300

    dhcpd: reverd mod upd
---
 CHANGELOG.md                     |  2 ++
 go.mod                           |  6 +++---
 internal/dhcpd/conn_unix.go      | 16 ++++++----------
 internal/dhcpd/conn_unix_test.go |  8 +++++---
 internal/dhcpd/v4.go             |  6 ++++--
 internal/dhcpd/v4_test.go        |  6 ++++--
 6 files changed, 24 insertions(+), 20 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 00b2820d..edee3432 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -78,6 +78,7 @@ See also the [v0.107.9 GitHub milestone][ms-v0.107.9].
 
 ### Fixed
 
+- DHCP not working on most OSes ([#4836]).
 - Several UI issues ([#4775], [#4776], [#4782]).
 
 ### Removed
@@ -89,6 +90,7 @@ See also the [v0.107.9 GitHub milestone][ms-v0.107.9].
 [#4775]: https://github.com/AdguardTeam/AdGuardHome/issues/4775
 [#4776]: https://github.com/AdguardTeam/AdGuardHome/issues/4776
 [#4782]: https://github.com/AdguardTeam/AdGuardHome/issues/4782
+[#4836]: https://github.com/AdguardTeam/AdGuardHome/issues/4836
 
 [go-1.18.5]:   https://groups.google.com/g/golang-announce/c/YqYYG87xB10
 [ms-v0.107.9]: https://github.com/AdguardTeam/AdGuardHome/milestone/45?closed=1
diff --git a/go.mod b/go.mod
index 43f2847c..53e5eb1b 100644
--- a/go.mod
+++ b/go.mod
@@ -21,10 +21,9 @@ require (
 	github.com/lucas-clemente/quic-go v0.28.1
 	github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
 	github.com/mdlayher/netlink v1.6.0
-	github.com/mdlayher/packet v1.0.0
 	// TODO(a.garipov): This package is deprecated; find a new one or use
-	// our own code for that.
-	github.com/mdlayher/raw v0.1.0 // indirect
+	// our own code for that.  Perhaps, use gopacket.
+	github.com/mdlayher/raw v0.1.0
 	github.com/miekg/dns v1.1.50
 	github.com/stretchr/testify v1.7.1
 	github.com/ti-mo/netfilter v0.4.0
@@ -52,6 +51,7 @@ require (
 	github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
 	github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
 	github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
+	github.com/mdlayher/packet v1.0.0 // indirect
 	github.com/mdlayher/socket v0.2.3 // indirect
 	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/onsi/ginkgo v1.16.5 // indirect
diff --git a/internal/dhcpd/conn_unix.go b/internal/dhcpd/conn_unix.go
index 837211af..1ed2105a 100644
--- a/internal/dhcpd/conn_unix.go
+++ b/internal/dhcpd/conn_unix.go
@@ -16,16 +16,18 @@ import (
 	"github.com/insomniacslk/dhcp/dhcpv4"
 	"github.com/insomniacslk/dhcp/dhcpv4/server4"
 	"github.com/mdlayher/ethernet"
-	"github.com/mdlayher/packet"
+
+	//lint:ignore SA1019 See the TODO in go.mod.
+	"github.com/mdlayher/raw"
 )
 
 // dhcpUnicastAddr is the combination of MAC and IP addresses for responding to
 // the unconfigured host.
 type dhcpUnicastAddr struct {
-	// packet.Addr is embedded here to make *dhcpUcastAddr a net.Addr without
+	// raw.Addr is embedded here to make *dhcpUcastAddr a net.Addr without
 	// actually implementing all methods.  It also contains the client's
 	// hardware address.
-	packet.Addr
+	raw.Addr
 
 	// yiaddr is an IP address just allocated by server for the host.
 	yiaddr net.IP
@@ -51,13 +53,7 @@ type dhcpConn struct {
 // newDHCPConn creates the special connection for DHCP server.
 func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err error) {
 	var ucast net.PacketConn
-	ucast, err = packet.Listen(
-		iface,
-		packet.Raw,
-		int(ethernet.EtherTypeIPv4),
-		nil,
-	)
-	if err != nil {
+	if ucast, err = raw.ListenPacket(iface, uint16(ethernet.EtherTypeIPv4), nil); err != nil {
 		return nil, fmt.Errorf("creating raw udp connection: %w", err)
 	}
 
diff --git a/internal/dhcpd/conn_unix_test.go b/internal/dhcpd/conn_unix_test.go
index 66899af6..84020acd 100644
--- a/internal/dhcpd/conn_unix_test.go
+++ b/internal/dhcpd/conn_unix_test.go
@@ -11,9 +11,11 @@ import (
 	"github.com/google/gopacket"
 	"github.com/google/gopacket/layers"
 	"github.com/insomniacslk/dhcp/dhcpv4"
-	"github.com/mdlayher/packet"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	//lint:ignore SA1019 See the TODO in go.mod.
+	"github.com/mdlayher/raw"
 )
 
 func TestDHCPConn_WriteTo_common(t *testing.T) {
@@ -56,7 +58,7 @@ func TestBuildEtherPkt(t *testing.T) {
 		srcIP:  net.IP{1, 2, 3, 4},
 	}
 	peer := &dhcpUnicastAddr{
-		Addr:   packet.Addr{HardwareAddr: net.HardwareAddr{6, 5, 4, 3, 2, 1}},
+		Addr:   raw.Addr{HardwareAddr: net.HardwareAddr{6, 5, 4, 3, 2, 1}},
 		yiaddr: net.IP{4, 3, 2, 1},
 	}
 	payload := (&dhcpv4.DHCPv4{}).ToBytes()
@@ -102,7 +104,7 @@ func TestBuildEtherPkt(t *testing.T) {
 	t.Run("serializing_error", func(t *testing.T) {
 		// Create a peer with invalid MAC.
 		badPeer := &dhcpUnicastAddr{
-			Addr:   packet.Addr{HardwareAddr: net.HardwareAddr{5, 4, 3, 2, 1}},
+			Addr:   raw.Addr{HardwareAddr: net.HardwareAddr{5, 4, 3, 2, 1}},
 			yiaddr: net.IP{4, 3, 2, 1},
 		}
 
diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go
index 5639837f..17881d2c 100644
--- a/internal/dhcpd/v4.go
+++ b/internal/dhcpd/v4.go
@@ -20,7 +20,9 @@ import (
 	"github.com/go-ping/ping"
 	"github.com/insomniacslk/dhcp/dhcpv4"
 	"github.com/insomniacslk/dhcp/dhcpv4/server4"
-	"github.com/mdlayher/packet"
+
+	//lint:ignore SA1019 See the TODO in go.mod.
+	"github.com/mdlayher/raw"
 )
 
 // v4Server is a DHCPv4 server.
@@ -992,7 +994,7 @@ func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DH
 		// Unicast DHCPOFFER and DHCPACK messages to the client's
 		// hardware address and yiaddr.
 		peer = &dhcpUnicastAddr{
-			Addr:   packet.Addr{HardwareAddr: req.ClientHWAddr},
+			Addr:   raw.Addr{HardwareAddr: req.ClientHWAddr},
 			yiaddr: resp.YourIPAddr,
 		}
 	default:
diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go
index be9259d8..3c241a4a 100644
--- a/internal/dhcpd/v4_test.go
+++ b/internal/dhcpd/v4_test.go
@@ -12,9 +12,11 @@ import (
 	"github.com/AdguardTeam/golibs/stringutil"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/insomniacslk/dhcp/dhcpv4"
-	"github.com/mdlayher/packet"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	//lint:ignore SA1019 See the TODO in go.mod.
+	"github.com/mdlayher/raw"
 )
 
 var (
@@ -554,7 +556,7 @@ func TestV4Server_Send(t *testing.T) {
 		req:  &dhcpv4.DHCPv4{ClientHWAddr: knownMAC},
 		resp: &dhcpv4.DHCPv4{YourIPAddr: knownIP},
 		want: &dhcpUnicastAddr{
-			Addr:   packet.Addr{HardwareAddr: knownMAC},
+			Addr:   raw.Addr{HardwareAddr: knownMAC},
 			yiaddr: knownIP,
 		},
 	}, {

From fd1c84181040fb7d8c402971fd1573d4b96b2353 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 15 Aug 2022 18:46:19 +0300
Subject: [PATCH 119/143] Pull request: Separate front- and back- end builds

Merge in DNS/adguard-home from imp-bamboo-specs to master

Squashed commit of the following:

commit 3415b650e48aefef3ad16030be3d797de4015403
Merge: e37c0a2b f58265ec
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 18:42:42 2022 +0300

    Merge branch 'master' into imp-bamboo-specs

commit e37c0a2bb52fab98e133332e8f54d500d0f96b06
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 18:30:33 2022 +0300

    scripts: replace find with loop

commit 826a02f6a11000cce4b3205229d6bbb050c8dd73
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 18:00:41 2022 +0300

    all: ...again

commit 54aebf5d4aeba35e3dc320436236759f4d1ccdad
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 17:59:24 2022 +0300

    all: fix spec yaml

commit 87b92b30504f2427c40303265354afba4855e0bb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 17:48:19 2022 +0300

    all: separate front- and back-end builds
---
 Makefile                      |  2 +-
 bamboo-specs/release.yaml     | 39 +++++++++++++++++++++++++++++-
 scripts/make/build-release.sh | 45 +++++++++++++++++++++++++++--------
 3 files changed, 74 insertions(+), 12 deletions(-)

diff --git a/Makefile b/Makefile
index 7c8f205f..b4823bb7 100644
--- a/Makefile
+++ b/Makefile
@@ -41,7 +41,7 @@ V1API = 0
 # into BUILD_RELEASE_DEPS_0, and so both frontend and backend
 # dependencies are fetched and the frontend is built.  Otherwise, if
 # FRONTEND_PREBUILT is 1, only backend dependencies are fetched and the
-# frontend isn't reuilt.
+# frontend isn't rebuilt.
 #
 # TODO(a.garipov): We could probably do that from .../build-release.sh,
 # but that would mean either calling make from inside make or
diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index 7dd9a859..e3451bdc 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -10,6 +10,12 @@
   'dockerGo': 'adguard/golang-ubuntu:5.0'
 
 'stages':
+- 'Build frontend':
+    'manual': false
+    'final': false
+    'jobs':
+    - 'Build frontend'
+
 - 'Make release':
     'manual': false
     'final': false
@@ -40,11 +46,41 @@
     'jobs':
     - 'Publish to GitHub Releases'
 
-'Make release':
+'Build frontend':
   'docker':
     'image': '${bamboo.dockerGo}'
     'volumes':
       '${system.YARN_DIR}': '${bamboo.cacheYarn}'
+  'key': 'BF'
+  'other':
+    'clean-working-dir': true
+  'tasks':
+  - 'checkout':
+      'force-clean-build': true
+  - 'script':
+      'interpreter': 'SHELL'
+      'scripts':
+      - |
+        #!/bin/sh
+
+        set -e -f -u -x
+
+        # Explicitly checkout the revision that we need.
+        git checkout "${bamboo.repository.revision.number}"
+
+        make js-build
+  'artifacts':
+  - 'name': 'AdGuardHome frontend'
+    'pattern': 'build*/**'
+    'shared': true
+    'required': true
+  'requirements':
+  - 'adg-docker': 'true'
+
+'Make release':
+  'docker':
+    'image': '${bamboo.dockerGo}'
+    'volumes':
       '${system.GO_CACHE_DIR}': '${bamboo.cacheGo}'
       '${system.GO_PKG_CACHE_DIR}': '${bamboo.cacheGoPkg}'
   'key': 'MR'
@@ -72,6 +108,7 @@
         make\
                 CHANNEL=${bamboo.channel}\
                 GPG_KEY_PASSPHRASE=${bamboo.gpgPassword}\
+                FRONTEND_PREBUILT=1\
                 VERBOSE=1\
                 build-release
   # TODO(a.garipov): Use more fine-grained artifact rules.
diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh
index 072dfe7a..1c8cb9c0 100644
--- a/scripts/make/build-release.sh
+++ b/scripts/make/build-release.sh
@@ -109,17 +109,17 @@ log "checking tools"
 
 # Make sure we fail gracefully if one of the tools we need is missing.  Use
 # alternatives when available.
-sha256sum_cmd='sha256sum'
-for tool in gpg gzip sed "$sha256sum_cmd" snapcraft tar zip
+use_shasum='0'
+for tool in gpg gzip sed sha256sum snapcraft tar zip
 do
 	if ! command -v "$tool" > /dev/null
 	then
-		if [ "$tool" = "$sha256sum_cmd" ] && command -v 'shasum' > /dev/null
+		if [ "$tool" = 'sha256sum' ] && command -v 'shasum' > /dev/null
 		then
-			# macOS doesn't have sha256sum installed by default, but
-			# it does have shasum.
+			# macOS doesn't have sha256sum installed by default, but it does
+			# have shasum.
 			log 'replacing sha256sum with shasum -a 256'
-			sha256sum_cmd='shasum -a 256'
+			use_shasum='1'
 		else
 			log "pieces don't fit, '$tool' not found"
 
@@ -127,7 +127,7 @@ do
 		fi
 	fi
 done
-readonly sha256sum_cmd
+readonly use_shasum
 
 # Data section.  Arrange data into space-separated tables for read -r to read.
 # Use a hyphen for missing values.
@@ -332,15 +332,40 @@ log "$build_archive"
 
 log "calculating checksums"
 
+# calculate_checksums uses the previously detected SHA-256 tool to calculate
+# checksums.  Do not use find with -exec, since shasum requires arguments.
+calculate_checksums() {
+	if [ "$use_shasum" -eq '0' ]
+	then
+		sha256sum "$@"
+	else
+		shasum -a 256 "$@"
+	fi
+}
+
 # Calculate the checksums of the files in a subshell with a different working
 # directory.  Don't use ls, because files matching one of the patterns may be
 # absent, which will make ls return with a non-zero status code.
+#
+# TODO(a.garipov): Consider calculating these as the build goes.
 (
+	set +f
+
 	cd "./${dist}"
 
-	find . ! -name . -prune \( -name '*.tar.gz' -o -name '*.zip' \)\
-		-exec "$sha256sum_cmd" {} +\
-		> ./checksums.txt
+	: > ./checksums.txt
+
+	for archive in ./*.zip ./*.tar.gz
+	do
+		# Make sure that we don't try to calculate a checksum for a glob pattern
+		# that matched no files.
+		if [ ! -f "$archive" ]
+		then
+			continue
+		fi
+
+		calculate_checksums "$archive" >> ./checksums.txt
+	done
 )
 
 log "writing versions"

From 721397cee3f3dd6d370f3c638eaee94644752cac Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 15 Aug 2022 18:57:34 +0300
Subject: [PATCH 120/143] Pull request: Fix frontend CI build

Merge in DNS/adguard-home from fix-bamboo-specs to master

Squashed commit of the following:

commit e59b75ab9528bbe8fbf5e15054d848abffbae312
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 18:52:10 2022 +0300

    all: fix ci frontend build
---
 bamboo-specs/release.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index e3451bdc..399daff3 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -68,7 +68,7 @@
         # Explicitly checkout the revision that we need.
         git checkout "${bamboo.repository.revision.number}"
 
-        make js-build
+        make js-deps js-build
   'artifacts':
   - 'name': 'AdGuardHome frontend'
     'pattern': 'build*/**'

From 6e63757fc7847dd19a26b02a2c47ef13bc67291a Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Mon, 15 Aug 2022 19:20:33 +0300
Subject: [PATCH 121/143] Pull request: upd-specs

Merge in DNS/adguard-home from upd-specs to master

Squashed commit of the following:

commit d7ac1dc1ef305098ff741d557c13db8a60ffe1f9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 19:16:51 2022 +0300

    bamboo-specs: allow larger keys
---
 bamboo-specs/release.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml
index 399daff3..e379dc73 100644
--- a/bamboo-specs/release.yaml
+++ b/bamboo-specs/release.yaml
@@ -101,7 +101,7 @@
         git checkout "${bamboo.repository.revision.number}"
 
         # Run the build with the specified channel.
-        echo "${bamboo.gpgSecretKey}"\
+        echo "${bamboo.gpgSecretKeyPart1}${bamboo.gpgSecretKeyPart2}"\
                 | awk '{ gsub(/\\n/, "\n"); print; }'\
                 | gpg --import --batch --yes
 

From d4c3a43bcb1ac006fe92a5b5478f61dc60d0bd3f Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 16 Aug 2022 13:21:25 +0300
Subject: [PATCH 122/143] Pull request #1558: add-dnssvc

Merge in DNS/adguard-home from add-dnssvc to master

Squashed commit of the following:

commit 55f4f114bab65a03c0d65383e89020a7356cff32
Merge: 95dc28d9 6e63757f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 20:53:07 2022 +0300

    Merge branch 'master' into add-dnssvc

commit 95dc28d9d77d06e8ac98c1e6772557bffbf1705b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 20:52:50 2022 +0300

    all: imp tests, docs

commit 0d9d02950d84afd160b4b1c118da856cee6f12e5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 11 19:27:59 2022 +0300

    all: imp docs

commit 8990e038a81da4430468da12fcebedf79fe14df6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 11 19:05:29 2022 +0300

    all: imp tests more

commit 92730d93a2a1ac77888c2655508e43efaf0e9fde
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 11 18:37:48 2022 +0300

    all: imp tests more

commit 8cd45ba30da7ac310e9dc666fb2af438e577b02d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 11 18:11:15 2022 +0300

    all: add v1 dnssvc stub; refactor tests
---
 internal/aghalg/aghalg.go                  |  14 ++
 internal/aghnet/hostscontainer_test.go     |   2 +-
 internal/aghos/aghos_test.go               |   2 +-
 internal/aghos/filewalker_internal_test.go |  57 ++++++
 internal/aghos/filewalker_test.go          |  52 +-----
 internal/aghtest/exchanger.go              |  20 ---
 internal/aghtest/fswatcher.go              |  23 ---
 internal/aghtest/interface.go              | 135 ++++++++++++++
 internal/aghtest/interface_test.go         |   9 +
 internal/aghtest/testfs.go                 |  46 -----
 internal/aghtest/upstream.go               | 154 +++++++++-------
 internal/dnsforward/dnsforward_test.go     |  71 +++++---
 internal/filtering/filtering_test.go       |  92 +++++-----
 internal/filtering/safebrowsing.go         |   4 +-
 internal/filtering/safebrowsing_test.go    |  30 ++--
 internal/home/rdns_test.go                 |  59 +++++--
 internal/v1/dnssvc/dnssvc.go               | 193 +++++++++++++++++++++
 internal/v1/dnssvc/dnssvc_test.go          |  89 ++++++++++
 internal/v1/websvc/websvc.go               |   9 +-
 19 files changed, 742 insertions(+), 319 deletions(-)
 create mode 100644 internal/aghos/filewalker_internal_test.go
 delete mode 100644 internal/aghtest/exchanger.go
 delete mode 100644 internal/aghtest/fswatcher.go
 create mode 100644 internal/aghtest/interface.go
 create mode 100644 internal/aghtest/interface_test.go
 delete mode 100644 internal/aghtest/testfs.go
 create mode 100644 internal/v1/dnssvc/dnssvc.go
 create mode 100644 internal/v1/dnssvc/dnssvc_test.go

diff --git a/internal/aghalg/aghalg.go b/internal/aghalg/aghalg.go
index 65e81cbc..f0b71a09 100644
--- a/internal/aghalg/aghalg.go
+++ b/internal/aghalg/aghalg.go
@@ -10,6 +10,20 @@ import (
 	"golang.org/x/exp/slices"
 )
 
+// Coalesce returns the first non-zero value.  It is named after the function
+// COALESCE in SQL.  If values or all its elements are empty, it returns a zero
+// value.
+func Coalesce[T comparable](values ...T) (res T) {
+	var zero T
+	for _, v := range values {
+		if v != zero {
+			return v
+		}
+	}
+
+	return zero
+}
+
 // UniqChecker allows validating uniqueness of comparable items.
 //
 // TODO(a.garipov): The Ordered constraint is only really necessary in Validate.
diff --git a/internal/aghnet/hostscontainer_test.go b/internal/aghnet/hostscontainer_test.go
index 019c713e..1f75a3c9 100644
--- a/internal/aghnet/hostscontainer_test.go
+++ b/internal/aghnet/hostscontainer_test.go
@@ -470,7 +470,7 @@ func TestHostsContainer(t *testing.T) {
 		}},
 	}, {
 		req: &urlfilter.DNSRequest{
-			Hostname: "nonexisting",
+			Hostname: "nonexistent.example",
 			DNSType:  dns.TypeA,
 		},
 		name: "non-existing",
diff --git a/internal/aghos/aghos_test.go b/internal/aghos/aghos_test.go
index e68c26b7..684f646e 100644
--- a/internal/aghos/aghos_test.go
+++ b/internal/aghos/aghos_test.go
@@ -1,4 +1,4 @@
-package aghos
+package aghos_test
 
 import (
 	"testing"
diff --git a/internal/aghos/filewalker_internal_test.go b/internal/aghos/filewalker_internal_test.go
new file mode 100644
index 00000000..bb162812
--- /dev/null
+++ b/internal/aghos/filewalker_internal_test.go
@@ -0,0 +1,57 @@
+package aghos
+
+import (
+	"io/fs"
+	"path"
+	"testing"
+	"testing/fstest"
+
+	"github.com/AdguardTeam/golibs/errors"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// errFS is an fs.FS implementation, method Open of which always returns
+// errFSOpen.
+type errFS struct{}
+
+// errFSOpen is returned from errGlobFS.Open.
+const errFSOpen errors.Error = "test open error"
+
+// Open implements the fs.FS interface for *errGlobFS.  fsys is always nil and
+// err is always errFSOpen.
+func (efs *errFS) Open(name string) (fsys fs.File, err error) {
+	return nil, errFSOpen
+}
+
+func TestWalkerFunc_CheckFile(t *testing.T) {
+	emptyFS := fstest.MapFS{}
+
+	t.Run("non-existing", func(t *testing.T) {
+		_, ok, err := checkFile(emptyFS, nil, "lol")
+		require.NoError(t, err)
+
+		assert.True(t, ok)
+	})
+
+	t.Run("invalid_argument", func(t *testing.T) {
+		_, ok, err := checkFile(&errFS{}, nil, "")
+		require.ErrorIs(t, err, errFSOpen)
+
+		assert.False(t, ok)
+	})
+
+	t.Run("ignore_dirs", func(t *testing.T) {
+		const dirName = "dir"
+
+		testFS := fstest.MapFS{
+			path.Join(dirName, "file"): &fstest.MapFile{Data: []byte{}},
+		}
+
+		patterns, ok, err := checkFile(testFS, nil, dirName)
+		require.NoError(t, err)
+
+		assert.Empty(t, patterns)
+		assert.True(t, ok)
+	})
+}
diff --git a/internal/aghos/filewalker_test.go b/internal/aghos/filewalker_test.go
index 97d1a845..94443831 100644
--- a/internal/aghos/filewalker_test.go
+++ b/internal/aghos/filewalker_test.go
@@ -1,13 +1,13 @@
-package aghos
+package aghos_test
 
 import (
 	"bufio"
 	"io"
-	"io/fs"
 	"path"
 	"testing"
 	"testing/fstest"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -16,7 +16,7 @@ import (
 func TestFileWalker_Walk(t *testing.T) {
 	const attribute = `000`
 
-	makeFileWalker := func(_ string) (fw FileWalker) {
+	makeFileWalker := func(_ string) (fw aghos.FileWalker) {
 		return func(r io.Reader) (patterns []string, cont bool, err error) {
 			s := bufio.NewScanner(r)
 			for s.Scan() {
@@ -113,7 +113,7 @@ func TestFileWalker_Walk(t *testing.T) {
 		f := fstest.MapFS{
 			filename: &fstest.MapFile{Data: []byte("[]")},
 		}
-		ok, err := FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) {
+		ok, err := aghos.FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) {
 			s := bufio.NewScanner(r)
 			for s.Scan() {
 				patterns = append(patterns, s.Text())
@@ -134,7 +134,7 @@ func TestFileWalker_Walk(t *testing.T) {
 			"mockfile.txt": &fstest.MapFile{Data: []byte(`mockdata`)},
 		}
 
-		ok, err := FileWalker(func(r io.Reader) (patterns []string, ok bool, err error) {
+		ok, err := aghos.FileWalker(func(r io.Reader) (patterns []string, ok bool, err error) {
 			return nil, true, rerr
 		}).Walk(f, "*")
 		require.ErrorIs(t, err, rerr)
@@ -142,45 +142,3 @@ func TestFileWalker_Walk(t *testing.T) {
 		assert.False(t, ok)
 	})
 }
-
-type errFS struct {
-	fs.GlobFS
-}
-
-const errErrFSOpen errors.Error = "this error is always returned"
-
-func (efs *errFS) Open(name string) (fs.File, error) {
-	return nil, errErrFSOpen
-}
-
-func TestWalkerFunc_CheckFile(t *testing.T) {
-	emptyFS := fstest.MapFS{}
-
-	t.Run("non-existing", func(t *testing.T) {
-		_, ok, err := checkFile(emptyFS, nil, "lol")
-		require.NoError(t, err)
-
-		assert.True(t, ok)
-	})
-
-	t.Run("invalid_argument", func(t *testing.T) {
-		_, ok, err := checkFile(&errFS{}, nil, "")
-		require.ErrorIs(t, err, errErrFSOpen)
-
-		assert.False(t, ok)
-	})
-
-	t.Run("ignore_dirs", func(t *testing.T) {
-		const dirName = "dir"
-
-		testFS := fstest.MapFS{
-			path.Join(dirName, "file"): &fstest.MapFile{Data: []byte{}},
-		}
-
-		patterns, ok, err := checkFile(testFS, nil, dirName)
-		require.NoError(t, err)
-
-		assert.Empty(t, patterns)
-		assert.True(t, ok)
-	})
-}
diff --git a/internal/aghtest/exchanger.go b/internal/aghtest/exchanger.go
deleted file mode 100644
index 2c617814..00000000
--- a/internal/aghtest/exchanger.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package aghtest
-
-import (
-	"github.com/AdguardTeam/dnsproxy/upstream"
-	"github.com/miekg/dns"
-)
-
-// Exchanger is a mock aghnet.Exchanger implementation for tests.
-type Exchanger struct {
-	Ups upstream.Upstream
-}
-
-// Exchange implements aghnet.Exchanger interface for *Exchanger.
-func (e *Exchanger) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
-	if e.Ups == nil {
-		e.Ups = &TestErrUpstream{}
-	}
-
-	return e.Ups.Exchange(req)
-}
diff --git a/internal/aghtest/fswatcher.go b/internal/aghtest/fswatcher.go
deleted file mode 100644
index 0df4470d..00000000
--- a/internal/aghtest/fswatcher.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package aghtest
-
-// FSWatcher is a mock aghos.FSWatcher implementation to use in tests.
-type FSWatcher struct {
-	OnEvents func() (e <-chan struct{})
-	OnAdd    func(name string) (err error)
-	OnClose  func() (err error)
-}
-
-// Events implements the aghos.FSWatcher interface for *FSWatcher.
-func (w *FSWatcher) Events() (e <-chan struct{}) {
-	return w.OnEvents()
-}
-
-// Add implements the aghos.FSWatcher interface for *FSWatcher.
-func (w *FSWatcher) Add(name string) (err error) {
-	return w.OnAdd(name)
-}
-
-// Close implements the aghos.FSWatcher interface for *FSWatcher.
-func (w *FSWatcher) Close() (err error) {
-	return w.OnClose()
-}
diff --git a/internal/aghtest/interface.go b/internal/aghtest/interface.go
new file mode 100644
index 00000000..2de9d372
--- /dev/null
+++ b/internal/aghtest/interface.go
@@ -0,0 +1,135 @@
+package aghtest
+
+import (
+	"io/fs"
+	"net"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
+	"github.com/AdguardTeam/dnsproxy/upstream"
+	"github.com/miekg/dns"
+)
+
+// Interface Mocks
+//
+// Keep entities in this file in alphabetic order.
+
+// Standard Library
+
+// type check
+var _ fs.FS = &FS{}
+
+// FS is a mock [fs.FS] implementation for tests.
+type FS struct {
+	OnOpen func(name string) (fs.File, error)
+}
+
+// Open implements the [fs.FS] interface for *FS.
+func (fsys *FS) Open(name string) (fs.File, error) {
+	return fsys.OnOpen(name)
+}
+
+// type check
+var _ fs.GlobFS = &GlobFS{}
+
+// GlobFS is a mock [fs.GlobFS] implementation for tests.
+type GlobFS struct {
+	// FS is embedded here to avoid implementing all it's methods.
+	FS
+	OnGlob func(pattern string) ([]string, error)
+}
+
+// Glob implements the [fs.GlobFS] interface for *GlobFS.
+func (fsys *GlobFS) Glob(pattern string) ([]string, error) {
+	return fsys.OnGlob(pattern)
+}
+
+// type check
+var _ fs.StatFS = &StatFS{}
+
+// StatFS is a mock [fs.StatFS] implementation for tests.
+type StatFS struct {
+	// FS is embedded here to avoid implementing all it's methods.
+	FS
+	OnStat func(name string) (fs.FileInfo, error)
+}
+
+// Stat implements the [fs.StatFS] interface for *StatFS.
+func (fsys *StatFS) Stat(name string) (fs.FileInfo, error) {
+	return fsys.OnStat(name)
+}
+
+// type check
+var _ net.Listener = (*Listener)(nil)
+
+// Listener is a mock [net.Listener] implementation for tests.
+type Listener struct {
+	OnAccept func() (conn net.Conn, err error)
+	OnAddr   func() (addr net.Addr)
+	OnClose  func() (err error)
+}
+
+// Accept implements the [net.Listener] interface for *Listener.
+func (l *Listener) Accept() (conn net.Conn, err error) {
+	return l.OnAccept()
+}
+
+// Addr implements the [net.Listener] interface for *Listener.
+func (l *Listener) Addr() (addr net.Addr) {
+	return l.OnAddr()
+}
+
+// Close implements the [net.Listener] interface for *Listener.
+func (l *Listener) Close() (err error) {
+	return l.OnClose()
+}
+
+// Module dnsproxy
+
+// type check
+var _ upstream.Upstream = (*UpstreamMock)(nil)
+
+// UpstreamMock is a mock [upstream.Upstream] implementation for tests.
+//
+// TODO(a.garipov): Replace with all uses of Upstream with UpstreamMock and
+// rename it to just Upstream.
+type UpstreamMock struct {
+	OnAddress  func() (addr string)
+	OnExchange func(req *dns.Msg) (resp *dns.Msg, err error)
+}
+
+// Address implements the [upstream.Upstream] interface for *UpstreamMock.
+func (u *UpstreamMock) Address() (addr string) {
+	return u.OnAddress()
+}
+
+// Exchange implements the [upstream.Upstream] interface for *UpstreamMock.
+func (u *UpstreamMock) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
+	return u.OnExchange(req)
+}
+
+// Module AdGuardHome
+
+// type check
+var _ aghos.FSWatcher = (*FSWatcher)(nil)
+
+// FSWatcher is a mock [aghos.FSWatcher] implementation for tests.
+type FSWatcher struct {
+	OnEvents func() (e <-chan struct{})
+	OnAdd    func(name string) (err error)
+	OnClose  func() (err error)
+}
+
+// Events implements the [aghos.FSWatcher] interface for *FSWatcher.
+func (w *FSWatcher) Events() (e <-chan struct{}) {
+	return w.OnEvents()
+}
+
+// Add implements the [aghos.FSWatcher] interface for *FSWatcher.
+func (w *FSWatcher) Add(name string) (err error) {
+	return w.OnAdd(name)
+}
+
+// Close implements the [aghos.FSWatcher] interface for *FSWatcher.
+func (w *FSWatcher) Close() (err error) {
+	return w.OnClose()
+}
diff --git a/internal/aghtest/interface_test.go b/internal/aghtest/interface_test.go
new file mode 100644
index 00000000..5a465c2c
--- /dev/null
+++ b/internal/aghtest/interface_test.go
@@ -0,0 +1,9 @@
+package aghtest_test
+
+import (
+	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
+	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
+)
+
+// type check
+var _ aghos.FSWatcher = (*aghtest.FSWatcher)(nil)
diff --git a/internal/aghtest/testfs.go b/internal/aghtest/testfs.go
deleted file mode 100644
index 88203fec..00000000
--- a/internal/aghtest/testfs.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package aghtest
-
-import "io/fs"
-
-// type check
-var _ fs.FS = &FS{}
-
-// FS is a mock fs.FS implementation to use in tests.
-type FS struct {
-	OnOpen func(name string) (fs.File, error)
-}
-
-// Open implements the fs.FS interface for *FS.
-func (fsys *FS) Open(name string) (fs.File, error) {
-	return fsys.OnOpen(name)
-}
-
-// type check
-var _ fs.StatFS = &StatFS{}
-
-// StatFS is a mock fs.StatFS implementation to use in tests.
-type StatFS struct {
-	// FS is embedded here to avoid implementing all it's methods.
-	FS
-	OnStat func(name string) (fs.FileInfo, error)
-}
-
-// Stat implements the fs.StatFS interface for *StatFS.
-func (fsys *StatFS) Stat(name string) (fs.FileInfo, error) {
-	return fsys.OnStat(name)
-}
-
-// type check
-var _ fs.GlobFS = &GlobFS{}
-
-// GlobFS is a mock fs.GlobFS implementation to use in tests.
-type GlobFS struct {
-	// FS is embedded here to avoid implementing all it's methods.
-	FS
-	OnGlob func(pattern string) ([]string, error)
-}
-
-// Glob implements the fs.GlobFS interface for *GlobFS.
-func (fsys *GlobFS) Glob(pattern string) ([]string, error) {
-	return fsys.OnGlob(pattern)
-}
diff --git a/internal/aghtest/upstream.go b/internal/aghtest/upstream.go
index 95d8f5ad..699c14b9 100644
--- a/internal/aghtest/upstream.go
+++ b/internal/aghtest/upstream.go
@@ -6,12 +6,18 @@ import (
 	"fmt"
 	"net"
 	"strings"
-	"sync"
+	"testing"
 
+	"github.com/AdguardTeam/golibs/errors"
 	"github.com/miekg/dns"
+	"github.com/stretchr/testify/require"
 )
 
+// Additional Upstream Testing Utilities
+
 // Upstream is a mock implementation of upstream.Upstream.
+//
+// TODO(a.garipov): Replace with UpstreamMock and rename it to just Upstream.
 type Upstream struct {
 	// CName is a map of hostname to canonical name.
 	CName map[string][]string
@@ -25,6 +31,43 @@ type Upstream struct {
 	Addr string
 }
 
+// RespondTo returns a response with answer if req has class cl, question type
+// qt, and target targ.
+func RespondTo(t testing.TB, req *dns.Msg, cl, qt uint16, targ, answer string) (resp *dns.Msg) {
+	t.Helper()
+
+	require.NotNil(t, req)
+	require.Len(t, req.Question, 1)
+
+	q := req.Question[0]
+	targ = dns.Fqdn(targ)
+	if q.Qclass != cl || q.Qtype != qt || q.Name != targ {
+		return nil
+	}
+
+	respHdr := dns.RR_Header{
+		Name:   targ,
+		Rrtype: qt,
+		Class:  cl,
+		Ttl:    60,
+	}
+
+	resp = new(dns.Msg).SetReply(req)
+	switch qt {
+	case dns.TypePTR:
+		resp.Answer = []dns.RR{
+			&dns.PTR{
+				Hdr: respHdr,
+				Ptr: answer,
+			},
+		}
+	default:
+		t.Fatalf("unsupported question type: %s", dns.Type(qt))
+	}
+
+	return resp
+}
+
 // Exchange implements the upstream.Upstream interface for *Upstream.
 //
 // TODO(a.garipov): Split further into handlers.
@@ -76,74 +119,57 @@ func (u *Upstream) Address() string {
 	return u.Addr
 }
 
-// TestBlockUpstream implements upstream.Upstream interface for replacing real
-// upstream in tests.
-type TestBlockUpstream struct {
-	Hostname string
-
-	// lock protects reqNum.
-	lock   sync.RWMutex
-	reqNum int
-
-	Block bool
-}
-
-// Exchange returns a message unique for TestBlockUpstream's Hostname-Block
-// pair.
-func (u *TestBlockUpstream) Exchange(r *dns.Msg) (*dns.Msg, error) {
-	u.lock.Lock()
-	defer u.lock.Unlock()
-	u.reqNum++
-
-	hash := sha256.Sum256([]byte(u.Hostname))
-	hashToReturn := hex.EncodeToString(hash[:])
-	if !u.Block {
-		hashToReturn = hex.EncodeToString(hash[:])[:2] + strings.Repeat("ab", 28)
+// NewBlockUpstream returns an [*UpstreamMock] that works like an upstream that
+// supports hash-based safe-browsing/adult-blocking feature.  If shouldBlock is
+// true, hostname's actual hash is returned, blocking it.  Otherwise, it returns
+// a different hash.
+func NewBlockUpstream(hostname string, shouldBlock bool) (u *UpstreamMock) {
+	hash := sha256.Sum256([]byte(hostname))
+	hashStr := hex.EncodeToString(hash[:])
+	if !shouldBlock {
+		hashStr = hex.EncodeToString(hash[:])[:2] + strings.Repeat("ab", 28)
 	}
 
-	m := &dns.Msg{}
-	m.SetReply(r)
-	m.Answer = []dns.RR{
-		&dns.TXT{
-			Hdr: dns.RR_Header{
-				Name: r.Question[0].Name,
-			},
-			Txt: []string{
-				hashToReturn,
-			},
+	ans := &dns.TXT{
+		Hdr: dns.RR_Header{
+			Name:   "",
+			Rrtype: dns.TypeTXT,
+			Class:  dns.ClassINET,
+			Ttl:    60,
+		},
+		Txt: []string{hashStr},
+	}
+	respTmpl := &dns.Msg{
+		Answer: []dns.RR{ans},
+	}
+
+	return &UpstreamMock{
+		OnAddress: func() (addr string) {
+			return "sbpc.upstream.example"
+		},
+		OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
+			resp = respTmpl.Copy()
+			resp.SetReply(req)
+			resp.Answer[0].(*dns.TXT).Hdr.Name = req.Question[0].Name
+
+			return resp, nil
 		},
 	}
-
-	return m, nil
 }
 
-// Address always returns an empty string.
-func (u *TestBlockUpstream) Address() string {
-	return ""
-}
+// ErrUpstream is the error returned from the [*UpstreamMock] created by
+// [NewErrorUpstream].
+const ErrUpstream errors.Error = "test upstream error"
 
-// RequestsCount returns the number of handled requests. It's safe for
-// concurrent use.
-func (u *TestBlockUpstream) RequestsCount() int {
-	u.lock.Lock()
-	defer u.lock.Unlock()
-
-	return u.reqNum
-}
-
-// TestErrUpstream implements upstream.Upstream interface for replacing real
-// upstream in tests.
-type TestErrUpstream struct {
-	// The error returned by Exchange may be unwrapped to the Err.
-	Err error
-}
-
-// Exchange always returns nil Msg and non-nil error.
-func (u *TestErrUpstream) Exchange(*dns.Msg) (*dns.Msg, error) {
-	return nil, fmt.Errorf("errupstream: %w", u.Err)
-}
-
-// Address always returns an empty string.
-func (u *TestErrUpstream) Address() string {
-	return ""
+// NewErrorUpstream returns an [*UpstreamMock] that returns [ErrUpstream] from
+// its Exchange method.
+func NewErrorUpstream() (u *UpstreamMock) {
+	return &UpstreamMock{
+		OnAddress: func() (addr string) {
+			return "error.upstream.example"
+		},
+		OnExchange: func(_ *dns.Msg) (resp *dns.Msg, err error) {
+			return nil, errors.Error("test upstream error")
+		},
+	}
 }
diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go
index e4d61b48..5144680b 100644
--- a/internal/dnsforward/dnsforward_test.go
+++ b/internal/dnsforward/dnsforward_test.go
@@ -17,13 +17,13 @@ import (
 	"testing/fstest"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
 	"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
 	"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/netutil"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/AdguardTeam/golibs/timeutil"
@@ -853,10 +853,7 @@ func TestBlockedByHosts(t *testing.T) {
 func TestBlockedBySafeBrowsing(t *testing.T) {
 	const hostname = "wmconvirus.narod.ru"
 
-	sbUps := &aghtest.TestBlockUpstream{
-		Hostname: hostname,
-		Block:    true,
-	}
+	sbUps := aghtest.NewBlockUpstream(hostname, true)
 	ans4, _ := (&aghtest.TestResolver{}).HostToIPs(hostname)
 
 	filterConf := &filtering.Config{
@@ -1029,7 +1026,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
 	s.conf.UDPListenAddrs = []*net.UDPAddr{{}}
 	s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
 	s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
-	s.conf.FilteringConfig.ProtectionEnabled = true
+	s.conf.ProtectionEnabled = true
 
 	err = s.Prepare(nil)
 	require.NoError(t, err)
@@ -1177,25 +1174,48 @@ func TestNewServer(t *testing.T) {
 }
 
 func TestServer_Exchange(t *testing.T) {
-	extUpstream := &aghtest.Upstream{
-		Reverse: map[string][]string{
-			"1.1.1.1.in-addr.arpa.": {"one.one.one.one"},
+	const (
+		onesHost        = "one.one.one.one"
+		localDomainHost = "local.domain"
+	)
+
+	var (
+		onesIP  = net.IP{1, 1, 1, 1}
+		localIP = net.IP{192, 168, 1, 1}
+	)
+
+	revExtIPv4, err := netutil.IPToReversedAddr(onesIP)
+	require.NoError(t, err)
+
+	extUpstream := &aghtest.UpstreamMock{
+		OnAddress: func() (addr string) { return "external.upstream.example" },
+		OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
+			resp = aghalg.Coalesce(
+				aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revExtIPv4, onesHost),
+				new(dns.Msg).SetRcode(req, dns.RcodeNameError),
+			)
+
+			return resp, nil
 		},
 	}
-	locUpstream := &aghtest.Upstream{
-		Reverse: map[string][]string{
-			"1.1.168.192.in-addr.arpa.": {"local.domain"},
-			"2.1.168.192.in-addr.arpa.": {},
+
+	revLocIPv4, err := netutil.IPToReversedAddr(localIP)
+	require.NoError(t, err)
+
+	locUpstream := &aghtest.UpstreamMock{
+		OnAddress: func() (addr string) { return "local.upstream.example" },
+		OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
+			resp = aghalg.Coalesce(
+				aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revLocIPv4, localDomainHost),
+				new(dns.Msg).SetRcode(req, dns.RcodeNameError),
+			)
+
+			return resp, nil
 		},
 	}
-	upstreamErr := errors.Error("upstream error")
-	errUpstream := &aghtest.TestErrUpstream{
-		Err: upstreamErr,
-	}
-	nonPtrUpstream := &aghtest.TestBlockUpstream{
-		Hostname: "some-host",
-		Block:    true,
-	}
+
+	errUpstream := aghtest.NewErrorUpstream()
+	nonPtrUpstream := aghtest.NewBlockUpstream("some-host", true)
 
 	srv := NewCustomServer(&proxy.Proxy{
 		Config: proxy.Config{
@@ -1209,7 +1229,6 @@ func TestServer_Exchange(t *testing.T) {
 
 	srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
 
-	localIP := net.IP{192, 168, 1, 1}
 	testCases := []struct {
 		name        string
 		want        string
@@ -1218,20 +1237,20 @@ func TestServer_Exchange(t *testing.T) {
 		req         net.IP
 	}{{
 		name:        "external_good",
-		want:        "one.one.one.one",
+		want:        onesHost,
 		wantErr:     nil,
 		locUpstream: nil,
-		req:         net.IP{1, 1, 1, 1},
+		req:         onesIP,
 	}, {
 		name:        "local_good",
-		want:        "local.domain",
+		want:        localDomainHost,
 		wantErr:     nil,
 		locUpstream: locUpstream,
 		req:         localIP,
 	}, {
 		name:        "upstream_error",
 		want:        "",
-		wantErr:     upstreamErr,
+		wantErr:     aghtest.ErrUpstream,
 		locUpstream: errUpstream,
 		req:         localIP,
 	}, {
diff --git a/internal/filtering/filtering_test.go b/internal/filtering/filtering_test.go
index 79c4d040..95554b07 100644
--- a/internal/filtering/filtering_test.go
+++ b/internal/filtering/filtering_test.go
@@ -21,6 +21,11 @@ func TestMain(m *testing.M) {
 	aghtest.DiscardLogOutput(m)
 }
 
+const (
+	sbBlocked = "wmconvirus.narod.ru"
+	pcBlocked = "pornhub.com"
+)
+
 var setts = Settings{
 	ProtectionEnabled: true,
 }
@@ -173,43 +178,37 @@ func TestSafeBrowsing(t *testing.T) {
 
 	d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
 	t.Cleanup(d.Close)
-	const matching = "wmconvirus.narod.ru"
-	d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{
-		Hostname: matching,
-		Block:    true,
-	})
-	d.checkMatch(t, matching)
 
-	require.Contains(t, logOutput.String(), "SafeBrowsing lookup for "+matching)
+	d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
+	d.checkMatch(t, sbBlocked)
 
-	d.checkMatch(t, "test."+matching)
+	require.Contains(t, logOutput.String(), fmt.Sprintf("safebrowsing lookup for %q", sbBlocked))
+
+	d.checkMatch(t, "test."+sbBlocked)
 	d.checkMatchEmpty(t, "yandex.ru")
-	d.checkMatchEmpty(t, "pornhub.com")
+	d.checkMatchEmpty(t, pcBlocked)
 
 	// Cached result.
 	d.safeBrowsingServer = "127.0.0.1"
-	d.checkMatch(t, matching)
-	d.checkMatchEmpty(t, "pornhub.com")
+	d.checkMatch(t, sbBlocked)
+	d.checkMatchEmpty(t, pcBlocked)
 	d.safeBrowsingServer = defaultSafebrowsingServer
 }
 
 func TestParallelSB(t *testing.T) {
 	d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
 	t.Cleanup(d.Close)
-	const matching = "wmconvirus.narod.ru"
-	d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{
-		Hostname: matching,
-		Block:    true,
-	})
+
+	d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
 
 	t.Run("group", func(t *testing.T) {
 		for i := 0; i < 100; i++ {
 			t.Run(fmt.Sprintf("aaa%d", i), func(t *testing.T) {
 				t.Parallel()
-				d.checkMatch(t, matching)
-				d.checkMatch(t, "test."+matching)
+				d.checkMatch(t, sbBlocked)
+				d.checkMatch(t, "test."+sbBlocked)
 				d.checkMatchEmpty(t, "yandex.ru")
-				d.checkMatchEmpty(t, "pornhub.com")
+				d.checkMatchEmpty(t, pcBlocked)
 			})
 		}
 	})
@@ -382,23 +381,19 @@ func TestParentalControl(t *testing.T) {
 
 	d := newForTest(t, &Config{ParentalEnabled: true}, nil)
 	t.Cleanup(d.Close)
-	const matching = "pornhub.com"
-	d.SetParentalUpstream(&aghtest.TestBlockUpstream{
-		Hostname: matching,
-		Block:    true,
-	})
 
-	d.checkMatch(t, matching)
-	require.Contains(t, logOutput.String(), "Parental lookup for "+matching)
+	d.SetParentalUpstream(aghtest.NewBlockUpstream(pcBlocked, true))
+	d.checkMatch(t, pcBlocked)
+	require.Contains(t, logOutput.String(), fmt.Sprintf("parental lookup for %q", pcBlocked))
 
-	d.checkMatch(t, "www."+matching)
+	d.checkMatch(t, "www."+pcBlocked)
 	d.checkMatchEmpty(t, "www.yandex.ru")
 	d.checkMatchEmpty(t, "yandex.ru")
 	d.checkMatchEmpty(t, "api.jquery.com")
 
 	// Test cached result.
 	d.parentalServer = "127.0.0.1"
-	d.checkMatch(t, matching)
+	d.checkMatch(t, pcBlocked)
 	d.checkMatchEmpty(t, "yandex.ru")
 }
 
@@ -445,7 +440,7 @@ func TestMatching(t *testing.T) {
 	}, {
 		name:           "sanity",
 		rules:          "||doubleclick.net^",
-		host:           "wmconvirus.narod.ru",
+		host:           sbBlocked,
 		wantIsFiltered: false,
 		wantReason:     NotFilteredNotFound,
 		wantDNSType:    dns.TypeA,
@@ -765,14 +760,9 @@ func TestClientSettings(t *testing.T) {
 		}},
 	)
 	t.Cleanup(d.Close)
-	d.SetParentalUpstream(&aghtest.TestBlockUpstream{
-		Hostname: "pornhub.com",
-		Block:    true,
-	})
-	d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{
-		Hostname: "wmconvirus.narod.ru",
-		Block:    true,
-	})
+
+	d.SetParentalUpstream(aghtest.NewBlockUpstream(pcBlocked, true))
+	d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
 
 	type testCase struct {
 		name       string
@@ -787,12 +777,12 @@ func TestClientSettings(t *testing.T) {
 		wantReason: FilteredBlockList,
 	}, {
 		name:       "parental",
-		host:       "pornhub.com",
+		host:       pcBlocked,
 		before:     true,
 		wantReason: FilteredParental,
 	}, {
 		name:       "safebrowsing",
-		host:       "wmconvirus.narod.ru",
+		host:       sbBlocked,
 		before:     false,
 		wantReason: FilteredSafeBrowsing,
 	}, {
@@ -836,33 +826,29 @@ func TestClientSettings(t *testing.T) {
 func BenchmarkSafeBrowsing(b *testing.B) {
 	d := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil)
 	b.Cleanup(d.Close)
-	blocked := "wmconvirus.narod.ru"
-	d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{
-		Hostname: blocked,
-		Block:    true,
-	})
+
+	d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
+
 	for n := 0; n < b.N; n++ {
-		res, err := d.CheckHost(blocked, dns.TypeA, &setts)
+		res, err := d.CheckHost(sbBlocked, dns.TypeA, &setts)
 		require.NoError(b, err)
 
-		assert.True(b, res.IsFiltered, "Expected hostname %s to match", blocked)
+		assert.Truef(b, res.IsFiltered, "expected hostname %q to match", sbBlocked)
 	}
 }
 
 func BenchmarkSafeBrowsingParallel(b *testing.B) {
 	d := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil)
 	b.Cleanup(d.Close)
-	blocked := "wmconvirus.narod.ru"
-	d.SetSafeBrowsingUpstream(&aghtest.TestBlockUpstream{
-		Hostname: blocked,
-		Block:    true,
-	})
+
+	d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
+
 	b.RunParallel(func(pb *testing.PB) {
 		for pb.Next() {
-			res, err := d.CheckHost(blocked, dns.TypeA, &setts)
+			res, err := d.CheckHost(sbBlocked, dns.TypeA, &setts)
 			require.NoError(b, err)
 
-			assert.True(b, res.IsFiltered, "Expected hostname %s to match", blocked)
+			assert.Truef(b, res.IsFiltered, "expected hostname %q to match", sbBlocked)
 		}
 	})
 }
diff --git a/internal/filtering/safebrowsing.go b/internal/filtering/safebrowsing.go
index 046756ac..e49c4070 100644
--- a/internal/filtering/safebrowsing.go
+++ b/internal/filtering/safebrowsing.go
@@ -314,7 +314,7 @@ func (d *DNSFilter) checkSafeBrowsing(
 
 	if log.GetLevel() >= log.DEBUG {
 		timer := log.StartTimer()
-		defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
+		defer timer.LogElapsed("safebrowsing lookup for %q", host)
 	}
 
 	sctx := &sbCtx{
@@ -348,7 +348,7 @@ func (d *DNSFilter) checkParental(
 
 	if log.GetLevel() >= log.DEBUG {
 		timer := log.StartTimer()
-		defer timer.LogElapsed("Parental lookup for %s", host)
+		defer timer.LogElapsed("parental lookup for %q", host)
 	}
 
 	sctx := &sbCtx{
diff --git a/internal/filtering/safebrowsing_test.go b/internal/filtering/safebrowsing_test.go
index 2dec3668..f2cc846c 100644
--- a/internal/filtering/safebrowsing_test.go
+++ b/internal/filtering/safebrowsing_test.go
@@ -74,21 +74,20 @@ func TestSafeBrowsingCache(t *testing.T) {
 	c.hashToHost[hash] = "sub.host.com"
 	assert.Equal(t, -1, c.getCached())
 
-	// match "sub.host.com" from cache,
-	//  but another hash for "nonexisting.com" is not in cache
-	//  which means that we must get data from server for it
+	// Match "sub.host.com" from cache.  Another hash for "host.example" is not
+	// in the cache, so get data for it from the server.
 	c.hashToHost = make(map[[32]byte]string)
 	hash = sha256.Sum256([]byte("sub.host.com"))
 	c.hashToHost[hash] = "sub.host.com"
-	hash = sha256.Sum256([]byte("nonexisting.com"))
-	c.hashToHost[hash] = "nonexisting.com"
+	hash = sha256.Sum256([]byte("host.example"))
+	c.hashToHost[hash] = "host.example"
 	assert.Empty(t, c.getCached())
 
 	hash = sha256.Sum256([]byte("sub.host.com"))
 	_, ok := c.hashToHost[hash]
 	assert.False(t, ok)
 
-	hash = sha256.Sum256([]byte("nonexisting.com"))
+	hash = sha256.Sum256([]byte("host.example"))
 	_, ok = c.hashToHost[hash]
 	assert.True(t, ok)
 
@@ -111,8 +110,7 @@ func TestSBPC_checkErrorUpstream(t *testing.T) {
 	d := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
 	t.Cleanup(d.Close)
 
-	ups := &aghtest.TestErrUpstream{}
-
+	ups := aghtest.NewErrorUpstream()
 	d.SetSafeBrowsingUpstream(ups)
 	d.SetParentalUpstream(ups)
 
@@ -170,10 +168,16 @@ func TestSBPC(t *testing.T) {
 
 	for _, tc := range testCases {
 		// Prepare the upstream.
-		ups := &aghtest.TestBlockUpstream{
-			Hostname: hostname,
-			Block:    tc.block,
+		ups := aghtest.NewBlockUpstream(hostname, tc.block)
+
+		var numReq int
+		onExchange := ups.OnExchange
+		ups.OnExchange = func(req *dns.Msg) (resp *dns.Msg, err error) {
+			numReq++
+
+			return onExchange(req)
 		}
+
 		d.SetSafeBrowsingUpstream(ups)
 		d.SetParentalUpstream(ups)
 
@@ -196,7 +200,7 @@ func TestSBPC(t *testing.T) {
 			assert.Equal(t, hits, tc.testCache.Stats().Hit)
 
 			// There was one request to an upstream.
-			assert.Equal(t, 1, ups.RequestsCount())
+			assert.Equal(t, 1, numReq)
 
 			// Now make the same request to check the cache was used.
 			res, err = tc.testFunc(hostname, dns.TypeA, setts)
@@ -214,7 +218,7 @@ func TestSBPC(t *testing.T) {
 			assert.Equal(t, hits+1, tc.testCache.Stats().Hit)
 
 			// Check that there were no additional requests.
-			assert.Equal(t, 1, ups.RequestsCount())
+			assert.Equal(t, 1, numReq)
 		})
 
 		purgeCaches(d)
diff --git a/internal/home/rdns_test.go b/internal/home/rdns_test.go
index 08f4f013..870d0f04 100644
--- a/internal/home/rdns_test.go
+++ b/internal/home/rdns_test.go
@@ -3,15 +3,16 @@ package home
 import (
 	"bytes"
 	"encoding/binary"
+	"fmt"
 	"net"
 	"sync"
 	"testing"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
 	"github.com/AdguardTeam/dnsproxy/upstream"
 	"github.com/AdguardTeam/golibs/cache"
-	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/AdguardTeam/golibs/stringutil"
@@ -80,8 +81,10 @@ func TestRDNS_Begin(t *testing.T) {
 		binary.BigEndian.PutUint64(ttl, uint64(time.Now().Add(100*time.Hour).Unix()))
 
 		rdns := &RDNS{
-			ipCache:   ipCache,
-			exchanger: &rDNSExchanger{},
+			ipCache: ipCache,
+			exchanger: &rDNSExchanger{
+				ex: aghtest.NewErrorUpstream(),
+			},
 			clients: &clientsContainer{
 				list:    map[string]*Client{},
 				idIndex: tc.cliIDIndex,
@@ -108,16 +111,22 @@ func TestRDNS_Begin(t *testing.T) {
 
 // rDNSExchanger is a mock dnsforward.RDNSExchanger implementation for tests.
 type rDNSExchanger struct {
-	ex         aghtest.Exchanger
+	ex         upstream.Upstream
 	usePrivate bool
 }
 
 // Exchange implements dnsforward.RDNSExchanger interface for *RDNSExchanger.
 func (e *rDNSExchanger) Exchange(ip net.IP) (host string, err error) {
+	rev, err := netutil.IPToReversedAddr(ip)
+	if err != nil {
+		return "", fmt.Errorf("reversing ip: %w", err)
+	}
+
 	req := &dns.Msg{
 		Question: []dns.Question{{
-			Name:  ip.String(),
-			Qtype: dns.TypePTR,
+			Name:   dns.Fqdn(rev),
+			Qclass: dns.ClassINET,
+			Qtype:  dns.TypePTR,
 		}},
 	}
 
@@ -146,7 +155,9 @@ func TestRDNS_ensurePrivateCache(t *testing.T) {
 		MaxCount:  defaultRDNSCacheSize,
 	})
 
-	ex := &rDNSExchanger{}
+	ex := &rDNSExchanger{
+		ex: aghtest.NewErrorUpstream(),
+	}
 
 	rdns := &RDNS{
 		ipCache:   ipCache,
@@ -167,15 +178,27 @@ func TestRDNS_WorkerLoop(t *testing.T) {
 	w := &bytes.Buffer{}
 	aghtest.ReplaceLogWriter(t, w)
 
-	locUpstream := &aghtest.Upstream{
-		Reverse: map[string][]string{
-			"192.168.1.1":            {"local.domain"},
-			"2a00:1450:400c:c06::93": {"ipv6.domain"},
+	localIP := net.IP{192, 168, 1, 1}
+	revIPv4, err := netutil.IPToReversedAddr(localIP)
+	require.NoError(t, err)
+
+	revIPv6, err := netutil.IPToReversedAddr(net.ParseIP("2a00:1450:400c:c06::93"))
+	require.NoError(t, err)
+
+	locUpstream := &aghtest.UpstreamMock{
+		OnAddress: func() (addr string) { return "local.upstream.example" },
+		OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
+			resp = aghalg.Coalesce(
+				aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revIPv4, "local.domain"),
+				aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revIPv6, "ipv6.domain"),
+				new(dns.Msg).SetRcode(req, dns.RcodeNameError),
+			)
+
+			return resp, nil
 		},
 	}
-	errUpstream := &aghtest.TestErrUpstream{
-		Err: errors.Error("1234"),
-	}
+
+	errUpstream := aghtest.NewErrorUpstream()
 
 	testCases := []struct {
 		ups     upstream.Upstream
@@ -186,10 +209,10 @@ func TestRDNS_WorkerLoop(t *testing.T) {
 		ups:     locUpstream,
 		wantLog: "",
 		name:    "all_good",
-		cliIP:   net.IP{192, 168, 1, 1},
+		cliIP:   localIP,
 	}, {
 		ups:     errUpstream,
-		wantLog: `rdns: resolving "192.168.1.2": errupstream: 1234`,
+		wantLog: `rdns: resolving "192.168.1.2": test upstream error`,
 		name:    "resolve_error",
 		cliIP:   net.IP{192, 168, 1, 2},
 	}, {
@@ -211,9 +234,7 @@ func TestRDNS_WorkerLoop(t *testing.T) {
 		ch := make(chan net.IP)
 		rdns := &RDNS{
 			exchanger: &rDNSExchanger{
-				ex: aghtest.Exchanger{
-					Ups: tc.ups,
-				},
+				ex: tc.ups,
 			},
 			clients: cc,
 			ipCh:    ch,
diff --git a/internal/v1/dnssvc/dnssvc.go b/internal/v1/dnssvc/dnssvc.go
new file mode 100644
index 00000000..ffe5b080
--- /dev/null
+++ b/internal/v1/dnssvc/dnssvc.go
@@ -0,0 +1,193 @@
+// Package dnssvc contains the AdGuard Home DNS service.
+//
+// TODO(a.garipov): Define, if all methods of a *Service should work with a nil
+// receiver.
+package dnssvc
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"net/netip"
+	"time"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/v1/agh"
+	// TODO(a.garipov): Add a “dnsproxy proxy” package to shield us from changes
+	// and replacement of module dnsproxy.
+	"github.com/AdguardTeam/dnsproxy/proxy"
+	"github.com/AdguardTeam/dnsproxy/upstream"
+)
+
+// Config is the AdGuard Home DNS service configuration structure.
+//
+// TODO(a.garipov): Add timeout for incoming requests.
+type Config struct {
+	// Addresses are the addresses on which to serve plain DNS queries.
+	Addresses []netip.AddrPort
+
+	// Upstreams are the DNS upstreams to use.  If not set, upstreams are
+	// created using data from BootstrapServers, UpstreamServers, and
+	// UpstreamTimeout.
+	//
+	// TODO(a.garipov): Think of a better scheme.  Those other three parameters
+	// are here only to make Config work properly.
+	Upstreams []upstream.Upstream
+
+	// BootstrapServers are the addresses for bootstrapping the upstream DNS
+	// server addresses.
+	BootstrapServers []string
+
+	// UpstreamServers are the upstream DNS server addresses to use.
+	UpstreamServers []string
+
+	// UpstreamTimeout is the timeout for upstream requests.
+	UpstreamTimeout time.Duration
+}
+
+// Service is the AdGuard Home DNS service.  A nil *Service is a valid
+// [agh.Service] that does nothing.
+type Service struct {
+	proxy      *proxy.Proxy
+	bootstraps []string
+	upstreams  []string
+	upsTimeout time.Duration
+}
+
+// New returns a new properly initialized *Service.  If c is nil, svc is a nil
+// *Service that does nothing.  The fields of c must not be modified after
+// calling New.
+func New(c *Config) (svc *Service, err error) {
+	if c == nil {
+		return nil, nil
+	}
+
+	svc = &Service{
+		bootstraps: c.BootstrapServers,
+		upstreams:  c.UpstreamServers,
+		upsTimeout: c.UpstreamTimeout,
+	}
+
+	var upstreams []upstream.Upstream
+	if len(c.Upstreams) > 0 {
+		upstreams = c.Upstreams
+	} else {
+		upstreams, err = addressesToUpstreams(
+			c.UpstreamServers,
+			c.BootstrapServers,
+			c.UpstreamTimeout,
+		)
+		if err != nil {
+			return nil, fmt.Errorf("converting upstreams: %w", err)
+		}
+	}
+
+	svc.proxy = &proxy.Proxy{
+		Config: proxy.Config{
+			UDPListenAddr: udpAddrs(c.Addresses),
+			TCPListenAddr: tcpAddrs(c.Addresses),
+			UpstreamConfig: &proxy.UpstreamConfig{
+				Upstreams: upstreams,
+			},
+		},
+	}
+
+	err = svc.proxy.Init()
+	if err != nil {
+		return nil, fmt.Errorf("proxy: %w", err)
+	}
+
+	return svc, nil
+}
+
+// addressesToUpstreams is a wrapper around [upstream.AddressToUpstream].  It
+// accepts a slice of addresses and other upstream parameters, and returns a
+// slice of upstreams.
+func addressesToUpstreams(
+	upsStrs []string,
+	bootstraps []string,
+	timeout time.Duration,
+) (upstreams []upstream.Upstream, err error) {
+	upstreams = make([]upstream.Upstream, len(upsStrs))
+	for i, upsStr := range upsStrs {
+		upstreams[i], err = upstream.AddressToUpstream(upsStr, &upstream.Options{
+			Bootstrap: bootstraps,
+			Timeout:   timeout,
+		})
+		if err != nil {
+			return nil, fmt.Errorf("upstream at index %d: %w", i, err)
+		}
+	}
+
+	return upstreams, nil
+}
+
+// tcpAddrs converts []netip.AddrPort into []*net.TCPAddr.
+func tcpAddrs(addrPorts []netip.AddrPort) (tcpAddrs []*net.TCPAddr) {
+	if addrPorts == nil {
+		return nil
+	}
+
+	tcpAddrs = make([]*net.TCPAddr, len(addrPorts))
+	for i, a := range addrPorts {
+		tcpAddrs[i] = net.TCPAddrFromAddrPort(a)
+	}
+
+	return tcpAddrs
+}
+
+// udpAddrs converts []netip.AddrPort into []*net.UDPAddr.
+func udpAddrs(addrPorts []netip.AddrPort) (udpAddrs []*net.UDPAddr) {
+	if addrPorts == nil {
+		return nil
+	}
+
+	udpAddrs = make([]*net.UDPAddr, len(addrPorts))
+	for i, a := range addrPorts {
+		udpAddrs[i] = net.UDPAddrFromAddrPort(a)
+	}
+
+	return udpAddrs
+}
+
+// type check
+var _ agh.Service = (*Service)(nil)
+
+// Start implements the [agh.Service] interface for *Service.  svc may be nil.
+// After Start exits, all DNS servers have tried to start, but there is no
+// guarantee that they did.  Errors from the servers are written to the log.
+func (svc *Service) Start() (err error) {
+	if svc == nil {
+		return nil
+	}
+
+	return svc.proxy.Start()
+}
+
+// Shutdown implements the [agh.Service] interface for *Service.  svc may be
+// nil.
+func (svc *Service) Shutdown(ctx context.Context) (err error) {
+	if svc == nil {
+		return nil
+	}
+
+	return svc.proxy.Stop()
+}
+
+// Config returns the current configuration of the web service.
+func (svc *Service) Config() (c *Config) {
+	// TODO(a.garipov): Do we need to get the TCP addresses separately?
+	udpAddrs := svc.proxy.Addrs(proxy.ProtoUDP)
+	addrs := make([]netip.AddrPort, len(udpAddrs))
+	for i, a := range udpAddrs {
+		addrs[i] = a.(*net.UDPAddr).AddrPort()
+	}
+
+	c = &Config{
+		Addresses:        addrs,
+		BootstrapServers: svc.bootstraps,
+		UpstreamServers:  svc.upstreams,
+		UpstreamTimeout:  svc.upsTimeout,
+	}
+
+	return c
+}
diff --git a/internal/v1/dnssvc/dnssvc_test.go b/internal/v1/dnssvc/dnssvc_test.go
new file mode 100644
index 00000000..5bc3b562
--- /dev/null
+++ b/internal/v1/dnssvc/dnssvc_test.go
@@ -0,0 +1,89 @@
+package dnssvc_test
+
+import (
+	"context"
+	"net/netip"
+	"testing"
+	"time"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
+	"github.com/AdguardTeam/AdGuardHome/internal/v1/dnssvc"
+	"github.com/AdguardTeam/dnsproxy/upstream"
+	"github.com/miekg/dns"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestMain(m *testing.M) {
+	aghtest.DiscardLogOutput(m)
+}
+
+// testTimeout is the common timeout for tests.
+const testTimeout = 100 * time.Millisecond
+
+func TestService(t *testing.T) {
+	const (
+		bootstrapAddr = "bootstrap.example"
+		upstreamAddr  = "upstream.example"
+	)
+
+	ups := &aghtest.UpstreamMock{
+		OnAddress: func() (addr string) {
+			return upstreamAddr
+		},
+		OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
+			resp = (&dns.Msg{}).SetReply(req)
+
+			return resp, nil
+		},
+	}
+
+	c := &dnssvc.Config{
+		Addresses:        []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")},
+		Upstreams:        []upstream.Upstream{ups},
+		BootstrapServers: []string{bootstrapAddr},
+		UpstreamServers:  []string{upstreamAddr},
+		UpstreamTimeout:  testTimeout,
+	}
+
+	svc, err := dnssvc.New(c)
+	require.NoError(t, err)
+
+	err = svc.Start()
+	require.NoError(t, err)
+
+	gotConf := svc.Config()
+	require.NotNil(t, gotConf)
+	require.Len(t, gotConf.Addresses, 1)
+
+	addr := gotConf.Addresses[0]
+
+	t.Run("dns", func(t *testing.T) {
+		req := &dns.Msg{
+			MsgHdr: dns.MsgHdr{
+				Id:               dns.Id(),
+				RecursionDesired: true,
+			},
+			Question: []dns.Question{{
+				Name:   "example.com.",
+				Qtype:  dns.TypeA,
+				Qclass: dns.ClassINET,
+			}},
+		}
+
+		ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+		defer cancel()
+
+		cli := &dns.Client{}
+		resp, _, excErr := cli.ExchangeContext(ctx, req, addr.String())
+		require.NoError(t, excErr)
+
+		assert.NotNil(t, resp)
+	})
+
+	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+	defer cancel()
+
+	err = svc.Shutdown(ctx)
+	require.NoError(t, err)
+}
diff --git a/internal/v1/websvc/websvc.go b/internal/v1/websvc/websvc.go
index 1a9a6a09..bbaac005 100644
--- a/internal/v1/websvc/websvc.go
+++ b/internal/v1/websvc/websvc.go
@@ -40,8 +40,8 @@ type Config struct {
 	Timeout time.Duration
 }
 
-// Service is the AdGuard Home web service.  A nil *Service is a valid service
-// that does nothing.
+// Service is the AdGuard Home web service.  A nil *Service is a valid
+// [agh.Service] that does nothing.
 type Service struct {
 	tls     *tls.Config
 	servers []*http.Server
@@ -155,7 +155,7 @@ type unit = struct{}
 // type check
 var _ agh.Service = (*Service)(nil)
 
-// Start implements the agh.Service interface for *Service.  svc may be nil.
+// Start implements the [agh.Service] interface for *Service.  svc may be nil.
 // After Start exits, all HTTP servers have tried to start, possibly failing and
 // writing error messages to the log.
 func (svc *Service) Start() (err error) {
@@ -205,7 +205,8 @@ func serve(srv *http.Server, wg *sync.WaitGroup) {
 	}
 }
 
-// Shutdown implements the agh.Service interface for *Service.  svc may be nil.
+// Shutdown implements the [agh.Service] interface for *Service.  svc may be
+// nil.
 func (svc *Service) Shutdown(ctx context.Context) (err error) {
 	if svc == nil {
 		return nil

From 572d2794e23cb6493d5f532e455ac637760129cb Mon Sep 17 00:00:00 2001
From: Justin <95468948+jslawler@users.noreply.github.com>
Date: Wed, 17 Aug 2022 02:29:41 +1000
Subject: [PATCH 123/143] Update Snap to Ubuntu Core 22 #4843

---
 scripts/snap/snap.tmpl.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/snap/snap.tmpl.yaml b/scripts/snap/snap.tmpl.yaml
index 51b094de..8bff5c53 100644
--- a/scripts/snap/snap.tmpl.yaml
+++ b/scripts/snap/snap.tmpl.yaml
@@ -1,7 +1,7 @@
 # The %VARIABLES% are be replaced by actual values by the build script.
 
 'name': 'adguard-home'
-'base': 'core20'
+'base': 'core22'
 'version': '%VERSION%'
 'summary': Network-wide ads & trackers blocking DNS server
 'description': |

From 72098d22551313c3a76b7dcf36594bc09188db86 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Wed, 17 Aug 2022 14:09:13 +0300
Subject: [PATCH 124/143] Pull request: 4358 stats races

Merge in DNS/adguard-home from 4358-stats-races to master

Updates #4358

Squashed commit of the following:

commit 162d17b04d95adad21fb9b3c5a6fb64df2e037ec
Merge: 17732cfa d4c3a43b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 17 14:04:20 2022 +0300

    Merge branch 'master' into 4358-stats-races

commit 17732cfa0f3b2589bf2c252697eee1d6b358a66c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 17 13:53:42 2022 +0300

    stats: imp docs, locking

commit 4ee090869af0fa2b777c12027c3b77d5acd6e4de
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 16 20:26:19 2022 +0300

    stats: revert const

commit a7681a1b882cef04511fcd5d569f5abe2f955239
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 16 20:23:00 2022 +0300

    stats: imp concurrency

commit a6c6c1a0572e4201cd24644fd3f86f51fc27f633
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 16 19:51:30 2022 +0300

    stats: imp code, tests, docs

commit 954196b49f5ad91d91f445ff656e63c318e4124c
Merge: 281e00da 6e63757f
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 16 13:07:32 2022 +0300

    Merge branch 'master' into 4358-stats-races

commit 281e00daf781d045269584ce0158eed1d77918df
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Aug 12 16:22:18 2022 +0300

    stats: imp closing

commit ed036d9aa7e25498869edfb866b6e923538970eb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Aug 12 16:11:12 2022 +0300

    stats: imp tests more

commit f848a12487ecd2afc8416e800510090cc1be7330
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Aug 12 13:54:19 2022 +0300

    stats: imp tests, code

commit 60e11f042d51ec68850143129e61c701c5e4f3a4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 11 16:36:07 2022 +0300

    stats: fix test

commit 6d97f1db093b5ce0d37984ff96a9ef6f4e02dba1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 11 14:53:21 2022 +0300

    stats: imp code, docs

commit 20c70c2847b0de6c7f9271a8d9a831175ed0c499
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 10 20:53:36 2022 +0300

    stats: imp shared memory safety

commit 8b3945670a190bab070171e6b4976edab1e3e2a2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 10 17:22:55 2022 +0300

    stats: imp code
---
 internal/home/dns.go                  |   9 +-
 internal/stats/http.go                |  66 ++-
 internal/stats/stats.go               | 524 ++++++++++++++++++--
 internal/stats/stats_internal_test.go |  26 +
 internal/stats/stats_test.go          | 263 +++++-----
 internal/stats/unit.go                | 673 +++++---------------------
 6 files changed, 822 insertions(+), 739 deletions(-)
 create mode 100644 internal/stats/stats_internal_test.go

diff --git a/internal/home/dns.go b/internal/home/dns.go
index 52837826..ab5b0330 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -396,7 +396,7 @@ func startDNSServer() error {
 	Context.queryLog.Start()
 
 	const topClientsNumber = 100 // the number of clients to get
-	for _, ip := range Context.stats.GetTopClientsIP(topClientsNumber) {
+	for _, ip := range Context.stats.TopClientsIP(topClientsNumber) {
 		if ip == nil {
 			continue
 		}
@@ -455,7 +455,12 @@ func closeDNSServer() {
 	}
 
 	if Context.stats != nil {
-		Context.stats.Close()
+		err := Context.stats.Close()
+		if err != nil {
+			log.Debug("closing stats: %s", err)
+		}
+
+		// TODO(e.burkov):  Find out if it's safe.
 		Context.stats = nil
 	}
 
diff --git a/internal/stats/http.go b/internal/stats/http.go
index 033dd3bb..ae980bf3 100644
--- a/internal/stats/http.go
+++ b/internal/stats/http.go
@@ -5,6 +5,7 @@ package stats
 import (
 	"encoding/json"
 	"net/http"
+	"sync/atomic"
 	"time"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
@@ -15,18 +16,10 @@ import (
 // The key is either a client's address or a requested address.
 type topAddrs = map[string]uint64
 
-// statsResponse is a response for getting statistics.
-type statsResponse struct {
+// StatsResp is a response to the GET /control/stats.
+type StatsResp struct {
 	TimeUnits string `json:"time_units"`
 
-	NumDNSQueries           uint64 `json:"num_dns_queries"`
-	NumBlockedFiltering     uint64 `json:"num_blocked_filtering"`
-	NumReplacedSafebrowsing uint64 `json:"num_replaced_safebrowsing"`
-	NumReplacedSafesearch   uint64 `json:"num_replaced_safesearch"`
-	NumReplacedParental     uint64 `json:"num_replaced_parental"`
-
-	AvgProcessingTime float64 `json:"avg_processing_time"`
-
 	TopQueried []topAddrs `json:"top_queried_domains"`
 	TopClients []topAddrs `json:"top_clients"`
 	TopBlocked []topAddrs `json:"top_blocked_domains"`
@@ -36,16 +29,22 @@ type statsResponse struct {
 	BlockedFiltering     []uint64 `json:"blocked_filtering"`
 	ReplacedSafebrowsing []uint64 `json:"replaced_safebrowsing"`
 	ReplacedParental     []uint64 `json:"replaced_parental"`
+
+	NumDNSQueries           uint64 `json:"num_dns_queries"`
+	NumBlockedFiltering     uint64 `json:"num_blocked_filtering"`
+	NumReplacedSafebrowsing uint64 `json:"num_replaced_safebrowsing"`
+	NumReplacedSafesearch   uint64 `json:"num_replaced_safesearch"`
+	NumReplacedParental     uint64 `json:"num_replaced_parental"`
+
+	AvgProcessingTime float64 `json:"avg_processing_time"`
 }
 
-// handleStats is a handler for getting statistics.
+// handleStats handles requests to the GET /control/stats endpoint.
 func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
+	limit := atomic.LoadUint32(&s.limitHours)
+
 	start := time.Now()
-
-	var resp statsResponse
-	var ok bool
-	resp, ok = s.getData()
-
+	resp, ok := s.getData(limit)
 	log.Debug("stats: prepared data in %v", time.Since(start))
 
 	if !ok {
@@ -61,36 +60,30 @@ func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
 	err := json.NewEncoder(w).Encode(resp)
 	if err != nil {
 		aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
-
-		return
 	}
 }
 
-type config struct {
+// configResp is the response to the GET /control/stats_info.
+type configResp struct {
 	IntervalDays uint32 `json:"interval"`
 }
 
-// Get configuration
+// handleStatsInfo handles requests to the GET /control/stats_info endpoint.
 func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
-	resp := config{}
-	resp.IntervalDays = s.limitHours / 24
+	resp := configResp{IntervalDays: atomic.LoadUint32(&s.limitHours) / 24}
 
-	data, err := json.Marshal(resp)
+	w.Header().Set("Content-Type", "application/json")
+
+	err := json.NewEncoder(w).Encode(resp)
 	if err != nil {
 		aghhttp.Error(r, w, http.StatusInternalServerError, "json encode: %s", err)
-
-		return
-	}
-	w.Header().Set("Content-Type", "application/json")
-	_, err = w.Write(data)
-	if err != nil {
-		aghhttp.Error(r, w, http.StatusInternalServerError, "http write: %s", err)
 	}
 }
 
-// Set configuration
+// handleStatsConfig handles requests to the POST /control/stats_config
+// endpoint.
 func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
-	reqData := config{}
+	reqData := configResp{}
 	err := json.NewDecoder(r.Body).Decode(&reqData)
 	if err != nil {
 		aghhttp.Error(r, w, http.StatusBadRequest, "json decode: %s", err)
@@ -108,12 +101,15 @@ func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
 	s.configModified()
 }
 
-// Reset data
+// handleStatsReset handles requests to the POST /control/stats_reset endpoint.
 func (s *StatsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) {
-	s.clear()
+	err := s.clear()
+	if err != nil {
+		aghhttp.Error(r, w, http.StatusInternalServerError, "stats: %s", err)
+	}
 }
 
-// Register web handlers
+// initWeb registers the handlers for web endpoints of statistics module.
 func (s *StatsCtx) initWeb() {
 	if s.httpRegister == nil {
 		return
diff --git a/internal/stats/stats.go b/internal/stats/stats.go
index 04a933d4..e483dbba 100644
--- a/internal/stats/stats.go
+++ b/internal/stats/stats.go
@@ -3,15 +3,20 @@
 package stats
 
 import (
+	"fmt"
+	"io"
 	"net"
+	"os"
+	"sync"
+	"sync/atomic"
+	"time"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
+	"github.com/AdguardTeam/golibs/errors"
+	"github.com/AdguardTeam/golibs/log"
+	"go.etcd.io/bbolt"
 )
 
-// UnitIDGenFunc is the signature of a function that generates a unique ID for
-// the statistics unit.
-type UnitIDGenFunc func() (id uint32)
-
 // DiskConfig is the configuration structure that is stored in file.
 type DiskConfig struct {
 	// Interval is the number of days for which the statistics are collected
@@ -19,6 +24,12 @@ type DiskConfig struct {
 	Interval uint32 `yaml:"statistics_interval"`
 }
 
+// checkInterval returns true if days is valid to be used as statistics
+// retention interval.  The valid values are 0, 1, 7, 30 and 90.
+func checkInterval(days uint32) (ok bool) {
+	return days == 0 || days == 1 || days == 7 || days == 30 || days == 90
+}
+
 // Config is the configuration structure for the statistics collecting.
 type Config struct {
 	// UnitID is the function to generate the identifier for current unit.  If
@@ -46,58 +57,487 @@ type Interface interface {
 	// Start begins the statistics collecting.
 	Start()
 
-	// Close stops the statistics collecting.
-	Close()
+	io.Closer
 
 	// Update collects the incoming statistics data.
 	Update(e Entry)
 
 	// GetTopClientIP returns at most limit IP addresses corresponding to the
 	// clients with the most number of requests.
-	GetTopClientsIP(limit uint) []net.IP
+	TopClientsIP(limit uint) []net.IP
 
 	// WriteDiskConfig puts the Interface's configuration to the dc.
 	WriteDiskConfig(dc *DiskConfig)
 }
 
-// TimeUnit is the unit of measuring time while aggregating the statistics.
-type TimeUnit int
-
-// Supported TimeUnit values.
-const (
-	Hours TimeUnit = iota
-	Days
-)
-
-// Result is the resulting code of processing the DNS request.
-type Result int
-
-// Supported Result values.
+// StatsCtx collects the statistics and flushes it to the database.  Its default
+// flushing interval is one hour.
 //
-// TODO(e.burkov):  Think about better naming.
-const (
-	RNotFiltered Result = iota + 1
-	RFiltered
-	RSafeBrowsing
-	RSafeSearch
-	RParental
-
-	resultLast = RParental + 1
-)
-
-// Entry is a statistics data entry.
-type Entry struct {
-	// Clients is the client's primary ID.
+// TODO(e.burkov):  Use atomic.Pointer for accessing db in go1.19.
+type StatsCtx struct {
+	// limitHours is the maximum number of hours to collect statistics into the
+	// current unit.
 	//
-	// TODO(a.garipov): Make this a {net.IP, string} enum?
-	Client string
+	// It is of type uint32 to be accessed by atomic.  It's arranged at the
+	// beginning of the structure to keep 64-bit alignment.
+	limitHours uint32
 
-	// Domain is the domain name requested.
-	Domain string
+	// currMu protects curr.
+	currMu *sync.RWMutex
+	// curr is the actual statistics collection result.
+	curr *unit
 
-	// Result is the result of processing the request.
-	Result Result
+	// dbMu protects db.
+	dbMu *sync.Mutex
+	// db is the opened statistics database, if any.
+	db *bbolt.DB
 
-	// Time is the duration of the request processing in milliseconds.
-	Time uint32
+	// unitIDGen is the function that generates an identifier for the current
+	// unit.  It's here for only testing purposes.
+	unitIDGen UnitIDGenFunc
+
+	// httpRegister is used to set HTTP handlers.
+	httpRegister aghhttp.RegisterFunc
+
+	// configModified is called whenever the configuration is modified via web
+	// interface.
+	configModified func()
+
+	// filename is the name of database file.
+	filename string
+}
+
+var _ Interface = &StatsCtx{}
+
+// New creates s from conf and properly initializes it.  Don't use s before
+// calling it's Start method.
+func New(conf Config) (s *StatsCtx, err error) {
+	defer withRecovered(&err)
+
+	s = &StatsCtx{
+		currMu:         &sync.RWMutex{},
+		dbMu:           &sync.Mutex{},
+		filename:       conf.Filename,
+		configModified: conf.ConfigModified,
+		httpRegister:   conf.HTTPRegister,
+	}
+	if s.limitHours = conf.LimitDays * 24; !checkInterval(conf.LimitDays) {
+		s.limitHours = 24
+	}
+	if s.unitIDGen = newUnitID; conf.UnitID != nil {
+		s.unitIDGen = conf.UnitID
+	}
+
+	// TODO(e.burkov):  Move the code below to the Start method.
+
+	err = s.openDB()
+	if err != nil {
+		return nil, fmt.Errorf("opening database: %w", err)
+	}
+
+	var udb *unitDB
+	id := s.unitIDGen()
+
+	tx, err := s.db.Begin(true)
+	if err != nil {
+		return nil, fmt.Errorf("stats: opening a transaction: %w", err)
+	}
+
+	deleted := deleteOldUnits(tx, id-s.limitHours-1)
+	udb = loadUnitFromDB(tx, id)
+
+	err = finishTxn(tx, deleted > 0)
+	if err != nil {
+		log.Error("stats: %s", err)
+	}
+
+	s.curr = newUnit(id)
+	s.curr.deserialize(udb)
+
+	log.Debug("stats: initialized")
+
+	return s, nil
+}
+
+// withRecovered turns the value recovered from panic if any into an error and
+// combines it with the one pointed by orig.  orig must be non-nil.
+func withRecovered(orig *error) {
+	p := recover()
+	if p == nil {
+		return
+	}
+
+	var err error
+	switch p := p.(type) {
+	case error:
+		err = fmt.Errorf("panic: %w", p)
+	default:
+		err = fmt.Errorf("panic: recovered value of type %[1]T: %[1]v", p)
+	}
+
+	*orig = errors.WithDeferred(*orig, err)
+}
+
+// Start implements the Interface interface for *StatsCtx.
+func (s *StatsCtx) Start() {
+	s.initWeb()
+
+	go s.periodicFlush()
+}
+
+// Close implements the io.Closer interface for *StatsCtx.
+func (s *StatsCtx) Close() (err error) {
+	defer func() { err = errors.Annotate(err, "stats: closing: %w") }()
+
+	db := s.swapDatabase(nil)
+	if db == nil {
+		return nil
+	}
+	defer func() {
+		cerr := db.Close()
+		if cerr == nil {
+			log.Debug("stats: database closed")
+		}
+
+		err = errors.WithDeferred(err, cerr)
+	}()
+
+	tx, err := db.Begin(true)
+	if err != nil {
+		return fmt.Errorf("opening transaction: %w", err)
+	}
+	defer func() { err = errors.WithDeferred(err, finishTxn(tx, err == nil)) }()
+
+	s.currMu.RLock()
+	defer s.currMu.RUnlock()
+
+	udb := s.curr.serialize()
+
+	return udb.flushUnitToDB(tx, s.curr.id)
+}
+
+// Update implements the Interface interface for *StatsCtx.
+func (s *StatsCtx) Update(e Entry) {
+	if atomic.LoadUint32(&s.limitHours) == 0 {
+		return
+	}
+
+	if e.Result == 0 || e.Result >= resultLast || e.Domain == "" || e.Client == "" {
+		log.Debug("stats: malformed entry")
+
+		return
+	}
+
+	s.currMu.Lock()
+	defer s.currMu.Unlock()
+
+	if s.curr == nil {
+		log.Error("stats: current unit is nil")
+
+		return
+	}
+
+	clientID := e.Client
+	if ip := net.ParseIP(clientID); ip != nil {
+		clientID = ip.String()
+	}
+
+	s.curr.add(e.Result, e.Domain, clientID, uint64(e.Time))
+}
+
+// WriteDiskConfig implements the Interface interface for *StatsCtx.
+func (s *StatsCtx) WriteDiskConfig(dc *DiskConfig) {
+	dc.Interval = atomic.LoadUint32(&s.limitHours) / 24
+}
+
+// TopClientsIP implements the Interface interface for *StatsCtx.
+func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []net.IP) {
+	limit := atomic.LoadUint32(&s.limitHours)
+	if limit == 0 {
+		return nil
+	}
+
+	units, _ := s.loadUnits(limit)
+	if units == nil {
+		return nil
+	}
+
+	// Collect data for all the clients to sort and crop it afterwards.
+	m := map[string]uint64{}
+	for _, u := range units {
+		for _, it := range u.Clients {
+			m[it.Name] += it.Count
+		}
+	}
+
+	a := convertMapToSlice(m, int(maxCount))
+	ips = []net.IP{}
+	for _, it := range a {
+		ip := net.ParseIP(it.Name)
+		if ip != nil {
+			ips = append(ips, ip)
+		}
+	}
+
+	return ips
+}
+
+// database returns the database if it's opened.  It's safe for concurrent use.
+func (s *StatsCtx) database() (db *bbolt.DB) {
+	s.dbMu.Lock()
+	defer s.dbMu.Unlock()
+
+	return s.db
+}
+
+// swapDatabase swaps the database with another one and returns it.  It's safe
+// for concurrent use.
+func (s *StatsCtx) swapDatabase(with *bbolt.DB) (old *bbolt.DB) {
+	s.dbMu.Lock()
+	defer s.dbMu.Unlock()
+
+	old, s.db = s.db, with
+
+	return old
+}
+
+// deleteOldUnits walks the buckets available to tx and deletes old units.  It
+// returns the number of deletions performed.
+func deleteOldUnits(tx *bbolt.Tx, firstID uint32) (deleted int) {
+	log.Debug("stats: deleting old units until id %d", firstID)
+
+	// TODO(a.garipov): See if this is actually necessary.  Looks like a rather
+	// bizarre solution.
+	const errStop errors.Error = "stop iteration"
+
+	walk := func(name []byte, _ *bbolt.Bucket) (err error) {
+		nameID, ok := unitNameToID(name)
+		if ok && nameID >= firstID {
+			return errStop
+		}
+
+		err = tx.DeleteBucket(name)
+		if err != nil {
+			log.Debug("stats: deleting bucket: %s", err)
+
+			return nil
+		}
+
+		log.Debug("stats: deleted unit %d (name %x)", nameID, name)
+
+		deleted++
+
+		return nil
+	}
+
+	err := tx.ForEach(walk)
+	if err != nil && !errors.Is(err, errStop) {
+		log.Debug("stats: deleting units: %s", err)
+	}
+
+	return deleted
+}
+
+// openDB returns an error if the database can't be opened from the specified
+// file.  It's safe for concurrent use.
+func (s *StatsCtx) openDB() (err error) {
+	log.Debug("stats: opening database")
+
+	var db *bbolt.DB
+	db, err = bbolt.Open(s.filename, 0o644, nil)
+	if err != nil {
+		if err.Error() == "invalid argument" {
+			log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#limitations")
+		}
+
+		return err
+	}
+
+	// Use defer to unlock the mutex as soon as possible.
+	defer log.Debug("stats: database opened")
+
+	s.dbMu.Lock()
+	defer s.dbMu.Unlock()
+
+	s.db = db
+
+	return nil
+}
+
+func (s *StatsCtx) flush() (cont bool, sleepFor time.Duration) {
+	id := s.unitIDGen()
+
+	s.currMu.Lock()
+	defer s.currMu.Unlock()
+
+	ptr := s.curr
+	if ptr == nil {
+		return false, 0
+	}
+
+	limit := atomic.LoadUint32(&s.limitHours)
+	if limit == 0 || ptr.id == id {
+		return true, time.Second
+	}
+
+	db := s.database()
+	if db == nil {
+		return true, 0
+	}
+
+	tx, err := db.Begin(true)
+	if err != nil {
+		log.Error("stats: opening transaction: %s", err)
+
+		return true, 0
+	}
+
+	s.curr = newUnit(id)
+	isCommitable := true
+
+	ferr := ptr.serialize().flushUnitToDB(tx, ptr.id)
+	if ferr != nil {
+		log.Error("stats: flushing unit: %s", ferr)
+		isCommitable = false
+	}
+
+	derr := tx.DeleteBucket(idToUnitName(id - limit))
+	if derr != nil {
+		log.Error("stats: deleting unit: %s", derr)
+		if !errors.Is(derr, bbolt.ErrBucketNotFound) {
+			isCommitable = false
+		}
+	}
+
+	err = finishTxn(tx, isCommitable)
+	if err != nil {
+		log.Error("stats: %s", err)
+	}
+
+	return true, 0
+}
+
+// periodicFlush checks and flushes the unit to the database if the freshly
+// generated unit ID differs from the current's ID.  Flushing process includes:
+//  - swapping the current unit with the new empty one;
+//  - writing the current unit to the database;
+//  - removing the stale unit from the database.
+func (s *StatsCtx) periodicFlush() {
+	for cont, sleepFor := true, time.Duration(0); cont; time.Sleep(sleepFor) {
+		cont, sleepFor = s.flush()
+	}
+
+	log.Debug("periodic flushing finished")
+}
+
+func (s *StatsCtx) setLimit(limitDays int) {
+	atomic.StoreUint32(&s.limitHours, uint32(24*limitDays))
+	if limitDays == 0 {
+		if err := s.clear(); err != nil {
+			log.Error("stats: %s", err)
+		}
+	}
+
+	log.Debug("stats: set limit: %d days", limitDays)
+}
+
+// Reset counters and clear database
+func (s *StatsCtx) clear() (err error) {
+	defer func() { err = errors.Annotate(err, "clearing: %w") }()
+
+	db := s.swapDatabase(nil)
+	if db != nil {
+		var tx *bbolt.Tx
+		tx, err = db.Begin(true)
+		if err != nil {
+			log.Error("stats: opening a transaction: %s", err)
+		} else if err = finishTxn(tx, false); err != nil {
+			// Don't wrap the error since it's informative enough as is.
+			return err
+		}
+
+		// Active transactions will continue using database, but new ones won't
+		// be created.
+		err = db.Close()
+		if err != nil {
+			return fmt.Errorf("closing database: %w", err)
+		}
+
+		// All active transactions are now closed.
+		log.Debug("stats: database closed")
+	}
+
+	err = os.Remove(s.filename)
+	if err != nil {
+		log.Error("stats: %s", err)
+	}
+
+	err = s.openDB()
+	if err != nil {
+		log.Error("stats: opening database: %s", err)
+	}
+
+	// Use defer to unlock the mutex as soon as possible.
+	defer log.Debug("stats: cleared")
+
+	s.currMu.Lock()
+	defer s.currMu.Unlock()
+
+	s.curr = newUnit(s.unitIDGen())
+
+	return nil
+}
+
+func (s *StatsCtx) loadUnits(limit uint32) (units []*unitDB, firstID uint32) {
+	db := s.database()
+	if db == nil {
+		return nil, 0
+	}
+
+	// Use writable transaction to ensure any ongoing writable transaction is
+	// taken into account.
+	tx, err := db.Begin(true)
+	if err != nil {
+		log.Error("stats: opening transaction: %s", err)
+
+		return nil, 0
+	}
+
+	s.currMu.RLock()
+	defer s.currMu.RUnlock()
+
+	cur := s.curr
+
+	var curID uint32
+	if cur != nil {
+		curID = cur.id
+	} else {
+		curID = s.unitIDGen()
+	}
+
+	// Per-hour units.
+	units = make([]*unitDB, 0, limit)
+	firstID = curID - limit + 1
+	for i := firstID; i != curID; i++ {
+		u := loadUnitFromDB(tx, i)
+		if u == nil {
+			u = &unitDB{NResult: make([]uint64, resultLast)}
+		}
+		units = append(units, u)
+	}
+
+	err = finishTxn(tx, false)
+	if err != nil {
+		log.Error("stats: %s", err)
+	}
+
+	if cur != nil {
+		units = append(units, cur.serialize())
+	}
+
+	if unitsLen := len(units); unitsLen != int(limit) {
+		log.Fatalf("loaded %d units whilst the desired number is %d", unitsLen, limit)
+	}
+
+	return units, firstID
 }
diff --git a/internal/stats/stats_internal_test.go b/internal/stats/stats_internal_test.go
new file mode 100644
index 00000000..28a556d3
--- /dev/null
+++ b/internal/stats/stats_internal_test.go
@@ -0,0 +1,26 @@
+package stats
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TODO(e.burkov):  Use more realistic data.
+func TestStatsCollector(t *testing.T) {
+	ng := func(_ *unitDB) uint64 { return 0 }
+	units := make([]*unitDB, 720)
+
+	t.Run("hours", func(t *testing.T) {
+		statsData := statsCollector(units, 0, Hours, ng)
+		assert.Len(t, statsData, 720)
+	})
+
+	t.Run("days", func(t *testing.T) {
+		for i := 0; i != 25; i++ {
+			statsData := statsCollector(units, uint32(i), Days, ng)
+			require.Lenf(t, statsData, 30, "i=%d", i)
+		}
+	})
+}
diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go
index 0cffd2e3..5d86024b 100644
--- a/internal/stats/stats_test.go
+++ b/internal/stats/stats_test.go
@@ -1,13 +1,17 @@
-package stats
+package stats_test
 
 import (
+	"encoding/json"
 	"fmt"
 	"net"
-	"os"
+	"net/http"
+	"net/http/httptest"
+	"path/filepath"
 	"sync/atomic"
 	"testing"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
+	"github.com/AdguardTeam/AdGuardHome/internal/stats"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -17,147 +21,176 @@ func TestMain(m *testing.M) {
 	aghtest.DiscardLogOutput(m)
 }
 
-func UIntArrayEquals(a, b []uint64) bool {
-	if len(a) != len(b) {
-		return false
+// constUnitID is the UnitIDGenFunc which always return 0.
+func constUnitID() (id uint32) { return 0 }
+
+func assertSuccessAndUnmarshal(t *testing.T, to any, handler http.Handler, req *http.Request) {
+	t.Helper()
+
+	require.NotNil(t, handler)
+
+	rw := httptest.NewRecorder()
+
+	handler.ServeHTTP(rw, req)
+	require.Equal(t, http.StatusOK, rw.Code)
+
+	data := rw.Body.Bytes()
+	if to == nil {
+		assert.Empty(t, data)
+
+		return
 	}
 
-	for i := range a {
-		if a[i] != b[i] {
-			return false
-		}
-	}
-
-	return true
+	err := json.Unmarshal(data, to)
+	require.NoError(t, err)
 }
 
 func TestStats(t *testing.T) {
-	conf := Config{
-		Filename:  "./stats.db",
+	cliIP := net.IP{127, 0, 0, 1}
+	cliIPStr := cliIP.String()
+
+	handlers := map[string]http.Handler{}
+	conf := stats.Config{
+		Filename:  filepath.Join(t.TempDir(), "stats.db"),
 		LimitDays: 1,
+		UnitID:    constUnitID,
+		HTTPRegister: func(_, url string, handler http.HandlerFunc) {
+			handlers[url] = handler
+		},
 	}
 
-	s, err := New(conf)
+	s, err := stats.New(conf)
 	require.NoError(t, err)
-	testutil.CleanupAndRequireSuccess(t, func() (err error) {
-		s.clear()
-		s.Close()
 
-		return os.Remove(conf.Filename)
+	s.Start()
+	testutil.CleanupAndRequireSuccess(t, s.Close)
+
+	t.Run("data", func(t *testing.T) {
+		const reqDomain = "domain"
+
+		entries := []stats.Entry{{
+			Domain: reqDomain,
+			Client: cliIPStr,
+			Result: stats.RFiltered,
+			Time:   123456,
+		}, {
+			Domain: reqDomain,
+			Client: cliIPStr,
+			Result: stats.RNotFiltered,
+			Time:   123456,
+		}}
+
+		wantData := &stats.StatsResp{
+			TimeUnits:  "hours",
+			TopQueried: []map[string]uint64{0: {reqDomain: 1}},
+			TopClients: []map[string]uint64{0: {cliIPStr: 2}},
+			TopBlocked: []map[string]uint64{0: {reqDomain: 1}},
+			DNSQueries: []uint64{
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+			},
+			BlockedFiltering: []uint64{
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+			},
+			ReplacedSafebrowsing: []uint64{
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+			},
+			ReplacedParental: []uint64{
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+			},
+			NumDNSQueries:           2,
+			NumBlockedFiltering:     1,
+			NumReplacedSafebrowsing: 0,
+			NumReplacedSafesearch:   0,
+			NumReplacedParental:     0,
+			AvgProcessingTime:       0.123456,
+		}
+
+		for _, e := range entries {
+			s.Update(e)
+		}
+
+		data := &stats.StatsResp{}
+		req := httptest.NewRequest(http.MethodGet, "/control/stats", nil)
+		assertSuccessAndUnmarshal(t, data, handlers["/control/stats"], req)
+
+		assert.Equal(t, wantData, data)
 	})
 
-	s.Update(Entry{
-		Domain: "domain",
-		Client: "127.0.0.1",
-		Result: RFiltered,
-		Time:   123456,
-	})
-	s.Update(Entry{
-		Domain: "domain",
-		Client: "127.0.0.1",
-		Result: RNotFiltered,
-		Time:   123456,
+	t.Run("tops", func(t *testing.T) {
+		topClients := s.TopClientsIP(2)
+		require.NotEmpty(t, topClients)
+
+		assert.True(t, cliIP.Equal(topClients[0]))
 	})
 
-	d, ok := s.getData()
-	require.True(t, ok)
+	t.Run("reset", func(t *testing.T) {
+		req := httptest.NewRequest(http.MethodPost, "/control/stats_reset", nil)
+		assertSuccessAndUnmarshal(t, nil, handlers["/control/stats_reset"], req)
 
-	a := []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
-	assert.True(t, UIntArrayEquals(d.DNSQueries, a))
+		_24zeroes := [24]uint64{}
+		emptyData := &stats.StatsResp{
+			TimeUnits:            "hours",
+			TopQueried:           []map[string]uint64{},
+			TopClients:           []map[string]uint64{},
+			TopBlocked:           []map[string]uint64{},
+			DNSQueries:           _24zeroes[:],
+			BlockedFiltering:     _24zeroes[:],
+			ReplacedSafebrowsing: _24zeroes[:],
+			ReplacedParental:     _24zeroes[:],
+		}
 
-	a = []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
-	assert.True(t, UIntArrayEquals(d.BlockedFiltering, a))
+		req = httptest.NewRequest(http.MethodGet, "/control/stats", nil)
+		data := &stats.StatsResp{}
 
-	a = []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
-	assert.True(t, UIntArrayEquals(d.ReplacedSafebrowsing, a))
-
-	a = []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
-	assert.True(t, UIntArrayEquals(d.ReplacedParental, a))
-
-	m := d.TopQueried
-	require.NotEmpty(t, m)
-	assert.EqualValues(t, 1, m[0]["domain"])
-
-	m = d.TopBlocked
-	require.NotEmpty(t, m)
-	assert.EqualValues(t, 1, m[0]["domain"])
-
-	m = d.TopClients
-	require.NotEmpty(t, m)
-	assert.EqualValues(t, 2, m[0]["127.0.0.1"])
-
-	assert.EqualValues(t, 2, d.NumDNSQueries)
-	assert.EqualValues(t, 1, d.NumBlockedFiltering)
-	assert.EqualValues(t, 0, d.NumReplacedSafebrowsing)
-	assert.EqualValues(t, 0, d.NumReplacedSafesearch)
-	assert.EqualValues(t, 0, d.NumReplacedParental)
-	assert.EqualValues(t, 0.123456, d.AvgProcessingTime)
-
-	topClients := s.GetTopClientsIP(2)
-	require.NotEmpty(t, topClients)
-	assert.True(t, net.IP{127, 0, 0, 1}.Equal(topClients[0]))
+		assertSuccessAndUnmarshal(t, data, handlers["/control/stats"], req)
+		assert.Equal(t, emptyData, data)
+	})
 }
 
 func TestLargeNumbers(t *testing.T) {
-	var hour int32 = 0
-	newID := func() uint32 {
-		// Use "atomic" to make go race detector happy.
-		return uint32(atomic.LoadInt32(&hour))
+	var curHour uint32 = 1
+	handlers := map[string]http.Handler{}
+
+	conf := stats.Config{
+		Filename:     filepath.Join(t.TempDir(), "stats.db"),
+		LimitDays:    1,
+		UnitID:       func() (id uint32) { return atomic.LoadUint32(&curHour) },
+		HTTPRegister: func(_, url string, handler http.HandlerFunc) { handlers[url] = handler },
 	}
 
-	conf := Config{
-		Filename:  "./stats.db",
-		LimitDays: 1,
-		UnitID:    newID,
-	}
-	s, err := New(conf)
+	s, err := stats.New(conf)
 	require.NoError(t, err)
-	testutil.CleanupAndRequireSuccess(t, func() (err error) {
-		s.Close()
 
-		return os.Remove(conf.Filename)
-	})
+	s.Start()
+	testutil.CleanupAndRequireSuccess(t, s.Close)
 
-	// Number of distinct clients and domains every hour.
-	const n = 1000
+	const (
+		hoursNum      = 12
+		cliNumPerHour = 1000
+	)
 
-	for h := 0; h < 12; h++ {
-		atomic.AddInt32(&hour, 1)
-		for i := 0; i < n; i++ {
-			s.Update(Entry{
-				Domain: fmt.Sprintf("domain%d", i),
-				Client: net.IP{
-					127,
-					0,
-					byte((i & 0xff00) >> 8),
-					byte(i & 0xff),
-				}.String(),
-				Result: RNotFiltered,
+	req := httptest.NewRequest(http.MethodGet, "/control/stats", nil)
+
+	for h := 0; h < hoursNum; h++ {
+		atomic.AddUint32(&curHour, 1)
+
+		for i := 0; i < cliNumPerHour; i++ {
+			ip := net.IP{127, 0, byte((i & 0xff00) >> 8), byte(i & 0xff)}
+			e := stats.Entry{
+				Domain: fmt.Sprintf("domain%d.hour%d", i, h),
+				Client: ip.String(),
+				Result: stats.RNotFiltered,
 				Time:   123456,
-			})
+			}
+			s.Update(e)
 		}
 	}
 
-	d, ok := s.getData()
-	require.True(t, ok)
-	assert.EqualValues(t, hour*n, d.NumDNSQueries)
-}
-
-func TestStatsCollector(t *testing.T) {
-	ng := func(_ *unitDB) uint64 {
-		return 0
-	}
-	units := make([]*unitDB, 720)
-
-	t.Run("hours", func(t *testing.T) {
-		statsData := statsCollector(units, 0, Hours, ng)
-		assert.Len(t, statsData, 720)
-	})
-
-	t.Run("days", func(t *testing.T) {
-		for i := 0; i != 25; i++ {
-			statsData := statsCollector(units, uint32(i), Days, ng)
-			require.Lenf(t, statsData, 30, "i=%d", i)
-		}
-	})
+	data := &stats.StatsResp{}
+	assertSuccessAndUnmarshal(t, data, handlers["/control/stats"], req)
+	assert.Equal(t, hoursNum*cliNumPerHour, int(data.NumDNSQueries))
 }
diff --git a/internal/stats/unit.go b/internal/stats/unit.go
index 6d32a6d1..28e0b2bc 100644
--- a/internal/stats/unit.go
+++ b/internal/stats/unit.go
@@ -5,14 +5,9 @@ import (
 	"encoding/binary"
 	"encoding/gob"
 	"fmt"
-	"net"
-	"os"
 	"sort"
-	"sync"
-	"sync/atomic"
 	"time"
 
-	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
 	"go.etcd.io/bbolt"
@@ -22,51 +17,65 @@ import (
 // inspection.  Improve logging.  Decrease complexity.
 
 const (
-	maxDomains = 100 // max number of top domains to store in file or return via Get()
-	maxClients = 100 // max number of top clients to store in file or return via Get()
+	// maxDomains is the max number of top domains to return.
+	maxDomains = 100
+	// maxClients is the max number of top clients to return.
+	maxClients = 100
 )
 
-// StatsCtx collects the statistics and flushes it to the database.  Its default
-// flushing interval is one hour.
+// UnitIDGenFunc is the signature of a function that generates a unique ID for
+// the statistics unit.
+type UnitIDGenFunc func() (id uint32)
+
+// TimeUnit is the unit of measuring time while aggregating the statistics.
+type TimeUnit int
+
+// Supported TimeUnit values.
+const (
+	Hours TimeUnit = iota
+	Days
+)
+
+// Result is the resulting code of processing the DNS request.
+type Result int
+
+// Supported Result values.
 //
-// TODO(e.burkov):  Use atomic.Pointer for accessing curr and db in go1.19.
-type StatsCtx struct {
-	// currMu protects the current unit.
-	currMu *sync.Mutex
-	// curr is the actual statistics collection result.
-	curr *unit
+// TODO(e.burkov):  Think about better naming.
+const (
+	RNotFiltered Result = iota + 1
+	RFiltered
+	RSafeBrowsing
+	RSafeSearch
+	RParental
 
-	// dbMu protects db.
-	dbMu *sync.Mutex
-	// db is the opened statistics database, if any.
-	db *bbolt.DB
+	resultLast = RParental + 1
+)
 
-	// unitIDGen is the function that generates an identifier for the current
-	// unit.  It's here for only testing purposes.
-	unitIDGen UnitIDGenFunc
+// Entry is a statistics data entry.
+type Entry struct {
+	// Clients is the client's primary ID.
+	//
+	// TODO(a.garipov): Make this a {net.IP, string} enum?
+	Client string
 
-	// httpRegister is used to set HTTP handlers.
-	httpRegister aghhttp.RegisterFunc
+	// Domain is the domain name requested.
+	Domain string
 
-	// configModified is called whenever the configuration is modified via web
-	// interface.
-	configModified func()
+	// Result is the result of processing the request.
+	Result Result
 
-	// filename is the name of database file.
-	filename string
-
-	// limitHours is the maximum number of hours to collect statistics into the
-	// current unit.
-	limitHours uint32
+	// Time is the duration of the request processing in milliseconds.
+	Time uint32
 }
 
 // unit collects the statistics data for a specific period of time.
 type unit struct {
-	// mu protects all the fields of a unit.
-	mu *sync.RWMutex
-
 	// id is the unique unit's identifier.  It's set to an absolute hour number
 	// since the beginning of UNIX time by the default ID generating function.
+	//
+	// Must not be rewritten after creating to be accessed concurrently without
+	// using mu.
 	id uint32
 
 	// nTotal stores the total number of requests.
@@ -86,44 +95,15 @@ type unit struct {
 	clients map[string]uint64
 }
 
-// ongoing returns the current unit.  It's safe for concurrent use.
-//
-// Note that the unit itself should be locked before accessing.
-func (s *StatsCtx) ongoing() (u *unit) {
-	s.currMu.Lock()
-	defer s.currMu.Unlock()
-
-	return s.curr
-}
-
-// swapCurrent swaps the current unit with another and returns it.  It's safe
-// for concurrent use.
-func (s *StatsCtx) swapCurrent(with *unit) (old *unit) {
-	s.currMu.Lock()
-	defer s.currMu.Unlock()
-
-	old, s.curr = s.curr, with
-
-	return old
-}
-
-// database returns the database if it's opened.  It's safe for concurrent use.
-func (s *StatsCtx) database() (db *bbolt.DB) {
-	s.dbMu.Lock()
-	defer s.dbMu.Unlock()
-
-	return s.db
-}
-
-// swapDatabase swaps the database with another one and returns it.  It's safe
-// for concurrent use.
-func (s *StatsCtx) swapDatabase(with *bbolt.DB) (old *bbolt.DB) {
-	s.dbMu.Lock()
-	defer s.dbMu.Unlock()
-
-	old, s.db = s.db, with
-
-	return old
+// newUnit allocates the new *unit.
+func newUnit(id uint32) (u *unit) {
+	return &unit{
+		id:             id,
+		nResult:        make([]uint64, resultLast),
+		domains:        make(map[string]uint64),
+		blockedDomains: make(map[string]uint64),
+		clients:        make(map[string]uint64),
+	}
 }
 
 // countPair is a single name-number pair for deserializing statistics data into
@@ -133,7 +113,7 @@ type countPair struct {
 	Count uint64
 }
 
-// unitDB is the structure for deserializing statistics data into the database.
+// unitDB is the structure for serializing statistics data into the database.
 type unitDB struct {
 	// NTotal is the total number of requests.
 	NTotal uint64
@@ -152,157 +132,6 @@ type unitDB struct {
 	TimeAvg uint32
 }
 
-// withRecovered turns the value recovered from panic if any into an error and
-// combines it with the one pointed by orig.  orig must be non-nil.
-func withRecovered(orig *error) {
-	p := recover()
-	if p == nil {
-		return
-	}
-
-	var err error
-	switch p := p.(type) {
-	case error:
-		err = fmt.Errorf("panic: %w", p)
-	default:
-		err = fmt.Errorf("panic: recovered value of type %[1]T: %[1]v", p)
-	}
-
-	*orig = errors.WithDeferred(*orig, err)
-}
-
-// isEnabled is a helper that check if the statistics collecting is enabled.
-func (s *StatsCtx) isEnabled() (ok bool) {
-	return atomic.LoadUint32(&s.limitHours) != 0
-}
-
-// New creates s from conf and properly initializes it.  Don't use s before
-// calling it's Start method.
-func New(conf Config) (s *StatsCtx, err error) {
-	defer withRecovered(&err)
-
-	s = &StatsCtx{
-		currMu:         &sync.Mutex{},
-		dbMu:           &sync.Mutex{},
-		filename:       conf.Filename,
-		configModified: conf.ConfigModified,
-		httpRegister:   conf.HTTPRegister,
-	}
-	if s.limitHours = conf.LimitDays * 24; !checkInterval(conf.LimitDays) {
-		s.limitHours = 24
-	}
-	if s.unitIDGen = newUnitID; conf.UnitID != nil {
-		s.unitIDGen = conf.UnitID
-	}
-
-	if err = s.dbOpen(); err != nil {
-		return nil, fmt.Errorf("opening database: %w", err)
-	}
-
-	id := s.unitIDGen()
-	tx := beginTxn(s.db, true)
-	var udb *unitDB
-	if tx != nil {
-		log.Tracef("Deleting old units...")
-		firstID := id - s.limitHours - 1
-		unitDel := 0
-
-		err = tx.ForEach(newBucketWalker(tx, &unitDel, firstID))
-		if err != nil && !errors.Is(err, errStop) {
-			log.Debug("stats: deleting units: %s", err)
-		}
-
-		udb = s.loadUnitFromDB(tx, id)
-
-		if unitDel != 0 {
-			s.commitTxn(tx)
-		} else {
-			err = tx.Rollback()
-			if err != nil {
-				log.Debug("rolling back: %s", err)
-			}
-		}
-	}
-
-	u := newUnit(id)
-	// This use of deserialize is safe since the accessed unit has just been
-	// created.
-	u.deserialize(udb)
-	s.curr = u
-
-	log.Debug("stats: initialized")
-
-	return s, nil
-}
-
-// TODO(a.garipov): See if this is actually necessary.  Looks like a rather
-// bizarre solution.
-const errStop errors.Error = "stop iteration"
-
-// newBucketWalker returns a new bucket walker that deletes old units.  The
-// integer that unitDelPtr points to is incremented for every successful
-// deletion.  If the bucket isn't deleted, f returns errStop.
-func newBucketWalker(
-	tx *bbolt.Tx,
-	unitDelPtr *int,
-	firstID uint32,
-) (f func(name []byte, b *bbolt.Bucket) (err error)) {
-	return func(name []byte, _ *bbolt.Bucket) (err error) {
-		nameID, ok := unitNameToID(name)
-		if !ok || nameID < firstID {
-			err = tx.DeleteBucket(name)
-			if err != nil {
-				log.Debug("stats: tx.DeleteBucket: %s", err)
-
-				return nil
-			}
-
-			log.Debug("stats: deleted unit %d (name %x)", nameID, name)
-
-			*unitDelPtr++
-
-			return nil
-		}
-
-		return errStop
-	}
-}
-
-// Start makes s process the incoming data.
-func (s *StatsCtx) Start() {
-	s.initWeb()
-	go s.periodicFlush()
-}
-
-// checkInterval returns true if days is valid to be used as statistics
-// retention interval.  The valid values are 0, 1, 7, 30 and 90.
-func checkInterval(days uint32) (ok bool) {
-	return days == 0 || days == 1 || days == 7 || days == 30 || days == 90
-}
-
-// dbOpen returns an error if the database can't be opened from the specified
-// file.  It's safe for concurrent use.
-func (s *StatsCtx) dbOpen() (err error) {
-	log.Tracef("db.Open...")
-
-	s.dbMu.Lock()
-	defer s.dbMu.Unlock()
-
-	s.db, err = bbolt.Open(s.filename, 0o644, nil)
-	if err != nil {
-		log.Error("stats: open DB: %s: %s", s.filename, err)
-		if err.Error() == "invalid argument" {
-			log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#limitations")
-		}
-
-		return err
-	}
-
-	log.Tracef("db.Open")
-
-	return nil
-}
-
 // newUnitID is the default UnitIDGenFunc that generates the unique id hourly.
 func newUnitID() (id uint32) {
 	const secsInHour = int64(time.Hour / time.Second)
@@ -310,50 +139,14 @@ func newUnitID() (id uint32) {
 	return uint32(time.Now().Unix() / secsInHour)
 }
 
-// newUnit allocates the new *unit.
-func newUnit(id uint32) (u *unit) {
-	return &unit{
-		mu:             &sync.RWMutex{},
-		id:             id,
-		nResult:        make([]uint64, resultLast),
-		domains:        make(map[string]uint64),
-		blockedDomains: make(map[string]uint64),
-		clients:        make(map[string]uint64),
-	}
-}
-
-// beginTxn opens a new database transaction.  If writable is true, the
-// transaction will be opened for writing, and for reading otherwise.  It
-// returns nil if the transaction can't be created.
-func beginTxn(db *bbolt.DB, writable bool) (tx *bbolt.Tx) {
-	if db == nil {
-		return nil
+func finishTxn(tx *bbolt.Tx, commit bool) (err error) {
+	if commit {
+		err = errors.Annotate(tx.Commit(), "committing: %w")
+	} else {
+		err = errors.Annotate(tx.Rollback(), "rolling back: %w")
 	}
 
-	log.Tracef("opening a database transaction")
-
-	tx, err := db.Begin(writable)
-	if err != nil {
-		log.Error("stats: opening a transaction: %s", err)
-
-		return nil
-	}
-
-	log.Tracef("transaction has been opened")
-
-	return tx
-}
-
-// commitTxn applies the changes made in tx to the database.
-func (s *StatsCtx) commitTxn(tx *bbolt.Tx) {
-	err := tx.Commit()
-	if err != nil {
-		log.Error("stats: committing a transaction: %s", err)
-
-		return
-	}
-
-	log.Tracef("transaction has been committed")
+	return err
 }
 
 // bucketNameLen is the length of a bucket, a 64-bit unsigned integer.
@@ -380,88 +173,34 @@ func unitNameToID(name []byte) (id uint32, ok bool) {
 	return uint32(binary.BigEndian.Uint64(name)), true
 }
 
-// Flush the current unit to DB and delete an old unit when a new hour is started
-// If a unit must be flushed:
-// . lock DB
-// . atomically set a new empty unit as the current one and get the old unit
-//   This is important to do it inside DB lock, so the reader won't get inconsistent results.
-// . write the unit to DB
-// . remove the stale unit from DB
-// . unlock DB
-func (s *StatsCtx) periodicFlush() {
-	for ptr := s.ongoing(); ptr != nil; ptr = s.ongoing() {
-		id := s.unitIDGen()
-		// Access the unit's ID with atomic to avoid locking the whole unit.
-		if !s.isEnabled() || atomic.LoadUint32(&ptr.id) == id {
-			time.Sleep(time.Second)
-
-			continue
-		}
-
-		tx := beginTxn(s.database(), true)
-
-		nu := newUnit(id)
-		u := s.swapCurrent(nu)
-		udb := u.serialize()
-
-		if tx == nil {
-			continue
-		}
-
-		flushOK := flushUnitToDB(tx, u.id, udb)
-		delOK := s.deleteUnit(tx, id-atomic.LoadUint32(&s.limitHours))
-		if flushOK || delOK {
-			s.commitTxn(tx)
-		} else {
-			_ = tx.Rollback()
-		}
-	}
-
-	log.Tracef("periodicFlush() exited")
-}
-
-// deleteUnit removes the unit by it's id from the database the tx belongs to.
-func (s *StatsCtx) deleteUnit(tx *bbolt.Tx, id uint32) bool {
-	err := tx.DeleteBucket(idToUnitName(id))
-	if err != nil {
-		log.Tracef("stats: bolt DeleteBucket: %s", err)
-
-		return false
-	}
-
-	log.Debug("stats: deleted unit %d", id)
-
-	return true
-}
-
-func convertMapToSlice(m map[string]uint64, max int) []countPair {
-	a := []countPair{}
+func convertMapToSlice(m map[string]uint64, max int) (s []countPair) {
+	s = make([]countPair, 0, len(m))
 	for k, v := range m {
-		a = append(a, countPair{Name: k, Count: v})
+		s = append(s, countPair{Name: k, Count: v})
 	}
-	less := func(i, j int) bool {
-		return a[j].Count < a[i].Count
+
+	sort.Slice(s, func(i, j int) bool {
+		return s[j].Count < s[i].Count
+	})
+	if max > len(s) {
+		max = len(s)
 	}
-	sort.Slice(a, less)
-	if max > len(a) {
-		max = len(a)
-	}
-	return a[:max]
+
+	return s[:max]
 }
 
-func convertSliceToMap(a []countPair) map[string]uint64 {
-	m := map[string]uint64{}
+func convertSliceToMap(a []countPair) (m map[string]uint64) {
+	m = map[string]uint64{}
 	for _, it := range a {
 		m[it.Name] = it.Count
 	}
+
 	return m
 }
 
-// serialize converts u to the *unitDB.  It's safe for concurrent use.
+// serialize converts u to the *unitDB.  It's safe for concurrent use.  u must
+// not be nil.
 func (u *unit) serialize() (udb *unitDB) {
-	u.mu.RLock()
-	defer u.mu.RUnlock()
-
 	var timeAvg uint32 = 0
 	if u.nTotal != 0 {
 		timeAvg = uint32(u.timeSum / u.nTotal)
@@ -477,6 +216,28 @@ func (u *unit) serialize() (udb *unitDB) {
 	}
 }
 
+func loadUnitFromDB(tx *bbolt.Tx, id uint32) (udb *unitDB) {
+	bkt := tx.Bucket(idToUnitName(id))
+	if bkt == nil {
+		return nil
+	}
+
+	log.Tracef("Loading unit %d", id)
+
+	var buf bytes.Buffer
+	buf.Write(bkt.Get([]byte{0}))
+	udb = &unitDB{}
+
+	err := gob.NewDecoder(&buf).Decode(udb)
+	if err != nil {
+		log.Error("gob Decode: %s", err)
+
+		return nil
+	}
+
+	return udb
+}
+
 // deserealize assigns the appropriate values from udb to u.  u must not be nil.
 // It's safe for concurrent use.
 func (u *unit) deserialize(udb *unitDB) {
@@ -484,9 +245,6 @@ func (u *unit) deserialize(udb *unitDB) {
 		return
 	}
 
-	u.mu.Lock()
-	defer u.mu.Unlock()
-
 	u.nTotal = udb.NTotal
 	u.nResult = make([]uint64, resultLast)
 	copy(u.nResult, udb.NResult)
@@ -496,51 +254,41 @@ func (u *unit) deserialize(udb *unitDB) {
 	u.timeSum = uint64(udb.TimeAvg) * udb.NTotal
 }
 
-func flushUnitToDB(tx *bbolt.Tx, id uint32, udb *unitDB) bool {
-	log.Tracef("Flushing unit %d", id)
+// add adds new data to u.  It's safe for concurrent use.
+func (u *unit) add(res Result, domain, cli string, dur uint64) {
+	u.nResult[res]++
+	if res == RNotFiltered {
+		u.domains[domain]++
+	} else {
+		u.blockedDomains[domain]++
+	}
+
+	u.clients[cli]++
+	u.timeSum += dur
+	u.nTotal++
+}
+
+// flushUnitToDB puts udb to the database at id.
+func (udb *unitDB) flushUnitToDB(tx *bbolt.Tx, id uint32) (err error) {
+	log.Debug("stats: flushing unit with id %d and total of %d", id, udb.NTotal)
 
 	bkt, err := tx.CreateBucketIfNotExists(idToUnitName(id))
 	if err != nil {
-		log.Error("tx.CreateBucketIfNotExists: %s", err)
-		return false
+		return fmt.Errorf("creating bucket: %w", err)
 	}
 
-	var buf bytes.Buffer
-	enc := gob.NewEncoder(&buf)
-	err = enc.Encode(udb)
+	buf := &bytes.Buffer{}
+	err = gob.NewEncoder(buf).Encode(udb)
 	if err != nil {
-		log.Error("gob.Encode: %s", err)
-		return false
+		return fmt.Errorf("encoding unit: %w", err)
 	}
 
 	err = bkt.Put([]byte{0}, buf.Bytes())
 	if err != nil {
-		log.Error("bkt.Put: %s", err)
-		return false
+		return fmt.Errorf("putting unit to database: %w", err)
 	}
 
-	return true
-}
-
-func (s *StatsCtx) loadUnitFromDB(tx *bbolt.Tx, id uint32) *unitDB {
-	bkt := tx.Bucket(idToUnitName(id))
-	if bkt == nil {
-		return nil
-	}
-
-	// log.Tracef("Loading unit %d", id)
-
-	var buf bytes.Buffer
-	buf.Write(bkt.Get([]byte{0}))
-	dec := gob.NewDecoder(&buf)
-	udb := unitDB{}
-	err := dec.Decode(&udb)
-	if err != nil {
-		log.Error("gob Decode: %s", err)
-		return nil
-	}
-
-	return &udb
+	return nil
 }
 
 func convertTopSlice(a []countPair) (m []map[string]uint64) {
@@ -552,144 +300,6 @@ func convertTopSlice(a []countPair) (m []map[string]uint64) {
 	return m
 }
 
-func (s *StatsCtx) setLimit(limitDays int) {
-	atomic.StoreUint32(&s.limitHours, uint32(24*limitDays))
-	if limitDays == 0 {
-		s.clear()
-	}
-
-	log.Debug("stats: set limit: %d days", limitDays)
-}
-
-func (s *StatsCtx) WriteDiskConfig(dc *DiskConfig) {
-	dc.Interval = atomic.LoadUint32(&s.limitHours) / 24
-}
-
-func (s *StatsCtx) Close() {
-	u := s.swapCurrent(nil)
-
-	db := s.database()
-	if tx := beginTxn(db, true); tx != nil {
-		udb := u.serialize()
-		if flushUnitToDB(tx, u.id, udb) {
-			s.commitTxn(tx)
-		} else {
-			_ = tx.Rollback()
-		}
-	}
-
-	if db != nil {
-		log.Tracef("db.Close...")
-		_ = db.Close()
-		log.Tracef("db.Close")
-	}
-
-	log.Debug("stats: closed")
-}
-
-// Reset counters and clear database
-func (s *StatsCtx) clear() {
-	db := s.database()
-	tx := beginTxn(db, true)
-	if tx != nil {
-		_ = s.swapDatabase(nil)
-		_ = tx.Rollback()
-		// the active transactions can continue using database,
-		//  but no new transactions will be opened
-		_ = db.Close()
-		log.Tracef("db.Close")
-		// all active transactions are now closed
-	}
-
-	u := newUnit(s.unitIDGen())
-	_ = s.swapCurrent(u)
-
-	err := os.Remove(s.filename)
-	if err != nil {
-		log.Error("os.Remove: %s", err)
-	}
-
-	_ = s.dbOpen()
-
-	log.Debug("stats: cleared")
-}
-
-func (s *StatsCtx) Update(e Entry) {
-	if !s.isEnabled() {
-		return
-	}
-
-	if e.Result == 0 ||
-		e.Result >= resultLast ||
-		e.Domain == "" ||
-		e.Client == "" {
-		return
-	}
-
-	clientID := e.Client
-	if ip := net.ParseIP(clientID); ip != nil {
-		clientID = ip.String()
-	}
-
-	u := s.ongoing()
-	if u == nil {
-		return
-	}
-
-	u.mu.Lock()
-	defer u.mu.Unlock()
-
-	u.nResult[e.Result]++
-	if e.Result == RNotFiltered {
-		u.domains[e.Domain]++
-	} else {
-		u.blockedDomains[e.Domain]++
-	}
-
-	u.clients[clientID]++
-	u.timeSum += uint64(e.Time)
-	u.nTotal++
-}
-
-func (s *StatsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) {
-	tx := beginTxn(s.database(), false)
-	if tx == nil {
-		return nil, 0
-	}
-
-	cur := s.ongoing()
-	var curID uint32
-	if cur != nil {
-		curID = atomic.LoadUint32(&cur.id)
-	} else {
-		curID = s.unitIDGen()
-	}
-
-	// Per-hour units.
-	units := []*unitDB{}
-	firstID := curID - limit + 1
-	for i := firstID; i != curID; i++ {
-		u := s.loadUnitFromDB(tx, i)
-		if u == nil {
-			u = &unitDB{}
-			u.NResult = make([]uint64, resultLast)
-		}
-		units = append(units, u)
-	}
-
-	_ = tx.Rollback()
-
-	if cur != nil {
-		units = append(units, cur.serialize())
-	}
-
-	if len(units) != int(limit) {
-		log.Fatalf("len(units) != limit: %d %d", len(units), limit)
-	}
-
-	return units, firstID
-}
-
 // numsGetter is a signature for statsCollector argument.
 type numsGetter func(u *unitDB) (num uint64)
 
@@ -697,6 +307,7 @@ type numsGetter func(u *unitDB) (num uint64)
 // timeUnit using ng to retrieve data.
 func statsCollector(units []*unitDB, firstID uint32, timeUnit TimeUnit, ng numsGetter) (nums []uint64) {
 	if timeUnit == Hours {
+		nums = make([]uint64, 0, len(units))
 		for _, u := range units {
 			nums = append(nums, ng(u))
 		}
@@ -738,6 +349,7 @@ func topsCollector(units []*unitDB, max int, pg pairsGetter) []map[string]uint64
 		}
 	}
 	a2 := convertMapToSlice(m, max)
+
 	return convertTopSlice(a2)
 }
 
@@ -768,10 +380,9 @@ func topsCollector(units []*unitDB, max int, pg pairsGetter) []map[string]uint64
   * parental-blocked
   These values are just the sum of data for all units.
 */
-func (s *StatsCtx) getData() (statsResponse, bool) {
-	limit := atomic.LoadUint32(&s.limitHours)
+func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) {
 	if limit == 0 {
-		return statsResponse{
+		return StatsResp{
 			TimeUnits: "days",
 
 			TopBlocked: []topAddrs{},
@@ -792,7 +403,7 @@ func (s *StatsCtx) getData() (statsResponse, bool) {
 
 	units, firstID := s.loadUnits(limit)
 	if units == nil {
-		return statsResponse{}, false
+		return StatsResp{}, false
 	}
 
 	dnsQueries := statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NTotal })
@@ -800,7 +411,7 @@ func (s *StatsCtx) getData() (statsResponse, bool) {
 		log.Fatalf("len(dnsQueries) != limit: %d %d", len(dnsQueries), limit)
 	}
 
-	data := statsResponse{
+	data := StatsResp{
 		DNSQueries:           dnsQueries,
 		BlockedFiltering:     statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }),
 		ReplacedSafebrowsing: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }),
@@ -844,31 +455,3 @@ func (s *StatsCtx) getData() (statsResponse, bool) {
 
 	return data, true
 }
-
-func (s *StatsCtx) GetTopClientsIP(maxCount uint) []net.IP {
-	if !s.isEnabled() {
-		return nil
-	}
-
-	units, _ := s.loadUnits(atomic.LoadUint32(&s.limitHours))
-	if units == nil {
-		return nil
-	}
-
-	// top clients
-	m := map[string]uint64{}
-	for _, u := range units {
-		for _, it := range u.Clients {
-			m[it.Name] += it.Count
-		}
-	}
-	a := convertMapToSlice(m, int(maxCount))
-	d := []net.IP{}
-	for _, it := range a {
-		ip := net.ParseIP(it.Name)
-		if ip != nil {
-			d = append(d, ip)
-		}
-	}
-	return d
-}

From 385a873b0f006f26832e73744845fdbc2864aad0 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Wed, 17 Aug 2022 15:02:31 +0300
Subject: [PATCH 125/143] all: chlog

---
 CHANGELOG.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index edee3432..b3d14b40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,10 @@ and this project adheres to
 - Support for Discovery of Designated Resolvers (DDR) according to the [RFC
   draft][ddr-draft] ([#4463]).
 
+### Changed
+
+- Our snap package now uses the `core22` image as its base ([#4843]).
+
 ### Fixed
 
 - Data races and concurrent map access in statistics module ([#4358], [#4342]).
@@ -43,6 +47,7 @@ and this project adheres to
 [#4342]: https://github.com/AdguardTeam/AdGuardHome/issues/4342
 [#4358]: https://github.com/AdguardTeam/AdGuardHome/issues/4358
 [#4670]: https://github.com/AdguardTeam/AdGuardHome/issues/4670
+[#4843]: https://github.com/AdguardTeam/AdGuardHome/issues/4843
 
 [ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
 

From 71b8e7513812ca76df00ad66b2f3be06cd3ea550 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 17 Aug 2022 15:33:41 +0300
Subject: [PATCH 126/143] Pull request: upd-i18n

Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 218bf20b6bec0c7699d8ae3c0c2500af63569689
Merge: 4aaab375 0bcc6699
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 17 15:29:10 2022 +0300

    Merge branch 'master' into upd-i18n

commit 4aaab3757bd7b5e5a689aed07ccf9648f05f6fcc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 17 15:23:23 2022 +0300

    client: upd i18n
---
 client/src/__locales/da.json | 1 +
 client/src/__locales/es.json | 6 +++---
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json
index 7e800e1c..32bec1e2 100644
--- a/client/src/__locales/da.json
+++ b/client/src/__locales/da.json
@@ -222,6 +222,7 @@
     "updated_upstream_dns_toast": "Upstream-servere er gemt",
     "dns_test_ok_toast": "Angivne DNS-servere fungerer korrekt",
     "dns_test_not_ok_toast": "Server \"{{key}}\": Kunne ikke bruges. Tjek, at du har angivet den korrekt",
+    "dns_test_warning_toast": "Upstream \"{{key}}\" svarer ikke på testforespørgsler og fungerer muligvis ikke korrekt",
     "unblock": "Afblokering",
     "block": "Blokering",
     "disallow_this_client": "Afvis denne klient",
diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json
index 56895b58..895d0c33 100644
--- a/client/src/__locales/es.json
+++ b/client/src/__locales/es.json
@@ -47,7 +47,7 @@
     "form_error_server_name": "Nombre de servidor no válido",
     "form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\"",
     "form_error_positive": "Debe ser mayor que 0",
-    "form_error_gateway_ip": "Asignación no puede tener la dirección IP de la puerta de enlace",
+    "form_error_gateway_ip": "La asignación no puede tener la dirección IP de la puerta de enlace",
     "out_of_range_error": "Debe estar fuera del rango \"{{start}}\"-\"{{end}}\"",
     "lower_range_start_error": "Debe ser inferior que el inicio de rango",
     "greater_range_start_error": "Debe ser mayor que el inicio de rango",
@@ -222,7 +222,7 @@
     "updated_upstream_dns_toast": "Servidores DNS de subida guardados correctamente",
     "dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
     "dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revisa si lo has escrito correctamente",
-    "dns_test_warning_toast": "Upstream \"{{key}}\" no responde a las peticiones de prueba y es posible que no funcione correctamente",
+    "dns_test_warning_toast": "DNS de subida \"{{key}}\" no responde a las peticiones de prueba y es posible que no funcione correctamente",
     "unblock": "Desbloquear",
     "block": "Bloquear",
     "disallow_this_client": "No permitir a este cliente",
@@ -364,7 +364,7 @@
     "encryption_config_saved": "Configuración de cifrado guardado",
     "encryption_server": "Nombre del servidor",
     "encryption_server_enter": "Ingresa el nombre del dominio",
-    "encryption_server_desc": "Si se configura, AdGuard Home detecta los ClientID, responde a las consultas DDR y realiza validaciones de conexión adicionales. Si no se configura, estas funciones están deshabilitadas. Debe coincidir con uno de los nombres DNS del certificado.",
+    "encryption_server_desc": "Si se configura, AdGuard Home detecta los ID de clientes, responde a las consultas DDR y realiza validaciones de conexión adicionales. Si no se configura, estas funciones se deshabilitarán. Debe coincidir con uno de los nombres DNS del certificado.",
     "encryption_redirect": "Redireccionar a HTTPS automáticamente",
     "encryption_redirect_desc": "Si está marcado, AdGuard Home redireccionará automáticamente de HTTP a las direcciones HTTPS.",
     "encryption_https": "Puerto HTTPS",

From 12edc05ab0590a4ec1d9e9da638c028d62fa272f Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Wed, 17 Aug 2022 19:02:30 +0300
Subject: [PATCH 127/143] Pull request: upd-chlog

Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 075a81165f143d4d5886e14d14247ea57abee866
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 17 18:56:38 2022 +0300

    all: upd chlog
---
 CHANGELOG.md | 50 +++++++++++++++++++++++++++++++-------------------
 1 file changed, 31 insertions(+), 19 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b3d14b40..b4ad05b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,28 @@ and this project adheres to
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
 
+### Deprecated
+
+- Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
+
+[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+
+
+
+<!--
+## [v0.107.11] - 2022-09-28 (APPROX.)
+
+See also the [v0.107.11 GitHub milestone][ms-v0.107.11].
+
+[ms-v0.107.11]: https://github.com/AdguardTeam/AdGuardHome/milestone/47?closed=1
+-->
+
+
+
+## [v0.107.10] - 2022-08-17
+
+See also the [v0.107.10 GitHub milestone][ms-v0.107.10].
+
 ### Added
 
 - Arabic localization.
@@ -32,30 +54,19 @@ and this project adheres to
 
 ### Fixed
 
-- Data races and concurrent map access in statistics module ([#4358], [#4342]).
-
-### Deprecated
-
-- Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
-
-### Fixed
-
+- DHCP not working on most OSes ([#4836]).
 - `invalid argument` errors during update checks on older Linux kernels
   ([#4670]).
+- Data races and concurrent map access in statistics module ([#4358], [#4342]).
 
-[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#4342]: https://github.com/AdguardTeam/AdGuardHome/issues/4342
 [#4358]: https://github.com/AdguardTeam/AdGuardHome/issues/4358
 [#4670]: https://github.com/AdguardTeam/AdGuardHome/issues/4670
+[#4836]: https://github.com/AdguardTeam/AdGuardHome/issues/4836
 [#4843]: https://github.com/AdguardTeam/AdGuardHome/issues/4843
 
-[ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
-
-
-
-<!--
-## [v0.107.10] - 2022-09-06 (APPROX.)
--->
+[ddr-draft]:    https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
+[ms-v0.107.10]: https://github.com/AdguardTeam/AdGuardHome/milestone/46?closed=1
 
 
 
@@ -1105,11 +1116,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
 
 
 <!--
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...HEAD
-[v0.107.9]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...v0.107.10
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.11...HEAD
+[v0.107.11]:  https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...v0.107.11
 -->
 
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...HEAD
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...HEAD
+[v0.107.10]:  https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...v0.107.10
 [v0.107.9]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...v0.107.9
 [v0.107.8]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...v0.107.8
 [v0.107.7]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...v0.107.7

From f54a2dc1da5dfd578f156cf1e0f53f32516eb844 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Wed, 17 Aug 2022 20:40:47 +0300
Subject: [PATCH 128/143] home: imp filtering handling

---
 internal/home/controlfiltering.go | 53 +++++++++++++++++--------------
 openapi/openapi.yaml              | 19 ++++++++++-
 2 files changed, 48 insertions(+), 24 deletions(-)

diff --git a/internal/home/controlfiltering.go b/internal/home/controlfiltering.go
index 51d86146..a4c8651a 100644
--- a/internal/home/controlfiltering.go
+++ b/internal/home/controlfiltering.go
@@ -13,6 +13,7 @@ import (
 	"time"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
+	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/miekg/dns"
 )
@@ -57,8 +58,8 @@ func (f *Filtering) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
 
 	err = validateFilterURL(fj.URL)
 	if err != nil {
-		msg := fmt.Sprintf("invalid url: %s", err)
-		http.Error(w, msg, http.StatusBadRequest)
+		err = fmt.Errorf("invalid url: %s", err)
+		aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
 
 		return
 	}
@@ -178,16 +179,16 @@ func (f *Filtering) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ
 	}
 }
 
-type filterURLJSON struct {
+type filterURLReqData struct {
 	Name    string `json:"name"`
 	URL     string `json:"url"`
 	Enabled bool   `json:"enabled"`
 }
 
 type filterURLReq struct {
-	URL       string        `json:"url"`
-	Whitelist bool          `json:"whitelist"`
-	Data      filterURLJSON `json:"data"`
+	Data      *filterURLReqData `json:"data"`
+	URL       string            `json:"url"`
+	Whitelist bool              `json:"whitelist"`
 }
 
 func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
@@ -199,10 +200,17 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
 		return
 	}
 
+	if fj.Data == nil {
+		err = errors.Error("data cannot be null")
+		aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
+
+		return
+	}
+
 	err = validateFilterURL(fj.Data.URL)
 	if err != nil {
-		msg := fmt.Sprintf("invalid url: %s", err)
-		http.Error(w, msg, http.StatusBadRequest)
+		err = fmt.Errorf("invalid url: %s", err)
+		aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
 
 		return
 	}
@@ -223,11 +231,8 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
 	}
 
 	onConfigModified()
-	restart := false
-	if (status & statusEnabledChanged) != 0 {
-		// we must add or remove filter rules
-		restart = true
-	}
+
+	restart := (status & statusEnabledChanged) != 0
 	if (status&statusUpdateRequired) != 0 && fj.Data.Enabled {
 		// download new filter and apply its rules
 		flags := filterRefreshBlocklists
@@ -242,6 +247,7 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
 			restart = true
 		}
 	}
+
 	if restart {
 		enableFilters(true)
 	}
@@ -311,20 +317,20 @@ func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
 }
 
 type filterJSON struct {
-	ID          int64  `json:"id"`
-	Enabled     bool   `json:"enabled"`
 	URL         string `json:"url"`
 	Name        string `json:"name"`
-	RulesCount  uint32 `json:"rules_count"`
 	LastUpdated string `json:"last_updated,omitempty"`
+	ID          int64  `json:"id"`
+	RulesCount  uint32 `json:"rules_count"`
+	Enabled     bool   `json:"enabled"`
 }
 
 type filteringConfig struct {
-	Enabled          bool         `json:"enabled"`
-	Interval         uint32       `json:"interval"` // in hours
 	Filters          []filterJSON `json:"filters"`
 	WhitelistFilters []filterJSON `json:"whitelist_filters"`
 	UserRules        []string     `json:"user_rules"`
+	Interval         uint32       `json:"interval"` // in hours
+	Enabled          bool         `json:"enabled"`
 }
 
 func filterToJSON(f filter) filterJSON {
@@ -402,16 +408,12 @@ func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request
 }
 
 type checkHostRespRule struct {
-	FilterListID int64  `json:"filter_list_id"`
 	Text         string `json:"text"`
+	FilterListID int64  `json:"filter_list_id"`
 }
 
 type checkHostResp struct {
 	Reason string `json:"reason"`
-	// FilterID is the ID of the rule's filter list.
-	//
-	// Deprecated: Use Rules[*].FilterListID.
-	FilterID int64 `json:"filter_id"`
 
 	// Rule is the text of the matched rule.
 	//
@@ -426,6 +428,11 @@ type checkHostResp struct {
 	// for Rewrite:
 	CanonName string   `json:"cname"`    // CNAME value
 	IPList    []net.IP `json:"ip_addrs"` // list of IP addresses
+
+	// FilterID is the ID of the rule's filter list.
+	//
+	// Deprecated: Use Rules[*].FilterListID.
+	FilterID int64 `json:"filter_id"`
 }
 
 func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) {
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index bb3bfe09..eb7b1894 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -1454,11 +1454,28 @@
       'description': 'Filtering URL settings'
       'properties':
         'data':
-          '$ref': '#/components/schemas/Filter'
+          '$ref': '#/components/schemas/FilterSetUrlData'
         'url':
           'type': 'string'
         'whitelist':
           'type': 'boolean'
+    'FilterSetUrlData':
+      'type': 'object'
+      'description': 'Filter update data'
+      'required':
+      - 'enabled'
+      - 'name'
+      - 'url'
+      'properties':
+        'enabled':
+          'type': 'boolean'
+        'name':
+          'example': 'AdGuard Simplified Domain Names filter'
+          'type': 'string'
+        'url':
+          'type': 'string'
+          'example': >
+            https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt
     'FilterRefreshRequest':
       'type': 'object'
       'description': 'Refresh Filters request data'

From ea5d165a703dd37ef40254f3f775e049b6cebf93 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Wed, 17 Aug 2022 21:43:31 +0300
Subject: [PATCH 129/143] all: imp readme

---
 README.md | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index fd876506..32478a62 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,9 @@ and both share a lot of code.
 ## Getting Started
 
 ### Automated install (Linux and Mac)
+
 Run the following command in your terminal:
+
 ```sh
 curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v
 ```
@@ -204,7 +206,7 @@ You will need this to build AdGuard Home:
 
 Open Terminal and execute these commands:
 
-```bash
+```sh
 git clone https://github.com/AdguardTeam/AdGuardHome
 cd AdGuardHome
 make
@@ -222,10 +224,13 @@ Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Ma
 In order to do this, specify `GOOS` and `GOARCH` env variables before running make.
 
 For example:
+
 ```sh
 env GOOS='linux' GOARCH='arm64' make
 ```
-Or:
+
+or:
+
 ```sh
 make GOOS='linux' GOARCH='arm64'
 ```
@@ -281,11 +286,13 @@ There are three options how you can install an unstable version:
 3. Standalone builds. Use the automated installation script or look for the available builds below.
 
 Beta:
+
 ```sh
 curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c beta
 ```
 
 Edge:
+
 ```sh
 curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c edge
 ```

From e6ebb8efef4430c48b06469ba566349bba3d9856 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Thu, 18 Aug 2022 14:51:28 +0300
Subject: [PATCH 130/143] filtering: fmt

---
 CHANGELOG.md                  |  5 +++
 internal/filtering/blocked.go | 83 +++++++++++++++++++++++++----------
 2 files changed, 64 insertions(+), 24 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4ad05b4..b92e14c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,11 +20,16 @@ and this project adheres to
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
 
+### Added
+
+- Bilibili service blocking ([#4795]).
+
 ### Deprecated
 
 - Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
 
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+[#4795]: https://github.com/AdguardTeam/AdGuardHome/issues/4795
 
 
 
diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index 89531a22..665b62d4 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -20,8 +20,11 @@ type svc struct {
 // client/src/helpers/constants.js
 // client/src/components/ui/Icons.js
 var serviceRulesArray = []svc{{
-	name:  "whatsapp",
-	rules: []string{"||whatsapp.net^", "||whatsapp.com^"},
+	name: "whatsapp",
+	rules: []string{
+		"||whatsapp.com^",
+		"||whatsapp.net^",
+	},
 }, {
 	name: "facebook",
 	rules: []string{
@@ -38,8 +41,13 @@ var serviceRulesArray = []svc{{
 		"||fb.watch^",
 	},
 }, {
-	name:  "twitter",
-	rules: []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"},
+	name: "twitter",
+	rules: []string{
+		"||t.co^",
+		"||twimg.com^",
+		"||twitter.com^",
+		"||twttr.com^",
+	},
 }, {
 	name: "youtube",
 	rules: []string{
@@ -53,8 +61,13 @@ var serviceRulesArray = []svc{{
 		"||ytimg.com^",
 	},
 }, {
-	name:  "twitch",
-	rules: []string{"||twitch.tv^", "||ttvnw.net^", "||jtvnw.net^", "||twitchcdn.net^"},
+	name: "twitch",
+	rules: []string{
+		"||jtvnw.net^",
+		"||ttvnw.net^",
+		"||twitch.tv^",
+		"||twitchcdn.net^",
+	},
 }, {
 	name: "netflix",
 	rules: []string{
@@ -220,17 +233,28 @@ var serviceRulesArray = []svc{{
 		"||douyincdn.com^",
 	},
 }, {
-	name:  "vimeo",
-	rules: []string{"||vimeo.com^", "||vimeocdn.com^", "*vod-adaptive.akamaized.net^"},
+	name: "vimeo",
+	rules: []string{
+		"*vod-adaptive.akamaized.net^",
+		"||vimeo.com^",
+		"||vimeocdn.com^",
+	},
 }, {
-	name:  "pinterest",
-	rules: []string{"||pinterest.*^", "||pinimg.com^"},
+	name: "pinterest",
+	rules: []string{
+		"||pinimg.com^",
+		"||pinterest.*^",
+	},
 }, {
 	name:  "imgur",
 	rules: []string{"||imgur.com^"},
 }, {
-	name:  "dailymotion",
-	rules: []string{"||dailymotion.com^", "||dm-event.net^", "||dmcdn.net^"},
+	name: "dailymotion",
+	rules: []string{
+		"||dailymotion.com^",
+		"||dm-event.net^",
+		"||dmcdn.net^",
+	},
 }, {
 	name: "qq",
 	rules: []string{
@@ -244,23 +268,34 @@ var serviceRulesArray = []svc{{
 	name: "wechat",
 	rules: []string{
 		"||wechat.com^",
-		"||wx.qq.com^",
-		"||weixin.qq.com^",
 		"||weixin.qq.com.cn^",
+		"||weixin.qq.com^",
 		"||weixinbridge.com^",
+		"||wx.qq.com^",
 	},
 }, {
 	name:  "viber",
 	rules: []string{"||viber.com^"},
 }, {
-	name:  "weibo",
-	rules: []string{"||weibo.com^", "||weibo.cn^", "||weibocdn.com^"},
+	name: "weibo",
+	rules: []string{
+		"||weibo.cn^",
+		"||weibo.com^",
+		"||weibocdn.com^",
+	},
 }, {
-	name:  "9gag",
-	rules: []string{"||9cache.com^", "||9gag.com^"},
+	name: "9gag",
+	rules: []string{
+		"||9cache.com^",
+		"||9gag.com^",
+	},
 }, {
-	name:  "telegram",
-	rules: []string{"||t.me^", "||telegram.me^", "||telegram.org^"},
+	name: "telegram",
+	rules: []string{
+		"||t.me^",
+		"||telegram.me^",
+		"||telegram.org^",
+	},
 }, {
 	name: "disneyplus",
 	rules: []string{
@@ -297,11 +332,11 @@ var serviceRulesArray = []svc{{
 }, {
 	name: "bilibili",
 	rules: []string{
-		"||bilibili.com^",
-		"||bilivideo.com^",
-		"||bilivideo.cn^",
-		"||biligame.com^",
 		"||biliapi.net^",
+		"||bilibili.com^",
+		"||biligame.com^",
+		"||bilivideo.cn^",
+		"||bilivideo.com^",
 		"||dreamcast.hk^",
 		"||hdslb.com^",
 	},

From 4a7b4d03a1d81062bb6e708b51c42e64ee23618a Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 18 Aug 2022 16:34:08 +0300
Subject: [PATCH 131/143] Pull request: 4846-migration-fix

Updates #4846.

Squashed commit of the following:

commit 22e2e89e5390c7b1486fb69064c55da40fc5c7e7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 18 16:25:07 2022 +0300

    home: fix yaml object type
---
 CHANGELOG.md             | 5 +++++
 internal/home/upgrade.go | 6 +++---
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b92e14c1..0f5c9319 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,8 +28,13 @@ and this project adheres to
 
 - Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
 
+### Fixed
+
+- Migrations from releases older than v0.107.7 failing ([#4846]).
+
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#4795]: https://github.com/AdguardTeam/AdGuardHome/issues/4795
+[#4846]: https://github.com/AdguardTeam/AdGuardHome/issues/4846
 
 
 
diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go
index 15970e7b..4426149c 100644
--- a/internal/home/upgrade.go
+++ b/internal/home/upgrade.go
@@ -27,7 +27,7 @@ const currentSchemaVersion = 14
 // These aliases are provided for convenience.
 type (
 	yarr = []any
-	yobj = map[any]any
+	yobj = map[string]any
 )
 
 // Performs necessary upgrade operations if needed
@@ -182,12 +182,12 @@ func upgradeSchema2to3(diskConf yobj) error {
 	newDNSConfig := make(yobj)
 
 	switch v := dnsConfig.(type) {
-	case map[any]any:
+	case yobj:
 		for k, v := range v {
 			newDNSConfig[fmt.Sprint(k)] = v
 		}
 	default:
-		return fmt.Errorf("dns configuration is not a map")
+		return fmt.Errorf("unexpected type of dns: %T", dnsConfig)
 	}
 
 	// Replace bootstrap_dns value filed with new array contains old bootstrap_dns inside

From 06e4658da9e9c31df369804d29f1165ec0fe7d63 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Thu, 18 Aug 2022 18:22:37 +0300
Subject: [PATCH 132/143] Pull request: upd-dnsproxy

Merge in DNS/adguard-home from upd-dnsproxy to master

Squashed commit of the following:

commit 3c5b683e96191b9cf0abf35229c3c665370d782e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 18 18:04:13 2022 +0300

    all: upd dnsproxy
---
 CHANGELOG.md |  4 ++++
 go.mod       | 12 ++++++------
 go.sum       | 23 ++++++++++++-----------
 3 files changed, 22 insertions(+), 17 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f5c9319..2ea28283 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,10 @@ and this project adheres to
 
 - Bilibili service blocking ([#4795]).
 
+### Changed
+
+- DNS-over-QUIC connections now use keptalive.
+
 ### Deprecated
 
 - Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
diff --git a/go.mod b/go.mod
index 53e5eb1b..018afe80 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
 go 1.18
 
 require (
-	github.com/AdguardTeam/dnsproxy v0.43.1
+	github.com/AdguardTeam/dnsproxy v0.44.0
 	github.com/AdguardTeam/golibs v0.10.9
 	github.com/AdguardTeam/urlfilter v0.16.0
 	github.com/NYTimes/gziphandler v1.1.1
@@ -28,10 +28,10 @@ require (
 	github.com/stretchr/testify v1.7.1
 	github.com/ti-mo/netfilter v0.4.0
 	go.etcd.io/bbolt v1.3.6
-	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
+	golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
-	golang.org/x/net v0.0.0-20220728211354-c7608f3a8462
-	golang.org/x/sys v0.0.0-20220731174439-a90be440212d
+	golang.org/x/net v0.0.0-20220812174116-3211cb980234
+	golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v3 v3.0.1
 	howett.net/plist v1.0.0
@@ -50,7 +50,7 @@ require (
 	github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
 	github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
 	github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
-	github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
+	github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
 	github.com/mdlayher/packet v1.0.0 // indirect
 	github.com/mdlayher/socket v0.2.3 // indirect
 	github.com/nxadm/tail v1.4.8 // indirect
@@ -60,7 +60,7 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/stretchr/objx v0.1.1 // indirect
 	github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
-	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 // indirect
 	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/tools v0.1.12 // indirect
diff --git a/go.sum b/go.sum
index afd170d2..240544dc 100644
--- a/go.sum
+++ b/go.sum
@@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/AdguardTeam/dnsproxy v0.43.1 h1:E777KfQAi+VurOoWEdGQ5iqjSOOAzzbTfLOEzj8heCs=
-github.com/AdguardTeam/dnsproxy v0.43.1/go.mod h1:JUGTm5dmlll47JltztsT0N//pVJjdg6zu0SNeUeaA7g=
+github.com/AdguardTeam/dnsproxy v0.44.0 h1:JzIxEXF4OyJq4wZVHeZkM1af4VfuwcgrUzjgdBGljsE=
+github.com/AdguardTeam/dnsproxy v0.44.0/go.mod h1:HsxYYW/bC8uo+4eX9pRW21hFD6gWZdrvcfBb1R6/AzU=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
@@ -150,8 +150,9 @@ github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR
 github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
 github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
 github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
-github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ=
 github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
+github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
+github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
@@ -278,8 +279,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
-golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
+golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
@@ -290,8 +291,8 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 h1:VtCrPQXM5Wo9l7XN64SjBMczl48j8mkP+2e3OhYlz+0=
+golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -326,8 +327,8 @@ golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I=
-golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
+golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -389,8 +390,8 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/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-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
+golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

From 64df882c5eeaf0455a27f8b1d5b190eea0129699 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Thu, 18 Aug 2022 18:39:53 +0300
Subject: [PATCH 133/143] all: fix abbreviation

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 316ffa5f..2a0fce39 100644
--- a/README.md
+++ b/README.md
@@ -345,7 +345,7 @@ Here's what you can also do to contribute:
 * [Prometheus exporter for AdGuard Home](https://github.com/ebrianne/adguard-exporter) by [@ebrianne](https://github.com/ebrianne)
 * [AdGuard Home on GLInet routers](https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) by [Gl-Inet](https://gl-inet.com/)
 * [Cloudron app](https://git.cloudron.io/cloudron/adguard-home-app) by [@gramakri](https://github.com/gramakri)
-* [Asuswrt-Merlin-AdGuardHome-Installer](https://github.com/jumpsmm7/Asuswrt-Merlin-AdGuardHome-Installer) by [@jumpsmm7](https://github.com/jumpsmm7) a.k.a [@SomeWhereOverTheRainBow](https://www.snbforums.com/members/somewhereovertherainbow.64179/)
+* [Asuswrt-Merlin-AdGuardHome-Installer](https://github.com/jumpsmm7/Asuswrt-Merlin-AdGuardHome-Installer) by [@jumpsmm7](https://github.com/jumpsmm7) aka [@SomeWhereOverTheRainBow](https://www.snbforums.com/members/somewhereovertherainbow.64179/)
 
 <a id="acknowledgments"></a>
 ## Acknowledgments

From 6856a80380f25b3e50e6b560e41a7f89258cf3dd Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Fri, 19 Aug 2022 16:57:56 +0300
Subject: [PATCH 134/143] Pull request #1573: all: upd chlog

Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit d6d55a9a35a8810c6b334d19ba9747fb2b3e7f82
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Aug 19 16:44:18 2022 +0300

    all: upd chlog
---
 CHANGELOG.md | 46 ++++++++++++++++++++++++++++------------------
 1 file changed, 28 insertions(+), 18 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ea28283..12118439 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,7 @@ and this project adheres to
 ## [Unreleased]
 
 <!--
-## [v0.108.0] - 2022-10-01 (APPROX.)
+## [v0.108.0] - 2022-12-01 (APPROX.)
 -->
 
 ### Security
@@ -20,35 +20,44 @@ and this project adheres to
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
 
+### Deprecated
+
+- Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
+
+[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+
+
+
+<!--
+## [v0.107.12] - 2022-09-28 (APPROX.)
+
+See also the [v0.107.12 GitHub milestone][ms-v0.107.12].
+
+[ms-v0.107.12]: https://github.com/AdguardTeam/AdGuardHome/milestone/47?closed=1
+-->
+
+
+
+## [v0.107.11] - 2022-08-19
+
+See also the [v0.107.11 GitHub milestone][ms-v0.107.11].
+
 ### Added
 
 - Bilibili service blocking ([#4795]).
 
 ### Changed
 
-- DNS-over-QUIC connections now use keptalive.
-
-### Deprecated
-
-- Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
+- DNS-over-QUIC connections now use keepalive.
 
 ### Fixed
 
 - Migrations from releases older than v0.107.7 failing ([#4846]).
 
-[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
 [#4795]: https://github.com/AdguardTeam/AdGuardHome/issues/4795
 [#4846]: https://github.com/AdguardTeam/AdGuardHome/issues/4846
 
-
-
-<!--
-## [v0.107.11] - 2022-09-28 (APPROX.)
-
-See also the [v0.107.11 GitHub milestone][ms-v0.107.11].
-
 [ms-v0.107.11]: https://github.com/AdguardTeam/AdGuardHome/milestone/47?closed=1
--->
 
 
 
@@ -1130,11 +1139,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
 
 
 <!--
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.11...HEAD
-[v0.107.11]:  https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...v0.107.11
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.12...HEAD
+[v0.107.12]:  https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.11...v0.107.12
 -->
 
-[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...HEAD
+[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.11...HEAD
+[v0.107.11]:  https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...v0.107.11
 [v0.107.10]:  https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...v0.107.10
 [v0.107.9]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...v0.107.9
 [v0.107.8]:   https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...v0.107.8

From 1afd73ad0b50037bdb58130c7a21e080de88ea7d Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Fri, 19 Aug 2022 17:36:51 +0300
Subject: [PATCH 135/143] Pull request #1572: 4640-imp-upstream-doc

Updates #4640.

Squashed commit of the following:

commit 764b024e7a5a5f6ea2b18b5e13fdc4fa38c49af2
Merge: 7bace870 6856a803
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Aug 19 17:17:44 2022 +0300

    Merge branch 'master' into 4640-imp-upstream-doc

commit 7bace870102633a2b8323c5f448ed38b65f4b482
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 18 19:49:07 2022 +0300

    all: imp upstream examples
---
 CHANGELOG.md                                  |   6 +-
 client/src/__locales/en.json                  |   2 +
 .../Settings/Dns/Upstream/Examples.js         | 204 +++++++++---------
 client/src/helpers/constants.js               |   2 +-
 openapi/v1.yaml                               |  11 +-
 scripts/make/Dockerfile                       |   3 +
 6 files changed, 113 insertions(+), 115 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12118439..56360e30 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,10 @@ and this project adheres to
 
 ### Deprecated
 
+- Ports 784 and 8853 for DNS-over-QUIC in Docker images.  Users who still serve
+  DoQ on these ports are encouraged to move to the standard port 853.  These
+  ports will be removed from the `EXPOSE` section of our `Dockerfile` in a
+  future release.
 - Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
 
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
@@ -48,7 +52,7 @@ See also the [v0.107.11 GitHub milestone][ms-v0.107.11].
 
 ### Changed
 
-- DNS-over-QUIC connections now use keepalive.
+- DNS-over-QUIC connections now use keptalive.
 
 ### Fixed
 
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index af748038..ca423562 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -211,12 +211,14 @@
     "example_comment_hash": "# Also a comment.",
     "example_regex_meaning": "block access to domains matching the specified regular expression.",
     "example_upstream_regular": "regular DNS (over UDP);",
+    "example_upstream_regular_port": "regular DNS (over UDP, with port);",
     "example_upstream_udp": "regular DNS (over UDP, hostname);",
     "example_upstream_dot": "encrypted <0>DNS-over-TLS</0>;",
     "example_upstream_doh": "encrypted <0>DNS-over-HTTPS</0>;",
     "example_upstream_doq": "encrypted <0>DNS-over-QUIC</0>;",
     "example_upstream_sdns": "<0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers;",
     "example_upstream_tcp": "regular DNS (over TCP);",
+    "example_upstream_tcp_port": "regular DNS (over TCP, with port);",
     "example_upstream_tcp_hostname": "regular DNS (over TCP, hostname);",
     "all_lists_up_to_date_toast": "All lists are already up-to-date",
     "updated_upstream_dns_toast": "Upstream servers successfully saved",
diff --git a/client/src/components/Settings/Dns/Upstream/Examples.js b/client/src/components/Settings/Dns/Upstream/Examples.js
index 81d171d3..c17e9456 100644
--- a/client/src/components/Settings/Dns/Upstream/Examples.js
+++ b/client/src/components/Settings/Dns/Upstream/Examples.js
@@ -8,133 +8,123 @@ const Examples = (props) => (
         <Trans>examples_title</Trans>:
         <ol className="leading-loose">
             <li>
-                <code>94.140.14.140</code>: {props.t('example_upstream_regular')}
+                <code>94.140.14.140</code>, <code>2a10:50c0::1:ff</code>: {props.t('example_upstream_regular')}
+            </li>
+            <li>
+                <code>94.140.14.140:53</code>, <code>[2a10:50c0::1:ff]:53</code>: {props.t('example_upstream_regular_port')}
             </li>
             <li>
                 <code>udp://unfiltered.adguard-dns.com</code>: <Trans>example_upstream_udp</Trans>
             </li>
             <li>
-                <code>tcp://94.140.14.140</code>: <Trans>example_upstream_tcp</Trans>
+                <code>tcp://94.140.14.140</code>, <code>tcp://[2a10:50c0::1:ff]</code>: <Trans>example_upstream_tcp</Trans>
+            </li>
+            <li>
+                <code>tcp://94.140.14.140:53</code>, <code>tcp://[2a10:50c0::1:ff]:53</code>: <Trans>example_upstream_tcp_port</Trans>
             </li>
             <li>
                 <code>tcp://unfiltered.adguard-dns.com</code>: <Trans>example_upstream_tcp_hostname</Trans>
             </li>
             <li>
-                <code>tls://unfiltered.adguard-dns.com</code>:
-                <span>
-                    <Trans
-                        components={[
-                            <a
-                                href="https://en.wikipedia.org/wiki/DNS_over_TLS"
-                                target="_blank"
-                                rel="noopener noreferrer"
-                                key="0"
-                            >
-                                DNS-over-TLS
-                            </a>,
-                        ]}
-                    >
-                        example_upstream_dot
-                    </Trans>
-                </span>
+                <code>tls://unfiltered.adguard-dns.com</code>: <Trans
+                    components={[
+                        <a
+                            href="https://en.wikipedia.org/wiki/DNS_over_TLS"
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            key="0"
+                        >
+                            DNS-over-TLS
+                        </a>,
+                    ]}
+                >
+                    example_upstream_dot
+                </Trans>
             </li>
             <li>
-                <code>https://unfiltered.adguard-dns.com/dns-query</code>:
-                <span>
-                    <Trans
-                        components={[
-                            <a
-                                href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
-                                target="_blank"
-                                rel="noopener noreferrer"
-                                key="0"
-                            >
-                                DNS-over-HTTPS
-                            </a>,
-                        ]}
-                    >
-                        example_upstream_doh
-                    </Trans>
-                </span>
+                <code>https://unfiltered.adguard-dns.com/dns-query</code>: <Trans
+                    components={[
+                        <a
+                            href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            key="0"
+                        >
+                            DNS-over-HTTPS
+                        </a>,
+                    ]}
+                >
+                    example_upstream_doh
+                </Trans>
             </li>
             <li>
-                <code>quic://unfiltered.adguard-dns.com:784</code>:
-                <span>
-                    <Trans
-                        components={[
-                            <a
-                                href="https://datatracker.ietf.org/doc/html/rfc9250"
-                                target="_blank"
-                                rel="noopener noreferrer"
-                                key="0"
-                            >
-                                DNS-over-QUIC
-                            </a>,
-                        ]}
-                    >
-                        example_upstream_doq
-                    </Trans>
-                </span>
+                <code>quic://unfiltered.adguard-dns.com</code>: <Trans
+                    components={[
+                        <a
+                            href="https://datatracker.ietf.org/doc/html/rfc9250"
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            key="0"
+                        >
+                            DNS-over-QUIC
+                        </a>,
+                    ]}
+                >
+                    example_upstream_doq
+                </Trans>
             </li>
             <li>
-                <code>sdns://...</code>:
-                <span>
-                    <Trans
-                        components={[
-                            <a
-                                href="https://dnscrypt.info/stamps/"
-                                target="_blank"
-                                rel="noopener noreferrer"
-                                key="0"
-                            >
-                                DNS Stamps
-                            </a>,
-                            <a
-                                href="https://dnscrypt.info/"
-                                target="_blank"
-                                rel="noopener noreferrer"
-                                key="1"
-                            >
-                                DNSCrypt
-                            </a>,
-                            <a
-                                href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
-                                target="_blank"
-                                rel="noopener noreferrer"
-                                key="2"
-                            >
-                                DNS-over-HTTPS
-                            </a>,
-                        ]}
-                    >
-                        example_upstream_sdns
-                    </Trans>
-                </span>
+                <code>sdns://...</code>: <Trans
+                    components={[
+                        <a
+                            href="https://dnscrypt.info/stamps/"
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            key="0"
+                        >
+                            DNS Stamps
+                        </a>,
+                        <a
+                            href="https://dnscrypt.info/"
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            key="1"
+                        >
+                            DNSCrypt
+                        </a>,
+                        <a
+                            href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            key="2"
+                        >
+                            DNS-over-HTTPS
+                        </a>,
+                    ]}
+                >
+                    example_upstream_sdns
+                </Trans>
             </li>
             <li>
-                <code>[/example.local/]94.140.14.140</code>:
-                <span>
-                    <Trans
-                        components={[
-                            <a
-                                href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams-for-domains"
-                                target="_blank"
-                                rel="noopener noreferrer"
-                                key="0"
-                            >
-                                Link
-                            </a>,
-                        ]}
-                    >
-                        example_upstream_reserved
-                    </Trans>
-                </span>
+                <code>[/example.local/]94.140.14.140</code>: <Trans
+                    components={[
+                        <a
+                            href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams-for-domains"
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            key="0"
+                        >
+                            Link
+                        </a>,
+                    ]}
+                >
+                    example_upstream_reserved
+                </Trans>
             </li>
             <li>
-                <code>{COMMENT_LINE_DEFAULT_TOKEN} comment</code>:
-                <span>
-                    <Trans>example_upstream_comment</Trans>
-                </span>
+                <code>{COMMENT_LINE_DEFAULT_TOKEN} comment</code>: <Trans>
+                    example_upstream_comment
+                </Trans>
             </li>
         </ol>
     </div>
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index a5c1d2ee..935e3655 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -81,7 +81,7 @@ export const STANDARD_DNS_PORT = 53;
 export const STANDARD_WEB_PORT = 80;
 export const STANDARD_HTTPS_PORT = 443;
 export const DNS_OVER_TLS_PORT = 853;
-export const DNS_OVER_QUIC_PORT = 784;
+export const DNS_OVER_QUIC_PORT = 853;
 export const MAX_PORT = 65535;
 
 export const EMPTY_DATE = '0001-01-01T00:00:00Z';
diff --git a/openapi/v1.yaml b/openapi/v1.yaml
index bdf49383..77eb1a09 100644
--- a/openapi/v1.yaml
+++ b/openapi/v1.yaml
@@ -4405,7 +4405,7 @@
         Validatable TLS settings.
       'example':
         'certificate_path': '/etc/ssl/example.com.cert'
-        'port_dns_over_quic': 784
+        'port_dns_over_quic': 853
         'port_dns_over_tls': 853
         'port_https': 443
         'private_key_path': '/etc/ssl/example.com.key'
@@ -4427,7 +4427,7 @@
             sent.
           'type': 'string'
         'port_dns_over_quic':
-          'default': 784
+          'default': 853
           'description': >
             The DNS-over-QUIC port.  If `0`, DNS-over-QUIC is disabled.
           'format': 'int64'
@@ -4865,7 +4865,7 @@
         'example':
           'certificate_path': '/etc/ssl/example.com.cert'
           'enabled': true
-          'port_dns_over_quic': 784
+          'port_dns_over_quic': 853
           'port_dns_over_tls': 853
           'port_https': 443
           'private_key_path': '/etc/ssl/example.com.key'
@@ -4907,7 +4907,7 @@
             DNS-over-TLS and other protocols.
           'type': 'boolean'
         'port_dns_over_quic':
-          'default': 784
+          'default': 853
           'description': >
             The DNS-over-QUIC port.  If `0`, DNS-over-QUIC is disabled.
           'format': 'int64'
@@ -5002,8 +5002,7 @@
          *  `https://unfiltered.adguard-dns.com/dns-query`: encrypted
              DNS-over-HTTPS.
 
-         *  `quic://unfiltered.adguard-dns.com:784`: encrypted DNS-over-QUIC
-             (experimental).
+         *  `quic://unfiltered.adguard-dns.com`: encrypted DNS-over-QUIC.
 
          *  `tcp://94.140.14.140`: plain DNS-over-TCP.
 
diff --git a/scripts/make/Dockerfile b/scripts/make/Dockerfile
index b87ee360..a940a155 100644
--- a/scripts/make/Dockerfile
+++ b/scripts/make/Dockerfile
@@ -48,6 +48,9 @@ RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
 # 5443   : TCP, UDP : DNSCrypt (alt)
 # 6060   : TCP      : HTTP (pprof)
 # 8853   :      UDP : DNS-over-QUIC (experimental)
+#
+# TODO(a.garipov): Remove the old, non-standard 784 and 8853 ports for
+# DNS-over-QUIC in a future release.
 EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 443/udp 784/udp\
 	853/tcp 853/udp 3000/tcp 3000/udp 3001/tcp 3001/udp 5443/tcp\
 	5443/udp 6060/tcp 8853/udp

From 1a1a48482a9ea943a34b8bafea34f8281325ef4e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ya=C4=9F=C4=B1zhan?= <yagizhan49@protonmail.com>
Date: Fri, 19 Aug 2022 18:18:17 +0300
Subject: [PATCH 136/143] Added some domains

---
 internal/filtering/blocked.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index 665b62d4..b8fa7e5a 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -22,6 +22,7 @@ type svc struct {
 var serviceRulesArray = []svc{{
 	name: "whatsapp",
 	rules: []string{
+		"||wa.me^",
 		"||whatsapp.com^",
 		"||whatsapp.net^",
 	},
@@ -34,7 +35,9 @@ var serviceRulesArray = []svc{{
 		"||accountkit.com^",
 		"||fb.me^",
 		"||fb.com^",
+		"||fb.gg^",
 		"||fbsbx.com^",
+		"||fbwat.ch^",
 		"||messenger.com^",
 		"||facebookcorewwwi.onion^",
 		"||fbcdn.com^",
@@ -58,6 +61,7 @@ var serviceRulesArray = []svc{{
 		"||youtube-nocookie.com^",
 		"||youtube.com^",
 		"||youtubei.googleapis.com^",
+		"||youtubekids.com^",
 		"||ytimg.com^",
 	},
 }, {
@@ -97,6 +101,7 @@ var serviceRulesArray = []svc{{
 		"||discordapp.net^",
 		"||discordapp.com^",
 		"||discord.com^",
+		"||discord.gift",
 		"||discord.media^",
 	},
 }, {
@@ -174,6 +179,7 @@ var serviceRulesArray = []svc{{
 		"||amazon.com.br^",
 		"||amazon.co.jp^",
 		"||amazon.com.mx^",
+		"||amazon.com.tr^",
 		"||amazon.co.uk^",
 		"||createspace.com^",
 		"||aws",
@@ -223,7 +229,9 @@ var serviceRulesArray = []svc{{
 		"||toutiaocloud.net^",
 		"||bdurl.com^",
 		"||bytecdn.cn^",
+		"||bytedapm.com^",
 		"||byteimg.com^",
+		"||byteoversea.com^",
 		"||ixigua.com^",
 		"||muscdn.com^",
 		"||bytedance.map.fastly.net^",

From 970b6cf6987cfadd108ae0db2cfa6747e345b9c1 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 22 Aug 2022 14:21:41 +0300
Subject: [PATCH 137/143] Pull request: 4850 stats: imp logging

Merge in DNS/adguard-home from 4850-imp-stats-logging to master

Updates #4850.

Squashed commit of the following:

commit 3c1ee8dd794fab2b604a0e710a513f75273ed417
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 22 14:17:56 2022 +0300

    all: imp chlog

commit 0c7adc72740114eb7ae0105199ccbdbfabf8f9fe
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 22 14:12:01 2022 +0300

    stats: fix err check

commit d14a5cabecba75e9f0d401e61994d0efd2b324ff
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 22 14:09:15 2022 +0300

    stats: imp logging again

commit 34fc6663484924466171f46dc320382cf02f360b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 22 12:49:43 2022 +0300

    stats: imp code, logging

commit 09aa857a5e449e62c8c870b7eb5c5ce744d78ae7
Merge: 09a732af eccfbf6a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Aug 19 19:43:45 2022 +0300

    Merge branch 'master' into 4850-imp-stats-logging

commit 09a732afdc9b6dad4439be83aab7df72c5d68dac
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Aug 19 19:38:51 2022 +0300

    stats: imp logging
---
 CHANGELOG.md            |  5 +++++
 internal/stats/stats.go | 30 +++++++++++++++++-------------
 2 files changed, 22 insertions(+), 13 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56360e30..65cc777c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,7 +28,12 @@ and this project adheres to
   future release.
 - Go 1.18 support.  v0.109.0 will require at least Go 1.19 to build.
 
+### Fixed
+
+- Unnecessary logging of non-critical statistics errors ([#4850]).
+
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+[#4850]: https://github.com/AdguardTeam/AdGuardHome/issues/4850
 
 
 
diff --git a/internal/stats/stats.go b/internal/stats/stats.go
index e483dbba..aea6d92d 100644
--- a/internal/stats/stats.go
+++ b/internal/stats/stats.go
@@ -385,35 +385,39 @@ func (s *StatsCtx) flush() (cont bool, sleepFor time.Duration) {
 		return true, 0
 	}
 
+	isCommitable := true
 	tx, err := db.Begin(true)
 	if err != nil {
 		log.Error("stats: opening transaction: %s", err)
 
 		return true, 0
 	}
+	defer func() {
+		if err = finishTxn(tx, isCommitable); err != nil {
+			log.Error("stats: %s", err)
+		}
+	}()
 
 	s.curr = newUnit(id)
-	isCommitable := true
 
-	ferr := ptr.serialize().flushUnitToDB(tx, ptr.id)
-	if ferr != nil {
-		log.Error("stats: flushing unit: %s", ferr)
+	flushErr := ptr.serialize().flushUnitToDB(tx, ptr.id)
+	if flushErr != nil {
+		log.Error("stats: flushing unit: %s", flushErr)
 		isCommitable = false
 	}
 
-	derr := tx.DeleteBucket(idToUnitName(id - limit))
-	if derr != nil {
-		log.Error("stats: deleting unit: %s", derr)
-		if !errors.Is(derr, bbolt.ErrBucketNotFound) {
+	delErr := tx.DeleteBucket(idToUnitName(id - limit))
+	if delErr != nil {
+		// TODO(e.burkov):  Improve the algorithm of deleting the oldest bucket
+		// to avoid the error.
+		if errors.Is(delErr, bbolt.ErrBucketNotFound) {
+			log.Debug("stats: warning: deleting unit: %s", delErr)
+		} else {
 			isCommitable = false
+			log.Error("stats: deleting unit: %s", delErr)
 		}
 	}
 
-	err = finishTxn(tx, isCommitable)
-	if err != nil {
-		log.Error("stats: %s", err)
-	}
-
 	return true, 0
 }
 

From 307654f648ede191966b5efca6556eb80f554d12 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 23 Aug 2022 14:19:57 +0300
Subject: [PATCH 138/143] Pull request: 4625-imp-skype-blocking

Updates #4625.

Squashed commit of the following:

commit 2d49d9f9a339aeceeb64b5dcd03656e5a273d4c4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 22 20:52:46 2022 +0300

    filtering: add skype rules
---
 internal/filtering/blocked.go    | 27 +++++++++++++++++++++------
 scripts/translations/download.js |  2 +-
 2 files changed, 22 insertions(+), 7 deletions(-)

diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index b8fa7e5a..bbf525e1 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -108,14 +108,29 @@ var serviceRulesArray = []svc{{
 	name:  "ok",
 	rules: []string{"||ok.ru^"},
 }, {
-	name:  "skype",
-	rules: []string{"||skype.com^", "||skypeassets.com^"},
+	name: "skype",
+	rules: []string{
+		"||edge-skype-com.s-0001.s-msedge.net^",
+		"||skype-edf.akadns.net^",
+		"||skype.com^",
+		"||skypeassets.com^",
+		"||skypedata.akadns.net^",
+	},
 }, {
-	name:  "vk",
-	rules: []string{"||vk.com^", "||userapi.com^", "||vk-cdn.net^", "||vkuservideo.net^"},
+	name: "vk",
+	rules: []string{
+		"||userapi.com^",
+		"||vk-cdn.net^",
+		"||vk.com^",
+		"||vkuservideo.net^",
+	},
 }, {
-	name:  "origin",
-	rules: []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"},
+	name: "origin",
+	rules: []string{
+		"||accounts.ea.com^",
+		"||origin.com^",
+		"||signin.ea.com^",
+	},
 }, {
 	name: "steam",
 	rules: []string{
diff --git a/scripts/translations/download.js b/scripts/translations/download.js
index c511b56f..0748fff8 100644
--- a/scripts/translations/download.js
+++ b/scripts/translations/download.js
@@ -107,7 +107,7 @@ const download = async () => {
 
         // Don't request the Crowdin API too aggressively to prevent spurious
         // 400 errors.
-        await sleep(200);
+        await sleep(300);
     }
 
     Promise

From bdcf345155207a25e48872175de9ddcbbc44af62 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Tue, 23 Aug 2022 18:22:49 +0300
Subject: [PATCH 139/143] Pull request: 4745 Fix DHCP hostnames

Merge in DNS/adguard-home from 4745-fix-dhcp-hostnames to master

Closes #4745.

Squashed commit of the following:

commit fe03c8eda6c8ee35a10eb5f5a8e4d4d0c7373246
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 23 18:16:16 2022 +0300

    dhcpd: imp code, naming

commit 7a7129268917d99ba16781b7f2e9bfb7ae84ff3e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 23 18:10:12 2022 +0300

    dhcpd: add tests

commit bb14a4a62df1eed6492d30f622c3e22da9a6f4be
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 23 14:58:29 2022 +0300

    dhcpd: imp code, docs

commit 2ada39f994cb9dbb2208d47a588eb72056bb5306
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 23 14:44:35 2022 +0300

    all: log changes

commit cbd3ed254865921be09376097dac9f5926b2349a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 23 14:40:54 2022 +0300

    dhcpd: imp option 81

commit 64dabb52560f5edc08f17aadaa43172a5d74463d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 23 14:10:15 2022 +0300

    dhcpd: fix empty hostname in static lease

commit 0df5d10d0d94863b9bbab28129bcc3436fb71222
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 23 13:34:31 2022 +0300

    dhcpd: report dupl hostnames of static lease
---
 CHANGELOG.md              |   2 +
 internal/dhcpd/v4.go      | 123 ++++++++++++++------------------
 internal/dhcpd/v4_test.go | 146 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 201 insertions(+), 70 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 65cc777c..b5edd91b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,9 +30,11 @@ and this project adheres to
 
 ### Fixed
 
+- Dynamic leases created with empty hostnames ([#4745]).
 - Unnecessary logging of non-critical statistics errors ([#4850]).
 
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+[#4745]: https://github.com/AdguardTeam/AdGuardHome/issues/4745
 [#4850]: https://github.com/AdguardTeam/AdGuardHome/issues/4850
 
 
diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go
index 17881d2c..4fb85552 100644
--- a/internal/dhcpd/v4.go
+++ b/internal/dhcpd/v4.go
@@ -20,6 +20,7 @@ import (
 	"github.com/go-ping/ping"
 	"github.com/insomniacslk/dhcp/dhcpv4"
 	"github.com/insomniacslk/dhcp/dhcpv4/server4"
+	"golang.org/x/exp/slices"
 
 	//lint:ignore SA1019 See the TODO in go.mod.
 	"github.com/mdlayher/raw"
@@ -93,6 +94,9 @@ func (s *v4Server) validHostnameForClient(cliHostname string, ip net.IP) (hostna
 
 	if hostname == "" {
 		hostname = aghnet.GenerateHostname(ip)
+	} else if s.leaseHosts.Has(hostname) {
+		log.Info("dhcpv4: hostname %q already exists", hostname)
+		hostname = aghnet.GenerateHostname(ip)
 	}
 
 	err = netutil.ValidateDomainName(hostname)
@@ -252,11 +256,11 @@ func (s *v4Server) rmLeaseByIndex(i int) {
 // Remove a dynamic lease with the same properties
 // Return error if a static lease is found
 func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
-	for i := 0; i < len(s.leases); i++ {
-		l := s.leases[i]
+	for i, l := range s.leases {
+		isStatic := l.IsStatic()
 
-		if bytes.Equal(l.HWAddr, lease.HWAddr) {
-			if l.IsStatic() {
+		if bytes.Equal(l.HWAddr, lease.HWAddr) || l.IP.Equal(lease.IP) {
+			if isStatic {
 				return errors.Error("static lease already exists")
 			}
 
@@ -268,20 +272,7 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
 			l = s.leases[i]
 		}
 
-		if l.IP.Equal(lease.IP) {
-			if l.IsStatic() {
-				return errors.Error("static lease already exists")
-			}
-
-			s.rmLeaseByIndex(i)
-			if i == len(s.leases) {
-				break
-			}
-
-			l = s.leases[i]
-		}
-
-		if l.Hostname == lease.Hostname {
+		if !isStatic && l.Hostname == lease.Hostname {
 			l.Hostname = ""
 		}
 	}
@@ -289,6 +280,10 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
 	return nil
 }
 
+// ErrDupHostname is returned by addLease when the added lease has a not empty
+// non-unique hostname.
+const ErrDupHostname = errors.Error("hostname is not unique")
+
 // addLease adds a dynamic or static lease.
 func (s *v4Server) addLease(l *Lease) (err error) {
 	r := s.conf.ipRange
@@ -304,13 +299,17 @@ func (s *v4Server) addLease(l *Lease) (err error) {
 		return fmt.Errorf("lease %s (%s) out of range, not adding", l.IP, l.HWAddr)
 	}
 
-	s.leases = append(s.leases, l)
-	s.leasedOffsets.set(offset, true)
-
 	if l.Hostname != "" {
+		if s.leaseHosts.Has(l.Hostname) {
+			return ErrDupHostname
+		}
+
 		s.leaseHosts.Add(l.Hostname)
 	}
 
+	s.leases = append(s.leases, l)
+	s.leasedOffsets.set(offset, true)
+
 	return nil
 }
 
@@ -365,10 +364,11 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
 			return fmt.Errorf("validating hostname: %w", err)
 		}
 
-		// Don't check for hostname uniqueness, since we try to emulate
-		// dnsmasq here, which means that rmDynamicLease below will
-		// simply empty the hostname of the dynamic lease if there even
-		// is one.
+		// Don't check for hostname uniqueness, since we try to emulate dnsmasq
+		// here, which means that rmDynamicLease below will simply empty the
+		// hostname of the dynamic lease if there even is one.  In case a static
+		// lease with the same name already exists, addLease will return an
+		// error and the lease won't be added.
 
 		l.Hostname = hostname
 	}
@@ -523,11 +523,7 @@ func (s *v4Server) findExpiredLease() int {
 // reserveLease reserves a lease for a client by its MAC-address.  It returns
 // nil if it couldn't allocate a new lease.
 func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) {
-	l = &Lease{
-		HWAddr: make([]byte, len(mac)),
-	}
-
-	copy(l.HWAddr, mac)
+	l = &Lease{HWAddr: slices.Clone(mac)}
 
 	l.IP = s.nextIP()
 	if l.IP == nil {
@@ -620,33 +616,25 @@ func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err erro
 	return l, nil
 }
 
-type optFQDN struct {
-	name string
-}
+// OptionFQDN returns a DHCPv4 option for sending the FQDN to the client
+// requested another hostname.
+//
+// See https://datatracker.ietf.org/doc/html/rfc4702.
+func OptionFQDN(fqdn string) (opt dhcpv4.Option) {
+	optData := []byte{
+		// Set only S and O DHCP client FQDN option flags.
+		//
+		// See https://datatracker.ietf.org/doc/html/rfc4702#section-2.1.
+		1<<0 | 1<<1,
+		// The RCODE fields should be set to 0xFF in the server responses.
+		//
+		// See https://datatracker.ietf.org/doc/html/rfc4702#section-2.2.
+		0xFF,
+		0xFF,
+	}
+	optData = append(optData, fqdn...)
 
-func (o *optFQDN) String() string {
-	return "optFQDN"
-}
-
-// flags[1]
-// A-RR[1]
-// PTR-RR[1]
-// name[]
-func (o *optFQDN) ToBytes() []byte {
-	b := make([]byte, 3+len(o.name))
-	i := 0
-
-	b[i] = 0x03 // f_server_overrides | f_server
-	i++
-
-	b[i] = 255 // A-RR
-	i++
-
-	b[i] = 255 // PTR-RR
-	i++
-
-	copy(b[i:], []byte(o.name))
-	return b
+	return dhcpv4.OptGeneric(dhcpv4.OptionFQDN, optData)
 }
 
 // checkLease checks if the pair of mac and ip is already leased.  The mismatch
@@ -679,6 +667,8 @@ func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (lease *Lease, mi
 // processRequest is the handler for the DHCP Request request.
 func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) {
 	mac := req.ClientHWAddr
+	// TODO(e.burkov):  The IP address can only be requested in DHCPDISCOVER
+	// message.
 	reqIP := req.RequestedIPAddress()
 	if reqIP == nil {
 		reqIP = req.ClientIPAddr
@@ -711,24 +701,17 @@ func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needs
 	if !lease.IsStatic() {
 		cliHostname := req.HostName()
 		hostname := s.validHostnameForClient(cliHostname, reqIP)
-		if hostname != lease.Hostname && s.leaseHosts.Has(hostname) {
-			log.Info("dhcpv4: hostname %q already exists", hostname)
-			lease.Hostname = ""
-		} else {
+		if lease.Hostname != hostname {
 			lease.Hostname = hostname
+			resp.UpdateOption(dhcpv4.OptHostName(hostname))
 		}
 
 		s.commitLease(lease)
 	} else if lease.Hostname != "" {
-		o := &optFQDN{
-			name: lease.Hostname,
-		}
-		fqdn := dhcpv4.Option{
-			Code:  dhcpv4.OptionFQDN,
-			Value: o,
-		}
-
-		resp.UpdateOption(fqdn)
+		// TODO(e.burkov):  This option is used to update the server's DNS
+		// mapping.  The option should only be answered when it has been
+		// requested.
+		resp.UpdateOption(OptionFQDN(lease.Hostname))
 	}
 
 	resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
@@ -851,7 +834,7 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
 
 	// TODO(a.garipov): Refactor this into handlers.
 	var l *Lease
-	switch req.MessageType() {
+	switch mt := req.MessageType(); mt {
 	case dhcpv4.MessageTypeDiscover:
 		l, err = s.processDiscover(req, resp)
 		if err != nil {
diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go
index 3c241a4a..e720d113 100644
--- a/internal/dhcpd/v4_test.go
+++ b/internal/dhcpd/v4_test.go
@@ -8,7 +8,9 @@ import (
 	"net"
 	"strings"
 	"testing"
+	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
 	"github.com/AdguardTeam/golibs/stringutil"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/insomniacslk/dhcp/dhcpv4"
@@ -23,6 +25,7 @@ var (
 	DefaultRangeStart = net.IP{192, 168, 10, 100}
 	DefaultRangeEnd   = net.IP{192, 168, 10, 200}
 	DefaultGatewayIP  = net.IP{192, 168, 10, 1}
+	DefaultSelfIP     = net.IP{192, 168, 10, 2}
 	DefaultSubnetMask = net.IP{255, 255, 255, 0}
 )
 
@@ -39,6 +42,7 @@ func defaultV4ServerConf() (conf V4ServerConf) {
 		GatewayIP:  DefaultGatewayIP,
 		SubnetMask: DefaultSubnetMask,
 		notify:     notify4,
+		dnsIPAddrs: []net.IP{DefaultSelfIP},
 	}
 }
 
@@ -54,6 +58,148 @@ func defaultSrv(t *testing.T) (s DHCPServer) {
 	return s
 }
 
+func TestV4Server_leasing(t *testing.T) {
+	const (
+		staticName  = "static-client"
+		anotherName = "another-client"
+	)
+
+	staticIP := net.IP{192, 168, 10, 10}
+	anotherIP := DefaultRangeStart
+	staticMAC := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
+	anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB}
+
+	s := defaultSrv(t)
+
+	t.Run("add_static", func(t *testing.T) {
+		err := s.AddStaticLease(&Lease{
+			Expiry:   time.Unix(leaseExpireStatic, 0),
+			Hostname: staticName,
+			HWAddr:   staticMAC,
+			IP:       staticIP,
+		})
+		require.NoError(t, err)
+
+		t.Run("same_name", func(t *testing.T) {
+			err = s.AddStaticLease(&Lease{
+				Expiry:   time.Unix(leaseExpireStatic, 0),
+				Hostname: staticName,
+				HWAddr:   anotherMAC,
+				IP:       anotherIP,
+			})
+			assert.ErrorIs(t, err, ErrDupHostname)
+		})
+
+		t.Run("same_mac", func(t *testing.T) {
+			wantErrMsg := "dhcpv4: adding static lease: removing " +
+				"dynamic leases for " + anotherIP.String() +
+				" (" + staticMAC.String() + "): static lease already exists"
+
+			err = s.AddStaticLease(&Lease{
+				Expiry:   time.Unix(leaseExpireStatic, 0),
+				Hostname: anotherName,
+				HWAddr:   staticMAC,
+				IP:       anotherIP,
+			})
+			testutil.AssertErrorMsg(t, wantErrMsg, err)
+		})
+
+		t.Run("same_ip", func(t *testing.T) {
+			wantErrMsg := "dhcpv4: adding static lease: removing " +
+				"dynamic leases for " + staticIP.String() +
+				" (" + anotherMAC.String() + "): static lease already exists"
+
+			err = s.AddStaticLease(&Lease{
+				Expiry:   time.Unix(leaseExpireStatic, 0),
+				Hostname: anotherName,
+				HWAddr:   anotherMAC,
+				IP:       staticIP,
+			})
+			testutil.AssertErrorMsg(t, wantErrMsg, err)
+		})
+	})
+
+	t.Run("add_dynamic", func(t *testing.T) {
+		s4, ok := s.(*v4Server)
+		require.True(t, ok)
+
+		discoverAnOffer := func(
+			t *testing.T,
+			name string,
+			ip net.IP,
+			mac net.HardwareAddr,
+		) (resp *dhcpv4.DHCPv4) {
+			testutil.CleanupAndRequireSuccess(t, func() (err error) {
+				return s.ResetLeases(s.GetLeases(LeasesStatic))
+			})
+
+			req, err := dhcpv4.NewDiscovery(
+				mac,
+				dhcpv4.WithOption(dhcpv4.OptHostName(name)),
+				dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(ip)),
+				dhcpv4.WithOption(dhcpv4.OptClientIdentifier([]byte{1, 2, 3, 4, 5, 6, 8})),
+				dhcpv4.WithGatewayIP(DefaultGatewayIP),
+			)
+			require.NoError(t, err)
+
+			resp = &dhcpv4.DHCPv4{}
+			res := s4.process(req, resp)
+			require.Positive(t, res)
+			require.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
+
+			resp.ClientHWAddr = mac
+
+			return resp
+		}
+
+		t.Run("same_name", func(t *testing.T) {
+			resp := discoverAnOffer(t, staticName, anotherIP, anotherMAC)
+
+			req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
+				dhcpv4.OptHostName(staticName),
+			))
+			require.NoError(t, err)
+
+			res := s4.process(req, resp)
+			require.Positive(t, res)
+
+			assert.Equal(t, aghnet.GenerateHostname(resp.YourIPAddr), resp.HostName())
+		})
+
+		t.Run("same_mac", func(t *testing.T) {
+			resp := discoverAnOffer(t, anotherName, anotherIP, staticMAC)
+
+			req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
+				dhcpv4.OptHostName(anotherName),
+			))
+			require.NoError(t, err)
+
+			res := s4.process(req, resp)
+			require.Positive(t, res)
+
+			fqdnOptData := resp.Options.Get(dhcpv4.OptionFQDN)
+			require.Len(t, fqdnOptData, 3+len(staticName))
+			assert.Equal(t, []uint8(staticName), fqdnOptData[3:])
+
+			assert.Equal(t, staticIP, resp.YourIPAddr)
+		})
+
+		t.Run("same_ip", func(t *testing.T) {
+			resp := discoverAnOffer(t, anotherName, staticIP, anotherMAC)
+
+			req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
+				dhcpv4.OptHostName(anotherName),
+			))
+			require.NoError(t, err)
+
+			res := s4.process(req, resp)
+			require.Positive(t, res)
+
+			assert.NotEqual(t, staticIP, resp.YourIPAddr)
+		})
+	})
+}
+
 func TestV4Server_AddRemove_static(t *testing.T) {
 	s := defaultSrv(t)
 

From e35eeacd742959c5a2942c97a2fbd48c31da1809 Mon Sep 17 00:00:00 2001
From: scripthunter7 <57285466+scripthunter7@users.noreply.github.com>
Date: Wed, 24 Aug 2022 13:43:34 +0200
Subject: [PATCH 140/143] Fix
 https://github.com/AdguardTeam/AdGuardHome/issues/4366 Color scheme based
 logo

---
 README.md                      |  5 ++-
 doc/adguard_home_darkmode.svg  | 56 ++++++++++++++++++++++++++++++++++
 doc/adguard_home_lightmode.svg | 55 +++++++++++++++++++++++++++++++++
 3 files changed, 115 insertions(+), 1 deletion(-)
 create mode 100644 doc/adguard_home_darkmode.svg
 create mode 100644 doc/adguard_home_lightmode.svg

diff --git a/README.md b/README.md
index cac993d0..305b3f1b 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
 &nbsp;
 <p align="center">
-  <img src="https://cdn.adtidy.org/public/Adguard/Common/adguard_home.svg" width="300px" alt="AdGuard Home" />
+  <picture>
+    <source media="(prefers-color-scheme: dark)" srcset="doc/adguard_home_darkmode.svg">
+    <img alt="AdGuard Home" src="doc/adguard_home_lightmode.svg" width="300px">
+  </picture>
 </p>
 <h3 align="center">Privacy protection center for you and your devices</h3>
 <p align="center">
diff --git a/doc/adguard_home_darkmode.svg b/doc/adguard_home_darkmode.svg
new file mode 100644
index 00000000..419134c4
--- /dev/null
+++ b/doc/adguard_home_darkmode.svg
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 374 93.5" style="enable-background:new 0 0 374 93.5;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#D8D8D8;}
+	.st1{fill:#68BC71;}
+	.st2{fill:#67B279;}
+	.st3{fill:#FFFFFF;}
+</style>
+<g>
+	<path class="st0" d="M296.4,50.1l-2.7-6.7h-13.2l-2.5,6.7h-9l14.3-34.2h8l14.2,34.2H296.4z M287.3,25l-4.3,11.7h8.6L287.3,25z
+		 M266.8,36.6c0,2-0.3,3.8-1,5.5c-0.7,1.6-1.7,3.1-3,4.2c-1.3,1.2-2.9,2.1-4.7,2.8c-1.8,0.7-3.9,1-6.1,1c-2.3,0-4.3-0.3-6.2-1
+		c-1.8-0.7-3.4-1.6-4.6-2.8c-1.3-1.2-2.3-2.6-2.9-4.2c-0.7-1.6-1-3.5-1-5.5V15.9h8.3V36c0,0.9,0.1,1.8,0.4,2.6
+		c0.2,0.8,0.6,1.5,1.1,2.2c0.5,0.6,1.2,1.2,2,1.5c0.8,0.4,1.8,0.6,3,0.6s2.2-0.2,3-0.6c0.8-0.4,1.5-0.9,2-1.5
+		c0.5-0.6,0.9-1.4,1.1-2.2c0.2-0.8,0.3-1.7,0.3-2.6V15.9h8.4V36.6L266.8,36.6z M230.3,47.3c-1.7,0.8-3.6,1.5-5.9,2
+		c-2.2,0.5-4.7,0.8-7.3,0.8c-2.7,0-5.3-0.4-7.6-1.2c-2.3-0.8-4.3-2-5.9-3.5c-1.7-1.5-3-3.3-3.9-5.4c-0.9-2.1-1.4-4.4-1.4-7
+		c0-2.6,0.5-5,1.4-7.1c0.9-2.1,2.2-3.9,3.9-5.4c1.7-1.5,3.7-2.7,5.9-3.4c2.3-0.8,4.7-1.2,7.3-1.2c2.7,0,5.2,0.4,7.5,1.2
+		c2.3,0.8,4.2,1.8,5.6,3.1l-5.2,5.6c-0.8-0.9-1.9-1.6-3.2-2.2c-1.3-0.6-2.8-0.9-4.5-0.9c-1.4,0-2.8,0.3-4,0.8
+		c-1.2,0.5-2.3,1.2-3.2,2.1c-0.9,0.9-1.6,2-2.1,3.2c-0.5,1.3-0.8,2.7-0.7,4.1c0,1.5,0.2,2.9,0.7,4.1c0.4,1.3,1.1,2.3,2,3.2
+		c0.9,0.9,2,1.6,3.3,2.1c1.3,0.5,2.8,0.8,4.5,0.8c1,0,1.9-0.1,2.7-0.2c0.9-0.1,1.7-0.4,2.4-0.7v-5.9h-6.5v-6.3h14.1V47.3L230.3,47.3
+		z M374,32.9c0,3-0.6,5.6-1.7,7.8c-1,2.1-2.5,3.9-4.4,5.4c-1.8,1.4-3.9,2.5-6.2,3.1c-2.3,0.7-4.6,1-7,1h-12.8V15.9h12.4
+		c2.4,0,4.8,0.3,7.2,0.8c2.3,0.5,4.4,1.5,6.3,2.8c1.8,1.3,3.3,3.1,4.4,5.2C373.5,27,374,29.7,374,32.9L374,32.9z M365.4,32.9
+		c0-1.9-0.3-3.5-0.9-4.8c-0.6-1.2-1.4-2.3-2.5-3c-1.1-0.8-2.3-1.3-3.6-1.6c-1.4-0.3-2.7-0.5-4.1-0.5h-4.1v20h3.9
+		c1.5,0,2.9-0.2,4.3-0.5c1.3-0.3,2.5-0.9,3.6-1.7c1.1-0.8,1.9-1.9,2.5-3.1C365.1,36.4,365.4,34.8,365.4,32.9L365.4,32.9z
+		 M193.8,32.9c0,3-0.6,5.6-1.7,7.8c-1,2.1-2.5,3.9-4.4,5.4c-1.8,1.4-3.9,2.5-6.2,3.1c-2.3,0.7-4.6,1-7,1h-12.8V15.9h12.4
+		c2.4,0,4.8,0.3,7.2,0.8c2.3,0.5,4.4,1.5,6.3,2.8c1.8,1.3,3.3,3.1,4.4,5.2C193.2,27,193.8,29.7,193.8,32.9L193.8,32.9z M185.2,32.9
+		c0-1.9-0.3-3.5-0.9-4.8c-0.6-1.2-1.4-2.3-2.5-3c-1.1-0.8-2.3-1.3-3.6-1.6c-1.4-0.3-2.7-0.5-4.1-0.5h-4.1v20h3.9
+		c1.5,0,2.9-0.2,4.3-0.5c1.3-0.3,2.5-0.9,3.6-1.7c1.1-0.8,1.9-1.9,2.5-3.1C184.9,36.4,185.2,34.8,185.2,32.9z M150.4,50.1l-2.7-6.7
+		h-13.2l-2.5,6.7h-9l14.3-34.2h8l14.2,34.2H150.4z M141.2,25l-4.3,11.7h8.6L141.2,25z M328.1,50.1l-7.2-13.6h-2.7v13.6h-7.9V15.9
+		h12.8c1.6,0,3.2,0.2,4.7,0.5c1.5,0.3,2.9,0.9,4.1,1.7c1.2,0.8,2.2,1.9,2.9,3.2c0.7,1.3,1.1,3,1.1,4.9c0,2.3-0.6,4.3-1.8,5.9
+		c-1.2,1.6-2.9,2.7-5.1,3.4l8.7,14.6H328.1z M327.7,26.4c0-0.8-0.2-1.5-0.5-2c-0.3-0.5-0.8-0.9-1.3-1.2c-0.5-0.3-1.1-0.5-1.7-0.6
+		c-0.6-0.1-1.2-0.1-1.9-0.1h-4.3v8h3.8c0.7,0,1.3-0.1,2-0.2c0.7-0.1,1.3-0.3,1.9-0.6c0.6-0.3,1-0.7,1.4-1.3
+		C327.6,27.9,327.7,27.2,327.7,26.4z M125.3,86.3V66.4h2.2v8.8h11.1v-8.8h2.2v19.9h-2.2v-8.9h-11.1v8.9L125.3,86.3L125.3,86.3z
+		 M155.2,86.6c-5.9,0-9.8-4.7-9.8-10.3c0-5.5,4-10.3,9.8-10.3c5.9,0,9.8,4.7,9.8,10.3C165,81.8,161,86.6,155.2,86.6z M155.2,84.5
+		c4.4,0,7.5-3.6,7.5-8.2c0-4.5-3.2-8.2-7.6-8.2s-7.5,3.6-7.5,8.2C147.6,80.9,150.8,84.5,155.2,84.5z M169.6,86.3V66.4h2.2l7,10.8
+		l7-10.8h2.2v19.9h-2.2V70.2l-7,10.6h-0.1l-7-10.6v16.1H169.6L169.6,86.3z M193.4,86.3V66.4h13.9v2h-11.7v6.8h10.5v2h-10.5v6.9h11.9
+		v2C207.5,86.3,193.4,86.3,193.4,86.3z"/>
+	<g id="logo">
+		<g id="Group-10" transform="translate(12.000000, 12.000000)">
+			<g id="Group-9">
+				<g id="Group-8">
+					<g id="Group-7">
+						<path id="Path" class="st1" d="M33.6-12C19.4-12,2.2-8.6-12-1.3c0,15.9-0.2,55.7,45.6,82.8C79.5,54.4,79.3,14.7,79.3-1.3
+							C65.1-8.6,47.9-12,33.6-12L33.6-12z"/>
+						<path id="Combined-Shape" class="st2" d="M33.6,81.5C-12.2,54.4-12,14.7-12-1.3C2.1-8.6,19.3-12,33.6-12
+							C33.6-12,33.6,81.5,33.6,81.5z"/>
+					</g>
+					<path id="Fill-11" class="st3" d="M32,50.4l27.6-37.2c-2-1.6-3.8-0.5-4.8,0.4l0,0l-23,23.9l-8.7-10.4C18.9,22.4,13.3,26,12,27
+						L32,50.4"/>
+				</g>
+			</g>
+		</g>
+	</g>
+</g>
+</svg>
diff --git a/doc/adguard_home_lightmode.svg b/doc/adguard_home_lightmode.svg
new file mode 100644
index 00000000..00965130
--- /dev/null
+++ b/doc/adguard_home_lightmode.svg
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 374 93.5" style="enable-background:new 0 0 374 93.5;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#68BC71;}
+	.st1{fill:#67B279;}
+	.st2{fill:#FFFFFF;}
+</style>
+<g>
+	<path d="M296.4,50.1l-2.7-6.7h-13.2l-2.5,6.7h-9l14.3-34.2h8l14.2,34.2H296.4z M287.3,25l-4.3,11.7h8.6L287.3,25z M266.8,36.6
+		c0,2-0.3,3.8-1,5.5c-0.7,1.6-1.7,3.1-3,4.2c-1.3,1.2-2.9,2.1-4.7,2.8c-1.8,0.7-3.9,1-6.1,1c-2.3,0-4.3-0.3-6.2-1
+		c-1.8-0.7-3.4-1.6-4.6-2.8c-1.3-1.2-2.3-2.6-2.9-4.2c-0.7-1.6-1-3.5-1-5.5V15.9h8.3V36c0,0.9,0.1,1.8,0.4,2.6
+		c0.2,0.8,0.6,1.5,1.1,2.2c0.5,0.6,1.2,1.2,2,1.5c0.8,0.4,1.8,0.6,3,0.6s2.2-0.2,3-0.6c0.8-0.4,1.5-0.9,2-1.5
+		c0.5-0.6,0.9-1.4,1.1-2.2c0.2-0.8,0.3-1.7,0.3-2.6V15.9h8.4V36.6L266.8,36.6z M230.3,47.3c-1.7,0.8-3.6,1.5-5.9,2
+		c-2.2,0.5-4.7,0.8-7.3,0.8c-2.7,0-5.3-0.4-7.6-1.2c-2.3-0.8-4.3-2-5.9-3.5c-1.7-1.5-3-3.3-3.9-5.4c-0.9-2.1-1.4-4.4-1.4-7
+		c0-2.6,0.5-5,1.4-7.1c0.9-2.1,2.2-3.9,3.9-5.4c1.7-1.5,3.7-2.7,5.9-3.4c2.3-0.8,4.7-1.2,7.3-1.2c2.7,0,5.2,0.4,7.5,1.2
+		c2.3,0.8,4.2,1.8,5.6,3.1l-5.2,5.6c-0.8-0.9-1.9-1.6-3.2-2.2c-1.3-0.6-2.8-0.9-4.5-0.9c-1.4,0-2.8,0.3-4,0.8
+		c-1.2,0.5-2.3,1.2-3.2,2.1c-0.9,0.9-1.6,2-2.1,3.2c-0.5,1.3-0.8,2.7-0.7,4.1c0,1.5,0.2,2.9,0.7,4.1c0.4,1.3,1.1,2.3,2,3.2
+		c0.9,0.9,2,1.6,3.3,2.1c1.3,0.5,2.8,0.8,4.5,0.8c1,0,1.9-0.1,2.7-0.2c0.9-0.1,1.7-0.4,2.4-0.7v-5.9h-6.5v-6.3h14.1V47.3L230.3,47.3
+		z M374,32.9c0,3-0.6,5.6-1.7,7.8c-1,2.1-2.5,3.9-4.4,5.4c-1.8,1.4-3.9,2.5-6.2,3.1c-2.3,0.7-4.6,1-7,1h-12.8V15.9h12.4
+		c2.4,0,4.8,0.3,7.2,0.8c2.3,0.5,4.4,1.5,6.3,2.8c1.8,1.3,3.3,3.1,4.4,5.2C373.5,27,374,29.7,374,32.9L374,32.9z M365.4,32.9
+		c0-1.9-0.3-3.5-0.9-4.8c-0.6-1.2-1.4-2.3-2.5-3c-1.1-0.8-2.3-1.3-3.6-1.6c-1.4-0.3-2.7-0.5-4.1-0.5h-4.1v20h3.9
+		c1.5,0,2.9-0.2,4.3-0.5c1.3-0.3,2.5-0.9,3.6-1.7c1.1-0.8,1.9-1.9,2.5-3.1C365.1,36.4,365.4,34.8,365.4,32.9L365.4,32.9z
+		 M193.8,32.9c0,3-0.6,5.6-1.7,7.8c-1,2.1-2.5,3.9-4.4,5.4c-1.8,1.4-3.9,2.5-6.2,3.1c-2.3,0.7-4.6,1-7,1h-12.8V15.9h12.4
+		c2.4,0,4.8,0.3,7.2,0.8c2.3,0.5,4.4,1.5,6.3,2.8c1.8,1.3,3.3,3.1,4.4,5.2C193.2,27,193.8,29.7,193.8,32.9L193.8,32.9z M185.2,32.9
+		c0-1.9-0.3-3.5-0.9-4.8c-0.6-1.2-1.4-2.3-2.5-3c-1.1-0.8-2.3-1.3-3.6-1.6c-1.4-0.3-2.7-0.5-4.1-0.5h-4.1v20h3.9
+		c1.5,0,2.9-0.2,4.3-0.5c1.3-0.3,2.5-0.9,3.6-1.7c1.1-0.8,1.9-1.9,2.5-3.1C184.9,36.4,185.2,34.8,185.2,32.9z M150.4,50.1l-2.7-6.7
+		h-13.2l-2.5,6.7h-9l14.3-34.2h8l14.2,34.2H150.4z M141.2,25l-4.3,11.7h8.6L141.2,25z M328.1,50.1l-7.2-13.6h-2.7v13.6h-7.9V15.9
+		h12.8c1.6,0,3.2,0.2,4.7,0.5c1.5,0.3,2.9,0.9,4.1,1.7c1.2,0.8,2.2,1.9,2.9,3.2c0.7,1.3,1.1,3,1.1,4.9c0,2.3-0.6,4.3-1.8,5.9
+		c-1.2,1.6-2.9,2.7-5.1,3.4l8.7,14.6H328.1z M327.7,26.4c0-0.8-0.2-1.5-0.5-2c-0.3-0.5-0.8-0.9-1.3-1.2c-0.5-0.3-1.1-0.5-1.7-0.6
+		c-0.6-0.1-1.2-0.1-1.9-0.1h-4.3v8h3.8c0.7,0,1.3-0.1,2-0.2c0.7-0.1,1.3-0.3,1.9-0.6c0.6-0.3,1-0.7,1.4-1.3
+		C327.6,27.9,327.7,27.2,327.7,26.4z M125.3,86.3V66.4h2.2v8.8h11.1v-8.8h2.2v19.9h-2.2v-8.9h-11.1v8.9L125.3,86.3L125.3,86.3z
+		 M155.2,86.6c-5.9,0-9.8-4.7-9.8-10.3c0-5.5,4-10.3,9.8-10.3c5.9,0,9.8,4.7,9.8,10.3C165,81.8,161,86.6,155.2,86.6z M155.2,84.5
+		c4.4,0,7.5-3.6,7.5-8.2c0-4.5-3.2-8.2-7.6-8.2s-7.5,3.6-7.5,8.2C147.6,80.9,150.8,84.5,155.2,84.5z M169.6,86.3V66.4h2.2l7,10.8
+		l7-10.8h2.2v19.9h-2.2V70.2l-7,10.6h-0.1l-7-10.6v16.1H169.6L169.6,86.3z M193.4,86.3V66.4h13.9v2h-11.7v6.8h10.5v2h-10.5v6.9h11.9
+		v2C207.5,86.3,193.4,86.3,193.4,86.3z"/>
+	<g id="logo">
+		<g id="Group-10" transform="translate(12.000000, 12.000000)">
+			<g id="Group-9">
+				<g id="Group-8">
+					<g id="Group-7">
+						<path id="Path" class="st0" d="M33.6-12C19.4-12,2.2-8.6-12-1.3c0,15.9-0.2,55.7,45.6,82.8C79.5,54.4,79.3,14.7,79.3-1.3
+							C65.1-8.6,47.9-12,33.6-12L33.6-12z"/>
+						<path id="Combined-Shape" class="st1" d="M33.6,81.5C-12.2,54.4-12,14.7-12-1.3C2.1-8.6,19.3-12,33.6-12
+							C33.6-12,33.6,81.5,33.6,81.5z"/>
+					</g>
+					<path id="Fill-11" class="st2" d="M32,50.4l27.6-37.2c-2-1.6-3.8-0.5-4.8,0.4l0,0l-23,23.9l-8.7-10.4C18.9,22.4,13.3,26,12,27
+						L32,50.4"/>
+				</g>
+			</g>
+		</g>
+	</g>
+</g>
+</svg>

From 6913ebb29f0041bda9cf99e2883f5ed4eaff10db Mon Sep 17 00:00:00 2001
From: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Wed, 24 Aug 2022 17:02:32 +0300
Subject: [PATCH 141/143] all: fmt readme

---
 README.md | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 305b3f1b..2118b116 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,14 @@
 &nbsp;
 <p align="center">
-  <picture>
-    <source media="(prefers-color-scheme: dark)" srcset="doc/adguard_home_darkmode.svg">
-    <img alt="AdGuard Home" src="doc/adguard_home_lightmode.svg" width="300px">
-  </picture>
+    <picture>
+        <source media="(prefers-color-scheme: dark)" srcset="doc/adguard_home_darkmode.svg">
+        <img alt="AdGuard Home" src="doc/adguard_home_lightmode.svg" width="300px">
+    </picture>
 </p>
 <h3 align="center">Privacy protection center for you and your devices</h3>
 <p align="center">
-  Free and open source, powerful network-wide ads & trackers blocking DNS server.
+    Free and open source, powerful network-wide ads & trackers blocking DNS
+    server.
 </p>
 
 <p align="center">

From fa76ad2a3c3b8efd91a03aacbc9439eff350f8b9 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Thu, 25 Aug 2022 18:44:19 +0300
Subject: [PATCH 142/143] filtering: imp code

---
 CHANGELOG.md                    |  8 ++++-
 internal/filtering/blocked.go   | 52 +++++++++++++++++++++------------
 internal/filtering/filtering.go |  8 +++--
 internal/filtering/rewrites.go  |  7 ++---
 4 files changed, 48 insertions(+), 27 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5edd91b..ca77782c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,11 @@ and this project adheres to
 - Weaker cipher suites that use the CBC (cipher block chaining) mode of
   operation have been disabled ([#2993]).
 
+### Added
+
+- A new HTTP API, `GET /control/blocked_services/services`, that lists all
+  available blocked services ([#4535]).
+
 ### Deprecated
 
 - Ports 784 and 8853 for DNS-over-QUIC in Docker images.  Users who still serve
@@ -34,6 +39,7 @@ and this project adheres to
 - Unnecessary logging of non-critical statistics errors ([#4850]).
 
 [#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
+[#4535]: https://github.com/AdguardTeam/AdGuardHome/issues/4535
 [#4745]: https://github.com/AdguardTeam/AdGuardHome/issues/4745
 [#4850]: https://github.com/AdguardTeam/AdGuardHome/issues/4850
 
@@ -59,7 +65,7 @@ See also the [v0.107.11 GitHub milestone][ms-v0.107.11].
 
 ### Changed
 
-- DNS-over-QUIC connections now use keptalive.
+- DNS-over-QUIC connections now use keepalive.
 
 ### Fixed
 
diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index 41e8ad04..203407f2 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -7,19 +7,21 @@ import (
 	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/urlfilter/rules"
+	"golang.org/x/exp/slices"
 )
 
-var serviceRules map[string][]*rules.NetworkRule // service name -> filtering rules
-
+// svc represents a single blocked service.
 type svc struct {
 	name  string
 	rules []string
 }
 
+// servicesData contains raw blocked service data.
+//
 // Keep in sync with:
-// client/src/helpers/constants.js
-// client/src/components/ui/Icons.js
-var serviceRulesArray = []svc{{
+//   - client/src/helpers/constants.js
+//   - client/src/components/ui/Icons.js
+var servicesData = []svc{{
 	name: "whatsapp",
 	rules: []string{
 		"||wa.me^",
@@ -365,21 +367,38 @@ var serviceRulesArray = []svc{{
 	},
 }}
 
-// convert array to map
+// serviceRules maps a service ID to its filtering rules.
+var serviceRules map[string][]*rules.NetworkRule
+
+// serviceIDs contains service IDs sorted alphabetically.
+var serviceIDs []string
+
+// initBlockedServices initializes package-level blocked service data.
 func initBlockedServices() {
-	serviceRules = make(map[string][]*rules.NetworkRule)
-	for _, s := range serviceRulesArray {
-		netRules := []*rules.NetworkRule{}
+	l := len(servicesData)
+	serviceIDs = make([]string, l)
+	serviceRules = make(map[string][]*rules.NetworkRule, l)
+
+	for i, s := range servicesData {
+		netRules := make([]*rules.NetworkRule, 0, len(s.rules))
 		for _, text := range s.rules {
 			rule, err := rules.NewNetworkRule(text, BlockedSvcsListID)
 			if err != nil {
-				log.Error("rules.NewNetworkRule: %s  rule: %s", err, text)
+				log.Error("parsing blocked service %q rule %q: %s", s.name, text, err)
+
 				continue
 			}
+
 			netRules = append(netRules, rule)
 		}
+
+		serviceIDs[i] = s.name
 		serviceRules[s.name] = netRules
 	}
+
+	slices.Sort(serviceIDs)
+
+	log.Debug("filtering: initialized %d services", l)
 }
 
 // BlockedSvcKnown - return TRUE if a blocked service name is known
@@ -411,16 +430,11 @@ func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string, global
 	}
 }
 
-func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) {	
-	var list []string
-	for _, v := range serviceRulesArray {
-		list = append(list, s.name)
-	}
-
+func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
-	err := json.NewEncoder(w).Encode(list)
+	err := json.NewEncoder(w).Encode(serviceIDs)
 	if err != nil {
-		aghhttp.Error(r, w, http.StatusInternalServerError, "json.Encode: %s", err)
+		aghhttp.Error(r, w, http.StatusInternalServerError, "encoding available services: %s", err)
 
 		return
 	}
@@ -434,7 +448,7 @@ func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Req
 	w.Header().Set("Content-Type", "application/json")
 	err := json.NewEncoder(w).Encode(list)
 	if err != nil {
-		aghhttp.Error(r, w, http.StatusInternalServerError, "json.Encode: %s", err)
+		aghhttp.Error(r, w, http.StatusInternalServerError, "encoding services: %s", err)
 
 		return
 	}
diff --git a/internal/filtering/filtering.go b/internal/filtering/filtering.go
index 4a3e6b28..4d438dfe 100644
--- a/internal/filtering/filtering.go
+++ b/internal/filtering/filtering.go
@@ -296,9 +296,11 @@ func cloneRewrites(entries []*LegacyRewrite) (clone []*LegacyRewrite) {
 	return clone
 }
 
-// SetFilters - set new filters (synchronously or asynchronously)
-// When filters are set asynchronously, the old filters continue working until the new filters are ready.
-//  In this case the caller must ensure that the old filter files are intact.
+// SetFilters sets new filters, synchronously or asynchronously.  When filters
+// are set asynchronously, the old filters continue working until the new
+// filters are ready.
+//
+// In this case the caller must ensure that the old filter files are intact.
 func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
 	if async {
 		params := filtersInitializerParams{
diff --git a/internal/filtering/rewrites.go b/internal/filtering/rewrites.go
index dab4c034..c1557158 100644
--- a/internal/filtering/rewrites.go
+++ b/internal/filtering/rewrites.go
@@ -130,10 +130,9 @@ func matchDomainWildcard(host, wildcard string) (ok bool) {
 //
 // The sorting priority:
 //
-//   A and AAAA > CNAME
-//   wildcard > exact
-//   lower level wildcard > higher level wildcard
-//
+//  1. A and AAAA > CNAME
+//  2. wildcard > exact
+//  3. lower level wildcard > higher level wildcard
 type rewritesSorted []*LegacyRewrite
 
 // Len implements the sort.Interface interface for legacyRewritesSorted.

From 986124948a21b5dfebb0bd6a9f948911b0a4b938 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Thu, 25 Aug 2022 18:58:49 +0300
Subject: [PATCH 143/143] all: imp client, add api chlog

---
 client/src/api/Api.js | 2 +-
 openapi/CHANGELOG.md  | 9 +++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/client/src/api/Api.js b/client/src/api/Api.js
index 625da9e0..d5693bfe 100644
--- a/client/src/api/Api.js
+++ b/client/src/api/Api.js
@@ -491,7 +491,7 @@ class Api {
         const { path, method } = this.BLOCKED_SERVICES_SERVICES;
         return this.makeRequest(path, method);
     }
-    
+
     getBlockedServices() {
         const { path, method } = this.BLOCKED_SERVICES_LIST;
         return this.makeRequest(path, method);
diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md
index 1e2852be..fbbe7169 100644
--- a/openapi/CHANGELOG.md
+++ b/openapi/CHANGELOG.md
@@ -4,6 +4,15 @@
 
 ## v0.108.0: API changes
 
+## v0.107.12: API changes
+
+### `GET /control/blocked_services/services`
+
+* The new `GET /control/blocked_services/services` HTTP API allows inspecting
+  all available services.
+
+## v0.107.7: API changes
+
 ### The new optional field `"ecs"` in `QueryLogItem`
 
 * The new optional field `"ecs"` in `GET /control/querylog` contains the IP