all: sync with master; upd chlog

This commit is contained in:
Ainar Garipov 2023-10-11 17:31:41 +03:00
parent 258eecc55b
commit 760d466b38
139 changed files with 39736 additions and 18364 deletions

View file

@ -1,8 +1,8 @@
'name': 'build'
'env':
'GO_VERSION': '1.20.8'
'NODE_VERSION': '14'
'GO_VERSION': '1.20.10'
'NODE_VERSION': '16'
'on':
'push':

View file

@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.20.8'
'GO_VERSION': '1.20.10'
'on':
'push':

View file

@ -14,11 +14,11 @@ and this project adheres to
<!--
## [v0.108.0] - TBA
## [v0.107.39] - 2023-09-27 (APPROX.)
## [v0.107.40] - 2023-10-09 (APPROX.)
See also the [v0.107.39 GitHub milestone][ms-v0.107.39].
See also the [v0.107.40 GitHub milestone][ms-v0.107.40].
[ms-v0.107.39]: https://github.com/AdguardTeam/AdGuardHome/milestone/74?closed=1
[ms-v0.107.40]: https://github.com/AdguardTeam/AdGuardHome/milestone/75?closed=1
NOTE: Add new changes BELOW THIS COMMENT.
-->
@ -29,6 +29,50 @@ NOTE: Add new changes ABOVE THIS COMMENT.
## [v0.107.39] - 2023-10-11
See also the [v0.107.39 GitHub milestone][ms-v0.107.39].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2023-39323 and CVE-2023-39325 Go vulnerabilities fixed in
[Go 1.20.9][go-1.20.9] and [Go 1.20.10][go-1.20.10].
### Added
- Ability to edit static leases on *DHCP settings* page ([#1700]).
- Ability to specify for how long clients should cache a filtered response,
using the *Blocked response TTL* field on the *DNS settings* page ([#4569]).
### Changed
- ipset entries are updated more often ([#6233]).
- Node.JS 16 is now required to build the frontend.
### Fixed
- Incorrect domain-specific upstream matching for `DS` queries ([#6156]).
- Improper validation of password length ([#6280]).
- Wrong algorithm for filtering self addresses from the list of private upstream
DNS servers ([#6231]).
- An accidental change in DNS rewrite priority ([#6226]).
[#1700]: https://github.com/AdguardTeam/AdGuardHome/issues/1700
[#4569]: https://github.com/AdguardTeam/AdGuardHome/issues/4569
[#6156]: https://github.com/AdguardTeam/AdGuardHome/issues/6156
[#6226]: https://github.com/AdguardTeam/AdGuardHome/issues/6226
[#6231]: https://github.com/AdguardTeam/AdGuardHome/issues/6231
[#6233]: https://github.com/AdguardTeam/AdGuardHome/issues/6233
[#6280]: https://github.com/AdguardTeam/AdGuardHome/issues/6280
[go-1.20.9]: https://groups.google.com/g/golang-announce/c/XBa1oHDevAo/m/desYyx3qAgAJ
[go-1.20.10]: https://groups.google.com/g/golang-announce/c/iNNxDTCjZvo/m/UDd7VKQuAAAJ
[ms-v0.107.39]: https://github.com/AdguardTeam/AdGuardHome/milestone/74?closed=1
## [v0.107.38] - 2023-09-11
See also the [v0.107.38 GitHub milestone][ms-v0.107.38].
@ -2487,11 +2531,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.39...HEAD
[v0.107.39]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.38...v0.107.39
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.40...HEAD
[v0.107.40]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.39...v0.107.40
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.38...HEAD
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.39...HEAD
[v0.107.39]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.38...v0.107.39
[v0.107.38]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.37...v0.107.38
[v0.107.37]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.36...v0.107.37
[v0.107.36]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.35...v0.107.36

View file

@ -24,6 +24,7 @@ CHANNEL = development
CLIENT_DIR = client
COMMIT = $$( git rev-parse --short HEAD )
DIST_DIR = dist
GOAMD64 = v1
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
GOSUMDB = sum.golang.google.cn
GPG_KEY = devteam@adguard.com
@ -61,6 +62,7 @@ ENV = env\
GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\
DIST_DIR='$(DIST_DIR)'\
GO="$(GO.MACRO)"\
GOAMD64="$(GOAMD64)"\
GOPROXY='$(GOPROXY)'\
GOSUMDB='$(GOSUMDB)'\
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\

View file

@ -262,8 +262,8 @@ Run `make init` to prepare the development environment.
You will need this to build AdGuard Home:
* [Go](https://golang.org/dl/) v1.20 or later;
* [Node.js](https://nodejs.org/en/download/) v10.16.2 or later;
* [npm](https://www.npmjs.com/) v6.14 or later;
* [Node.js](https://nodejs.org/en/download/) v16 or later;
* [npm](https://www.npmjs.com/) v8 or later;
* [yarn](https://yarnpkg.com/) v1.22.5 or later.

View file

@ -7,7 +7,7 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:7.1'
'dockerGo': 'adguard/golang-ubuntu:7.4'
'stages':
- 'Build frontend':
@ -272,7 +272,7 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:7.1'
'dockerGo': 'adguard/golang-ubuntu:7.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]+':
@ -287,4 +287,4 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:7.1'
'dockerGo': 'adguard/golang-ubuntu:7.4'

View file

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

View file

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

25137
client/package-lock.json generated vendored

File diff suppressed because it is too large Load diff

1
client/package.json vendored
View file

@ -42,7 +42,6 @@
"redux-actions": "^2.6.5",
"redux-form": "^8.3.5",
"redux-thunk": "^2.3.0",
"string-length": "^5.0.1",
"timezones-list": "^3.0.2",
"url-polyfill": "^1.1.9"
},

View file

@ -278,6 +278,7 @@
"custom_ip": "عنوان IP مخصص",
"blocking_ipv4": "حجب عنوان IPv4",
"blocking_ipv6": "حجب عنوان IPv6",
"blocked_response_ttl": "زمن حظر الاستجابة",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -598,7 +599,7 @@
"all_queries": "كافة الاستفسارات",
"show_blocked_responses": "حظر",
"show_whitelisted_responses": "القائمة البيضاء",
"show_processed_responses": "المعالجة",
"show_processed_responses": "معالجة",
"blocked_safebrowsing": "محظور بواسطة التصفح الآمن",
"blocked_adult_websites": "محظور بواسطة الرقابة الأبوية",
"blocked_threats": "التهديدات المحظورة",

View file

@ -73,7 +73,9 @@
"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_updated": "Статычная арэнда \"{{key}}\" паспяхова абноўлена",
"dhcp_new_static_lease": "Новая статычная арэнда",
"dhcp_edit_static_lease": "Рэдагаваць статычную арэнду",
"dhcp_static_leases_not_found": "Не знойдзена статычных арэнд DHCP",
"dhcp_add_static_lease": "Дадаць статычную арэнду",
"dhcp_reset_leases": "Скінуць усё арэнды",
@ -174,6 +176,7 @@
"enabled_parental_toast": "Уключаны бацькоўскі кантроль",
"disabled_safe_search_toast": "Адключаны бяспечны пошук",
"enabled_save_search_toast": "Уключаны бяспечны пошук",
"updated_save_search_toast": "Налады бяспечнага пошуку абноўлены",
"enabled_table_header": "УКЛ.",
"name_table_header": "Імя",
"list_url_table_header": "URL-адрас спіса",
@ -282,6 +285,9 @@
"custom_ip": "Свой IP",
"blocking_ipv4": "Блакаванне IPv4",
"blocking_ipv6": "Блакаванне IPv6",
"blocked_response_ttl": "TTL заблакаванага адказу",
"blocked_response_ttl_desc": "Паказвае, на працягу колькіх секунд кліенты павінны кэшаваць адфільтраваць адказ",
"form_enter_blocked_response_ttl": "Увядзіце TTL заблакіраванага адказу (у секундах)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -297,6 +303,8 @@
"rate_limit": "Ограничение скорости",
"edns_enable": "Уключыць адпраўленне EDNS Client Subnet",
"edns_cs_desc": "Дадайце опцыю кліенцкай падсеткі EDNS (ECS) да запытаў вышэй па плыні і запісвайце значэнні, адпраўленыя кліентамі, у журнал запытаў.",
"edns_use_custom_ip": "Выкарыстоўваць указаны IP для DNS",
"edns_use_custom_ip_desc": "Дазволіць выкарыстоўваць уласны IP для DNS",
"rate_limit_desc": "Абмежаванне на колькасць запытаў у секунду для кожнага кліента (0 — неабмежавана)",
"blocking_ipv4_desc": "IP-адрас, што вяртаецца пры блакаванню A-запыту",
"blocking_ipv6_desc": "IP-адрас, што вяртаецца пры блакаванню AAAA-запыту",
@ -532,6 +540,8 @@
"statistics_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання статыстыкі? Пры памяншэнні інтэрвалу, некаторыя даныя могуць быць страчаны",
"statistics_cleared": "Статыстыка паспяхова вычышчана",
"statistics_enable": "Уключыць статыстыку",
"ignore_domains": "Ігнаруемыя дамены (парадкова)",
"ignore_domains_title": "Ігнаруемыя дамены",
"ignore_domains_desc_stats": "Запыты, якія адпавядаюць гэтым правілам, не запісваюцца ў статыстыку",
"ignore_domains_desc_query": "Запыты, якія адпавядаюць гэтым правілам, не запісваюцца ў часопіс запытаў",
"interval_hours": "{{count}} гадзіна",
@ -649,13 +659,32 @@
"parental_control": "Бацькоўскі кантроль",
"safe_browsing": "Бяспечны інтэрнэт",
"served_from_cache": "{{value}} <i>(атрымана з кэша)</i>",
"form_error_password_length": "Пароль павінен быць не менш за {{value}} сімвалаў",
"form_error_password_length": "Пароль павінен утрымліваць ад {{min}} да {{max}} сімвалаў",
"anonymizer_notification": "<0>Заўвага:</0> Ананімізацыя IP уключана. Вы можаце адключыць яе ў <1>Агульных наладах</1>.",
"confirm_dns_cache_clear": "Вы ўпэўнены, што хочаце ачысціць кэш DNS?",
"cache_cleared": "Кэш DNS паспяхова ачышчаны",
"clear_cache": "Ачысціць кэш",
"make_static": "Зрабіць статычнай",
"theme_auto_desc": "Аўто (на аснове каляровай схемы вашага прылады)",
"theme_dark_desc": "Цёмная тэма",
"theme_light_desc": "Светлая тэма",
"disable_for_seconds": "На {{count}} секунд",
"disable_for_seconds_plural": "На {{count}} секунд",
"disable_for_minutes": "На {{count}} хвіліну",
"disable_for_minutes_plural": "На {{count}} хвілін",
"disable_for_hours": "На {{count}} гадзін",
"disable_for_hours_plural": "На {{count}} гадзін",
"disable_until_tomorrow": "Да заўтра",
"disable_notify_for_seconds": "Адключыць абарону на {{count}} секунд",
"disable_notify_for_seconds_plural": "Адключыць абарону на {{count}} секунд",
"disable_notify_for_minutes": "Адключыць абарону на {{count}} хвіліну",
"disable_notify_for_minutes_plural": "Адключыць абарону на {{count}} хвілін",
"disable_notify_for_hours": "Адключыць абарону на {{count}} гадзін",
"disable_notify_for_hours_plural": "Адключыць абарону на {{count}} гадзін",
"disable_notify_until_tomorrow": "Адключыць абарону да заўтра",
"enable_protection_timer": "Абарона будзе ўключана ў {{time}}",
"custom_retention_input": "Увядзіце тэрмін захоўвання ў гадзінах",
"custom_rotation_input": "Увядзіце частату ратацыі ў гадзінах",
"protection_section_label": "Ахова",
"log_and_stats_section_label": "Журнал запытаў і статыстыка",
"ignore_query_log": "Ігнараваць гэтага кліента ў журнале запытаў",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Statický pronájem \"{{key}}\" byl úspěšně odstraněn",
"dhcp_lease_updated": "Statický pronájem \"{{key}}\" byl úspěšně aktualizován",
"dhcp_new_static_lease": "Nový statický pronájem",
"dhcp_edit_static_lease": "Upravit statický pronájem",
"dhcp_static_leases_not_found": "Nebyly nalezeny žádné statické pronájmy DHCP",
"dhcp_add_static_lease": "Přidat statický pronájem",
"dhcp_reset_leases": "Resetovat všechny pronájmy",
@ -283,6 +285,9 @@
"custom_ip": "Vlastní IP",
"blocking_ipv4": "Blokování IPv4",
"blocking_ipv6": "Blokování IPv6",
"blocked_response_ttl": "TTL blokované odezvy",
"blocked_response_ttl_desc": "Určuje, na kolik sekund by měli klienti ukládat filtrovanou odezvu do mezipaměti",
"form_enter_blocked_response_ttl": "Zadejte TTL blokované odezvy (v sekundách)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS skrze HTTPS",
"dns_over_tls": "DNS skrze TLS",
@ -538,7 +543,7 @@
"ignore_domains": "Ignorované domény (oddělené novým řádkem)",
"ignore_domains_title": "Ignorované domény",
"ignore_domains_desc_stats": "Dotazy odpovídající těmto pravidlům se do statistik nezapisují",
"ignore_domains_desc_query": "Dotazy odpovídající těmto pravidlům se do protokolu dotazů nezapisují",
"ignore_domains_desc_query": "Dotazy odpovídající těmto pravidlům se do záznamu dotazů nezapisují",
"interval_hours": "Hodiny: {{count}}",
"interval_hours_plural": "Hodiny: {{count}}",
"filters_configuration": "Konfigurace filtrů",
@ -654,7 +659,7 @@
"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í obsahovat od {{min}} do {{max}} znaků",
"anonymizer_notification": "<0>Poznámka:</0> Anonymizace IP je zapnuta. Můžete ji vypnout v <1>Obecných nastaveních</1>.",
"confirm_dns_cache_clear": "Opravdu chcete vymazat mezipaměť DNS?",
"cache_cleared": "Mezipaměť DNS úspěšně vymazána",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Statisk lease \"{{key}}\" slettet",
"dhcp_lease_updated": "Statisk tildeling \"{{key}}\" hermed opdateret",
"dhcp_new_static_lease": "Ny statisk lease",
"dhcp_edit_static_lease": "Redigér statisk tildeling",
"dhcp_static_leases_not_found": "Intet DHCP statisk leases fundet",
"dhcp_add_static_lease": "Tilføj statisk lease",
"dhcp_reset_leases": "Nulstil alle gyldighedsperioder",
@ -283,6 +285,9 @@
"custom_ip": "Tilpasset IP",
"blocking_ipv4": "IPv4-blokering",
"blocking_ipv6": "IPv6-blokering",
"blocked_response_ttl": "Blokeret svar TTL",
"blocked_response_ttl_desc": "Angiver, i hvor mange sekunder klienterne skal cache-lagre et filtreret svar",
"form_enter_blocked_response_ttl": "Angiv blokeringssvar TTL (sekunder)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"parental_control": "Forældrekontrol",
"safe_browsing": "Sikker Browsing",
"served_from_cache": "{{value}} <i>(leveret fra cache)</i>",
"form_error_password_length": "Adgangskoden skal udgøre mindst {{value}} tegn.",
"form_error_password_length": "Adgangskode skal udgøre fra {{min}} til {{max}} tegn",
"anonymizer_notification": "<0>Bemærk:</0> IP-anonymisering er aktiveret. Det kan deaktiveres via <1>Generelle indstillinger</1>.",
"confirm_dns_cache_clear": "Sikker på, at DNS-cache skal ryddes?",
"cache_cleared": "DNS-cache hermed ryddet",

View file

@ -6,14 +6,14 @@
"parallel_requests": "Paralleles Abfragen",
"load_balancing": "Lastverteilung",
"load_balancing_desc": "Einen Server nach dem anderen abfragen. AdGuard Home verwendet den gewichteten Zufallsalgorithmus, um den Server so auszuwählen, dass der schnellste Server häufiger verwendet wird.",
"bootstrap_dns": "Bootstrap DNS-Server",
"bootstrap_dns": "Bootstrap-DNS-Server",
"bootstrap_dns_desc": "IP-Adressen der DNS-Server, die zum Auflösen der IP-Adressen von DoH/DoT Upstream-Servern verwendet werden, die Sie angegeben haben. Kommentare sind nicht erlaubt.",
"fallback_dns_title": "Fallback-DNS-Server",
"fallback_dns_desc": "Liste der Fallback-DNS-Server, die verwendet werden, wenn die Upstream-DNS-Server nicht antworten. Die Syntax ist die gleiche wie im Hauptfeld für Upstream-Server oben.",
"fallback_dns_placeholder": "Geben Sie einen Fallback-DNS-Server pro Zeile ein",
"local_ptr_title": "Private inverse DNS-Server",
"local_ptr_desc": "Die DNS-Server, die AdGuard Home für lokale PTR-Abfragen verwendet. Diese Server werden verwendet, um die Hostnamen von Clients mit privaten IP-Adressen, z. B. „192.168.12.34“, per inverse DNS-Anfragen aufzulösen. Wenn nicht festgelegt, verwendet AdGuard Home die Adressen der Standard-DNS-Auflöser Ihres Betriebssystems mit Ausnahme der Adressen von AdGuard Home selbst.",
"local_ptr_default_resolver": "Standardmäßig verwendet AdGuard Home die folgenden Invers-DNS-Resolver: {{ip}}.",
"local_ptr_default_resolver": "Standardmäßig verwendet AdGuard Home die folgenden inversen DNS-Resolver: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home konnte keine geeigneten privaten Invers-DNS-Resolver für dieses System ermitteln.",
"local_ptr_placeholder": "Geben Sie eine IP-Adresse pro Zeile ein",
"resolve_clients_title": "Hostnamenauflösung der Clients aktivieren",
@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Statische Zuweisung „{{key}}“ erfolgreich entfernt",
"dhcp_lease_updated": "Statische Zuweisung „{{key}}“ erfolgreich aktualisiert",
"dhcp_new_static_lease": "Neue statische Zuweisung",
"dhcp_edit_static_lease": "Statische Zuweisung bearbeiten",
"dhcp_static_leases_not_found": "Keine statischen DHCP-Zuweisungen gefunden",
"dhcp_add_static_lease": "Statische Zuweisung hinzufügen",
"dhcp_reset_leases": "Alle Zuweisungen zurücksetzen",
@ -283,6 +285,9 @@
"custom_ip": "Benutzerdefinierte IP",
"blocking_ipv4": "IPv4-Sperren",
"blocking_ipv6": "IPv6-Sperren",
"blocked_response_ttl": "Gültigkeitsdauer der blockierten Antwort",
"blocked_response_ttl_desc": "Gibt an, wie viele Sekunden lang die Clients eine gefilterte Antwort zwischenspeichern sollen",
"form_enter_blocked_response_ttl": "Geben Sie die Gültigkeitsdauer für blockierte Antworten ein (in Sekunden)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -296,7 +301,7 @@
"plain_dns": "Einfaches DNS",
"form_enter_rate_limit": "Begrenzungswert eingeben",
"rate_limit": "Begrenzungswert",
"edns_enable": "EDNS Client Subnetz aktivieren",
"edns_enable": "EDNS-Client-Subnetz aktivieren",
"edns_cs_desc": "Die Option EDNS Client Subnetz (ECS) zu Upstream-Anfragen hinzufügen und die von Clients gesendeten Werte protokollieren.",
"edns_use_custom_ip": "Benutzerdefinierte IP für EDNS verwenden",
"edns_use_custom_ip_desc": "Benutzerdefinierte IP für EDNS zulassen",
@ -322,16 +327,16 @@
"known_tracker": "Bekannte Tracker",
"install_welcome_title": "Willkommen bei AdGuard Home!",
"install_welcome_desc": "AdGuard Home ist ein netzwerkweiter Werbung- und Tracking sperrender DNS-Server. Sein Zweck ist es, Ihnen die Kontrolle über Ihr gesamtes Netzwerk und alle Ihre Geräte zu ermöglichen, und es ist nicht erforderlich, eine clientseitige Anwendung zu verwenden.",
"install_settings_title": "Admin Weboberfläche",
"install_settings_title": "Admin-Weboberfläche",
"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:",
"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",
"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",
"install_auth_title": "Authentifizierung",
"install_auth_desc": "Die Passwort-Authentifizierung für Ihre AdGuard Home Admin-Web-Oberfläche muss konfiguriert werden. Auch wenn AdGuard Home nur in Ihrem lokalen Netzwerk zugänglich ist, ist es dennoch wichtig, es vor unberechtigtem Zugriff zu schützen.",
"install_auth_desc": "Die Passwort-Authentifizierung für Ihre AdGuard-Home-Admin-Web-Oberfläche muss konfiguriert werden. Auch wenn AdGuard Home nur in Ihrem lokalen Netzwerk zugänglich ist, so ist es dennoch wichtig, es vor unberechtigtem Zugriff zu schützen.",
"install_auth_username": "Benutzername",
"install_auth_password": "Passwort",
"install_auth_confirm": "Passwort bestätigen",
@ -344,7 +349,7 @@
"install_submit_desc": "Die Einrichtung ist abgeschlossen und Sie können mit der Verwendung von AdGuard Home beginnen.",
"install_devices_router": "Router",
"install_devices_router_desc": "Diese Einrichtung deckt automatisch alle an Ihren Heimrouter angeschlossenen Geräte ab, und Sie müssen nicht jedes einzelne davon manuell konfigurieren.",
"install_devices_address": "Der AdGuard Home DNS-Server belauscht die folgenden Adressen",
"install_devices_address": "Der AdGuard-Home-DNS-Server lauscht unter folgenden Adressen",
"install_devices_router_list_1": "Öffnen Sie die Einstellungen für Ihren Router. In der Regel können Sie über eine URL (z. B. http://192.168.0.1/ oder http://192.168.1.1) von Ihrem Browser aus darauf zugreifen. Möglicherweise werden Sie aufgefordert, ein Passwort einzugeben. Wenn Sie sich nicht mehr daran erinnern, können Sie das Passwort oft durch Drücken einer Taste am Router selbst zurücksetzen, aber seien Sie sich bewusst, dass Sie bei dieser Vorgehensweise wahrscheinlich die gesamte Routerkonfiguration verlieren. Wenn für die Einrichtung Ihres Routers eine App erforderlich ist, installieren Sie bitte die App auf Ihrem mobilen Endgerät oder PC und verwenden Sie sie für den Zugriff auf die Einstellungen des Routers.",
"install_devices_router_list_2": "Wechseln Sie zu den DHCP/DNS-Einstellungen. Suchen sie dort nach einem Eintrag „DNS“ und einem Bereich, welches zwei oder drei Zahlengruppen zulässt, die jeweils in vier Blöcke von ein bis drei Ziffern unterteilt sind.",
"install_devices_router_list_3": "Geben Sie dort Ihre AdGuard Home Server-Adressen ein.",
@ -537,8 +542,8 @@
"statistics_enable": "Statistiken aktivieren",
"ignore_domains": "Ignorierte Domains (durch Zeilenumbruch getrennt)",
"ignore_domains_title": "Ignorierte Domains",
"ignore_domains_desc_stats": "Anfragen, die diesen Regeln entsprechen, werden nicht in die Statistik aufgenommen",
"ignore_domains_desc_query": "Anfragen, die diesen Regeln entsprechen, werden nicht in das Anfragenprotokoll aufgenommen",
"ignore_domains_desc_stats": "Abfragen, die diesen Regeln entsprechen, werden nicht in die Statistik aufgenommen",
"ignore_domains_desc_query": "Abfragen, die diesen Regeln entsprechen, werden nicht in das Anfragenprotokoll aufgenommen",
"interval_hours": "{{count}} Stunde",
"interval_hours_plural": "{{count}} Stunden",
"filters_configuration": "Filterkonfiguration",
@ -640,7 +645,7 @@
"filter_category_regional": "Regional",
"filter_category_other": "Weitere",
"filter_category_general_desc": "Listen, die Tracking und Werbung auf den meisten Geräten sperren",
"filter_category_security_desc": "Listen, die auf das Sperren von Malware, Phishing- oder Scam-Domains spezialisiert sind",
"filter_category_security_desc": "Listen, die auf das Sperren von Malware-, Phishing- oder Scam-Domains spezialisiert sind",
"filter_category_regional_desc": "Listen, die sich auf regionale Werbeanzeigen und Tracking-Server konzentrieren",
"filter_category_other_desc": "Weitere Sperrlisten",
"setup_config_to_enable_dhcp_server": "Einrichten der Konfiguration zur Aktivierung des DHCP-Servers",
@ -654,7 +659,7 @@
"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 zwischen {{min}} und {{max}} Zeichen enthalten",
"anonymizer_notification": "<0>Hinweis:</0> Die IP-Anonymisierung ist aktiviert. Sie können sie in den <1>Allgemeinen Einstellungen</1> deaktivieren.",
"confirm_dns_cache_clear": "Möchten Sie den DNS-Cache wirklich leeren?",
"cache_cleared": "DNS-Cache erfolgreich geleert",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
"dhcp_lease_updated": "Static lease \"{{key}}\" successfully updated",
"dhcp_new_static_lease": "New static lease",
"dhcp_edit_static_lease": "Edit static lease",
"dhcp_static_leases_not_found": "No DHCP static leases found",
"dhcp_add_static_lease": "Add static lease",
"dhcp_reset_leases": "Reset all leases",
@ -283,6 +285,9 @@
"custom_ip": "Custom IP",
"blocking_ipv4": "Blocking IPv4",
"blocking_ipv6": "Blocking IPv6",
"blocked_response_ttl": "Blocked response TTL",
"blocked_response_ttl_desc": "Specifies for how many seconds the clients should cache a filtered response",
"form_enter_blocked_response_ttl": "Enter blocked response TTL (seconds)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"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 {{min}} to {{max}} characters long",
"anonymizer_notification": "<0>Note:</0> IP anonymization is enabled. You can disable it in <1>General settings</1>.",
"confirm_dns_cache_clear": "Are you sure you want to clear DNS cache?",
"cache_cleared": "DNS cache successfully cleared",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Asignación estática \"{{key}}\" eliminado correctamente",
"dhcp_lease_updated": "Asignación estática \"{{key}}\" actualizado correctamente",
"dhcp_new_static_lease": "Nueva asignación estática",
"dhcp_edit_static_lease": "Editar asignación estática",
"dhcp_static_leases_not_found": "No se han encontrado asignaciones DHCP estáticas",
"dhcp_add_static_lease": "Añadir asignación estática",
"dhcp_reset_leases": "Restablecer todas las asignaciones",
@ -283,6 +285,9 @@
"custom_ip": "IP personalizada",
"blocking_ipv4": "Bloqueo de IPv4",
"blocking_ipv6": "Bloqueo de IPv6",
"blocked_response_ttl": "Respuesta TTL bloqueada",
"blocked_response_ttl_desc": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada",
"form_enter_blocked_response_ttl": "Ingresa el TTL de respuesta bloqueada (segundos)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS mediante HTTPS",
"dns_over_tls": "DNS mediante TLS",
@ -654,7 +659,7 @@
"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 entre {{min}} y {{max}} caracteres",
"anonymizer_notification": "<0>Nota:</0> La anonimización de IP está habilitada. Puedes deshabilitarla en <1>Configuración general</1>.",
"confirm_dns_cache_clear": "¿Estás seguro de que deseas borrar la caché DNS?",
"cache_cleared": "Caché DNS borrado correctamente",

View file

@ -271,6 +271,9 @@
"custom_ip": "آی پی دستی",
"blocking_ipv4": "مسدودسازی IPv4",
"blocking_ipv6": "مسدودسازی IPv6",
"blocked_response_ttl": "TTL پاسخ مسدود شده",
"blocked_response_ttl_desc": "مشخص می کند که کلاینت ها برای چند ثانیه یک پاسخ فیلتر شده را در حافظه پنهان نگه دارند",
"form_enter_blocked_response_ttl": "پاسخ مسدود شده TTL (ثانیه) را وارد کنید",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",

View file

@ -73,7 +73,9 @@
"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}}\" lisättiin",
"dhcp_lease_deleted": "Kiinteä laina \"{{key}}\" poistettiin",
"dhcp_lease_updated": "Kiinteä laina \"{{key}}\" päivitettiin",
"dhcp_new_static_lease": "Uusi kiinteä laina",
"dhcp_edit_static_lease": "Muokkaa kiinteää laina",
"dhcp_static_leases_not_found": "Kiinteitä DHCP-lainoja ei löytynyt",
"dhcp_add_static_lease": "Lisää kiinteä laina",
"dhcp_reset_leases": "Tyhjennä kaikki lainat",
@ -283,6 +285,9 @@
"custom_ip": "Mukautettu IP-osoite",
"blocking_ipv4": "IPv4-esto",
"blocking_ipv6": "IPv6-esto",
"blocked_response_ttl": "Estetyn vastauksen elinaika",
"blocked_response_ttl_desc": "Määrittää montako sekuntia päätteiden tulee puskuroida suodatettuja vastauksia.",
"form_enter_blocked_response_ttl": "Syötä estetyn vastauksen elinaika (sekuntia)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -611,7 +616,7 @@
"dnssec_enable_desc": "Määritä DNSSEC-lippu ulos lähteville DNS-pyynnöille ja tarkasta tulos (vaatii DNSSEC-yhteensopivan resolverin).",
"validated_with_dnssec": "DNSSEC-vahvistettu",
"all_queries": "Kaikki pyynnöt",
"show_blocked_responses": "Estetyt",
"show_blocked_responses": "Estetty",
"show_whitelisted_responses": "Sallitut",
"show_processed_responses": "Käsitelty",
"blocked_safebrowsing": "Turvallisen selauksen estämät",
@ -654,7 +659,7 @@
"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 {{min}} - {{max}} merkkiä pitkä",
"anonymizer_notification": "<0>Huomioi:</0> IP-osoitteen anonymisointi on käytössä. Voit poistaa sen käytöstä <1>Yleisistä asetuksista</1>.",
"confirm_dns_cache_clear": "Haluatko varmasti tyhjentää DNS-välimuistin?",
"cache_cleared": "DNS-välimuistin tyhjennys onnistui",

View file

@ -73,7 +73,9 @@
"dhcp_dynamic_ip_found": "Votre système utilise une configuration d'adresses IP dynamiques pour l'interface <0>{{interfaceName}}</0>. Pour utiliser un serveur DHCP, une adresse IP statique est requise. Votre adresse IP actuelle est <0>{{ipAddress}}</0>. AdGuard Home va automatiquement définir cette adresse IP comme statique si vous appuyez sur le bouton « Activer le serveur DHCP ».",
"dhcp_lease_added": "« {{key}} » de bail statique ajoutée",
"dhcp_lease_deleted": "« {{key}} » de bail statique supprimée",
"dhcp_lease_updated": "Bail statique « {{key}} » mis à jour correctement",
"dhcp_new_static_lease": "Nouveau bail statique",
"dhcp_edit_static_lease": "Modifier le bail statique",
"dhcp_static_leases_not_found": "Aucun bail statique DHCP trouvé",
"dhcp_add_static_lease": "Ajoutez un bail statique",
"dhcp_reset_leases": "Réinitialiser tous les baux",
@ -283,6 +285,9 @@
"custom_ip": "IP personnalisée",
"blocking_ipv4": "Blocage IPv4",
"blocking_ipv6": "Blocage IPv6",
"blocked_response_ttl": "Réponse bloquée TTL",
"blocked_response_ttl_desc": "Spécifie pendant combien de secondes les clients doivent mettre en cache une réponse filtrée",
"form_enter_blocked_response_ttl": "Saisir le TTL de la réponse bloquée (secondes)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -319,7 +324,7 @@
"rule_label": "Règle(s)",
"list_label": "Liste",
"unknown_filter": "Filtre inconnu {{filterId}}",
"known_tracker": "Pisteur connu",
"known_tracker": "Traqueur connu",
"install_welcome_title": "Bienvenue sur AdGuard Home !",
"install_welcome_desc": "AdGuard Home est un seveur DNS pour bloquer les pubs et traceurs sur tout un réseau. Son but est de vous donner le contrôle sur l'ensemble de votre réseau et tous vos appareils sans programme côté client supplémentaire.",
"install_settings_title": "Interface web administrateur",
@ -537,8 +542,8 @@
"statistics_enable": "Activer les statistiques",
"ignore_domains": "Domaines ignorés (séparés par une nouvelle ligne)",
"ignore_domains_title": "Domaines ignorés",
"ignore_domains_desc_stats": "Les requêtes pour ces domaines ne sont pas écrites dans les statistiques",
"ignore_domains_desc_query": "Les requêtes pour ces domaines ne sont pas écrites dans le journal des requêtes",
"ignore_domains_desc_stats": "Les requêtes correspondantes à ces règles ne sont pas écrites dans les statistiques",
"ignore_domains_desc_query": "Les requêtes correspondantes à ces règles ne sont pas écrites dans le journal des requêtes",
"interval_hours": "{{count}} heure",
"interval_hours_plural": "{{count}} heures",
"filters_configuration": "Configuration des filtres",
@ -587,7 +592,7 @@
"filtered_custom_rules": "Filtré par des règles de filtrage personnalisées",
"choose_from_list": "Choisissez dans la liste",
"add_custom_list": "Ajouter une liste personnalisée",
"host_whitelisted": "Lhôte est sur liste blanche",
"host_whitelisted": "Lhôte est autorisé",
"check_ip": "Adresses IP : {{ip}}",
"check_cname": "CNAME : {{cname}}",
"check_reason": "Raison : {{reason}}",
@ -612,12 +617,12 @@
"validated_with_dnssec": "Validé avec DNSSEC",
"all_queries": "Toutes les requêtes",
"show_blocked_responses": "Bloqué",
"show_whitelisted_responses": "Ajouté à la liste blanche",
"show_whitelisted_responses": "Autorisée",
"show_processed_responses": "Traité",
"blocked_safebrowsing": "Navigation sécurisée bloquée",
"blocked_safebrowsing": "Bloqué par la Navigation sécurisée",
"blocked_adult_websites": "Bloqué par le Contrôle Parental",
"blocked_threats": "Menaces bloquées",
"allowed": "Autorisé",
"allowed": "Autorisées",
"filtered": "Filtré",
"rewritten": "Réécrit",
"safe_search": "Recherche Sécurisée",
@ -639,9 +644,9 @@
"filter_category_security": "Sécurité",
"filter_category_regional": "Régional",
"filter_category_other": "Autre",
"filter_category_general_desc": "Listes qui bloquent le pistage et la publicité sur la plupart des appareils",
"filter_category_general_desc": "Listes qui bloquent le suivi et la publicité sur la plupart des appareils",
"filter_category_security_desc": "Listes créées exprès pour bloquer les logiciels malveillants, des domaines hameçonneurs ou frauduleux",
"filter_category_regional_desc": "Listes axées sur les annonces régionales et les serveurs de pistage",
"filter_category_regional_desc": "Listes axées sur les annonces régionales et les serveurs de suivi",
"filter_category_other_desc": "Autres listes noires",
"setup_config_to_enable_dhcp_server": "Configurer les paramètres pour activer le serveur DHCP",
"original_response": "Réponse originale",
@ -654,7 +659,7 @@
"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 entre {{min}} et {{max}}  caractères",
"anonymizer_notification": "<0>Note :</0> L'anonymisation IP est activée. Vous pouvez la désactiver dans les <1>paramètres généraux</1>.",
"confirm_dns_cache_clear": "Voulez-vous vraiment vider le cache DNS ?",
"cache_cleared": "Le cache DNS a été vidé",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Statični lease \"{{key}}\" je uspješno uklonjen",
"dhcp_lease_updated": "Statični lease \"{{key}}\" uspješno ažuriran",
"dhcp_new_static_lease": "Novi static lease",
"dhcp_edit_static_lease": "Uredi statični lease",
"dhcp_static_leases_not_found": "Nisu pronađeni statični DHCP leases",
"dhcp_add_static_lease": "Dodaj static lease",
"dhcp_reset_leases": "Ponovno postavljanje svih najmova",
@ -283,6 +285,9 @@
"custom_ip": "Prilagođen IP",
"blocking_ipv4": "Blokiranje IPv4",
"blocking_ipv6": "Blokiranje IPv6",
"blocked_response_ttl": "TTL blokiranog odgovora",
"blocked_response_ttl_desc": "Određuje koliko sekundi bi klijenti trebali keširati filtrirani odgovor",
"form_enter_blocked_response_ttl": "Unesite TTL blokiranog odgovora (sekunde)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"parental_control": "Roditeljska zaštita",
"safe_browsing": "Sigurno surfanje",
"served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>",
"form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova",
"form_error_password_length": "Lozinka mora sadržavati od {{min}} do {{max}} znakova",
"anonymizer_notification": "<0>Napomena:</0>IP anonimizacija je omogućena. Možete ju onemogućiti u <1>općim postavkama</1>.",
"confirm_dns_cache_clear": "Jeste li sigurni da želite očistiti DNS predmemoriju?",
"cache_cleared": "DNS predmemorija je uspješno izbrisana",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Statikus bérlet \"{{key}}\" sikeresen törölve",
"dhcp_lease_updated": "Statikus bérlet \"{{key}}\" sikeresen frissítve",
"dhcp_new_static_lease": "Új statikus bérlet",
"dhcp_edit_static_lease": "Statikus bérlet szerkesztése",
"dhcp_static_leases_not_found": "Nem találhatóak statikus DHCP bérletek",
"dhcp_add_static_lease": "Statikus bérlet hozzáadása",
"dhcp_reset_leases": "Bérletek alaphelyzetbe",
@ -283,6 +285,9 @@
"custom_ip": "Egyedi IP",
"blocking_ipv4": "IPv4 blokkolása",
"blocking_ipv6": "IPv6 blokkolása",
"blocked_response_ttl": "Tiltott válasz TTL-je",
"blocked_response_ttl_desc": "Meghatározza, hogy a klienseknek hány másodpercig kell gyorsítótárazniuk a szűrt választ",
"form_enter_blocked_response_ttl": "Írja be a blokkolt válasz TTL-jét (másodpercben)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"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ó legyen {{min}} és {{max}} karakter között",
"anonymizer_notification": "<0>Megjegyzés:</0> Az IP anonimizálás engedélyezve van. Az <1>Általános beállításoknál letilthatja</1> .",
"confirm_dns_cache_clear": "Biztos benne, hogy törölni szeretné a DNS-gyorsítótárat?",
"cache_cleared": "A DNS gyorsítótár sikeresen törlődött",

View file

@ -8,11 +8,14 @@
"load_balancing_desc": "Permintaan satu server pada satu waktu. AdGuard Home akan menggunakan algoritma acak tertimbang untuk memilih server sehingga server tercepat akan lebih sering digunakan.",
"bootstrap_dns": "Server DNS bootstrap",
"bootstrap_dns_desc": "Alamat IP server DNS yang digunakan untuk menyelesaikan alamat IP resolver DoH/DoT yang Anda tentukan sebagai upstream. Komentar tidak diizinkan.",
"fallback_dns_title": "Server DNS cadangan",
"fallback_dns_desc": "Daftar server DNS cadangan yang digunakan ketika server DNS hulu tidak merespons. Sintaksnya sama dengan bidang hulu utama di atas.",
"fallback_dns_placeholder": "Masukkan satu server DNS cadangan per baris",
"local_ptr_title": "Server pembalik DNS pribadi",
"local_ptr_desc": "Server DNS yang digunakan AdGuard Home untuk kueri PTR lokal. Server ini digunakan untuk menyelesaikan nama host klien dengan alamat IP pribadi, misalnya \"192.168.12.34\", menggunakan DNS terbalik. Jika tidak disetel, AdGuard Home menggunakan alamat resolver DNS default OS Anda kecuali untuk alamat AdGuard Home itu sendiri.",
"local_ptr_default_resolver": "Secara bawaan, AdGuard Home menggunakan pemecah DNS terbalik: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home tidak dapat menentukan pemecah DNS terbalik yang sesuai untuk sistem ini.",
"local_ptr_placeholder": "Masukkan satu alamat server per baris",
"local_ptr_placeholder": "Masukkan satu alamat IP per baris",
"resolve_clients_title": "Aktifkan resolusi hostname klien",
"resolve_clients_desc": "Menyelesaikan alamat IP klien secara terbalik ke nama host mereka dengan mengirimkan kueri PTR ke resolver yang sesuai (server DNS pribadi untuk klien lokal, server upstream untuk klien dengan alamat IP publik).",
"use_private_ptr_resolvers_title": "Gunakan server pembalik DNS pribadi",
@ -70,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Static lease \"{{key}}\" berhasil dihapus",
"dhcp_lease_updated": "Static lease \"{{key}}\" berhasil diperbarui",
"dhcp_new_static_lease": "Static lease baru",
"dhcp_edit_static_lease": "Mengedit static lease",
"dhcp_static_leases_not_found": "DHCP static lease tidak ditemukan",
"dhcp_add_static_lease": "Tambah static lease",
"dhcp_reset_leases": "Atur ulang semua kontrak",
@ -136,6 +141,7 @@
"enforced_save_search": "Paksa pencarian aman",
"number_of_dns_query_to_safe_search": "Jumlah perminataan DNS ke mesin pencari yang dipaksa Pencarian Aman",
"average_processing_time": "Rata-rata waktu pemrosesan",
"processing_time": "Waktu pemrosesan",
"average_processing_time_hint": "Rata-rata waktu dalam milidetik untuk pemrosesan sebuah permintaan DNS",
"block_domain_use_filters_and_hosts": "Blokir domain menggunakan filter dan file hosts",
"filters_block_toggle_hint": "Anda dapat menyiapkan aturan pemblokiran di pengaturan <a>Penyaringan</a>.",
@ -170,6 +176,7 @@
"enabled_parental_toast": "Kontrol orang tua diaktifkan",
"disabled_safe_search_toast": "Pencarian aman dinonaktifkan",
"enabled_save_search_toast": "Pencarian aman diaktifkan",
"updated_save_search_toast": "Pengaturan Pencarian Aman telah diperbarui",
"enabled_table_header": "Diaktifkan",
"name_table_header": "Nama",
"list_url_table_header": "Daftar URL",
@ -259,12 +266,12 @@
"query_log_cleared": "Kueri log telah berhasil dihapus",
"query_log_updated": "Log permintaan telah berhasil diperbarui",
"query_log_clear": "Hapus kueri log",
"query_log_retention": "Retensi kueri log",
"query_log_retention": "Rotasi kueri log",
"query_log_enable": "Aktifkan log",
"query_log_configuration": "Konfigurasi log",
"query_log_disabled": "Kueri log dinonaktifkan dan dapat dikonfigurasi di <0>pengaturan</0>",
"query_log_strict_search": "Gunakan tanda kutip ganda untuk pencarian ketat",
"query_log_retention_confirm": "Apakah Anda yakin ingin mengubah retensi kueri log? Jika Anda menurunkan nilai interval, beberapa data akan hilang",
"query_log_retention_confirm": "Apakah Anda yakin ingin mengubah rotasi kueri log? Jika Anda menurunkan nilai interval, beberapa data akan hilang",
"anonymize_client_ip": "Anonim IP klien",
"anonymize_client_ip_desc": "Jangan simpan alamat lengkap IP klien dalam log dan statistik",
"dns_config": "Konfigurasi server DNS",
@ -278,6 +285,9 @@
"custom_ip": "Custom IP",
"blocking_ipv4": "Blokiran IPv4",
"blocking_ipv6": "Blokiran IPv6",
"blocked_response_ttl": "Respon TLL diblokir",
"blocked_response_ttl_desc": "Menentukan berapa detik klien harus menyimpan respons yang difilter dalam cache",
"form_enter_blocked_response_ttl": "Masukkan TTL respons yang diblokir (detik)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -293,6 +303,8 @@
"rate_limit": "Batas nilai",
"edns_enable": "Aktifkan EDNS Klien Subnet",
"edns_cs_desc": "Tambahkan opsi EDNS Client Subnet (ECS) ke permintaan upstream dan catat nilai yang dikirim oleh klien di log kueri.",
"edns_use_custom_ip": "Gunakan IP khusus untuk EDNS",
"edns_use_custom_ip_desc": "Izinkan untuk menggunakan IP kustom untuk EDNS",
"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",
@ -396,7 +408,7 @@
"encryption_issuer": "Penerbit",
"encryption_hostnames": "Nama host",
"encryption_reset": "Anda yakin ingin mengatur ulang pengaturan enkripsi?",
"encryption_warning": "Perhatian",
"encryption_warning": "Peringatan",
"topline_expiring_certificate": "Sertifikat SSL Anda hampir kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.",
"topline_expired_certificate": "Sertifikat SSL Anda kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.",
"form_error_port_range": "Masukkan nomor port di kisaran 80-65535",
@ -444,7 +456,7 @@
"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": "Perangkat yang tidak ada dalam daftar klien Persisten yang mungkin masih menggunakan AdGuard Home",
"auto_clients_desc": "Informasi tentang alamat IP perangkat yang menggunakan atau mungkin menggunakan AdGuard Home. Informasi ini dikumpulkan dari beberapa sumber, termasuk file host, reverse DNS, dll.",
"access_title": "Pengaturan akses",
"access_desc": "Disini anda dapat mengatur aturan akses untuk server AdGuard Home DNS",
"access_allowed_title": "Klien yang diizinkan",
@ -457,6 +469,7 @@
"updates_checked": "Versi baru AdGuard Home tersedia\n",
"updates_version_equal": "AdGuard Home sudah tebaru",
"check_updates_now": "Periksa pembaruan sekarang",
"version_request_error": "Pemeriksaan pembaruan gagal. Harap periksa koneksi internet anda.",
"dns_privacy": "DNS Privasi",
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Memakai <1>{{address}}</1> string.",
"setup_dns_privacy_2": "<0>DNS-over-TLS:</0> Memakai <1>{{address}}</1> string.",
@ -527,6 +540,8 @@
"statistics_retention_confirm": "Apakah Anda yakin ingin mengubah retensi statistik? Jika Anda menurunkan nilai interval, beberapa data akan hilang",
"statistics_cleared": "Statistik berhasil dihapus",
"statistics_enable": "Aktifkan statistik",
"ignore_domains": "Domain yang diabaikan (dipisahkan oleh baris baru)",
"ignore_domains_title": "Domain yang diabaikan",
"ignore_domains_desc_stats": "Kueri yang cocok dengan aturan ini tidak ditulis ke statistik",
"ignore_domains_desc_query": "Kueri yang cocok dengan aturan ini tidak ditulis ke log kueri",
"interval_hours": "{{count}} jam",
@ -644,13 +659,36 @@
"parental_control": "Kontrol Orang Tua",
"safe_browsing": "Penjelajahan Aman",
"served_from_cache": "{{value}} <i>(disajikan dari cache)</i>",
"form_error_password_length": "Kata sandi harus minimal {{value}} karakter",
"form_error_password_length": "Kata sandi harus terdiri dari {{min}} hingga {{max}}",
"anonymizer_notification": "<0>Catatan:</0> Anonimisasi IP diaktifkan. Anda dapat menonaktifkannya di <1>Pengaturan umum</1> .",
"confirm_dns_cache_clear": "Apakah Anda yakin ingin menghapus cache DNS?",
"cache_cleared": "Cache DNS berhasil dibersihkan",
"clear_cache": "Hapus cache",
"make_static": "Jadikan statis",
"theme_auto_desc": "Otomatis (berdasarkan skema warna perangkat anda)",
"theme_dark_desc": "Tema gelap",
"theme_light_desc": "Tema terang",
"disable_for_seconds": "Untuk {{count}} detik",
"disable_for_seconds_plural": "Untuk {{count}} detik",
"disable_for_minutes": "Untuk {{count}} menit",
"disable_for_minutes_plural": "Untuk {{count}} menit",
"disable_for_hours": "Untuk {{count}} jam",
"disable_for_hours_plural": "Untuk {{count}} jam",
"disable_until_tomorrow": "Sampai besok",
"disable_notify_for_seconds": "Hentikan perlindungan selama {{count}} detik",
"disable_notify_for_seconds_plural": "Hentikan perlindungan selama {{count}} detik",
"disable_notify_for_minutes": "Hentikan perlindungan selama {{count}} menit",
"disable_notify_for_minutes_plural": "Hentikan perlindungan selama {{count}} menit",
"disable_notify_for_hours": "Hentikan perlindungan selama {{count}} jam",
"disable_notify_for_hours_plural": "Hentikan perlindungan selama {{count}} jam",
"disable_notify_until_tomorrow": "Hentikan perlindungan sampai besok",
"enable_protection_timer": "Perlindungan akan diaktifkan dalam {{time}}",
"custom_retention_input": "Masukkan retensi dalam hitungan jam",
"custom_rotation_input": "Masukkan rotasi dalam hitungan jam",
"protection_section_label": "Perlindungan",
"log_and_stats_section_label": "Log kueri dan statistik",
"ignore_query_log": "Abaikan klien ini di log kueri",
"ignore_statistics": "Abaikan klien ini di statistik",
"schedule_services": "Menjeda pemblokiran layanan",
"schedule_services_desc": "Mengonfigurasi jadwal jeda filter pemblokiran layanan",
"schedule_services_desc_client": "Mengonfigurasi jadwal jeda filter pemblokiran layanan untuk klien ini",
@ -665,6 +703,17 @@
"schedule_new": "Jadwal baru",
"schedule_edit": "Edit jadwal",
"schedule_save": "Simpan jadwal",
"schedule_add": "Tambahkan jadwal",
"schedule_remove": "Hapus jadwal",
"schedule_from": "Dari",
"schedule_to": "Hingga",
"sunday": "Minggu",
"monday": "Senin",
"tuesday": "Selasa",
"wednesday": "Rabu",
"thursday": "Kamis",
"friday": "Jumat",
"saturday": "Sabtu",
"sunday_short": "Ming",
"monday_short": "Sen",
"tuesday_short": "Sel",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Lease statico \"{{key}}\" eliminato correttamente",
"dhcp_lease_updated": "Locazione statica \"{{key}}\" aggiornata con successo",
"dhcp_new_static_lease": "Nuovo lease statico",
"dhcp_edit_static_lease": "Modifica locazione statica",
"dhcp_static_leases_not_found": "Non è stato trovato nessun leases statico DHCP",
"dhcp_add_static_lease": "Aggiungi lease statico",
"dhcp_reset_leases": "Reimposta tutti i temporanei",
@ -283,6 +285,9 @@
"custom_ip": "IP personalizzato",
"blocking_ipv4": "Blocca IPv4",
"blocking_ipv6": "Blocca IPv6",
"blocked_response_ttl": "Risposta TTL bloccata",
"blocked_response_ttl_desc": "Specifica per quanti secondi i client devono tenere nella cache una risposta filtrata",
"form_enter_blocked_response_ttl": "Inserisci tempo di vita (TTL) della risposta bloccata (secondi)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS su HTTPS",
"dns_over_tls": "DNS su TLS",
@ -537,8 +542,8 @@
"statistics_enable": "Attiva statistiche",
"ignore_domains": "Domini ignorati (separati da nuova riga)",
"ignore_domains_title": "Domini ignorati",
"ignore_domains_desc_stats": "Le richieste per questi domini non vengono scritte nelle statistiche",
"ignore_domains_desc_query": "Le richieste per questi domini non vengono scritte nel registro delle richieste",
"ignore_domains_desc_stats": "Le richieste che corrispondono a queste regole non vengono scritte nelle statistiche",
"ignore_domains_desc_query": "Le richieste che corrispondono a queste regole non vengono scritte nel registro delle richieste",
"interval_hours": "{{count}} ora",
"interval_hours_plural": "{{count}} ore",
"filters_configuration": "Configurazione filtri",
@ -654,7 +659,7 @@
"parental_control": "Controllo Parentale",
"safe_browsing": "Navigazione Sicura",
"served_from_cache": "{{value}} <i>(fornito dalla cache)</i>",
"form_error_password_length": "La password deve contenere almeno {{value}} caratteri",
"form_error_password_length": "La password deve contenere da {{min}} a {{max}} caratteri",
"anonymizer_notification": "<0>Attenzione:</0> L'anonimizzazione dell'IP è abilitata. Puoi disabilitarla in <1>Impostazioni generali</1>.",
"confirm_dns_cache_clear": "Sei sicuro di voler cancellare la cache DNS?",
"cache_cleared": "Cache DNS è stata cancellata correttamente",

View file

@ -73,7 +73,9 @@
"dhcp_dynamic_ip_found": "お使いのシステムは、インターフェース<0>{{interfaceName}}</0>用に動的IPアドレス構成を使用しています。DHCPサーバを使用するには、静的IPアドレスで設定する必要があります。あなたの現在のIPアドレスは<0>{{ipAddress}}</0>です。「DHCPサーバを有効にする」ボタンを押すと、AdGuard Homeは自動的にこのIPアドレスを静的IPアドレスとして設定します。",
"dhcp_lease_added": "静的リース \"{{key}}\" の追加が完了しました。",
"dhcp_lease_deleted": "静的リース \"{{key}}\" の削除が完了しました。",
"dhcp_lease_updated": "静的リース \"{{key}}\" の更新に成功しました。",
"dhcp_new_static_lease": "新規静的割り当て",
"dhcp_edit_static_lease": "静的リースを編集",
"dhcp_static_leases_not_found": "DHCP静的割り当てはありません",
"dhcp_add_static_lease": "静的割り当てを追加する",
"dhcp_reset_leases": "すべてのリースをリセットする",
@ -283,6 +285,9 @@
"custom_ip": "カスタムIP",
"blocking_ipv4": "ブロック中のIPv4",
"blocking_ipv6": "ブロック中のIPv6",
"blocked_response_ttl": "Blocked Response TTLブロック済み応答のTTL",
"blocked_response_ttl_desc": "フィルタリングされた応答をクライアントがキャッシュしておく時間(秒)を指定します。",
"form_enter_blocked_response_ttl": "ブロック済み応答のTTL秒単位を入力してください",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"parental_control": "ペアレンタルコントロール",
"safe_browsing": "セーフブラウジング",
"served_from_cache": "{{value}} <i>(キャッシュから応答)</i>",
"form_error_password_length": "パスワードは{{value}}文字以上にしてください",
"form_error_password_length": "パスワードの長さは{{min}}〜{{max}}文字にしてください。",
"anonymizer_notification": "【<0>注意</0>】IPの匿名化が有効になっています。 <1>一般設定</1>で無効にできます。",
"confirm_dns_cache_clear": "DNS キャッシュをクリアしてもよろしいですか?",
"cache_cleared": "DNSキャッシュのクリア完了です。",

View file

@ -73,7 +73,9 @@
"dhcp_dynamic_ip_found": "시스템은 <0>{{interfaceName}}</0> 인터페이스에 동적 IP 주소를 사용합니다. DHCP 서버를 사용하려면 고정 IP 주소를 설정해야 합니다. 현재 IP 주소는 <0>{{ipAddress}}</0>입니다. 'DHCP 서버 활성화' 버튼을 누르면 AdGuard Home이 이 IP 주소를 고정 IP 주소로 자동 설정합니다.",
"dhcp_lease_added": "'{{key}}' 고정 임대 정상적으로 추가되었습니다",
"dhcp_lease_deleted": "'{{key}}' 고정 임대 정상적으로 삭제되었습니다",
"dhcp_lease_updated": "'{{key}}' 고정 임대 정상적으로 업데이트되었습니다.",
"dhcp_new_static_lease": "새 고정 임대",
"dhcp_edit_static_lease": "고정 임대 수정",
"dhcp_static_leases_not_found": "DHCP 고정 임대를 찾을 수 없음",
"dhcp_add_static_lease": "고정 임대 추가",
"dhcp_reset_leases": "모든 임대 초기화",
@ -283,6 +285,9 @@
"custom_ip": "사용자 지정 IP",
"blocking_ipv4": "IPv4 차단",
"blocking_ipv6": "IPv6 차단",
"blocked_response_ttl": "차단된 TTL 응답",
"blocked_response_ttl_desc": "클라이언트가 필터링된 응답을 캐시해야 하는 시간(초)을 지정합니다.",
"form_enter_blocked_response_ttl": "차단된 응답 TTL(초)을 입력하세요.",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -403,7 +408,7 @@
"encryption_issuer": "발행자",
"encryption_hostnames": "호스트 이름",
"encryption_reset": "암호화 설정을 재설정하시겠습니까?",
"encryption_warning": "경고",
"encryption_warning": "주의",
"topline_expiring_certificate": "SSL 인증서가 곧 만료됩니다. 업데이트<0> 암호화 설정</0>.",
"topline_expired_certificate": "SSL 인증서가 만료되었습니다. 업데이트<0> 암호화 설정</0>.",
"form_error_port_range": "80-65535 범위의 포트 번호를 입력하세요",
@ -654,7 +659,7 @@
"parental_control": "자녀 보호",
"safe_browsing": "세이프 브라우징",
"served_from_cache": "{{value}} <i>(캐시에서 제공)</i>",
"form_error_password_length": "비밀번호는 {{value}}자 이상이어야 합니다",
"form_error_password_length": "비밀번호는 {{min}}~{{max}}자 길이여야 합니다.",
"anonymizer_notification": "<0>참고:</0> IP 익명화가 활성화되었습니다. <1>일반 설정</1>에서 비활성화할 수 있습니다.",
"confirm_dns_cache_clear": "정말로 DNS 캐시를 지우시겠습니까?",
"cache_cleared": "DNS 캐시를 성공적으로 지웠습니다",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Statische uitgifte \"{{key}}\" met succes verwijderd",
"dhcp_lease_updated": "Statische lease \"{{key}}\" succesvol bijgewerkt",
"dhcp_new_static_lease": "Voeg static lease toe",
"dhcp_edit_static_lease": "Statische lease bewerken",
"dhcp_static_leases_not_found": "Geen DHCP static lease gevonden",
"dhcp_add_static_lease": "Voeg statische lease toe",
"dhcp_reset_leases": "Alle leases resetten",
@ -283,6 +285,9 @@
"custom_ip": "Aangepast IP",
"blocking_ipv4": "Blokkeren IP4",
"blocking_ipv6": "Blokkeren IP6",
"blocked_response_ttl": "Geblokkeerde reactie TTL",
"blocked_response_ttl_desc": "Hiermee geef je op hoeveel seconden de clients een gefilterd antwoord in de cache moeten opslaan",
"form_enter_blocked_response_ttl": "Voer geblokkeerd antwoord TTL in (seconden)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-via-HTTPS",
"dns_over_tls": "DNS-via-TLS",
@ -654,7 +659,7 @@
"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 {{min}} tot {{max}} tekens lang zijn",
"anonymizer_notification": "<0>Opmerking:</0> IP-anonimisering is ingeschakeld. Je kunt het uitschakelen in <1>Algemene instellingen</1>.",
"confirm_dns_cache_clear": "Weet je zeker dat je de DNS-cache wilt wissen?",
"cache_cleared": "DNS-cache succesvol gewist",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Dzierżawa statyczna \"{{key}}\" pomyślnie usunięta",
"dhcp_lease_updated": "Dzierżawa statyczna \"{{key}}\" pomyślnie zaktualizowana",
"dhcp_new_static_lease": "Nowa dzierżawa statyczna",
"dhcp_edit_static_lease": "Edytuj dzierżawę statyczną",
"dhcp_static_leases_not_found": "Nie znaleziono statycznych dzierżaw DHCP",
"dhcp_add_static_lease": "Dodaj dzierżawę statyczną",
"dhcp_reset_leases": "Zresetuj wszystkie dzierżawy",
@ -283,6 +285,9 @@
"custom_ip": "Niestandardowy adres IP",
"blocking_ipv4": "Blokowanie IPv4",
"blocking_ipv6": "Blokowanie IPv6",
"blocked_response_ttl": "TTL zablokowanej odpowiedzi",
"blocked_response_ttl_desc": "Określa, przez ile sekund klienci powinni buforować przefiltrowaną odpowiedź",
"form_enter_blocked_response_ttl": "Wprowadź TTL zablokowanej odpowiedzi (sekundy)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -403,7 +408,7 @@
"encryption_issuer": "Zgłaszający",
"encryption_hostnames": "Nazwy hostów",
"encryption_reset": "Czy na pewno chcesz zresetować ustawienia szyfrowania?",
"encryption_warning": "Uwaga",
"encryption_warning": "Ostrzeżenie",
"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",
@ -654,7 +659,7 @@
"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ć co najmniej {{value}} znaków",
"form_error_password_length": "Hasło musi zawierać od {{min}} do {{max}} znaków",
"anonymizer_notification": "<0>Uwaga:</0> Anonimizacja IP jest włączona. Możesz ją wyłączyć w <1>Ustawieniach ogólnych</1>.",
"confirm_dns_cache_clear": "Czy na pewno chcesz wyczyścić pamięć podręczną DNS?",
"cache_cleared": "Pamięć podręczna DNS została pomyślnie wyczyszczona",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Concessão estática \"{{key}}\" excluída com sucesso",
"dhcp_lease_updated": "Concessão estática \"{{key}}\" atualizada com sucesso",
"dhcp_new_static_lease": "Nova concessão estática",
"dhcp_edit_static_lease": "Editar concessão estática",
"dhcp_static_leases_not_found": "Nenhuma concessão DHCP estática foi encontrada",
"dhcp_add_static_lease": "Adicionar nova concessão estática",
"dhcp_reset_leases": "Redefinir todas as concessões",
@ -283,6 +285,9 @@
"custom_ip": "IP personalizado",
"blocking_ipv4": "Bloqueando IPv4",
"blocking_ipv6": "Bloqueando IPv6",
"blocked_response_ttl": "Resposta bloqueada TTL",
"blocked_response_ttl_desc": "Especifica por quantos segundos os clientes devem armazenar em cache uma resposta filtrada",
"form_enter_blocked_response_ttl": "Insira o TTL da resposta bloqueada (segundos)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-sobre-HTTPS",
"dns_over_tls": "DNS-sobre-TLS",
@ -654,7 +659,7 @@
"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 entre {{min}} e {{max}} caracteres",
"anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-lo em <1>Configurações gerais</1>.",
"confirm_dns_cache_clear": "Tem certeza de que deseja limpar o cache DNS?",
"cache_cleared": "Cache DNS limpo com sucesso",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Concessão estática \"{{key}}\" excluída com sucesso",
"dhcp_lease_updated": "Concessão estática \"{{key}}\" atualizada com sucesso",
"dhcp_new_static_lease": "Nova concessão estática",
"dhcp_edit_static_lease": "Editar concessão estática",
"dhcp_static_leases_not_found": "Nenhuma concessão DHCP estática foi encontrada",
"dhcp_add_static_lease": "Adicionar nova concessão estática",
"dhcp_reset_leases": "Repor todas as concessões",
@ -283,6 +285,9 @@
"custom_ip": "IP Personalizado",
"blocking_ipv4": "A bloquear IPv4",
"blocking_ipv6": "A bloquear IPv6",
"blocked_response_ttl": "Resposta bloqueada TTL",
"blocked_response_ttl_desc": "Especifica por quantos segundos os clientes devem armazenar em cache uma resposta filtrada",
"form_enter_blocked_response_ttl": "Insira o TTL da resposta bloqueada (segundos)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-sobre-HTTPS",
"dns_over_tls": "DNS-sobre-TLS",
@ -403,7 +408,7 @@
"encryption_issuer": "Emissor",
"encryption_hostnames": "Nomes dos servidores",
"encryption_reset": "Tem a certeza de que deseja repor a definição de criptografia?",
"encryption_warning": "Aviso",
"encryption_warning": "Cuidado",
"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",
@ -654,7 +659,7 @@
"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 {{min}} a {{max}} caracteres",
"anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-la em <1>Definições gerais</1>.",
"confirm_dns_cache_clear": "Tem certeza de que quer limpar a cache DNS?",
"cache_cleared": "O cache DNS foi apagado com sucesso",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "\"{{key}}\" statică închiriată eliminată cu succes",
"dhcp_lease_updated": "\"{{key}}\" statică închiriată actualizată cu succes",
"dhcp_new_static_lease": "Închiriere statică nouă",
"dhcp_edit_static_lease": "Editați închiriere statică",
"dhcp_static_leases_not_found": "Nu s-au găsit închirieri statice DHCP",
"dhcp_add_static_lease": "Adăugați închiriere statică",
"dhcp_reset_leases": "Resetați toate închirierile",
@ -283,6 +285,9 @@
"custom_ip": "IP personalizat",
"blocking_ipv4": "Blocarea IPv4",
"blocking_ipv6": "Blocarea IPv6",
"blocked_response_ttl": "Răspuns blocat TTL",
"blocked_response_ttl_desc": "Specifică pentru câte secunde trebuie să memoreze clienții un răspuns filtrat",
"form_enter_blocked_response_ttl": "Introduceți răspunsul blocat TTL (secunde)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -403,7 +408,7 @@
"encryption_issuer": "Emitent",
"encryption_hostnames": "Nume de host",
"encryption_reset": "Sunteți sigur că doriți să resetați setările de criptare?",
"encryption_warning": "Avertisment",
"encryption_warning": "Atenție",
"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",
@ -654,7 +659,7 @@
"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ă între {{min}} și {{max}} caractere",
"anonymizer_notification": "<0>Nota:</0> Anonimizarea IP este activată. Puteți să o dezactivați în <1>Setări generale</1>.",
"confirm_dns_cache_clear": "Sunteți sigur că doriți să ștergeți memoria cache DNS?",
"cache_cleared": "Cache-ul DNS a fost golit cu succes",

View file

@ -73,7 +73,9 @@
"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_updated": "Статическая аренда «{{key}}» успешно обновлена",
"dhcp_new_static_lease": "Новая статическая аренда",
"dhcp_edit_static_lease": "Редактирование статической аренды",
"dhcp_static_leases_not_found": "Не найдено статических аренд DHCP",
"dhcp_add_static_lease": "Добавить статическую аренду",
"dhcp_reset_leases": "Сбросить все аренды",
@ -283,6 +285,9 @@
"custom_ip": "Свой IP",
"blocking_ipv4": "Блокировка IPv4",
"blocking_ipv6": "Блокировка IPv6",
"blocked_response_ttl": "TTL заблокированного ответа",
"blocked_response_ttl_desc": "Указывает, в течение скольких секунд клиенты должны кешировать отфильтрованный ответ",
"form_enter_blocked_response_ttl": "Введите TTL заблокированного ответа (в секундах)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"parental_control": "Родительский контроль",
"safe_browsing": "Безопасный интернет",
"served_from_cache": "{{value}} <i>(получено из кеша)</i>",
"form_error_password_length": "Пароль должен быть длиной не меньше {{value}} символов",
"form_error_password_length": "Пароль должен содержать от {{min}} до {{max}} символов",
"anonymizer_notification": "<0>Внимание:</0> включена анонимизация IP-адресов. Вы можете отключить её в разделе <1>Основные настройки</1>.",
"confirm_dns_cache_clear": "Вы уверены, что хотите очистить кеш DNS?",
"cache_cleared": "Кеш DNS успешно очищен",

View file

@ -73,7 +73,9 @@
"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ý",
"dhcp_lease_deleted": "Statický \"{{key}}\" prenájmu bol úspešne vymazaný",
"dhcp_lease_updated": "Statický prenájom \"{{key}}\" bol úspešne aktualizovaný",
"dhcp_new_static_lease": "Nový statický prenájom",
"dhcp_edit_static_lease": "Upraviť statický prenájom",
"dhcp_static_leases_not_found": "Nebol nájdený žiadny statický DHCP prenájom",
"dhcp_add_static_lease": "Pridať statický prenájom",
"dhcp_reset_leases": "Resetovať všetky prenájmy",
@ -283,6 +285,9 @@
"custom_ip": "Vlastná IP adresa",
"blocking_ipv4": "Blokovanie IPv4",
"blocking_ipv6": "Blokovanie IPv6",
"blocked_response_ttl": "Blokovaná odozva TTL",
"blocked_response_ttl_desc": "Určuje, na koľko sekúnd by mali klienti uložiť filtrovanú odozvu do vyrovnávacej pamäte",
"form_enter_blocked_response_ttl": "Zadajte TTL blokovanej odozve (sekundy)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"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ť od {{min}} do {{max}} znakov",
"anonymizer_notification": "<0>Poznámka:</0> Anonymizácia IP je zapnutá. Môžete ju vypnúť vo <1>Všeobecných nastaveniach</1>.",
"confirm_dns_cache_clear": "Naozaj chcete vymazať vyrovnávaciu pamäť DNS?",
"cache_cleared": "Vyrovnávacia pamäť DNS bola úspešne vymazaná",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Statičen najem \"{{key}}\" je uspešno izbrisan",
"dhcp_lease_updated": "Statični najem \"{{key}}\" je uspešno posodobljen",
"dhcp_new_static_lease": "Nov statični najem",
"dhcp_edit_static_lease": "Uredi statični najem",
"dhcp_static_leases_not_found": "Ni najdenih statičnih najemov DHCP",
"dhcp_add_static_lease": "Dodaj statičen najem",
"dhcp_reset_leases": "Ponastavi vse najeme",
@ -283,6 +285,9 @@
"custom_ip": "IP po meri",
"blocking_ipv4": "Onemogočanje IPv4",
"blocking_ipv6": "Onemogočanje IPv6",
"blocked_response_ttl": "Zaviran odziv TTL",
"blocked_response_ttl_desc": "Določa, koliko sekund naj odjemalci predpomnijo filtrirane odgovore",
"form_enter_blocked_response_ttl": "Vnesite TTL zaviranega odgovora (sekunde)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-prek-HTTPS",
"dns_over_tls": "DNS-prek-TLS",
@ -654,7 +659,7 @@
"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 od {{min}} do {{max}} znakov",
"anonymizer_notification": "<0>Opomba:</0> Anonimizacija IP je omogočena. Onemogočite ga lahko v <1>Splošnih nastavitvah</1>.",
"confirm_dns_cache_clear": "Ali ste prepričani, da želite počistiti predpomnilnik DNS?",
"cache_cleared": "Predpomnilnik DNS je bil uspešno počiščen",

View file

@ -73,7 +73,9 @@
"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_lease_updated": "Statičko iznajmljivanje \"{{key}}\" ažurirano",
"dhcp_new_static_lease": "Novo statičko iznajmljivanje",
"dhcp_edit_static_lease": "Uređivanje statičkog iznajmljivanja",
"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",
@ -283,6 +285,9 @@
"custom_ip": "Prilagođeni IP",
"blocking_ipv4": "Blokiranje IPv4",
"blocking_ipv6": "Blokiranje IPv6",
"blocked_response_ttl": "TTL blokiranog odgovora",
"blocked_response_ttl_desc": "Određuje koliko sekundi klijenti treba da keširaju filtrirani odgovor",
"form_enter_blocked_response_ttl": "Unesite TTL blokiranog odgovora (sekunde)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"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",
"form_error_password_length": "Lozinka mora imati od {{min}} do {{max}} znakova",
"anonymizer_notification": "<0>Nota:</0> IP prepoznavanje je omogućeno. Možete ga onemogućiti u opštim <1>postavkama</1>.",
"confirm_dns_cache_clear": "Želite li zaista da obrišite DNS keš?",
"cache_cleared": "DNS keš je uspešno očišćen",

View file

@ -73,7 +73,9 @@
"dhcp_dynamic_ip_found": "Din enhet använder en dynamisk IP-adress för gränssnittet <0>{{interfaceName}}</0>. För att kunna använda DHCP-servern behövs en statisk IP-adress. Din nuvarande IP-adress är <0>{{ipAddress}}</0>. AdGuard Home kommer automatiskt att göra denna IP-adress statisk om du trycker på knappen \"Aktivera DHCP\".",
"dhcp_lease_added": "Statisk lease \"{{key}}\" har lagts till",
"dhcp_lease_deleted": "Statisk lease \"{{key}}\" har raderats",
"dhcp_lease_updated": "Statiskt lease \"{{key}}\" har uppdaterats",
"dhcp_new_static_lease": "Ny statisk lease",
"dhcp_edit_static_lease": "Redigera statiskt lease",
"dhcp_static_leases_not_found": "Inga statiska DHCP-leases hittade",
"dhcp_add_static_lease": "Lägg till statisk lease",
"dhcp_reset_leases": "Återställ alla leasingavtal",
@ -174,6 +176,7 @@
"enabled_parental_toast": "Föräldrakontroll inkopplat",
"disabled_safe_search_toast": "Säker webbsökning bortkopplat",
"enabled_save_search_toast": "Säker webbsökning inkopplat",
"updated_save_search_toast": "Inställningarna för Säker sökning uppdaterade",
"enabled_table_header": "Inkopplat",
"name_table_header": "Namn",
"list_url_table_header": "Lista URL",
@ -282,6 +285,9 @@
"custom_ip": "Eget IP",
"blocking_ipv4": "Blockera IPv4",
"blocking_ipv6": "Blockera IPv6",
"blocked_response_ttl": "TTL för blockerat svar",
"blocked_response_ttl_desc": "Anger hur många sekunder klienterna ska cache ett filtrerat svar",
"form_enter_blocked_response_ttl": "Ange TTL för blockerat svar (sekunder)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -653,7 +659,7 @@
"parental_control": "Föräldrakontroll",
"safe_browsing": "Säker surfning",
"served_from_cache": "{{value}} <i>(levereras från cache)</i>",
"form_error_password_length": "Lösenordet måste vara minst {{value}} tecken långt",
"form_error_password_length": "Lösenordet måste vara {{min}} till {{max}} tecken långt",
"anonymizer_notification": "<0>Observera:</0> IP-anonymisering är aktiverad. Du kan inaktivera den i <1>Allmänna inställningar</1>.",
"confirm_dns_cache_clear": "Är du säker på att du vill rensa DNS-cache?",
"cache_cleared": "DNS-cacheminnet har rensats",
@ -669,8 +675,19 @@
"disable_for_hours": "I {{count}} timme",
"disable_for_hours_plural": "I {{count}} timmar",
"disable_until_tomorrow": "Tills imorgon",
"disable_notify_for_seconds": "Inaktivera skyddet i {{count}} sekund",
"disable_notify_for_seconds_plural": "Inaktivera skyddet i {{count}} sekunder",
"disable_notify_for_minutes": "Inaktivera skyddet i {{count}} minut",
"disable_notify_for_minutes_plural": "Inaktivera skyddet i {{count}} minuter",
"disable_notify_for_hours": "Inaktivera skyddet i {{count}} timme",
"disable_notify_for_hours_plural": "Inaktivera skyddet i {{count}} timmar",
"disable_notify_until_tomorrow": "Inaktivera skyddet tills imorgon",
"enable_protection_timer": "Skyddet kommer att aktiveras i {{time}}",
"custom_retention_input": "Ange retention i timmar",
"custom_rotation_input": "Ange rotation i timmar",
"protection_section_label": "Skydd",
"log_and_stats_section_label": "Förfrågningslogg och statistik",
"ignore_query_log": "Ignorera den här klienten i frågeloggen",
"ignore_statistics": "Ignorera denna kund i statistiken",
"schedule_services": "Pausa blockering av tjänst",
"schedule_services_desc": "Konfigurera pausschemat för det tjänsteblockerande filtret",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Sabit kiralama \"{{key}}\" başarıyla silindi",
"dhcp_lease_updated": "Statik kiralama \"{{key}}\" başarıyla güncellendi",
"dhcp_new_static_lease": "Yeni sabit kiralama",
"dhcp_edit_static_lease": "Statik kiralamayı düzenle",
"dhcp_static_leases_not_found": "Sabit DHCP kiralaması bulunamadı",
"dhcp_add_static_lease": "Sabit kiralama ekle",
"dhcp_reset_leases": "Tüm kiralamaları sıfırla",
@ -90,7 +92,7 @@
"client_details": "İstemci ayrıntıları",
"details": "Ayrıntılar",
"back": "Geri",
"dashboard": "Ana Sayfa",
"dashboard": "Pano",
"settings": "Ayarlar",
"filters": "Filtreler",
"filter": "Filtre",
@ -283,6 +285,9 @@
"custom_ip": "Özel IP",
"blocking_ipv4": "IPv4 engelleme",
"blocking_ipv6": "IPv6 engelleme",
"blocked_response_ttl": "Engellenen yanıt kullanım süresi",
"blocked_response_ttl_desc": "İstemcilerin filtrelenmiş bir yanıtı kaç saniye süreyle önbelleğe alması gerektiğini belirtir",
"form_enter_blocked_response_ttl": "Engellenen yanıt kullanım süresini girin (saniye)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -370,7 +375,7 @@
"install_devices_ios_list_4": "DNS alanına AdGuard Home sunucunuzun adreslerini girin.",
"get_started": "Başlayın",
"next": "Sonraki",
"open_dashboard": "Ana Sayfayı Aç",
"open_dashboard": "Panoyu 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",
@ -654,7 +659,7 @@
"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ıdır",
"form_error_password_length": "Parola {{min}} ila {{max}} karakter uzunluğunda olmalıdır",
"anonymizer_notification": "<0>Not:</0> IP anonimleştirme etkinleştirildi. Bunu <1>Genel ayarlardan</1> devre dışı bırakabilirsiniz.",
"confirm_dns_cache_clear": "DNS önbelleğini temizlemek istediğinizden emin misiniz?",
"cache_cleared": "DNS önbelleği başarıyla temizlendi",

View file

@ -73,7 +73,9 @@
"dhcp_dynamic_ip_found": "Ваша система використовує конфігурацію з динамічною IP-адресою для інтерфейсу <0>{{interfaceName}}</0>. Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Ваша поточна IP-адреса <0>{{ipAddress}}</0>. Ми автоматично встановимо цю IP-адресу як статичну, якщо ви натиснете кнопку «Увімкнути DHCP-сервер».",
"dhcp_lease_added": "Статичну оренду «{{key}}» успішно додано",
"dhcp_lease_deleted": "Статичну оренду «{{key}}» успішно видалено",
"dhcp_lease_updated": "Статичну оренду \"{{key}}\" успішно оновлено",
"dhcp_new_static_lease": "Нова статична оренда",
"dhcp_edit_static_lease": "Редагувати статичну оренду",
"dhcp_static_leases_not_found": "Не знайдено статичних оренд DHCP",
"dhcp_add_static_lease": "Додати статичну оренду",
"dhcp_reset_leases": "Скинути всі аренди",
@ -283,6 +285,9 @@
"custom_ip": "Власний IP",
"blocking_ipv4": "Блокування IPv4",
"blocking_ipv6": "Блокування IPv6",
"blocked_response_ttl": "TTL заблокованої відповіді",
"blocked_response_ttl_desc": "Вказує, скільки секунд клієнти повинні кешувати відфільтровану відповідь",
"form_enter_blocked_response_ttl": "Введіть TTL заблокованої відповіді (секунди)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"parental_control": "Батьківський контроль",
"safe_browsing": "Безпечний перегляд",
"served_from_cache": "{{value}} <i>(отримано з кешу)</i>",
"form_error_password_length": "Пароль мусить мати принаймні {{value}} символів",
"form_error_password_length": "Пароль має містити від {{min}} до {{max}} символів",
"anonymizer_notification": "<0>Примітка:</0> IP-анонімізацію ввімкнено. Ви можете вимкнути його в <1>Загальні налаштування</1> .",
"confirm_dns_cache_clear": "Ви впевнені, що бажаєте очистити кеш DNS?",
"cache_cleared": "Кеш DNS успішно очищено",

View file

@ -73,7 +73,9 @@
"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",
"dhcp_lease_deleted": "Cho thuê tĩnh \"{{key}}\" đã xóa thành công",
"dhcp_lease_updated": "Cho thuê tĩnh \"{{key}}\" được cập nhật thành công",
"dhcp_new_static_lease": "Cho thuê tĩnh mới",
"dhcp_edit_static_lease": "Chỉnh sửa hợp đồng thuê tĩnh",
"dhcp_static_leases_not_found": "Không tìm thấy DHCP cho thuê tĩnh",
"dhcp_add_static_lease": "Thêm thuê tĩnh",
"dhcp_reset_leases": "Đặt lại tất cả các hợp đồng thuê",
@ -283,6 +285,9 @@
"custom_ip": "IP tuỳ chỉnh",
"blocking_ipv4": "Chặn IPv4",
"blocking_ipv6": "Chặn IPv6",
"blocked_response_ttl": "Chặn phản hồi TTL",
"blocked_response_ttl_desc": "Chỉ định trong bao nhiêu giây máy khách sẽ lưu vào bộ đệm một phản hồi đã được lọc",
"form_enter_blocked_response_ttl": "Nhập phản hồi bị chặn TTL (giây)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"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ự",
"form_error_password_length": "Mật khẩu phải dài từ {{min}} đến {{max}} ký tự",
"anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>.",
"confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?",
"cache_cleared": "Đã xóa thành công bộ đệm DNS",

View file

@ -7,9 +7,9 @@
"load_balancing": "负载均衡",
"load_balancing_desc": "一次查询一台服务器。AdGuard Home 将使用加权随机算法来选择服务器,以便更常使用最快的服务器。",
"bootstrap_dns": "Bootstrap DNS 服务器",
"bootstrap_dns_desc": "DNS 服务器的 IP 地址,用于解析指定为上游的 DoH/DoT 解析器的 IP 地址。不允许评论。",
"bootstrap_dns_desc": "DNS 服务器的 IP 地址,用于解析指定为上游的 DoH/DoT 解析器的 IP 地址。不允许添加注释。",
"fallback_dns_title": "后备 DNS 服务器",
"fallback_dns_desc": "当上游 DNS 服务器没有响应时使用的后备 DNS 服务器列表。语法与上面的主要上游字段相同。",
"fallback_dns_desc": "当上游 DNS 服务器没有响应时使用的后备 DNS 服务器列表。语法与上面的主要上游字段相同。",
"fallback_dns_placeholder": "每行输入一个后备 DNS 服务器",
"local_ptr_title": "私人反向 DNS 服务器",
"local_ptr_desc": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器。这些服务器将使用反向 DNS 解析具有私人 IP 地址的客户机的主机名,比如 \"192.168.12.34\"。如果没有设置,除非是 AdGuard Home 里设置的地址AdGuard Home 都将自动使用您的操作系统的默认 DNS 解析器。",
@ -58,7 +58,7 @@
"dhcp_form_subnet_input": "子网掩码",
"dhcp_form_range_title": "IP 地址范围",
"dhcp_form_range_start": "起始 IP 地址",
"dhcp_form_range_end": "末尾 IP 地址",
"dhcp_form_range_end": "结束 IP 地址",
"dhcp_form_lease_title": "DHCP 租约时间(秒)",
"dhcp_form_lease_input": "租期",
"dhcp_interface_select": "选择 DHCP 接口",
@ -67,13 +67,15 @@
"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 地址。请手动为此网络接口设置静态 IP 地址。",
"dhcp_dynamic_ip_found": "您的系统对网络接口 <0>{{interfaceName}}</0> 使用了动态 IP 地址配置。要使用 DHCP 服务器,则必须对此网络接口使用静态 IP 地址配置。此网络接口当前的 IP 地址为 <0>{{ipAddress}}</0>。如您点击「启用 DHCP 服务器」按钮AdGuard Home 将自动修改此网络接口以使用静态 IP 地址。",
"dhcp_lease_added": "静态租约 \"{{key}}\" 已成功添加",
"dhcp_lease_deleted": "静态租约 \"{{key}}\" 已成功删除",
"dhcp_lease_updated": "静态租约 “{{key}}” 已成功更新",
"dhcp_new_static_lease": "新建静态租约",
"dhcp_edit_static_lease": "编辑静态租约",
"dhcp_static_leases_not_found": "未找到 DHCP 静态租约",
"dhcp_add_static_lease": "添加静态租约",
"dhcp_reset_leases": "重置所有租约",
@ -112,7 +114,7 @@
"disable_protection": "禁用保护",
"disabled_protection": "保护已禁用",
"refresh_statics": "刷新统计数据",
"dns_query": "DNS查询",
"dns_query": "DNS 查询",
"blocked_by": "<0>已被过滤器拦截</0>",
"stats_malware_phishing": "被拦截的恶意/钓鱼网站",
"stats_adult": "被拦截的成人网站",
@ -154,7 +156,7 @@
"dns_settings": "DNS 设置",
"dns_blocklists": "DNS 黑名单",
"dns_allowlists": "DNS 白名单",
"dns_blocklists_desc": "AdGuard Home将阻止匹配DNS拦截清单的域名",
"dns_blocklists_desc": "AdGuard Home 将阻止 DNS 黑名单里的域名",
"dns_allowlists_desc": "来自 DNS 白名单的域名将被允许,即使它们位于任意黑名单中也是如此。",
"custom_filtering_rules": "自定义过滤规则",
"encryption_settings": "加密设置",
@ -162,7 +164,7 @@
"upstream_dns": "上游 DNS 服务器",
"upstream_dns_help": "每行输入一个服务器地址。<a>了解更多</a>关于配置上游 DNS 服务器的内容",
"upstream_dns_configured_in_file": "配置路径{{path}}",
"test_upstream_btn": "测试上游 DNS",
"test_upstream_btn": "测试上游",
"upstreams": "上游服务器",
"upstream": "上游服务器",
"apply_btn": "应用",
@ -207,9 +209,9 @@
"custom_filter_rules": "自定义过滤器规则",
"custom_filter_rules_hint": "请确保每行只输入一条规则。你可以输入符合 adblock 语法或 Hosts 语法的规则。",
"system_host_files": "系统主机文件",
"examples_title": "例",
"examples_title": "例",
"example_meaning_filter_block": "阻止 example.org 域名及其所有子域名;",
"example_meaning_filter_whitelist": "解除 example.org 及其所有子域名的封锁",
"example_meaning_filter_whitelist": "解除对 example.org 及其所有子域名的拦截",
"example_meaning_host_block": "对 example.org不包括它的子域名以 127.0.0.1 作为响应;",
"example_comment": "! 这是一行注释。",
"example_comment_meaning": "只是一条注释;",
@ -220,7 +222,7 @@
"example_upstream_udp": "常规 DNS通过 UDP、主机名",
"example_upstream_dot": "加密 <0>DNS-over-TLS</0>",
"example_upstream_doh": "加密 <0>DNS-over-HTTPS</0>",
"example_upstream_doh3": "带有强制 <0>HTTP/3</0> 的加密 DNS-over-HTTPS并且没有回退到 HTTP/2 或更低版本;",
"example_upstream_doh3": "带有强制 <0>HTTP/3</0> 的加密 DNS-over-HTTPS不会回退到 HTTP/2 或更低版本;",
"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 ",
@ -260,7 +262,7 @@
"rule_added_to_custom_filtering_toast": "规则已添加到自定义过滤规则列表中 {{rule}}",
"query_log_response_status": "状态: {{value}}",
"query_log_filtered": "被 {{filter}} 过滤",
"query_log_confirm_clear": "确定想要清除全部查询日志吗?",
"query_log_confirm_clear": "确定想要清除全部查询日志吗?",
"query_log_cleared": "查询日志已成功清除",
"query_log_updated": "已成功更新查询日志",
"query_log_clear": "清除查询日志",
@ -268,12 +270,12 @@
"query_log_enable": "启用日志",
"query_log_configuration": "日志配置",
"query_log_disabled": "查询日志已禁用,在<0>这些设置</0>中能配置它们",
"query_log_strict_search": "使用双引号进行严谨搜索",
"query_log_retention_confirm": "确定要更改查询记录保留时间吗?如果减少时间间隔数值,某些数据可能会丢失",
"anonymize_client_ip": "匿名化客户端IP",
"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": "DNS 缓存配置",
"dns_cache_config_desc": "您可以在此处配置 DNS 缓存",
"blocking_mode": "拦截模式",
"default": "默认",
@ -283,6 +285,9 @@
"custom_ip": "自定义 IP",
"blocking_ipv4": "拦截 IPv4",
"blocking_ipv6": "拦截 IPv6",
"blocked_response_ttl": "屏蔽的 TTL 应答",
"blocked_response_ttl_desc": "指定客户端应缓存已过滤响应的秒数",
"form_enter_blocked_response_ttl": "输入拦截的 TTL 应答(秒)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -293,7 +298,7 @@
"download_mobileconfig_doh": "下载适用于 DNS-over-HTTPS 的 .mobileconfig",
"download_mobileconfig_dot": "下载适用于 DNS-over-TLS 的 .mobileconfig",
"download_mobileconfig": "下载配置文件",
"plain_dns": "无加密DNS",
"plain_dns": "无加密 DNS",
"form_enter_rate_limit": "输入限制速率",
"rate_limit": "速度限制",
"edns_enable": "启用 EDNS 客户端子网",
@ -303,11 +308,11 @@
"rate_limit_desc": "每个客户端每秒钟查询次数的限制。设置为 0 意味着不限制。",
"blocking_ipv4_desc": "拦截 A 记录请求返回的 IP 地址",
"blocking_ipv6_desc": "拦截 AAAA 记录请求返回的 IP 地址",
"blocking_mode_default": "默认:被 Adblock 规则拦截时反应为零 IP 地址A记录0.0.0.0AAAA记录::);被/etc/hosts 规则拦截时反应为规则中指定 IP 地址",
"blocking_mode_default": "默认:被 Adblock 规则拦截时反应为零 IP 地址A记录0.0.0.0AAAA记录::);被 /etc/hosts 规则拦截时反应为规则中指定 IP 地址",
"blocking_mode_refused": "REFUSED以 REFUSED 码响应请求",
"blocking_mode_nxdomain": "NXDOMAINNXDOMAIN码响应",
"blocking_mode_nxdomain": "NXDOMAIN NXDOMAIN 码响应",
"blocking_mode_null_ip": "空 IP以零 IP 地址响应A 记录 0.0.0.0AAAA 记录 ::",
"blocking_mode_custom_ip": "自定IP以手动设置的IP地址响应",
"blocking_mode_custom_ip": "自定IP以手动设置的 IP 地址响应",
"theme_auto": "自动",
"theme_light": "浅色主题",
"theme_dark": "深色主题",
@ -331,7 +336,7 @@
"install_settings_dns_desc": "您将需要使用以下地址来设置您的设备或路由器的 DNS 服务器:",
"install_settings_all_interfaces": "所有接口",
"install_auth_title": "身份认证",
"install_auth_desc": "需要对 AdGuard Home 的网页管理界面配置密码认证。即使该 AdGuard Home 只能通过您的本地网络访问,避免 AdGuard Home 被不受限制地访问依旧十分重要。",
"install_auth_desc": "需要对 AdGuard Home 的网页管理界面配置密码认证。即使 AdGuard Home 只能通过本地网络访问,为它添加访问限制依旧十分重要。",
"install_auth_username": "用户名",
"install_auth_password": "密码",
"install_auth_confirm": "确认密码",
@ -459,12 +464,12 @@
"access_disallowed_title": "不允许的客户端",
"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_blocked_desc": "不要将此功能与过滤器混淆。AdGuard Home 将排除匹配这些的 DNS 查询并且这些查询将不会在查询日志中显示。在此可以明确指定域名、通配符wildcard和网址过滤的规则例如 \"example.org\"、\"*.example.org\" 或 \"||example.org^\"。",
"access_settings_saved": "访问设置保存成功",
"updates_checked": "AdGuard Home 的新版本现在可用",
"updates_version_equal": "AdGuard Home已经是最新版本",
"check_updates_now": "立即检查更新",
"version_request_error": "检查更新失败。请检查您的因特网连接。",
"version_request_error": "检查更新失败。请检查互联网连接。",
"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> 字符串。",
@ -492,7 +497,7 @@
"rewrite_confirm_delete": "您确定要删除 \"{{key}}\" 的 DNS 重写?",
"rewrite_desc": "可以轻松地为特定域名配置自定义 DNS 响应。",
"rewrite_applied": " 重定向规则已应用",
"rewrite_hosts_applied": "根据hosts文件规则已被重写",
"rewrite_hosts_applied": "根据 hosts 文件规则已被重写",
"dns_rewrites": "DNS 重写",
"form_domain": "输入域",
"form_answer": "输入 IP 地址或域名",
@ -531,12 +536,12 @@
"statistics_retention": "统计保留",
"statistics_retention_desc": "如果您减少该间隔的数值, 某些数据可能会丢失",
"statistics_clear": " 清除统计数据",
"statistics_clear_confirm": "确定要清除统计数据?",
"statistics_clear_confirm": "确定要清除统计数据?",
"statistics_retention_confirm": "您确定要更改统计记录保留时间吗? 如果您减少间隔时间的值, 某些数据可能会丢失。",
"statistics_cleared": "统计数据已成功清除",
"statistics_enable": "启用统计数据",
"ignore_domains": "忽略的域(以换行符分隔)",
"ignore_domains_title": "被忽略的域",
"ignore_domains": "忽略的(以换行符分隔)",
"ignore_domains_title": "被忽略的",
"ignore_domains_desc_stats": "匹配这些规则的查询不在统计信息",
"ignore_domains_desc_query": "匹配这些规则的查询不在查询日志",
"interval_hours": "{{count}} 小时",
@ -559,24 +564,24 @@
"network": "网络",
"descr": "描述",
"whois": "WHOIS",
"filtering_rules_learn_more": "<0>了解更多</0>关于创建自己的hosts清单。",
"blocked_by_response": "因响应的CNAME或IP被屏蔽",
"blocked_by_cname_or_ip": "按CNAME或IP拦截",
"filtering_rules_learn_more": "<0>了解更多</0>关于创建自己的 hosts 清单。",
"blocked_by_response": "因响应的 CNAME 或 IP 被屏蔽",
"blocked_by_cname_or_ip": "按 CNAME 或 IP 拦截",
"try_again": "重试",
"domain_desc": "输入您要重写的域名或通配符。",
"example_rewrite_domain": "仅重写此域名的响应。",
"example_rewrite_wildcard": "重写所有<0>example.org</0> 子域的响应。",
"rewrite_ip_address": "IP地址:在 A或AAAA响应中使用这个IP",
"rewrite_domain_name": "域名 添加一个CNAME记录",
"rewrite_ip_address": "IP 地址:在 A 或 AAAA 响应中使用这个 IP",
"rewrite_domain_name": "域名:添加一个 CNAME 记录",
"rewrite_A": "<0>A</0>:特殊值,保持来自上游的<0>A</0>记录",
"rewrite_AAAA": "<0>AAAA</0>:特殊值,保持来自上游的<0>AAAA</0>记录",
"disable_ipv6": "禁用 IPv6 地址的解析",
"disable_ipv6_desc": "删除对 IPv6 地址(类型 AAAA的所有 DNS 查询,并从 HTTPS 响应中删除 IPv6 提示。",
"disable_ipv6_desc": "丢弃对 IPv6 地址(类型 AAAA的所有 DNS 查询,并从 HTTPS 响应中删除 IPv6 相关的信息。",
"fastest_addr": "最快的 IP 地址",
"fastest_addr_desc": "查询所有 DNS 服务器并返回所有响应中速度最快的 IP 地址。因 AdGuard Home 必须等待全部 DNS 服务器均有所回应,因而会降低 DNS 查询的速度,但同时,此举将会改善总体的连接。",
"autofix_warning_text": "若您单击「修复」AdGuardHome 将会配置您的系统以使用 AdGuardHome 的 DNS 服务器。",
"fastest_addr_desc": "查询所有 DNS 服务器并返回所有响应中速度最快的 IP 地址。因 AdGuard Home 必须等待全部 DNS 服务器响应,这会降低 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/resolv/resolv.conf</0><0>停止DNSStubListener重新加载系统解析服务</0>",
"autofix_warning_result": "因此,默认情况下所有来自系统的DNS请求都将由AdGuardHome处理。",
"autofix_warning_result": "因此,默认情况下所有来自系统的 DNS 请求都将由 AdGuard Home 处理。",
"tags_title": "标签",
"tags_desc": "您可以选择与客户端对应的标记。标签可以包含在过滤规则中,并允许您更准确地应用它们。<0>了解更多</0>。",
"form_select_tags": "选择客户端标签",
@ -594,22 +599,22 @@
"check_service": "服务名称:{{service}}",
"service_name": "服务名称",
"check_not_found": "未在您的筛选列表中找到",
"client_confirm_block": "确定要阻止客户端 \"{{ip}}\"?",
"client_confirm_block": "确定要阻止客户端 \"{{ip}}\"?",
"client_confirm_unblock": "您确定要解除对客户端 \"{{ip}}\" 的封锁吗?",
"client_blocked": "客户端 \"{{ip}}\" 被成功拦截",
"client_unblocked": "成功解锁客户端 \"{{ip}}\"",
"client_unblocked": "成功解除对 \"{{ip}}\" 客户端的封锁",
"static_ip": "静态IP地址",
"static_ip_desc": "AdGuard Home 是一个服务器,所以它需要一个静态IP地址才能正常工作。否则在某些情况下你的路由器可能会给这个设备分配一个不同的IP地址。",
"set_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地址。您想要继续吗",
"confirm_static_ip": "AdGuard Home 将把 {{ip}} 配置为静态 IP 地址。您想要继续吗?",
"list_updated": "{{count}} 列表已更新",
"list_updated_plural": "{{count}} 条列表已更新",
"dnssec_enable": "启用DNSSEC",
"dnssec_enable": "启用 DNSSEC",
"dnssec_enable_desc": "在发出 DNS 查询中设置 DNSSEC 标志并检查结果(需要启用 DNSSEC 的解析器)。",
"validated_with_dnssec": "通过DNSSEC验证",
"validated_with_dnssec": "通过 DNSSEC 验证",
"all_queries": "所有查询记录",
"show_blocked_responses": "已拦截",
"show_whitelisted_responses": "已列入白名单",
@ -624,9 +629,9 @@
"blocklist": "黑名单",
"milliseconds_abbreviation": "毫秒",
"cache_size": "缓存大小",
"cache_size_desc": "DNS 缓存大小(单位:字节)。要关闭缓存,请留空。",
"cache_ttl_min_override": "覆盖最小TTL值",
"cache_ttl_max_override": "覆盖最大TTL值",
"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 值(秒)",
@ -654,7 +659,7 @@
"parental_control": "家长控制",
"safe_browsing": "安全浏览",
"served_from_cache": "{{value}}<i>(由缓存提供)</i>",
"form_error_password_length": "密码必须至少有 {{value}} 个字符",
"form_error_password_length": "密码长度必须为 {{min}} 到 {{max}} 个字符",
"anonymizer_notification": "<0>注意:</0> IP 匿名化已启用。您可以在<1>常规设置</1>中禁用它。",
"confirm_dns_cache_clear": "您确定要清除 DNS 缓存吗?",
"cache_cleared": "已成功清除 DNS 缓存",
@ -685,9 +690,9 @@
"ignore_query_log": "在查询日志中忽略此客户端",
"ignore_statistics": "在统计数据中忽略此客户端",
"schedule_services": "暂停服务拦截",
"schedule_services_desc": "配置服务拦截过滤器的暂停计划",
"schedule_services_desc_client": "为此客户端配置服务拦截过滤器的暂停计划",
"schedule_desc": "安排已拦截服务禁用的时间",
"schedule_services_desc": "配置服务拦截过滤器的暂停计划",
"schedule_services_desc_client": "为此客户端配置服务拦截过滤器的暂停计划",
"schedule_desc": "安排暂停拦截服务的时间段",
"schedule_invalid_select": "开始时间必须在结束时间之前",
"schedule_select_days": "选择日",
"schedule_timezone": "选择时区",

View file

@ -73,7 +73,9 @@
"dhcp_dynamic_ip_found": "您的系統使用動態 IP 位址配置供介面 <0>{{interfaceName}}</0>。為了使用動態主機設定協定DHCP伺服器靜態 IP 位址必須被設定。您現行的 IP 位址為 <0>{{ipAddress}}</0>。如果您按\"啟用 DHCP 伺服器\" 按鈕AdGuard Home 將自動地設定此 IP 位址作為靜態。",
"dhcp_lease_added": "靜態租約 \"{{key}}\" 被成功地加入",
"dhcp_lease_deleted": "靜態租約 \"{{key}}\" 被成功地刪除",
"dhcp_lease_updated": "「{{key}}」的靜態租約已成功更新",
"dhcp_new_static_lease": "新的靜態租約",
"dhcp_edit_static_lease": "編輯靜態租約",
"dhcp_static_leases_not_found": "無已發現之動態主機設定協定DHCP靜態租約",
"dhcp_add_static_lease": "新增靜態租約",
"dhcp_reset_leases": "重置所有的租約",
@ -283,6 +285,9 @@
"custom_ip": "自訂的 IP",
"blocking_ipv4": "封鎖 IPv4",
"blocking_ipv6": "封鎖 IPv6",
"blocked_response_ttl": "已封鎖的回應之存活時間TTL",
"blocked_response_ttl_desc": "指定客戶端應將過濾後的回應存入快取的秒數",
"form_enter_blocked_response_ttl": "請輸入已封鎖回應的存活時間(秒)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@ -654,7 +659,7 @@
"parental_control": "家長控制",
"safe_browsing": "安全瀏覽",
"served_from_cache": "{{value}} <i>(由快取提供)</i>",
"form_error_password_length": "密碼必須為至少長 {{value}} 個字元",
"form_error_password_length": "密碼長度必須為 {{min}} 到 {{max}} 個字符",
"anonymizer_notification": "<0>注意:</0>IP 匿名化被啟用。您可在<1>一般設定</1>中禁用它。",
"confirm_dns_cache_clear": "您確定您想要清除 DNS 快取嗎?",
"cache_cleared": "DNS 快取被成功地清除",

View file

@ -660,6 +660,24 @@ export const removeStaticLease = (config) => async (dispatch) => {
}
};
export const updateStaticLeaseRequest = createAction('UPDATE_STATIC_LEASE_REQUEST');
export const updateStaticLeaseFailure = createAction('UPDATE_STATIC_LEASE_FAILURE');
export const updateStaticLeaseSuccess = createAction('UPDATE_STATIC_LEASE_SUCCESS');
export const updateStaticLease = (config) => async (dispatch) => {
dispatch(updateStaticLeaseRequest());
try {
await apiClient.updateStaticLease(config);
dispatch(updateStaticLeaseSuccess(config));
dispatch(addSuccessToast(i18next.t('dhcp_lease_updated', { key: config.hostname || config.ip })));
dispatch(toggleLeaseModal());
dispatch(getDhcpStatus());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(updateStaticLeaseFailure());
}
};
export const removeToast = createAction('REMOVE_TOAST');
export const toggleBlocking = (

View file

@ -274,6 +274,8 @@ class Api {
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
DHCP_UPDATE_STATIC_LEASE = { path: 'dhcp/update_static_lease', method: 'POST' };
DHCP_RESET = { path: 'dhcp/reset', method: 'POST' };
DHCP_LEASES_RESET = { path: 'dhcp/reset_leases', method: 'POST' };
@ -320,6 +322,14 @@ class Api {
return this.makeRequest(path, method, parameters);
}
updateStaticLease(config) {
const { path, method } = this.DHCP_UPDATE_STATIC_LEASE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
resetDhcp() {
const { path, method } = this.DHCP_RESET;
return this.makeRequest(path, method);

View file

@ -4,6 +4,7 @@ import ReactTable from 'react-table';
import { withTranslation } from 'react-i18next';
import { sortIp } from '../../../helpers/helpers';
import { MODAL_TYPE } from '../../../helpers/constants';
import { LocalStorageHelper, LOCAL_STORAGE_KEYS } from '../../../helpers/localStorageHelper';
class Table extends Component {
cellWrap = ({ value }) => (
@ -85,7 +86,8 @@ class Table extends Component {
loading={processing || processingAdd || processingDelete}
className="-striped -highlight card-table-overflow"
showPagination
defaultPageSize={10}
defaultPageSize={LocalStorageHelper.getItem(LOCAL_STORAGE_KEYS.REWRITES_PAGE_SIZE) || 10}
onPageSizeChange={(size) => LocalStorageHelper.setItem(LOCAL_STORAGE_KEYS.REWRITES_PAGE_SIZE, size)}
minRows={5}
ofText="/"
previousText={t('previous_btn')}

View file

@ -6,6 +6,7 @@ import CellWrap from '../ui/CellWrap';
import { MODAL_TYPE } from '../../helpers/constants';
import { formatDetailedDateTime } from '../../helpers/helpers';
import { isValidAbsolutePath } from '../../helpers/form';
import { LOCAL_STORAGE_KEYS, LocalStorageHelper } from '../../helpers/localStorageHelper';
class Table extends Component {
getDateCell = (row) => CellWrap(row, formatDetailedDateTime);
@ -126,12 +127,17 @@ class Table extends Component {
loading, filters, t, whitelist,
} = this.props;
const localStorageKey = whitelist
? LOCAL_STORAGE_KEYS.ALLOWLIST_PAGE_SIZE
: LOCAL_STORAGE_KEYS.BLOCKLIST_PAGE_SIZE;
return (
<ReactTable
data={filters}
columns={this.columns}
showPagination
defaultPageSize={10}
defaultPageSize={LocalStorageHelper.getItem(localStorageKey) || 10}
onPageSizeChange={(size) => LocalStorageHelper.setItem(localStorageKey, size)}
loading={loading}
minRows={6}
ofText="/"

View file

@ -9,6 +9,7 @@ import CellWrap from '../../ui/CellWrap';
import whoisCell from './whoisCell';
import LogsSearchLink from '../../ui/LogsSearchLink';
import { sortIp } from '../../../helpers/helpers';
import { LocalStorageHelper, LOCAL_STORAGE_KEYS } from '../../../helpers/localStorageHelper';
const COLUMN_MIN_WIDTH = 200;
@ -85,7 +86,10 @@ class AutoClients extends Component {
]}
className="-striped -highlight card-table-overflow"
showPagination
defaultPageSize={10}
defaultPageSize={LocalStorageHelper.getItem(LOCAL_STORAGE_KEYS.AUTO_CLIENTS_PAGE_SIZE) || 10}
onPageSizeChange={(size) => (
LocalStorageHelper.setItem(LOCAL_STORAGE_KEYS.AUTO_CLIENTS_PAGE_SIZE, size)
)}
minRows={5}
ofText="/"
previousText={t('previous_btn')}

View file

@ -19,6 +19,7 @@ import Card from '../../../ui/Card';
import CellWrap from '../../../ui/CellWrap';
import LogsSearchLink from '../../../ui/LogsSearchLink';
import Modal from '../Modal';
import { LocalStorageHelper, LOCAL_STORAGE_KEYS } from '../../../../helpers/localStorageHelper';
const ClientsTable = ({
clients,
@ -342,7 +343,10 @@ const ClientsTable = ({
]}
className="-striped -highlight card-table-overflow"
showPagination
defaultPageSize={10}
defaultPageSize={LocalStorageHelper.getItem(LOCAL_STORAGE_KEYS.CLIENTS_PAGE_SIZE) || 10}
onPageSizeChange={(size) => (
LocalStorageHelper.setItem(LOCAL_STORAGE_KEYS.CLIENTS_PAGE_SIZE, size)
)}
minRows={5}
ofText="/"
previousText={t('previous_btn')}

View file

@ -22,6 +22,7 @@ const Form = ({
submitting,
processingAdding,
cidr,
isEdit,
}) => {
const { t } = useTranslation();
const dispatch = useDispatch();
@ -45,6 +46,7 @@ const Form = ({
placeholder={t('form_enter_mac')}
normalize={normalizeMac}
validate={[validateRequiredValue, validateMac]}
disabled={isEdit}
/>
</div>
<div className="form__group">
@ -112,6 +114,7 @@ Form.propTypes = {
submitting: PropTypes.bool.isRequired,
processingAdding: PropTypes.bool.isRequired,
cidr: PropTypes.string.isRequired,
isEdit: PropTypes.bool,
};
export default reduxForm({ form: FORM_NAME.LEASE })(Form);

View file

@ -5,9 +5,11 @@ import ReactModal from 'react-modal';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Form from './Form';
import { toggleLeaseModal } from '../../../../actions';
import { MODAL_TYPE } from '../../../../helpers/constants';
const Modal = ({
isModalOpen,
modalType,
handleSubmit,
processingAdding,
cidr,
@ -32,7 +34,11 @@ const Modal = ({
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
{modalType === MODAL_TYPE.EDIT_LEASE ? (
<Trans>dhcp_edit_static_lease</Trans>
) : (
<Trans>dhcp_new_static_lease</Trans>
)}
</h4>
<button type="button" className="close" onClick={toggleModal}>
<span className="sr-only">Close</span>
@ -53,6 +59,7 @@ const Modal = ({
cidr={cidr}
rangeStart={rangeStart}
rangeEnd={rangeEnd}
isEdit={modalType === MODAL_TYPE.EDIT_LEASE}
/>
</div>
</ReactModal>
@ -61,6 +68,7 @@ const Modal = ({
Modal.propTypes = {
isModalOpen: PropTypes.bool.isRequired,
modalType: PropTypes.string.isRequired,
handleSubmit: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
cidr: PropTypes.string.isRequired,

View file

@ -3,10 +3,15 @@ import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../../helpers/constants';
import { LEASES_TABLE_DEFAULT_PAGE_SIZE, MODAL_TYPE } from '../../../../helpers/constants';
import { sortIp } from '../../../../helpers/helpers';
import Modal from './Modal';
import { addStaticLease, removeStaticLease } from '../../../../actions';
import {
addStaticLease,
removeStaticLease,
toggleLeaseModal,
updateStaticLease,
} from '../../../../actions';
const cellWrap = ({ value }) => (
<div className="logs__row o-hidden">
@ -18,8 +23,10 @@ const cellWrap = ({ value }) => (
const StaticLeases = ({
isModalOpen,
modalType,
processingAdding,
processingDeleting,
processingUpdating,
staticLeases,
cidr,
rangeStart,
@ -31,7 +38,12 @@ const StaticLeases = ({
const handleSubmit = (data) => {
const { mac, ip, hostname } = data;
if (modalType === MODAL_TYPE.EDIT_LEASE) {
dispatch(updateStaticLease({ mac, ip, hostname }));
} else {
dispatch(addStaticLease({ mac, ip, hostname }));
}
};
const handleDelete = (ip, mac, hostname = '') => {
@ -80,19 +92,35 @@ const StaticLeases = ({
Cell: (row) => {
const { ip, mac, hostname } = row.original;
return <div className="logs__row logs__row--center">
return (
<div className="logs__row logs__row--center">
<button
type="button"
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm"
title={t('delete_table_action')}
disabled={processingDeleting}
onClick={() => handleDelete(ip, mac, hostname)}
className="btn btn-icon btn-outline-primary btn-sm mr-2"
onClick={() => dispatch(toggleLeaseModal({
type: MODAL_TYPE.EDIT_LEASE,
config: { ip, mac, hostname },
}))}
disabled={processingUpdating}
title={t('edit_table_action')}
>
<svg className="icons">
<use xlinkHref="#delete"/>
<svg className="icons icon12">
<use xlinkHref="#edit" />
</svg>
</button>
</div>;
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() => handleDelete(ip, mac, hostname)}
disabled={processingDeleting}
title={t('delete_table_action')}
>
<svg className="icons icon12">
<use xlinkHref="#delete" />
</svg>
</button>
</div>
);
},
},
]}
@ -105,6 +133,7 @@ const StaticLeases = ({
/>
<Modal
isModalOpen={isModalOpen}
modalType={modalType}
handleSubmit={handleSubmit}
processingAdding={processingAdding}
cidr={cidr}
@ -119,8 +148,10 @@ const StaticLeases = ({
StaticLeases.propTypes = {
staticLeases: PropTypes.array.isRequired,
isModalOpen: PropTypes.bool.isRequired,
modalType: PropTypes.string.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingDeleting: PropTypes.bool.isRequired,
processingUpdating: PropTypes.bool.isRequired,
cidr: PropTypes.string.isRequired,
rangeStart: PropTypes.string,
rangeEnd: PropTypes.string,

View file

@ -49,6 +49,7 @@ const Dhcp = () => {
isModalOpen,
processingAdding,
processingDeleting,
processingUpdating,
processingDhcp,
v4,
v6,
@ -56,6 +57,7 @@ const Dhcp = () => {
enabled,
dhcp_available,
interfaces,
modalType,
} = useSelector((state) => state.dhcp, shallowEqual);
const interface_name = useSelector(
@ -273,8 +275,11 @@ const Dhcp = () => {
<StaticLeases
staticLeases={staticLeases}
isModalOpen={isModalOpen}
toggleModal={toggleModal}
modalType={modalType}
processingAdding={processingAdding}
processingDeleting={processingDeleting}
processingUpdating={processingUpdating}
cidr={cidr}
rangeStart={dhcp?.values?.v4?.range_start}
rangeEnd={dhcp?.values?.v4?.range_end}

View file

@ -68,7 +68,7 @@ const Form = ({
return <form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-sm-6">
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<label htmlFor="ratelimit"
className="form__label form__label--with-desc">
@ -178,6 +178,28 @@ const Form = ({
</div>)}
</>
)}
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<label htmlFor="blocked_response_ttl"
className="form__label form__label--with-desc">
<Trans>blocked_response_ttl</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>blocked_response_ttl_desc</Trans>
</div>
<Field
name="blocked_response_ttl"
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_blocked_response_ttl')}
normalize={toNumber}
validate={validateRequiredValue}
min={UINT32_RANGE.MIN}
max={UINT32_RANGE.MAX}
/>
</div>
</div>
</div>
<button
type="submit"

View file

@ -13,6 +13,7 @@ const Config = () => {
ratelimit,
blocking_ipv4,
blocking_ipv6,
blocked_response_ttl,
edns_cs_enabled,
edns_cs_use_custom,
edns_cs_custom_ip,
@ -38,6 +39,7 @@ const Config = () => {
blocking_mode,
blocking_ipv4,
blocking_ipv6,
blocked_response_ttl,
edns_cs_enabled,
disable_ipv6,
dnssec_enabled,

View file

@ -34,7 +34,7 @@ const validate = (values) => {
return errors;
};
const clearFields = (change, setTlsConfig, t) => {
const clearFields = (change, setTlsConfig, validateTlsConfig, t) => {
const fields = {
private_key: '',
certificate_chain: '',
@ -53,6 +53,7 @@ const clearFields = (change, setTlsConfig, t) => {
Object.keys(fields)
.forEach((field) => change(field, fields[field]));
setTlsConfig(fields);
validateTlsConfig(fields);
}
};
@ -102,6 +103,7 @@ let Form = (props) => {
subject,
warning_validation,
setTlsConfig,
validateTlsConfig,
certificateSource,
privateKeySource,
privateKeySaved,
@ -419,7 +421,7 @@ let Form = (props) => {
type="button"
className="btn btn-secondary btn-standart"
disabled={submitting || processingConfig}
onClick={() => clearFields(change, setTlsConfig, t)}
onClick={() => clearFields(change, setTlsConfig, validateTlsConfig, t)}
>
<Trans>reset_settings</Trans>
</button>
@ -455,6 +457,7 @@ Form.propTypes = {
subject: PropTypes.string,
t: PropTypes.func.isRequired,
setTlsConfig: PropTypes.func.isRequired,
validateTlsConfig: PropTypes.func.isRequired,
certificateSource: PropTypes.string,
privateKeySource: PropTypes.string,
privateKeySaved: PropTypes.bool,

View file

@ -116,6 +116,7 @@ class Encryption extends Component {
onSubmit={this.handleFormSubmit}
onChange={this.handleFormChange}
setTlsConfig={this.props.setTlsConfig}
validateTlsConfig={this.props.validateTlsConfig}
{...this.props.encryption}
/>
</Card>

View file

@ -62,7 +62,7 @@ class LogsConfig extends Component {
interval,
customInterval,
anonymize_client_ip,
ignored: ignored.join('\n'),
ignored: ignored?.join('\n'),
}}
onSubmit={this.handleFormSubmit}
processing={processing}

View file

@ -59,7 +59,7 @@ class StatsConfig extends Component {
interval,
customInterval,
enabled,
ignored: ignored.join('\n'),
ignored: ignored?.join('\n'),
}}
onSubmit={this.handleFormSubmit}
processing={processing}

View file

@ -20,7 +20,7 @@
}
.dashboard .card-table-overflow--limited {
max-height: 18rem;
max-height: 19rem;
}
.card-actions {

View file

@ -27,6 +27,7 @@ export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)
export const R_CLIENT_ID = /^[a-z0-9-]{1,63}$/;
export const MIN_PASSWORD_LENGTH = 8;
export const MAX_PASSWORD_LENGTH = 72;
export const HTML_PAGES = {
INSTALL: '/install.html',
@ -175,6 +176,7 @@ export const MODAL_TYPE = {
CHOOSE_FILTERING_LIST: 'CHOOSE_FILTERING_LIST',
ADD_REWRITE: 'ADD_REWRITE',
EDIT_REWRITE: 'EDIT_REWRITE',
EDIT_LEASE: 'EDIT_LEASE',
};
export const CLIENT_ID = {
@ -551,6 +553,4 @@ export const DISABLE_PROTECTION_TIMINGS = {
TOMORROW: 24 * 60 * 60 * 1000,
};
export const LOCAL_STORAGE_THEME_KEY = 'account_theme';
export const LOCAL_TIMEZONE_VALUE = 'Local';

View file

@ -220,12 +220,6 @@ export default {
"homepage": "https://github.com/hoshsadiq/adblock-nocoin-list/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_8.txt"
},
"notracking_hosts_blocklists": {
"name": "The NoTracking blocklist",
"categoryId": "general",
"homepage": "https://github.com/notracking/hosts-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_32.txt"
},
"oisd_basic": {
"name": "OISD Blocklist Small",
"categoryId": "general",
@ -286,6 +280,12 @@ export default {
"homepage": "https://github.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt"
},
"ublock_badware_risks": {
"name": "uBlock₀ filters Badware risks",
"categoryId": "security",
"homepage": "https://github.com/uBlockOrigin/uAssets",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt"
},
"urlhaus_filter_online": {
"name": "Malicious URL Blocklist (URLHaus)",
"categoryId": "security",

View file

@ -26,8 +26,8 @@ import {
STANDARD_WEB_PORT,
SPECIAL_FILTER_ID,
THEMES,
LOCAL_STORAGE_THEME_KEY,
} from './constants';
import { LOCAL_STORAGE_KEYS, LocalStorageHelper } from './localStorageHelper';
/**
* @param time {string} The time to format
@ -680,37 +680,13 @@ export const setHtmlLangAttr = (language) => {
window.document.documentElement.lang = language;
};
/**
* Set local storage field
*
* @param {string} key
* @param {string} value
*/
export const setStorageItem = (key, value) => {
if (window.localStorage) {
window.localStorage.setItem(key, value);
}
};
/**
* Get local storage field
*
* @param {string} key
*/
export const getStorageItem = (key) => (window.localStorage
? window.localStorage.getItem(key)
: null);
/**
* Set local storage theme field
*
* @param {string} theme
*/
export const setTheme = (theme) => {
setStorageItem(LOCAL_STORAGE_THEME_KEY, theme);
LocalStorageHelper.setItem(LOCAL_STORAGE_KEYS.THEME, theme);
};
/**
@ -719,7 +695,7 @@ export const setTheme = (theme) => {
* @returns {string}
*/
export const getTheme = () => getStorageItem(LOCAL_STORAGE_THEME_KEY) || THEMES.light;
export const getTheme = () => LocalStorageHelper.getItem(LOCAL_STORAGE_KEYS.THEME) || THEMES.light;
/**
* Sets UI theme.

View file

@ -0,0 +1,44 @@
export const LOCAL_STORAGE_KEYS = {
THEME: 'account_theme',
BLOCKLIST_PAGE_SIZE: 'blocklist_page_size',
ALLOWLIST_PAGE_SIZE: 'allowlist_page_size',
CLIENTS_PAGE_SIZE: 'clients_page_size',
REWRITES_PAGE_SIZE: 'rewrites_page_size',
AUTO_CLIENTS_PAGE_SIZE: 'auto_clients_page_size',
};
export const LocalStorageHelper = {
setItem(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(`Error setting ${key} in local storage: ${error.message}`);
}
},
getItem(key) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (error) {
console.error(`Error getting ${key} from local storage: ${error.message}`);
return null;
}
},
removeItem(key) {
try {
localStorage.removeItem(key);
} catch (error) {
console.error(`Error removing ${key} from local storage: ${error.message}`);
}
},
clear() {
try {
localStorage.clear();
} catch (error) {
console.error(`Error clearing local storage: ${error.message}`);
}
},
};

View file

@ -1,5 +1,5 @@
{
"timeUpdated": "2023-09-01T00:09:16.088Z",
"timeUpdated": "2023-10-08T00:09:45.691Z",
"categories": {
"0": "audio_video_player",
"1": "comments",
@ -4069,6 +4069,13 @@
"url": "https://www.canddi.com/",
"companyId": "canddi"
},
"canonical": {
"name": "Canonical",
"categoryId": 8,
"url": "https://canonical.com/",
"companyId": "canonical",
"source": "AdGuard"
},
"canvas": {
"name": "Canvas",
"categoryId": 2,
@ -7583,6 +7590,13 @@
"url": "http://www.game-advertising-online.com/",
"companyId": "game_advertising_online"
},
"gameanalytics": {
"name": "GameAnalytics",
"categoryId": 101,
"url": "https://gameanalytics.com/",
"companyId": "mobvista",
"source": "AdGuard"
},
"gamedistribution.com": {
"name": "Gamedistribution.com",
"categoryId": 8,
@ -9065,7 +9079,8 @@
"name": "Imgur",
"categoryId": 8,
"url": "https://imgur.com/",
"companyId": "imgur"
"companyId": "medialab",
"source": "AdGuard"
},
"imho_vi": {
"name": "imho vi",
@ -10001,7 +10016,7 @@
"name": "Kik",
"categoryId": 7,
"url": "https://kik.com/",
"companyId": "kik",
"companyId": "medialab",
"source": "AdGuard"
},
"king.com": {
@ -10184,6 +10199,13 @@
"url": "https://www.launchbit.com/",
"companyId": "launchbit"
},
"launchpad": {
"name": "Launchpad",
"categoryId": 8,
"url": "https://launchpad.net/",
"companyId": "canonical",
"source": "AdGuard"
},
"layer-ad.org": {
"name": "Layer-ADS.net",
"categoryId": 4,
@ -12835,7 +12857,7 @@
"name": "OPPO",
"categoryId": 101,
"url": "https://www.oppo.com/",
"companyId": "oppo",
"companyId": "bbk",
"source": "AdGuard"
},
"opta.net": {
@ -12934,6 +12956,13 @@
"url": "http://www.ora.tv/",
"companyId": "ora.tv"
},
"oracle_infinity": {
"name": "Oracle Infinity Behavioral Intelligence",
"categoryId": 6,
"url": "https://www.oracle.com/au/cx/marketing/digital-intelligence/",
"companyId": "oracle",
"source": "AdGuard"
},
"oracle_live_help": {
"name": "Oracle Live Help",
"categoryId": 2,
@ -14501,8 +14530,9 @@
"reddit": {
"name": "Reddit",
"categoryId": 7,
"url": "http://reddit.com",
"companyId": "reddit"
"url": "https://www.reddit.com",
"companyId": "advance",
"source": "AdGuard"
},
"redhelper": {
"name": "RedHelper",
@ -16113,6 +16143,13 @@
"url": "https://www.snapchat.com/",
"companyId": "snap_technologies"
},
"snapcraft": {
"name": "Snapcraft",
"categoryId": 8,
"url": "https://snapcraft.io",
"companyId": "canonical",
"source": "AdGuard"
},
"snigelweb": {
"name": "SnigelWeb, Inc.",
"categoryId": 4,
@ -17894,7 +17931,7 @@
"name": "Ubuntu",
"categoryId": 8,
"url": "https://ubuntu.com/",
"companyId": "ubuntu",
"companyId": "canonical",
"source": "AdGuard"
},
"ucfunnel": {
@ -18754,6 +18791,13 @@
"url": "http://vpscash.nl/home",
"companyId": "vps_cash"
},
"vs": {
"name": "Visual Studio",
"categoryId": 8,
"url": "https://visualstudio.microsoft.com",
"companyId": "microsoft",
"source": "AdGuard"
},
"vscode": {
"name": "Visual Studio Code",
"categoryId": 8,
@ -19081,6 +19125,13 @@
"companyId": "meta",
"source": "AdGuard"
},
"whisper": {
"name": "Whisper",
"categoryId": 7,
"url": "https://whisper.sh/",
"companyId": "medialab",
"source": "AdGuard"
},
"whos.amung.us": {
"name": "Whos.amung.us",
"categoryId": 6,
@ -20190,6 +20241,7 @@
"adtidy.org": "adguard",
"agrd.io": "adguard",
"adguard.app": "adguard",
"adguard.info": "adguard",
"adguard.io": "adguard",
"adguard.org": "adguard",
"adguard-dns.com": "adguard_dns",
@ -20969,6 +21021,7 @@
"camakaroda.com": "camakaroda.com",
"s.edkay.com": "campus_explorer",
"canddi.com": "canddi",
"canonical.com": "canonical",
"canvas.net": "canvas",
"canvasnetwork.com": "canvas",
"du11hjcvx0uqb.cloudfront.net": "canvas",
@ -21784,6 +21837,7 @@
"angsrvr.com": "fyber",
"fyber.com": "fyber",
"game-advertising-online.com": "game_advertising_online",
"gameanalytics.com": "gameanalytics",
"gamedistribution.com": "gamedistribution.com",
"gamerdna.com": "gamerdna",
"gannett-cdn.com": "gannett",
@ -22589,9 +22643,11 @@
"sa-as.com": "kickfire",
"sniff.visistat.com": "kickfire",
"stats.visistat.com": "kickfire",
"kik.com": "kik",
"apikik.com": "kik",
"kik-gateway-use1.meetme.com": "kik",
"kik-live.com": "kik",
"kik-stream.meetme.com": "kik",
"kik.com": "kik",
"king.com": "king.com",
"midasplayer.com": "king_com",
"kinja-img.com": "kinja.com",
@ -22633,6 +22689,8 @@
"events.launchdarkly.com": "launch_darkly",
"launchdarkly.com": "launch_darkly",
"launchbit.com": "launchbit",
"launchpad.net": "launchpad",
"launchpadcontent.net": "launchpad",
"layer-ad.org": "layer-ad.org",
"ph-live.slatic.net": "lazada",
"slatic.net": "lazada",
@ -23073,6 +23131,7 @@
"e-msedge.net": "msedge",
"l-msedge.net": "msedge",
"s-msedge.net": "msedge",
"t-msedge.net": "msedge",
"msn.com": "msn",
"s-msn.com": "msn",
"musculahq.appspot.com": "muscula",
@ -23171,6 +23230,7 @@
"d1ros97qkrwjf5.cloudfront.net": "new_relic",
"newrelic.com": "new_relic",
"nr-data.net": "new_relic",
"codestream.com": "new_relic",
"newscgp.com": "newscgp.com",
"nmcdn.us": "newsmax",
"newstogram.com": "newstogram",
@ -23315,6 +23375,7 @@
"optincollect.com": "optinproject.com",
"volvelle.tech": "optomaton",
"ora.tv": "ora.tv",
"oracleinfinity.io": "oracle_infinity",
"instantservice.com": "oracle_live_help",
"ts.istrack.com": "oracle_live_help",
"rightnowtech.com": "oracle_rightnow",
@ -23603,6 +23664,7 @@
"rcsmediagroup.it": "rcs.it",
"d335luupugsy2.cloudfront.net": "rd_station",
"rea-group.com": "rea_group",
"reastatic.net": "rea_group",
"d12ulf131zb0yj.cloudfront.net": "reachforce",
"reachforce.com": "reachforce",
"reachjunction.com": "reachjunction",
@ -24079,6 +24141,8 @@
"snapengage.com": "snap_engage",
"sc-static.net": "snapchat",
"snapchat.com": "snapchat",
"snapcraft.io": "snapcraft",
"snapcraftcontent.com": "snapcraft",
"h-bid.com": "snigelweb",
"eu2.snoobi.eu": "snoobi",
"snoobi.com": "snoobi_analytics",
@ -24482,11 +24546,6 @@
"tyroodr.com": "tyroo",
"tzetze.it": "tzetze",
"ubersetzung-app.com": "ubersetzung-app.com",
"canonical.com": "ubuntu",
"launchpad.net": "ubuntu",
"launchpadcontent.net": "ubuntu",
"snapcraft.io": "ubuntu",
"snapcraftcontent.com": "ubuntu",
"ubuntu.com": "ubuntu",
"ubuntucompanyservices.co.za": "ubuntu",
"aralego.net": "ucfunnel",
@ -24667,6 +24726,7 @@
"voxus-targeting-voxusmidia.netdna-ssl.com": "voxus",
"c-dsp.vpadn.com": "vpon",
"tools.vpscash.nl": "vpscash",
"vsassets.io": "vs",
"exp-tas.com": "vscode",
"vscode-unpkg.net": "vscode",
"v0cdn.net": "vscode",
@ -24745,6 +24805,8 @@
"whatsbroadcast.com": "whatbroadcast",
"whatsapp.net": "whatsapp",
"whatsapp.com": "whatsapp",
"whisper.onelink.me": "whisper",
"whisper.sh": "whisper",
"amung.us": "whos.amung.us",
"whoson.com": "whoson",
"api.wibbitz.com": "wibbitz",

View file

@ -1,5 +1,4 @@
import i18next from 'i18next';
import stringLength from 'string-length';
import {
MAX_PORT,
@ -14,6 +13,7 @@ import {
UNSAFE_PORTS,
R_CLIENT_ID,
R_DOMAIN,
MAX_PASSWORD_LENGTH,
MIN_PASSWORD_LENGTH,
} from './constants';
import { ip4ToInt, isValidAbsolutePath } from './form';
@ -325,14 +325,33 @@ export const validateIpv4InCidr = (valueIp, allValues) => {
return undefined;
};
/**
* @param value {string}
* @returns {number}
*/
const utf8StringLength = (value) => {
const encoder = new TextEncoder();
const view = encoder.encode(value);
return view.length;
};
/**
* @param value {string}
* @returns {Function}
*/
export const validatePasswordLength = (value) => {
if (value && stringLength(value) < MIN_PASSWORD_LENGTH) {
return i18next.t('form_error_password_length', { value: MIN_PASSWORD_LENGTH });
if (value) {
const length = utf8StringLength(value);
if (length < MIN_PASSWORD_LENGTH || length > MAX_PASSWORD_LENGTH) {
// TODO: Make the i18n clearer with regards to bytes vs. characters.
return i18next.t('form_error_password_length', {
min: MIN_PASSWORD_LENGTH,
max: MAX_PASSWORD_LENGTH,
});
}
}
return undefined;
};

View file

@ -128,7 +128,8 @@ const dhcp = handleActions(
const newState = {
...state,
isModalOpen: !state.isModalOpen,
leaseModalConfig: payload,
modalType: payload?.type || '',
leaseModalConfig: payload?.config,
};
return newState;
},
@ -175,6 +176,16 @@ const dhcp = handleActions(
};
return newState;
},
[actions.updateStaticLeaseRequest]: (state) => ({ ...state, processingUpdating: true }),
[actions.updateStaticLeaseFailure]: (state) => ({ ...state, processingUpdating: false }),
[actions.updateStaticLeaseSuccess]: (state) => {
const newState = {
...state,
processingUpdating: false,
};
return newState;
},
},
{
processing: true,
@ -184,6 +195,7 @@ const dhcp = handleActions(
processingConfig: false,
processingAdding: false,
processingDeleting: false,
processingUpdating: false,
enabled: false,
interface_name: '',
check: null,
@ -202,6 +214,7 @@ const dhcp = handleActions(
staticLeases: [],
isModalOpen: false,
leaseModalConfig: undefined,
modalType: '',
dhcp_available: false,
},
);

View file

@ -49,6 +49,7 @@ const dnsConfig = handleActions(
ratelimit: 20,
blocking_ipv4: DEFAULT_BLOCKING_IPV4,
blocking_ipv6: DEFAULT_BLOCKING_IPV6,
blocked_response_ttl: 10,
edns_cs_enabled: false,
disable_ipv6: false,
dnssec_enabled: false,

16
go.mod
View file

@ -3,8 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
go 1.20
require (
github.com/AdguardTeam/dnsproxy v0.54.0
github.com/AdguardTeam/golibs v0.15.0
github.com/AdguardTeam/dnsproxy v0.56.1
github.com/AdguardTeam/golibs v0.17.1
github.com/AdguardTeam/urlfilter v0.17.0
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.2.7
@ -17,7 +17,7 @@ require (
github.com/google/gopacket v1.1.19
github.com/google/renameio/v2 v2.0.0
github.com/google/uuid v1.3.1
github.com/insomniacslk/dhcp v0.0.0-20230906122924-c71a6be05968
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/kardianos/service v1.2.2
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
@ -26,8 +26,8 @@ require (
// TODO(a.garipov): This package is deprecated; find a new one or use our
// own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0
github.com/miekg/dns v1.1.55
github.com/quic-go/quic-go v0.38.1
github.com/miekg/dns v1.1.56
github.com/quic-go/quic-go v0.39.0
github.com/stretchr/testify v1.8.4
github.com/ti-mo/netfilter v0.5.0
go.etcd.io/bbolt v1.3.7
@ -47,10 +47,9 @@ require (
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230906154834-20cde9067b3b // indirect
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
github.com/onsi/ginkgo/v2 v2.12.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/pkg/errors v0.9.1 // indirect
@ -58,6 +57,7 @@ require (
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
go.uber.org/mock v0.3.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.13.0 // indirect

41
go.sum
View file

@ -1,7 +1,7 @@
github.com/AdguardTeam/dnsproxy v0.54.0 h1:OgSitM/EKrMMOi+guWZNwaU1cqRqJKWgR3l3fPWWayI=
github.com/AdguardTeam/dnsproxy v0.54.0/go.mod h1:tG/treaQekcKnugYoKOfm8vt3JGi6CliWta0MkQr15U=
github.com/AdguardTeam/golibs v0.15.0 h1:yOv/fdVkJIOWKr0NlUXAE9RA0DK9GKiBbiGzq47vY7o=
github.com/AdguardTeam/golibs v0.15.0/go.mod h1:66ZLs8P7nk/3IfKroQ1rqtieLk+5eXYXMBKXlVL7KeI=
github.com/AdguardTeam/dnsproxy v0.56.1 h1:QltfyWO7k4mxWERCEYDzkQnKzvZX/zkneWjbuJ0TU6o=
github.com/AdguardTeam/dnsproxy v0.56.1/go.mod h1:fqmehcE3cHFNqKbWQpIjGk7GqBy7ur1v5At499lFjRc=
github.com/AdguardTeam/golibs v0.17.1 h1:j3Ehhld5GI/amcHYG+CF0sJ4OOzAQ06BY3N/iBYJZ1M=
github.com/AdguardTeam/golibs v0.17.1/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
github.com/AdguardTeam/urlfilter v0.17.0 h1:tUzhtR9wMx704GIP3cibsDQJrixlMHfwoQbYJfPdFow=
github.com/AdguardTeam/urlfilter v0.17.0/go.mod h1:bbuZjPUzm/Ip+nz5qPPbwIP+9rZyQbQad8Lt/0fCulU=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
@ -33,8 +33,6 @@ 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-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@ -43,16 +41,16 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20230906154834-20cde9067b3b h1:AIwVPxYWRaz7GetyrRj3ShUL5hr7WeuqF52G0g8Hne8=
github.com/google/pprof v0.0.0-20230906154834-20cde9067b3b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 h1:gpptm606MZYGaMHMsB4Srmb6EbW/IVHnt04rcMXnkBQ=
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/insomniacslk/dhcp v0.0.0-20230906122924-c71a6be05968 h1:uBiv5/8x42J7myumCdFuDOc5HnEXRK6eOtefwvE6+TQ=
github.com/insomniacslk/dhcp v0.0.0-20230906122924-c71a6be05968/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@ -75,11 +73,11 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA=
github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
@ -96,8 +94,8 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
github.com/quic-go/quic-go v0.39.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3So=
github.com/quic-go/quic-go v0.39.0/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -113,10 +111,11 @@ github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+Kd
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
@ -125,7 +124,6 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjs
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -134,7 +132,6 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -148,8 +145,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -163,15 +158,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
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=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=

View file

@ -0,0 +1,102 @@
package aghalg
import (
"github.com/AdguardTeam/golibs/errors"
)
// RingBuffer is the implementation of ring buffer data structure.
type RingBuffer[T any] struct {
buf []T
cur int
full bool
}
// NewRingBuffer initializes the new instance of ring buffer. size must be
// greater or equal to zero.
func NewRingBuffer[T any](size int) (rb *RingBuffer[T]) {
if size < 0 {
panic(errors.Error("ring buffer: size must be greater or equal to zero"))
}
return &RingBuffer[T]{
buf: make([]T, size),
}
}
// Append appends an element to the buffer.
func (rb *RingBuffer[T]) Append(e T) {
if len(rb.buf) == 0 {
return
}
rb.buf[rb.cur] = e
rb.cur = (rb.cur + 1) % cap(rb.buf)
if rb.cur == 0 {
rb.full = true
}
}
// Range calls cb for each element of the buffer. If cb returns false it stops.
func (rb *RingBuffer[T]) Range(cb func(T) (cont bool)) {
before, after := rb.splitCur()
for _, e := range before {
if !cb(e) {
return
}
}
for _, e := range after {
if !cb(e) {
return
}
}
}
// ReverseRange calls cb for each element of the buffer in reverse order. If
// cb returns false it stops.
func (rb *RingBuffer[T]) ReverseRange(cb func(T) (cont bool)) {
before, after := rb.splitCur()
for i := len(after) - 1; i >= 0; i-- {
if !cb(after[i]) {
return
}
}
for i := len(before) - 1; i >= 0; i-- {
if !cb(before[i]) {
return
}
}
}
// splitCur splits the buffer in two, before and after current position in
// chronological order. If buffer is not full, after is nil.
func (rb *RingBuffer[T]) splitCur() (before, after []T) {
if len(rb.buf) == 0 {
return nil, nil
}
cur := rb.cur
if !rb.full {
return rb.buf[:cur], nil
}
return rb.buf[cur:], rb.buf[:cur]
}
// Len returns a length of the buffer.
func (rb *RingBuffer[T]) Len() (l int) {
if !rb.full {
return rb.cur
}
return cap(rb.buf)
}
// Clear clears the buffer.
func (rb *RingBuffer[T]) Clear() {
rb.full = false
rb.cur = 0
}

View file

@ -0,0 +1,173 @@
package aghalg_test
import (
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/stretchr/testify/assert"
"golang.org/x/exp/slices"
)
// elements is a helper function that returns n elements of the buffer.
func elements(b *aghalg.RingBuffer[int], n int, reverse bool) (es []int) {
fn := b.Range
if reverse {
fn = b.ReverseRange
}
i := 0
fn(func(e int) (cont bool) {
if i >= n {
return false
}
es = append(es, e)
i++
return true
})
return es
}
func TestNewRingBuffer(t *testing.T) {
t.Run("success_and_clear", func(t *testing.T) {
b := aghalg.NewRingBuffer[int](5)
for i := 0; i < 10; i++ {
b.Append(i)
}
assert.Equal(t, []int{5, 6, 7, 8, 9}, elements(b, b.Len(), false))
b.Clear()
assert.Zero(t, b.Len())
})
t.Run("negative_size", func(t *testing.T) {
assert.PanicsWithError(t, "ring buffer: size must be greater or equal to zero", func() {
aghalg.NewRingBuffer[int](-5)
})
})
t.Run("zero", func(t *testing.T) {
b := aghalg.NewRingBuffer[int](0)
for i := 0; i < 10; i++ {
b.Append(i)
assert.Equal(t, 0, b.Len())
assert.Empty(t, elements(b, b.Len(), false))
assert.Empty(t, elements(b, b.Len(), true))
}
})
t.Run("single", func(t *testing.T) {
b := aghalg.NewRingBuffer[int](1)
for i := 0; i < 10; i++ {
b.Append(i)
assert.Equal(t, 1, b.Len())
assert.Equal(t, []int{i}, elements(b, b.Len(), false))
assert.Equal(t, []int{i}, elements(b, b.Len(), true))
}
})
}
func TestRingBuffer_Range(t *testing.T) {
const size = 5
b := aghalg.NewRingBuffer[int](size)
testCases := []struct {
name string
want []int
count int
length int
}{{
name: "three",
count: 3,
length: 3,
want: []int{0, 1, 2},
}, {
name: "ten",
count: 10,
length: size,
want: []int{5, 6, 7, 8, 9},
}, {
name: "hundred",
count: 100,
length: size,
want: []int{95, 96, 97, 98, 99},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for i := 0; i < tc.count; i++ {
b.Append(i)
}
bufLen := b.Len()
assert.Equal(t, tc.length, bufLen)
want := tc.want
assert.Equal(t, want, elements(b, bufLen, false))
assert.Equal(t, want[:len(want)-1], elements(b, bufLen-1, false))
assert.Equal(t, want[:len(want)/2], elements(b, bufLen/2, false))
want = want[:cap(want)]
slices.Reverse(want)
assert.Equal(t, want, elements(b, bufLen, true))
assert.Equal(t, want[:len(want)-1], elements(b, bufLen-1, true))
assert.Equal(t, want[:len(want)/2], elements(b, bufLen/2, true))
})
}
}
func TestRingBuffer_Range_increment(t *testing.T) {
const size = 5
b := aghalg.NewRingBuffer[int](size)
testCases := []struct {
name string
want []int
}{{
name: "one",
want: []int{0},
}, {
name: "two",
want: []int{0, 1},
}, {
name: "three",
want: []int{0, 1, 2},
}, {
name: "four",
want: []int{0, 1, 2, 3},
}, {
name: "five",
want: []int{0, 1, 2, 3, 4},
}, {
name: "six",
want: []int{1, 2, 3, 4, 5},
}, {
name: "seven",
want: []int{2, 3, 4, 5, 6},
}, {
name: "eight",
want: []int{3, 4, 5, 6, 7},
}, {
name: "nine",
want: []int{4, 5, 6, 7, 8},
}, {
name: "ten",
want: []int{5, 6, 7, 8, 9},
}}
for i, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b.Append(i)
assert.Equal(t, tc.want, elements(b, b.Len(), false))
slices.Reverse(tc.want)
assert.Equal(t, tc.want, elements(b, b.Len(), true))
})
}
}

View file

@ -1,60 +0,0 @@
// Package aghio contains extensions for io package's types and methods
package aghio
import (
"fmt"
"io"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/mathutil"
)
// LimitReachedError records the limit and the operation that caused it.
type LimitReachedError struct {
Limit int64
}
// Error implements the [error] interface for *LimitReachedError.
//
// TODO(a.garipov): Think about error string format.
func (lre *LimitReachedError) Error() string {
return fmt.Sprintf("attempted to read more than %d bytes", lre.Limit)
}
// limitedReader is a wrapper for [io.Reader] limiting the input and dealing
// with errors package.
type limitedReader struct {
r io.Reader
limit int64
n int64
}
// Read implements the [io.Reader] interface.
func (lr *limitedReader) Read(p []byte) (n int, err error) {
if lr.n == 0 {
return 0, &LimitReachedError{
Limit: lr.limit,
}
}
p = p[:mathutil.Min(lr.n, int64(len(p)))]
n, err = lr.r.Read(p)
lr.n -= int64(n)
return n, err
}
// LimitReader wraps Reader to make it's Reader stop with ErrLimitReached after
// n bytes read.
func LimitReader(r io.Reader, n int64) (limited io.Reader, err error) {
if n < 0 {
return nil, errors.Error("limit must be non-negative")
}
return &limitedReader{
r: r,
limit: n,
n: n,
}, nil
}

View file

@ -1,96 +0,0 @@
package aghio_test
import (
"io"
"strings"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLimitReader(t *testing.T) {
testCases := []struct {
wantErrMsg string
name string
n int64
}{{
wantErrMsg: "",
name: "positive",
n: 1,
}, {
wantErrMsg: "",
name: "zero",
n: 0,
}, {
wantErrMsg: "limit must be non-negative",
name: "negative",
n: -1,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := aghio.LimitReader(nil, tc.n)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
})
}
}
func TestLimitedReader_Read(t *testing.T) {
testCases := []struct {
err error
name string
rStr string
limit int64
want int
}{{
err: nil,
name: "perfectly_match",
rStr: "abc",
limit: 3,
want: 3,
}, {
err: io.EOF,
name: "eof",
rStr: "",
limit: 3,
want: 0,
}, {
err: &aghio.LimitReachedError{
Limit: 0,
},
name: "limit_reached",
rStr: "abc",
limit: 0,
want: 0,
}, {
err: nil,
name: "truncated",
rStr: "abc",
limit: 2,
want: 2,
}}
for _, tc := range testCases {
readCloser := io.NopCloser(strings.NewReader(tc.rStr))
lreader, err := aghio.LimitReader(readCloser, tc.limit)
require.NoError(t, err)
require.NotNil(t, lreader)
t.Run(tc.name, func(t *testing.T) {
buf := make([]byte, tc.limit+1)
n, rerr := lreader.Read(buf)
require.Equal(t, rerr, tc.err)
assert.Equal(t, tc.want, n)
})
}
}
func TestLimitedReader_LimitReachedError(t *testing.T) {
testutil.AssertErrorMsg(t, "attempted to read more than 0 bytes", &aghio.LimitReachedError{
Limit: 0,
})
}

View file

@ -12,12 +12,12 @@ import (
// listenPacketReusable announces on the local network address additionally
// configuring the socket to have a reusable binding.
func listenPacketReusable(ifaceName, network, address string) (c net.PacketConn, err error) {
var port int
var port uint16
_, port, err = netutil.SplitHostPort(address)
if err != nil {
return nil, err
}
// TODO(e.burkov): Inspect nclient4.NewRawUDPConn and implement here.
return nclient4.NewRawUDPConn(ifaceName, port)
return nclient4.NewRawUDPConn(ifaceName, int(port))
}

View file

@ -9,6 +9,7 @@ import (
"io"
"net"
"net/netip"
"net/url"
"syscall"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
@ -263,7 +264,7 @@ 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) {
func CollectAllIfacesAddrs() (addrs []netip.Addr, err error) {
var ifaceAddrs []net.Addr
ifaceAddrs, err = netInterfaceAddrs()
if err != nil {
@ -271,19 +272,41 @@ func CollectAllIfacesAddrs() (addrs []string, err error) {
}
for _, addr := range ifaceAddrs {
cidr := addr.String()
var ip net.IP
ip, _, err = net.ParseCIDR(cidr)
var p netip.Prefix
p, err = netip.ParsePrefix(addr.String())
if err != nil {
return nil, fmt.Errorf("parsing cidr: %w", err)
// Don't wrap the error since it's informative enough as is.
return nil, err
}
addrs = append(addrs, ip.String())
addrs = append(addrs, p.Addr())
}
return addrs, nil
}
// ParseAddrPort parses an [netip.AddrPort] from s, which should be either a
// valid IP, optionally with port, or a valid URL with plain IP address. The
// defaultPort is used if s doesn't contain port number.
func ParseAddrPort(s string, defaultPort uint16) (ipp netip.AddrPort, err error) {
u, err := url.Parse(s)
if err == nil && u.Host != "" {
s = u.Host
}
ipp, err = netip.ParseAddrPort(s)
if err != nil {
ip, parseErr := netip.ParseAddr(s)
if parseErr != nil {
return ipp, errors.Join(err, parseErr)
}
return netip.AddrPortFrom(ip, defaultPort), nil
}
return ipp, nil
}
// BroadcastFromPref calculates the broadcast IP address for p.
func BroadcastFromPref(p netip.Prefix) (bc netip.Addr) {
bc = p.Addr().Unmap()

View file

@ -230,7 +230,7 @@ func TestCollectAllIfacesAddrs(t *testing.T) {
name string
wantErrMsg string
addrs []net.Addr
wantAddrs []string
wantAddrs []netip.Addr
}{{
name: "success",
wantErrMsg: ``,
@ -241,10 +241,13 @@ func TestCollectAllIfacesAddrs(t *testing.T) {
IP: net.IP{4, 3, 2, 1},
Mask: net.CIDRMask(16, netutil.IPv4BitLen),
}},
wantAddrs: []string{"1.2.3.4", "4.3.2.1"},
wantAddrs: []netip.Addr{
netip.MustParseAddr("1.2.3.4"),
netip.MustParseAddr("4.3.2.1"),
},
}, {
name: "not_cidr",
wantErrMsg: `parsing cidr: invalid CIDR address: 1.2.3.4`,
wantErrMsg: `netip.ParsePrefix("1.2.3.4"): no '/'`,
addrs: []net.Addr{&net.IPAddr{
IP: net.IP{1, 2, 3, 4},
}},
@ -269,12 +272,11 @@ func TestCollectAllIfacesAddrs(t *testing.T) {
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)
assert.ErrorIs(t, err, errAddrs)
})
}

View file

@ -2,10 +2,16 @@ package aghnet_test
import (
"io/fs"
"net"
"net/netip"
"net/url"
"os"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
@ -14,3 +20,76 @@ func TestMain(m *testing.M) {
// testdata is the filesystem containing data for testing the package.
var testdata fs.FS = os.DirFS("./testdata")
func TestParseAddrPort(t *testing.T) {
const defaultPort = 1
v4addr := netip.MustParseAddr("1.2.3.4")
testCases := []struct {
name string
input string
wantErrMsg string
want netip.AddrPort
}{{
name: "success_ip",
input: v4addr.String(),
wantErrMsg: "",
want: netip.AddrPortFrom(v4addr, defaultPort),
}, {
name: "success_ip_port",
input: netutil.JoinHostPort(v4addr.String(), 5),
wantErrMsg: "",
want: netip.AddrPortFrom(v4addr, 5),
}, {
name: "success_url",
input: (&url.URL{
Scheme: "tcp",
Host: v4addr.String(),
}).String(),
wantErrMsg: "",
want: netip.AddrPortFrom(v4addr, defaultPort),
}, {
name: "success_url_port",
input: (&url.URL{
Scheme: "tcp",
Host: netutil.JoinHostPort(v4addr.String(), 5),
}).String(),
wantErrMsg: "",
want: netip.AddrPortFrom(v4addr, 5),
}, {
name: "error_invalid_ip",
input: "256.256.256.256",
wantErrMsg: `not an ip:port
ParseAddr("256.256.256.256"): IPv4 field has value >255`,
want: netip.AddrPort{},
}, {
name: "error_invalid_port",
input: net.JoinHostPort(v4addr.String(), "-5"),
wantErrMsg: `invalid port "-5" parsing "1.2.3.4:-5"
ParseAddr("1.2.3.4:-5"): unexpected character (at ":-5")`,
want: netip.AddrPort{},
}, {
name: "error_invalid_url",
input: "tcp:://1.2.3.4",
wantErrMsg: `invalid port "//1.2.3.4" parsing "tcp:://1.2.3.4"
ParseAddr("tcp:://1.2.3.4"): each colon-separated field must have at least ` +
`one digit (at "tcp:://1.2.3.4")`,
want: netip.AddrPort{},
}, {
name: "empty",
input: "",
want: netip.AddrPort{},
wantErrMsg: `not an ip:port
ParseAddr(""): unable to parse IP`,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ap, err := aghnet.ParseAddrPort(tc.input, defaultPort)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
assert.Equal(t, tc.want, ap)
})
}
}

View file

@ -1,36 +0,0 @@
package aghnet
// DefaultRefreshIvl is the default period of time between refreshing cached
// addresses.
// const DefaultRefreshIvl = 5 * time.Minute
// HostGenFunc is the signature for functions generating fake hostnames. The
// implementation must be safe for concurrent use.
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 must be safe for
// concurrent use.
Get() (rs []string)
// refresh refreshes the local resolvers' addresses cache. It must be safe
// for concurrent use.
refresh() (err error)
}
// NewSystemResolvers returns a SystemResolvers with the cache refresh rate
// 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,
) (sr SystemResolvers, err error) {
sr = newSystemResolvers(hostGenFunc)
// Fill cache.
err = sr.refresh()
if err != nil {
return nil, err
}
return sr, nil
}

View file

@ -1,146 +0,0 @@
//go:build !windows
package aghnet
import (
"context"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
)
// defaultHostGen is the default method of generating host for Refresh.
func defaultHostGen() (host string) {
// TODO(e.burkov): Use strings.Builder.
return fmt.Sprintf("test%d.org", time.Now().UnixNano())
}
// systemResolvers is a default implementation of SystemResolvers interface.
type systemResolvers struct {
// 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 (
// errBadAddrPassed is returned when dialFunc can't parse an IP address.
errBadAddrPassed errors.Error = "the passed string is not a valid IP address"
// errFakeDial is an error which dialFunc is expected to return.
errFakeDial errors.Error = "this error signals the successful dialFunc work"
// errUnexpectedHostFormat is returned by validateDialedHost when the host has
// more than one percent sign.
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") }()
_, err = sr.resolver.LookupHost(context.Background(), sr.hostGenFunc())
dnserr := &net.DNSError{}
if errors.As(err, &dnserr) && dnserr.Err == errFakeDial.Error() {
return nil
}
return err
}
func newSystemResolvers(hostGenFunc HostGenFunc) (sr SystemResolvers) {
if hostGenFunc == nil {
hostGenFunc = defaultHostGen
}
s := &systemResolvers{
resolver: &net.Resolver{
PreferGo: true,
},
hostGenFunc: hostGenFunc,
addrs: stringutil.NewSet(),
}
s.resolver.Dial = s.dialFunc
return s
}
// validateDialedHost validated the host used by resolvers in dialFunc.
func validateDialedHost(host string) (err error) {
defer func() { err = errors.Annotate(err, "parsing %q: %w", host) }()
parts := strings.Split(host, "%")
switch len(parts) {
case 1:
// host
case 2:
// Remove the zone and check the IP address part.
host = parts[0]
default:
return errUnexpectedHostFormat
}
if _, err = netutil.ParseIP(host); err != nil {
return errBadAddrPassed
}
return nil
}
// dockerEmbeddedDNS is the address of Docker's embedded DNS server.
//
// See
// https://github.com/moby/moby/blob/v1.12.0/docs/userguide/networking/dockernetworks.md.
const dockerEmbeddedDNS = "127.0.0.11"
// dialFunc gets the resolver's address and puts it into internal cache.
func (sr *systemResolvers) dialFunc(_ context.Context, _, address string) (_ net.Conn, err error) {
// Just validate the passed address is a valid IP.
var host string
host, err = netutil.SplitHost(address)
if err != nil {
// TODO(e.burkov): Maybe use a structured errBadAddrPassed to
// allow unwrapping of the real error.
return nil, fmt.Errorf("%s: %w", err, errBadAddrPassed)
}
// Exclude Docker's embedded DNS server, as it may cause recursion if
// the container is set as the host system's default DNS server.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/3064.
//
// TODO(a.garipov): Perhaps only do this when we are in the container?
// Maybe use an environment variable?
if host == dockerEmbeddedDNS {
return nil, errFakeDial
}
err = validateDialedHost(host)
if err != nil {
return nil, fmt.Errorf("validating dialed host: %w", err)
}
sr.addrsLock.Lock()
defer sr.addrsLock.Unlock()
sr.addrs.Add(host)
return nil, errFakeDial
}
func (sr *systemResolvers) Get() (rs []string) {
sr.addrsLock.RLock()
defer sr.addrsLock.RUnlock()
return sr.addrs.Values()
}

View file

@ -1,81 +0,0 @@
//go:build !windows
package aghnet
import (
"context"
"testing"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func createTestSystemResolversImpl(
t *testing.T,
hostGenFunc HostGenFunc,
) (imp *systemResolvers) {
t.Helper()
sr := createTestSystemResolvers(t, hostGenFunc)
return testutil.RequireTypeAssert[*systemResolvers](t, sr)
}
func TestSystemResolvers_Refresh(t *testing.T) {
t.Run("expected_error", func(t *testing.T) {
sr := createTestSystemResolvers(t, nil)
assert.NoError(t, sr.refresh())
})
t.Run("unexpected_error", func(t *testing.T) {
_, err := NewSystemResolvers(func() string {
return "127.0.0.1::123"
})
assert.Error(t, err)
})
}
func TestSystemResolvers_DialFunc(t *testing.T) {
imp := createTestSystemResolversImpl(t, nil)
testCases := []struct {
want error
name string
address string
}{{
want: errFakeDial,
name: "valid_ipv4",
address: "127.0.0.1",
}, {
want: errFakeDial,
name: "valid_ipv6_port",
address: "[::1]:53",
}, {
want: errFakeDial,
name: "valid_ipv6_zone_port",
address: "[::1%lo0]:53",
}, {
want: errBadAddrPassed,
name: "invalid_split_host",
address: "127.0.0.1::123",
}, {
want: errUnexpectedHostFormat,
name: "invalid_ipv6_zone_port",
address: "[::1%%lo0]:53",
}, {
want: errBadAddrPassed,
name: "invalid_parse_ip",
address: "not-ip",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
conn, err := imp.dialFunc(context.Background(), "", tc.address)
require.Nil(t, conn)
assert.ErrorIs(t, err, tc.want)
})
}
}

View file

@ -1,37 +0,0 @@
package aghnet
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func createTestSystemResolvers(
t *testing.T,
hostGenFunc HostGenFunc,
) (sr SystemResolvers) {
t.Helper()
var err error
sr, err = NewSystemResolvers(hostGenFunc)
require.NoError(t, err)
require.NotNil(t, sr)
return sr
}
func TestSystemResolvers_Get(t *testing.T) {
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.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2846.

View file

@ -1,163 +0,0 @@
//go:build windows
package aghnet
import (
"bufio"
"fmt"
"io"
"net"
"os/exec"
"strings"
"sync"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
)
// systemResolvers implementation differs for Windows since Go's resolver
// doesn't work there.
//
// See https://github.com/golang/go/issues/33097.
type systemResolvers struct {
// addrs is the slice of cached local resolvers' addresses.
addrs []string
addrsLock sync.RWMutex
}
func newSystemResolvers(_ HostGenFunc) (sr SystemResolvers) {
return &systemResolvers{}
}
func (sr *systemResolvers) Get() (rs []string) {
sr.addrsLock.RLock()
defer sr.addrsLock.RUnlock()
addrs := sr.addrs
rs = make([]string, len(addrs))
copy(rs, addrs)
return rs
}
// writeExit writes "exit" to w and closes it. It is supposed to be run in
// a goroutine.
func writeExit(w io.WriteCloser) {
defer log.OnPanic("systemResolvers: writeExit")
defer func() {
derr := w.Close()
if derr != nil {
log.Error("systemResolvers: writeExit: closing: %s", derr)
}
}()
_, err := io.WriteString(w, "exit")
if err != nil {
log.Error("systemResolvers: writeExit: writing: %s", err)
}
}
// scanAddrs scans the DNS addresses from nslookup's output. The expected
// output of nslookup looks like this:
//
// Default Server: 192-168-1-1.qualified.domain.ru
// Address: 192.168.1.1
func scanAddrs(s *bufio.Scanner) (addrs []string) {
for s.Scan() {
line := strings.TrimSpace(s.Text())
fields := strings.Fields(line)
if len(fields) != 2 || fields[0] != "Address:" {
continue
}
// If the address contains port then it is separated with '#'.
ipPort := strings.Split(fields[1], "#")
if len(ipPort) == 0 {
continue
}
addr := ipPort[0]
if net.ParseIP(addr) == nil {
log.Debug("systemResolvers: %q is not a valid ip", addr)
continue
}
addrs = append(addrs, addr)
}
return addrs
}
// getAddrs gets local resolvers' addresses from OS in a special Windows way.
//
// TODO(e.burkov): This whole function needs more detailed research on getting
// local resolvers addresses on Windows. We execute the external command for
// now that is not the most accurate way.
func (sr *systemResolvers) getAddrs() (addrs []string, err error) {
var cmdPath string
cmdPath, err = exec.LookPath("nslookup.exe")
if err != nil {
return nil, fmt.Errorf("looking up cmd path: %w", err)
}
cmd := exec.Command(cmdPath)
var stdin io.WriteCloser
stdin, err = cmd.StdinPipe()
if err != nil {
return nil, fmt.Errorf("getting the command's stdin pipe: %w", err)
}
var stdout io.ReadCloser
stdout, err = cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("getting the command's stdout pipe: %w", err)
}
go writeExit(stdin)
err = cmd.Start()
if err != nil {
return nil, fmt.Errorf("start command executing: %w", err)
}
s := bufio.NewScanner(stdout)
addrs = scanAddrs(s)
err = cmd.Wait()
if err != nil {
return nil, fmt.Errorf("executing the command: %w", err)
}
err = s.Err()
if err != nil {
return nil, fmt.Errorf("scanning output: %w", err)
}
// Don't close StdoutPipe since Wait do it for us in ¿most? cases.
//
// See go doc os/exec.Cmd.StdoutPipe.
return addrs, nil
}
func (sr *systemResolvers) refresh() (err error) {
defer func() { err = errors.Annotate(err, "systemResolvers: %w") }()
got, err := sr.getAddrs()
if err != nil {
return fmt.Errorf("can't get addresses: %w", err)
}
if len(got) == 0 {
return nil
}
sr.addrsLock.Lock()
defer sr.addrsLock.Unlock()
sr.addrs = got
return nil
}

View file

@ -1,7 +0,0 @@
//go:build windows
package aghnet
// TODO(e.burkov): Write tests for Windows implementation.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2846.

View file

@ -4,7 +4,7 @@ import (
"bytes"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
"github.com/AdguardTeam/golibs/ioutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -72,11 +72,10 @@ func TestLargestLabeled(t *testing.T) {
}
t.Run("scanner_fail", func(t *testing.T) {
lr, err := aghio.LimitReader(bytes.NewReader([]byte{1, 2, 3}), 0)
require.NoError(t, err)
lr := ioutil.LimitReader(bytes.NewReader([]byte{1, 2, 3}), 0)
target := &aghio.LimitReachedError{}
_, _, err = parsePSOutput(lr, "", nil)
target := &ioutil.LimitError{}
_, _, err := parsePSOutput(lr, "", nil)
require.ErrorAs(t, err, &target)
assert.EqualValues(t, 0, target.Limit)

View file

@ -4,10 +4,14 @@ package aghtest
import (
"crypto/sha256"
"io"
"net/http"
"net/http/httptest"
"net/netip"
"net/url"
"testing"
"github.com/AdguardTeam/golibs/log"
"github.com/stretchr/testify/require"
)
const (
@ -51,3 +55,19 @@ func HostToIPs(host string) (ipv4, ipv6 netip.Addr) {
return netip.AddrFrom4([4]byte(hash[:4])), netip.AddrFrom16([16]byte(hash[4:20]))
}
// StartHTTPServer is a helper that starts the HTTP server, which is configured
// to return data on every request, and returns the client and server URL.
func StartHTTPServer(t testing.TB, data []byte) (c *http.Client, u *url.URL) {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write(data)
}))
t.Cleanup(srv.Close)
u, err := url.Parse(srv.URL)
require.NoError(t, err)
return srv.Client(), u
}

View file

@ -57,6 +57,9 @@ type DHCPServer interface {
// RemoveStaticLease - remove a static lease
RemoveStaticLease(l *Lease) (err error)
// UpdateStaticLease updates IP, hostname of the lease.
UpdateStaticLease(l *Lease) (err error)
// FindMACbyIP returns a MAC address by the IP address of its lease, if
// there is one.
FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)

View file

@ -5,6 +5,7 @@ package dhcpd
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/netip"
@ -290,12 +291,12 @@ func (s *server) handleDHCPSetConfigV6(
func (s *server) createServers(conf *dhcpServerConfigJSON) (srv4, srv6 DHCPServer, err error) {
srv4, v4Enabled, err := s.handleDHCPSetConfigV4(conf)
if err != nil {
return nil, nil, fmt.Errorf("bad dhcpv4 configuration: %s", err)
return nil, nil, fmt.Errorf("bad dhcpv4 configuration: %w", err)
}
srv6, v6Enabled, err := s.handleDHCPSetConfigV6(conf)
if err != nil {
return nil, nil, fmt.Errorf("bad dhcpv6 configuration: %s", err)
return nil, nil, fmt.Errorf("bad dhcpv6 configuration: %w", err)
}
if conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled {
@ -424,7 +425,7 @@ func newNetInterfaceJSON(iface net.Interface) (out *netInterfaceJSON, err error)
addrs, err := iface.Addrs()
if err != nil {
return nil, fmt.Errorf(
"failed to get addresses for interface %s: %s",
"failed to get addresses for interface %s: %w",
iface.Name,
err,
)
@ -590,82 +591,78 @@ func setOtherDHCPResult(ifaceName string, result *dhcpSearchResult) {
}
}
func (s *server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) {
// parseLease parses a lease from r. If there is no error returns DHCPServer
// and *Lease. r must be non-nil.
func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *Lease, err error) {
l := &leaseStatic{}
err := json.NewDecoder(r.Body).Decode(l)
err = json.NewDecoder(r).Decode(l)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
return nil, nil, fmt.Errorf("decoding json: %w", err)
}
if !l.IP.IsValid() {
aghhttp.Error(r, w, http.StatusBadRequest, "invalid IP")
return
return nil, nil, errors.Error("invalid ip")
}
l.IP = l.IP.Unmap()
var srv DHCPServer
if l.IP.Is4() {
lease, err = l.toLease()
if err != nil {
return nil, nil, fmt.Errorf("parsing: %w", err)
}
if lease.IP.Is4() {
srv = s.srv4
} else {
srv = s.srv6
}
lease, err := l.toLease()
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "parsing: %s", err)
return srv, lease, nil
}
return
}
err = srv.AddStaticLease(lease)
// handleDHCPAddStaticLease is the handler for the POST
// /control/dhcp/add_static_lease HTTP API.
func (s *server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) {
srv, lease, err := s.parseLease(r.Body)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
if err = srv.AddStaticLease(lease); err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
}
}
// handleDHCPRemoveStaticLease is the handler for the POST
// /control/dhcp/remove_static_lease HTTP API.
func (s *server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) {
l := &leaseStatic{}
err := json.NewDecoder(r.Body).Decode(l)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
if !l.IP.IsValid() {
aghhttp.Error(r, w, http.StatusBadRequest, "invalid IP")
return
}
l.IP = l.IP.Unmap()
var srv DHCPServer
if l.IP.Is4() {
srv = s.srv4
} else {
srv = s.srv6
}
lease, err := l.toLease()
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "parsing: %s", err)
return
}
err = srv.RemoveStaticLease(lease)
srv, lease, err := s.parseLease(r.Body)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
if err = srv.RemoveStaticLease(lease); err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
}
}
// handleDHCPUpdateStaticLease is the handler for the POST
// /control/dhcp/update_static_lease HTTP API.
func (s *server) handleDHCPUpdateStaticLease(w http.ResponseWriter, r *http.Request) {
srv, lease, err := s.parseLease(r.Body)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
if err = srv.UpdateStaticLease(lease); err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
}
}
func (s *server) handleReset(w http.ResponseWriter, r *http.Request) {
@ -729,6 +726,7 @@ func (s *server) registerHandlers() {
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/find_active_dhcp", s.handleDHCPFindActiveServer)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/add_static_lease", s.handleDHCPAddStaticLease)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/remove_static_lease", s.handleDHCPRemoveStaticLease)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/update_static_lease", s.handleDHCPUpdateStaticLease)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset", s.handleReset)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset_leases", s.handleResetLeases)
}

View file

@ -0,0 +1,319 @@
//go:build darwin || freebsd || linux || openbsd
package dhcpd
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// defaultResponse is a helper that returns the response with default
// configuration.
func defaultResponse() *dhcpStatusResponse {
conf4 := defaultV4ServerConf()
conf4.LeaseDuration = 86400
resp := &dhcpStatusResponse{
V4: *conf4,
V6: V6ServerConf{},
Leases: []*leaseDynamic{},
StaticLeases: []*leaseStatic{},
Enabled: true,
}
return resp
}
// handleLease is the helper function that calls handler with provided static
// lease as body and returns modified response recorder.
func handleLease(t *testing.T, lease *leaseStatic, handler http.HandlerFunc) (w *httptest.ResponseRecorder) {
t.Helper()
w = httptest.NewRecorder()
b := &bytes.Buffer{}
err := json.NewEncoder(b).Encode(lease)
require.NoError(t, err)
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
handler(w, r)
return w
}
// checkStatus is a helper that asserts the response of
// [*server.handleDHCPStatus].
func checkStatus(t *testing.T, s *server, want *dhcpStatusResponse) {
w := httptest.NewRecorder()
b := &bytes.Buffer{}
err := json.NewEncoder(b).Encode(&want)
require.NoError(t, err)
r, err := http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
s.handleDHCPStatus(w, r)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, b.String(), w.Body.String())
}
func TestServer_handleDHCPStatus(t *testing.T) {
const (
staticName = "static-client"
staticMAC = "aa:aa:aa:aa:aa:aa"
)
staticIP := netip.MustParseAddr("192.168.10.10")
staticLease := &leaseStatic{
HWAddr: staticMAC,
IP: staticIP,
Hostname: staticName,
}
s, err := Create(&ServerConfig{
Enabled: true,
Conf4: *defaultV4ServerConf(),
DataDir: t.TempDir(),
ConfigModified: func() {},
})
require.NoError(t, err)
ok := t.Run("status", func(t *testing.T) {
resp := defaultResponse()
checkStatus(t, s, resp)
})
require.True(t, ok)
ok = t.Run("add_static_lease", func(t *testing.T) {
w := handleLease(t, staticLease, s.handleDHCPAddStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
resp := defaultResponse()
resp.StaticLeases = []*leaseStatic{staticLease}
checkStatus(t, s, resp)
})
require.True(t, ok)
ok = t.Run("add_invalid_lease", func(t *testing.T) {
w := handleLease(t, staticLease, s.handleDHCPAddStaticLease)
assert.Equal(t, http.StatusBadRequest, w.Code)
})
require.True(t, ok)
ok = t.Run("remove_static_lease", func(t *testing.T) {
w := handleLease(t, staticLease, s.handleDHCPRemoveStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
resp := defaultResponse()
checkStatus(t, s, resp)
})
require.True(t, ok)
ok = t.Run("set_config", func(t *testing.T) {
w := httptest.NewRecorder()
resp := defaultResponse()
resp.Enabled = false
b := &bytes.Buffer{}
err = json.NewEncoder(b).Encode(&resp)
require.NoError(t, err)
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
s.handleDHCPSetConfig(w, r)
assert.Equal(t, http.StatusOK, w.Code)
checkStatus(t, s, resp)
})
require.True(t, ok)
}
func TestServer_HandleUpdateStaticLease(t *testing.T) {
const (
leaseV4Name = "static-client-v4"
leaseV4MAC = "44:44:44:44:44:44"
leaseV6Name = "static-client-v6"
leaseV6MAC = "66:66:66:66:66:66"
)
leaseV4IP := netip.MustParseAddr("192.168.10.10")
leaseV6IP := netip.MustParseAddr("2001::6")
const (
leaseV4Pos = iota
leaseV6Pos
)
leases := []*leaseStatic{
leaseV4Pos: {
HWAddr: leaseV4MAC,
IP: leaseV4IP,
Hostname: leaseV4Name,
},
leaseV6Pos: {
HWAddr: leaseV6MAC,
IP: leaseV6IP,
Hostname: leaseV6Name,
},
}
s, err := Create(&ServerConfig{
Enabled: true,
Conf4: *defaultV4ServerConf(),
Conf6: V6ServerConf{},
DataDir: t.TempDir(),
ConfigModified: func() {},
})
require.NoError(t, err)
for _, l := range leases {
w := handleLease(t, l, s.handleDHCPAddStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
}
testCases := []struct {
name string
pos int
lease *leaseStatic
}{{
name: "update_v4_name",
pos: leaseV4Pos,
lease: &leaseStatic{
HWAddr: leaseV4MAC,
IP: leaseV4IP,
Hostname: "updated-client-v4",
},
}, {
name: "update_v4_ip",
pos: leaseV4Pos,
lease: &leaseStatic{
HWAddr: leaseV4MAC,
IP: netip.MustParseAddr("192.168.10.200"),
Hostname: "updated-client-v4",
},
}, {
name: "update_v6_name",
pos: leaseV6Pos,
lease: &leaseStatic{
HWAddr: leaseV6MAC,
IP: leaseV6IP,
Hostname: "updated-client-v6",
},
}, {
name: "update_v6_ip",
pos: leaseV6Pos,
lease: &leaseStatic{
HWAddr: leaseV6MAC,
IP: netip.MustParseAddr("2001::666"),
Hostname: "updated-client-v6",
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
w := handleLease(t, tc.lease, s.handleDHCPUpdateStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
resp := defaultResponse()
leases[tc.pos] = tc.lease
resp.StaticLeases = leases
checkStatus(t, s, resp)
})
}
}
func TestServer_HandleUpdateStaticLease_validation(t *testing.T) {
const (
leaseV4Name = "static-client-v4"
leaseV4MAC = "44:44:44:44:44:44"
anotherV4Name = "another-client-v4"
anotherV4MAC = "55:55:55:55:55:55"
)
leaseV4IP := netip.MustParseAddr("192.168.10.10")
anotherV4IP := netip.MustParseAddr("192.168.10.20")
leases := []*leaseStatic{{
HWAddr: leaseV4MAC,
IP: leaseV4IP,
Hostname: leaseV4Name,
}, {
HWAddr: anotherV4MAC,
IP: anotherV4IP,
Hostname: anotherV4Name,
}}
s, err := Create(&ServerConfig{
Enabled: true,
Conf4: *defaultV4ServerConf(),
Conf6: V6ServerConf{},
DataDir: t.TempDir(),
ConfigModified: func() {},
})
require.NoError(t, err)
for _, l := range leases {
w := handleLease(t, l, s.handleDHCPAddStaticLease)
assert.Equal(t, http.StatusOK, w.Code)
}
testCases := []struct {
lease *leaseStatic
name string
want string
}{{
name: "v4_unknown_mac",
lease: &leaseStatic{
HWAddr: "aa:aa:aa:aa:aa:aa",
IP: leaseV4IP,
Hostname: leaseV4Name,
},
want: "dhcpv4: updating static lease: can't find lease aa:aa:aa:aa:aa:aa\n",
}, {
name: "update_v4_same_ip",
lease: &leaseStatic{
HWAddr: leaseV4MAC,
IP: anotherV4IP,
Hostname: leaseV4Name,
},
want: "dhcpv4: updating static lease: ip address is not unique\n",
}, {
name: "update_v4_same_name",
lease: &leaseStatic{
HWAddr: leaseV4MAC,
IP: leaseV4IP,
Hostname: anotherV4Name,
},
want: "dhcpv4: updating static lease: hostname is not unique\n",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
w := handleLease(t, tc.lease, s.handleDHCPUpdateStaticLease)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, tc.want, w.Body.String())
})
}
}

View file

@ -1,159 +0,0 @@
//go:build darwin || freebsd || linux || openbsd
package dhcpd
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestServer_handleDHCPStatus(t *testing.T) {
const (
staticName = "static-client"
staticMAC = "aa:aa:aa:aa:aa:aa"
)
staticIP := netip.MustParseAddr("192.168.10.10")
staticLease := &leaseStatic{
HWAddr: staticMAC,
IP: staticIP,
Hostname: staticName,
}
s, err := Create(&ServerConfig{
Enabled: true,
Conf4: *defaultV4ServerConf(),
DataDir: t.TempDir(),
ConfigModified: func() {},
})
require.NoError(t, err)
// checkStatus is a helper that asserts the response of
// [*server.handleDHCPStatus].
checkStatus := func(t *testing.T, want *dhcpStatusResponse) {
w := httptest.NewRecorder()
var req *http.Request
req, err = http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
b := &bytes.Buffer{}
err = json.NewEncoder(b).Encode(&want)
require.NoError(t, err)
s.handleDHCPStatus(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, b.String(), w.Body.String())
}
// defaultResponse is a helper that returns the response with default
// configuration.
defaultResponse := func() *dhcpStatusResponse {
conf4 := defaultV4ServerConf()
conf4.LeaseDuration = 86400
resp := &dhcpStatusResponse{
V4: *conf4,
V6: V6ServerConf{},
Leases: []*leaseDynamic{},
StaticLeases: []*leaseStatic{},
Enabled: true,
}
return resp
}
ok := t.Run("status", func(t *testing.T) {
resp := defaultResponse()
checkStatus(t, resp)
})
require.True(t, ok)
ok = t.Run("add_static_lease", func(t *testing.T) {
w := httptest.NewRecorder()
b := &bytes.Buffer{}
err = json.NewEncoder(b).Encode(staticLease)
require.NoError(t, err)
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
s.handleDHCPAddStaticLease(w, r)
assert.Equal(t, http.StatusOK, w.Code)
resp := defaultResponse()
resp.StaticLeases = []*leaseStatic{staticLease}
checkStatus(t, resp)
})
require.True(t, ok)
ok = t.Run("add_invalid_lease", func(t *testing.T) {
w := httptest.NewRecorder()
b := &bytes.Buffer{}
err = json.NewEncoder(b).Encode(&leaseStatic{})
require.NoError(t, err)
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
s.handleDHCPAddStaticLease(w, r)
assert.Equal(t, http.StatusBadRequest, w.Code)
})
require.True(t, ok)
ok = t.Run("remove_static_lease", func(t *testing.T) {
w := httptest.NewRecorder()
b := &bytes.Buffer{}
err = json.NewEncoder(b).Encode(staticLease)
require.NoError(t, err)
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
s.handleDHCPRemoveStaticLease(w, r)
assert.Equal(t, http.StatusOK, w.Code)
resp := defaultResponse()
checkStatus(t, resp)
})
require.True(t, ok)
ok = t.Run("set_config", func(t *testing.T) {
w := httptest.NewRecorder()
resp := defaultResponse()
resp.Enabled = false
b := &bytes.Buffer{}
err = json.NewEncoder(b).Encode(&resp)
require.NoError(t, err)
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", b)
require.NoError(t, err)
s.handleDHCPSetConfig(w, r)
assert.Equal(t, http.StatusOK, w.Code)
checkStatus(t, resp)
})
require.True(t, ok)
}

View file

@ -43,6 +43,7 @@ func (s *server) registerHandlers() {
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/find_active_dhcp", s.notImplemented)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/add_static_lease", s.notImplemented)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/remove_static_lease", s.notImplemented)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/update_static_lease", s.notImplemented)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset", s.notImplemented)
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset_leases", s.notImplemented)
}

View file

@ -19,6 +19,7 @@ func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil }
func (winServer) getLeasesRef() []*Lease { return nil }
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil }
func (winServer) RemoveStaticLease(_ *Lease) (err error) { return nil }
func (winServer) UpdateStaticLease(_ *Lease) (err error) { return nil }
func (winServer) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}

View file

@ -148,6 +148,9 @@ func (s *v4Server) ResetLeases(leases []*Lease) (err error) {
return nil
}
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
s.leasedOffsets = newBitSet()
s.hostsIndex = make(map[string]*Lease, len(leases))
s.ipIndex = make(map[netip.Addr]*Lease, len(leases))
@ -182,16 +185,13 @@ func (s *v4Server) isBlocklisted(l *Lease) (ok bool) {
return false
}
ok = true
for _, b := range l.HWAddr {
if b != 0 {
ok = false
break
return false
}
}
return ok
return true
}
// GetLeases returns the list of current DHCP leases. It is safe for concurrent
@ -309,9 +309,15 @@ 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")
const (
// ErrDupHostname is returned by addLease, validateStaticLease when the
// modified lease has a not empty non-unique hostname.
ErrDupHostname = errors.Error("hostname is not unique")
// ErrDupIP is returned by addLease, validateStaticLease when the modified
// lease has a non-unique IP address.
ErrDupIP = errors.Error("ip address is not unique")
)
// addLease adds a dynamic or static lease.
func (s *v4Server) addLease(l *Lease) (err error) {
@ -428,6 +434,81 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
return nil
}
// UpdateStaticLease updates IP, hostname of the static lease.
func (s *v4Server) UpdateStaticLease(l *Lease) (err error) {
defer func() {
if err != nil {
err = errors.Annotate(err, "dhcpv4: updating static lease: %w")
return
}
s.conf.notify(LeaseChangedDBStore)
s.conf.notify(LeaseChangedRemovedStatic)
}()
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
found := s.findLease(l.HWAddr)
if found == nil {
return fmt.Errorf("can't find lease %s", l.HWAddr)
}
err = s.validateStaticLease(l)
if err != nil {
return err
}
err = s.rmLease(found)
if err != nil {
return fmt.Errorf("removing previous lease for %s (%s): %w", l.IP, l.HWAddr, err)
}
err = s.addLease(l)
if err != nil {
return fmt.Errorf("adding updated static lease for %s (%s): %w", l.IP, l.HWAddr, err)
}
return nil
}
// validateStaticLease returns an error if the static lease is invalid.
func (s *v4Server) validateStaticLease(l *Lease) (err error) {
hostname, err := normalizeHostname(l.Hostname)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
err = netutil.ValidateHostname(hostname)
if err != nil {
return fmt.Errorf("validating hostname: %w", err)
}
dup, ok := s.hostsIndex[hostname]
if ok && !bytes.Equal(dup.HWAddr, l.HWAddr) {
return ErrDupHostname
}
dup, ok = s.ipIndex[l.IP]
if ok && !bytes.Equal(dup.HWAddr, l.HWAddr) {
return ErrDupIP
}
l.Hostname = hostname
if gwIP := s.conf.GatewayIP; gwIP == l.IP {
return fmt.Errorf("can't assign the gateway IP %q to the lease", gwIP)
}
if sn := s.conf.subnet; !sn.Contains(l.IP) {
return fmt.Errorf("subnet %s does not contain the ip %q", sn, l.IP)
}
return nil
}
// updateStaticLease safe removes dynamic lease with the same properties and
// then adds a static lease l.
func (s *v4Server) updateStaticLease(l *Lease) (err error) {

View file

@ -90,6 +90,9 @@ func (s *v6Server) IPByHost(host string) (ip netip.Addr) {
func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
s.leases = nil
for _, l := range leases {
ip := net.IP(l.IP.AsSlice())
@ -232,6 +235,37 @@ func (s *v6Server) AddStaticLease(l *Lease) (err error) {
return nil
}
// UpdateStaticLease updates IP, hostname of the static lease.
func (s *v6Server) UpdateStaticLease(l *Lease) (err error) {
defer func() {
if err != nil {
err = errors.Annotate(err, "dhcpv6: updating static lease: %w")
return
}
s.conf.notify(LeaseChangedDBStore)
s.conf.notify(LeaseChangedRemovedStatic)
}()
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
found := s.findLease(l.HWAddr)
if found == nil {
return fmt.Errorf("can't find lease %s", l.HWAddr)
}
err = s.rmLease(found)
if err != nil {
return fmt.Errorf("removing previous lease for %s (%s): %w", l.IP, l.HWAddr, err)
}
s.addLease(l)
return nil
}
// RemoveStaticLease removes a static lease. It is safe for concurrent use.
func (s *v6Server) RemoveStaticLease(l *Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
@ -283,16 +317,14 @@ func (s *v6Server) rmLease(lease *Lease) (err error) {
return fmt.Errorf("lease not found")
}
// Find lease by MAC
func (s *v6Server) findLease(mac net.HardwareAddr) *Lease {
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
// Find lease by MAC.
func (s *v6Server) findLease(mac net.HardwareAddr) (lease *Lease) {
for i := range s.leases {
if bytes.Equal(mac, s.leases[i].HWAddr) {
return s.leases[i]
}
}
return nil
}
@ -474,7 +506,14 @@ func (s *v6Server) process(msg *dhcpv6.Message, req, resp dhcpv6.DHCPv6) bool {
return false
}
lease := s.findLease(mac)
var lease *Lease
func() {
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
lease = s.findLease(mac)
}()
if lease == nil {
log.Debug("dhcpv6: no lease for: %s", mac)

View file

@ -12,10 +12,12 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
@ -392,6 +394,122 @@ func (s *Server) prepareIpsetListSettings() (err error) {
return s.ipset.init(ipsets)
}
// collectListenAddr adds addrPort to addrs. It also adds its port to
// unspecPorts if its address is unspecified.
func collectListenAddr(
addrPort netip.AddrPort,
addrs map[netip.AddrPort]unit,
unspecPorts map[uint16]unit,
) {
if addrPort == (netip.AddrPort{}) {
return
}
addrs[addrPort] = unit{}
if addrPort.Addr().IsUnspecified() {
unspecPorts[addrPort.Port()] = unit{}
}
}
// collectDNSAddrs returns configured set of listening addresses. It also
// returns a set of ports of each unspecified listening address.
func (conf *ServerConfig) collectDNSAddrs() (
addrs map[netip.AddrPort]unit,
unspecPorts map[uint16]unit,
) {
// TODO(e.burkov): Perhaps, we shouldn't allocate as much memory, since the
// TCP and UDP listening addresses are currently the same.
addrs = make(map[netip.AddrPort]unit, len(conf.TCPListenAddrs)+len(conf.UDPListenAddrs))
unspecPorts = map[uint16]unit{}
for _, laddr := range conf.TCPListenAddrs {
collectListenAddr(laddr.AddrPort(), addrs, unspecPorts)
}
for _, laddr := range conf.UDPListenAddrs {
collectListenAddr(laddr.AddrPort(), addrs, unspecPorts)
}
return addrs, unspecPorts
}
// defaultPlainDNSPort is the default port for plain DNS.
const defaultPlainDNSPort uint16 = 53
// addrPortMatcher is a function that matches an IP address with port.
type addrPortMatcher func(addr netip.AddrPort) (ok bool)
// filterOut filters out all the upstreams that match um. It returns all the
// closing errors joined.
func (m addrPortMatcher) filterOut(upsConf *proxy.UpstreamConfig) (err error) {
var errs []error
delFunc := func(u upstream.Upstream) (ok bool) {
// TODO(e.burkov): We should probably consider the protocol of u to
// only filter out the listening addresses of the same protocol.
addr, parseErr := aghnet.ParseAddrPort(u.Address(), defaultPlainDNSPort)
if parseErr != nil || !m(addr) {
// Don't filter out the upstream if it either cannot be parsed, or
// does not match um.
return false
}
errs = append(errs, u.Close())
return true
}
upsConf.Upstreams = slices.DeleteFunc(upsConf.Upstreams, delFunc)
for d, ups := range upsConf.DomainReservedUpstreams {
upsConf.DomainReservedUpstreams[d] = slices.DeleteFunc(ups, delFunc)
}
for d, ups := range upsConf.SpecifiedDomainUpstreams {
upsConf.SpecifiedDomainUpstreams[d] = slices.DeleteFunc(ups, delFunc)
}
return errors.Join(errs...)
}
// ourAddrsMatcher returns a matcher that matches all the configured listening
// addresses.
func (conf *ServerConfig) ourAddrsMatcher() (m addrPortMatcher, err error) {
addrs, unspecPorts := conf.collectDNSAddrs()
if len(addrs) == 0 {
log.Debug("dnsforward: no listen addresses")
// Match no addresses.
return func(_ netip.AddrPort) (ok bool) { return false }, nil
}
if len(unspecPorts) == 0 {
log.Debug("dnsforward: filtering out addresses %s", addrs)
m = func(a netip.AddrPort) (ok bool) {
_, ok = addrs[a]
return ok
}
} else {
var ifaceAddrs []netip.Addr
ifaceAddrs, err = aghnet.CollectAllIfacesAddrs()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
}
log.Debug("dnsforward: filtering out addresses %s on ports %d", ifaceAddrs, unspecPorts)
m = func(a netip.AddrPort) (ok bool) {
if _, ok = unspecPorts[a.Port()]; ok {
return slices.Contains(ifaceAddrs, a.Addr())
}
return false
}
}
return m, nil
}
// prepareTLS - prepares TLS configuration for the DNS proxy
func (s *Server) prepareTLS(proxyConfig *proxy.Config) (err error) {
if len(s.conf.CertificateChainData) == 0 || len(s.conf.PrivateKeyData) == 0 {

View file

@ -25,8 +25,10 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/sysresolv"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// DefaultTimeout is the default upstream timeout
@ -72,6 +74,11 @@ type DHCP interface {
Enabled() (ok bool)
}
type SystemResolvers interface {
// Addrs returns the list of system resolvers' addresses.
Addrs() (addrs []netip.AddrPort)
}
// Server is the main way to start a DNS server.
//
// Example:
@ -126,7 +133,7 @@ type Server struct {
// sysResolvers used to fetch system resolvers to use by default for private
// PTR resolving.
sysResolvers aghnet.SystemResolvers
sysResolvers SystemResolvers
// recDetector is a cache for recursive requests. It is used to detect
// and prevent recursive requests only for private upstreams.
@ -225,9 +232,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
anonymizer: p.Anonymizer,
}
// TODO(e.burkov): Enable the refresher after the actual implementation
// passes the public testing.
s.sysResolvers, err = aghnet.NewSystemResolvers(nil)
s.sysResolvers, err = sysresolv.NewSystemResolvers(nil, defaultPlainDNSPort)
if err != nil {
return nil, fmt.Errorf("initializing system resolvers: %w", err)
}
@ -439,73 +444,28 @@ func (s *Server) startLocked() error {
// faster than ordinary upstreams.
const defaultLocalTimeout = 1 * time.Second
// collectDNSIPAddrs returns IP addresses the server is listening on without
// port numbers. For internal use only.
func (s *Server) collectDNSIPAddrs() (addrs []string, err error) {
addrs = make([]string, len(s.conf.TCPListenAddrs)+len(s.conf.UDPListenAddrs))
var i int
var ip net.IP
for _, addr := range s.conf.TCPListenAddrs {
if addr == nil {
continue
}
if ip = addr.IP; ip.IsUnspecified() {
return aghnet.CollectAllIfacesAddrs()
}
addrs[i] = ip.String()
i++
}
for _, addr := range s.conf.UDPListenAddrs {
if addr == nil {
continue
}
if ip = addr.IP; ip.IsUnspecified() {
return aghnet.CollectAllIfacesAddrs()
}
addrs[i] = ip.String()
i++
}
return addrs[:i], nil
}
func (s *Server) filterOurDNSAddrs(addrs []string) (filtered []string, err error) {
var ourAddrs []string
ourAddrs, err = s.collectDNSIPAddrs()
if err != nil {
return nil, err
}
ourAddrsSet := stringutil.NewSet(ourAddrs...)
log.Debug("dnsforward: filtering out %s", ourAddrsSet.String())
// TODO(e.burkov): The approach of subtracting sets of strings is not
// really applicable here since in case of listening on all network
// interfaces we should check the whole interface's network to cut off
// all the loopback addresses as well.
return stringutil.FilterOut(addrs, ourAddrsSet.Has), nil
}
// setupLocalResolvers initializes the resolvers for local addresses. For
// internal use only.
func (s *Server) setupLocalResolvers() (err error) {
bootstraps := s.conf.BootstrapDNS
resolvers := s.conf.LocalPTRResolvers
if len(resolvers) == 0 {
resolvers = s.sysResolvers.Get()
bootstraps = nil
} else {
resolvers = stringutil.FilterOut(resolvers, IsCommentOrEmpty)
matcher, err := s.conf.ourAddrsMatcher()
if err != nil {
// Don't wrap the error because it's informative enough as is.
return err
}
resolvers, err = s.filterOurDNSAddrs(resolvers)
if err != nil {
return err
bootstraps := s.conf.BootstrapDNS
resolvers := s.conf.LocalPTRResolvers
filterConfig := false
if len(resolvers) == 0 {
sysResolvers := slices.DeleteFunc(s.sysResolvers.Addrs(), matcher)
resolvers = make([]string, 0, len(sysResolvers))
for _, r := range sysResolvers {
resolvers = append(resolvers, r.String())
}
} else {
resolvers = stringutil.FilterOut(resolvers, IsCommentOrEmpty)
filterConfig = true
}
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", resolvers)
@ -514,13 +474,18 @@ func (s *Server) setupLocalResolvers() (err error) {
Bootstrap: bootstraps,
Timeout: defaultLocalTimeout,
// TODO(e.burkov): Should we verify server's certificates?
PreferIPv6: s.conf.BootstrapPreferIPv6,
})
if err != nil {
return fmt.Errorf("preparing private upstreams: %w", err)
}
if filterConfig {
if err = matcher.filterOut(uc); err != nil {
return fmt.Errorf("filtering private upstreams: %w", err)
}
}
s.localResolvers = &proxy.Proxy{
Config: proxy.Config{
UpstreamConfig: uc,

View file

@ -65,6 +65,9 @@ type jsonDNSConfig struct {
// UpstreamMode defines the way DNS requests are constructed.
UpstreamMode *string `json:"upstream_mode"`
// BlockedResponseTTL is the TTL for blocked responses.
BlockedResponseTTL *uint32 `json:"blocked_response_ttl"`
// CacheSize in bytes.
CacheSize *uint32 `json:"cache_size"`
@ -115,6 +118,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
bootstraps := stringutil.CloneSliceOrEmpty(s.conf.BootstrapDNS)
fallbacks := stringutil.CloneSliceOrEmpty(s.conf.FallbackDNS)
blockingMode, blockingIPv4, blockingIPv6 := s.dnsFilter.BlockingMode()
blockedResponseTTL := s.dnsFilter.BlockedResponseTTL()
ratelimit := s.conf.Ratelimit
customIP := s.conf.EDNSClientSubnet.CustomIP
@ -138,9 +142,9 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
upstreamMode = "parallel"
}
defLocalPTRUps, err := s.filterOurDNSAddrs(s.sysResolvers.Get())
defPTRUps, err := s.defaultLocalPTRUpstreams()
if err != nil {
log.Debug("getting dns configuration: %s", err)
log.Error("dnsforward: %s", err)
}
return &jsonDNSConfig{
@ -158,6 +162,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
EDNSCSUseCustom: &useCustom,
DNSSECEnabled: &enableDNSSEC,
DisableIPv6: &aaaaDisabled,
BlockedResponseTTL: &blockedResponseTTL,
CacheSize: &cacheSize,
CacheMinTTL: &cacheMinTTL,
CacheMaxTTL: &cacheMaxTTL,
@ -166,11 +171,29 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
ResolveClients: &resolveClients,
UsePrivateRDNS: &usePrivateRDNS,
LocalPTRUpstreams: &localPTRUpstreams,
DefaultLocalPTRUpstreams: defLocalPTRUps,
DefaultLocalPTRUpstreams: defPTRUps,
DisabledUntil: protectionDisabledUntil,
}
}
// defaultLocalPTRUpstreams returns the list of default local PTR resolvers
// filtered of AdGuard Home's own DNS server addresses. It may appear empty.
func (s *Server) defaultLocalPTRUpstreams() (ups []string, err error) {
matcher, err := s.conf.ourAddrsMatcher()
if err != nil {
// Don't wrap the error because it's informative enough as is.
return nil, err
}
sysResolvers := slices.DeleteFunc(s.sysResolvers.Addrs(), matcher)
ups = make([]string, 0, len(sysResolvers))
for _, r := range sysResolvers {
ups = append(ups, r.String())
}
return ups, nil
}
// handleGetConfig handles requests to the GET /control/dns_info endpoint.
func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
resp := s.getDNSConfig()
@ -204,7 +227,7 @@ func (req *jsonDNSConfig) checkBootstrap() (err error) {
return errors.Error("empty")
}
if _, err = upstream.NewResolver(b, nil); err != nil {
if _, err = upstream.NewUpstreamResolver(b, nil); err != nil {
return err
}
}
@ -321,6 +344,10 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) {
s.dnsFilter.SetBlockingMode(*dc.BlockingMode, dc.BlockingIPv4, dc.BlockingIPv6)
}
if dc.BlockedResponseTTL != nil {
s.dnsFilter.SetBlockedResponseTTL(*dc.BlockedResponseTTL)
}
if dc.ProtectionEnabled != nil {
s.dnsFilter.SetProtectionEnabled(*dc.ProtectionEnabled)
}

View file

@ -28,17 +28,12 @@ import (
"github.com/stretchr/testify/require"
)
// fakeSystemResolvers is a mock aghnet.SystemResolvers implementation for
// tests.
type fakeSystemResolvers struct {
// SystemResolvers is embedded here simply to make *fakeSystemResolvers
// an aghnet.SystemResolvers without actually implementing all methods.
aghnet.SystemResolvers
}
// emptySysResolvers is an empty [SystemResolvers] implementation that always
// returns nil.
type emptySysResolvers struct{}
// Get implements the aghnet.SystemResolvers interface for *fakeSystemResolvers.
// It always returns nil.
func (fsr *fakeSystemResolvers) Get() (rs []string) {
// Addrs implements the aghnet.SystemResolvers interface for emptySysResolvers.
func (emptySysResolvers) Addrs() (addrs []netip.AddrPort) {
return nil
}
@ -60,6 +55,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
filterConf := &filtering.Config{
ProtectionEnabled: true,
BlockingMode: filtering.BlockingModeDefault,
BlockedResponseTTL: 10,
SafeBrowsingEnabled: true,
SafeBrowsingCacheSize: 1000,
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
@ -78,7 +74,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
ConfigModified: func() {},
}
s := createTestServer(t, filterConf, forwardConf, nil)
s.sysResolvers = &fakeSystemResolvers{}
s.sysResolvers = &emptySysResolvers{}
require.NoError(t, s.Start())
testutil.CleanupAndRequireSuccess(t, s.Stop)
@ -137,6 +133,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
filterConf := &filtering.Config{
ProtectionEnabled: true,
BlockingMode: filtering.BlockingModeDefault,
BlockedResponseTTL: 10,
SafeBrowsingEnabled: true,
SafeBrowsingCacheSize: 1000,
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
@ -154,7 +151,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
ConfigModified: func() {},
}
s := createTestServer(t, filterConf, forwardConf, nil)
s.sysResolvers = &fakeSystemResolvers{}
s.sysResolvers = &emptySysResolvers{}
defaultConf := s.conf
@ -229,6 +226,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
}, {
name: "fallbacks",
wantSet: "",
}, {
name: "blocked_response_ttl",
wantSet: "",
}}
var data map[string]struct {
@ -480,7 +480,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
hostsListener := newLocalUpstreamListener(t, 0, goodHandler)
hostsUps := (&url.URL{
Scheme: "tcp",
Host: netutil.JoinHostPort(upstreamHost, int(hostsListener.Port())),
Host: netutil.JoinHostPort(upstreamHost, hostsListener.Port()),
}).String()
hc, err := aghnet.NewHostsContainer(

View file

@ -6,8 +6,8 @@ import (
"os"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/ipset"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns"
@ -15,14 +15,14 @@ import (
// ipsetCtx is the ipset context. ipsetMgr can be nil.
type ipsetCtx struct {
ipsetMgr aghnet.IpsetManager
ipsetMgr ipset.Manager
}
// init initializes the ipset context. It is not safe for concurrent use.
//
// TODO(a.garipov): Rewrite into a simple constructor?
func (c *ipsetCtx) init(ipsetConf []string) (err error) {
c.ipsetMgr, err = aghnet.NewIpsetManager(ipsetConf)
c.ipsetMgr, err = ipset.NewManager(ipsetConf)
if errors.Is(err, os.ErrInvalid) || errors.Is(err, os.ErrPermission) {
// ipset cannot currently be initialized if the server was installed
// from Snap or when the user or the binary doesn't have the required

View file

@ -114,3 +114,74 @@ func TestIpsetCtx_process(t *testing.T) {
assert.NoError(t, err)
})
}
func TestIpsetCtx_SkipIpsetProcessing(t *testing.T) {
req4 := createTestMessage("example.com")
resp4 := &dns.Msg{
Answer: []dns.RR{&dns.A{
A: net.IP{1, 2, 3, 4},
}},
}
m := &fakeIpsetMgr{}
ictx := &ipsetCtx{
ipsetMgr: m,
}
testCases := []struct {
dctx *dnsContext
name string
want bool
}{{
name: "basic",
want: false,
dctx: &dnsContext{
proxyCtx: &proxy.DNSContext{
Req: req4,
Res: resp4,
},
responseFromUpstream: true,
},
}, {
name: "rewrite",
want: true,
dctx: &dnsContext{
proxyCtx: &proxy.DNSContext{
Req: req4,
Res: resp4,
},
responseFromUpstream: false,
},
}, {
name: "empty_req",
want: true,
dctx: &dnsContext{
proxyCtx: &proxy.DNSContext{
Req: nil,
Res: resp4,
},
responseFromUpstream: true,
},
}, {
name: "empty_res",
want: true,
dctx: &dnsContext{
proxyCtx: &proxy.DNSContext{
Req: req4,
Res: nil,
},
responseFromUpstream: true,
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := ictx.skipIpsetProcessing(tc.dctx)
assert.Equal(t, tc.want, got)
})
}
}

View file

@ -20,6 +20,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -55,6 +56,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -90,6 +92,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,

View file

@ -25,6 +25,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -62,6 +63,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -100,6 +102,7 @@
"blocking_mode": "refused",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -138,6 +141,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -176,6 +180,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -214,6 +219,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": true,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -254,6 +260,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": true,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -294,6 +301,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -332,6 +340,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": true,
"disable_ipv6": false,
@ -370,6 +379,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -408,6 +418,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -446,6 +457,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -486,6 +498,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -526,6 +539,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -565,6 +579,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -603,6 +618,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -643,6 +659,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -686,6 +703,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -724,6 +742,7 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
@ -766,6 +785,46 @@
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 10,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
"upstream_mode": "",
"cache_size": 0,
"cache_ttl_min": 0,
"cache_ttl_max": 0,
"cache_optimistic": false,
"resolve_clients": false,
"use_private_ptr_resolvers": false,
"local_ptr_upstreams": [],
"edns_cs_use_custom": false,
"edns_cs_custom_ip": ""
}
},
"blocked_response_ttl": {
"req": {
"blocked_response_ttl": 11
},
"want": {
"upstream_dns": [
"8.8.8.8:53",
"8.8.4.4:53"
],
"upstream_dns_file": "",
"bootstrap_dns": [
"9.9.9.10",
"149.112.112.10",
"2620:fe::10",
"2620:fe::fe:10"
],
"fallback_dns": [],
"protection_enabled": true,
"protection_disabled_until": null,
"ratelimit": 0,
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"blocked_response_ttl": 11,
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,

Some files were not shown because too many files have changed in this diff Show more