Compare commits
33 Commits
v0.106.0-b
...
v0.106.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caa90b0340 | ||
|
|
81a5225622 | ||
|
|
199e1f6a60 | ||
|
|
66d47b1462 | ||
|
|
e1ac2590c9 | ||
|
|
7696afd03c | ||
|
|
3d48ec4555 | ||
|
|
e576a23dd1 | ||
|
|
e0c839f105 | ||
|
|
49a0b90795 | ||
|
|
ee3b34ecb1 | ||
|
|
351d793b96 | ||
|
|
d59938d254 | ||
|
|
5b9bbce55d | ||
|
|
fd3b244a8a | ||
|
|
bc33868676 | ||
|
|
f603c21b55 | ||
|
|
3f1b71fdf3 | ||
|
|
45840948f5 | ||
|
|
51372d5c34 | ||
|
|
cb01eaa7f7 | ||
|
|
138dfaa85b | ||
|
|
4165e0ef3a | ||
|
|
2d87a0458e | ||
|
|
c9a5a5b9c8 | ||
|
|
c9c2a79a55 | ||
|
|
34c87ea709 | ||
|
|
aee09d29e9 | ||
|
|
2e4e1973d9 | ||
|
|
c26675585d | ||
|
|
55cd4ae254 | ||
|
|
93638a1936 | ||
|
|
71030bafd8 |
60
CHANGELOG.md
60
CHANGELOG.md
@@ -10,11 +10,47 @@ and this project adheres to
|
||||
## [Unreleased]
|
||||
|
||||
<!--
|
||||
## [v0.106.0] - 2021-05-01
|
||||
## [v0.107.0] - 2021-06-21 (APPROX.)
|
||||
-->
|
||||
|
||||
<!--
|
||||
## [v0.106.3] - 2021-05-17 (APPROX.)
|
||||
-->
|
||||
|
||||
|
||||
|
||||
## [v0.106.2] - 2021-05-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- Uniqueness validation for dynamic DHCP leases ([#3056]).
|
||||
|
||||
[#3056]: https://github.com/AdguardTeam/AdGuardHome/issues/3056
|
||||
|
||||
|
||||
|
||||
## [v0.106.1] - 2021-04-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- Local domain name handling when the DHCP server is disabled ([#3028]).
|
||||
- Normalization of perviously-saved invalid static DHCP leases ([#3027]).
|
||||
- Validation of IPv6 addresses with zones in system resolvers ([#3022]).
|
||||
|
||||
[#3022]: https://github.com/AdguardTeam/AdGuardHome/issues/3022
|
||||
[#3027]: https://github.com/AdguardTeam/AdGuardHome/issues/3027
|
||||
[#3028]: https://github.com/AdguardTeam/AdGuardHome/issues/3028
|
||||
|
||||
|
||||
|
||||
## [v0.106.0] - 2021-04-28
|
||||
|
||||
### Added
|
||||
|
||||
- The ability to block user for login after configurable number of unsuccessful
|
||||
attempts for configurable time ([#2826]).
|
||||
- `$denyallow` modifier for filters ([#2923]).
|
||||
- Hostname uniqueness validation in the DHCP server ([#2952]).
|
||||
- Hostname generating for DHCP clients which don't provide their own ([#2723]).
|
||||
- New flag `--no-etc-hosts` to disable client domain name lookups in the
|
||||
operating system's /etc/hosts files ([#1947]).
|
||||
@@ -32,6 +68,8 @@ and this project adheres to
|
||||
|
||||
### Changed
|
||||
|
||||
- Our DoQ implementation is now updated to conform to the latest standard
|
||||
[draft][doq-draft-02] ([#2843]).
|
||||
- Quality of logging ([#2954]).
|
||||
- Normalization of hostnames sent by DHCP clients ([#2945], [#2952]).
|
||||
- The access to the private hosts is now forbidden for users from external
|
||||
@@ -49,6 +87,9 @@ and this project adheres to
|
||||
|
||||
### Fixed
|
||||
|
||||
- Multiple answers for `$dnsrewrite` rule matching requests with repeating
|
||||
patterns in it ([#2981]).
|
||||
- Root server resolving when custom upstreams for hosts are specified ([#2994]).
|
||||
- Inconsistent resolving of DHCP clients when the DHCP server is disabled
|
||||
([#2934]).
|
||||
- Comment handling in clients' custom upstreams ([#2947]).
|
||||
@@ -74,10 +115,13 @@ and this project adheres to
|
||||
[#2704]: https://github.com/AdguardTeam/AdGuardHome/issues/2704
|
||||
[#2723]: https://github.com/AdguardTeam/AdGuardHome/issues/2723
|
||||
[#2824]: https://github.com/AdguardTeam/AdGuardHome/issues/2824
|
||||
[#2826]: https://github.com/AdguardTeam/AdGuardHome/issues/2826
|
||||
[#2828]: https://github.com/AdguardTeam/AdGuardHome/issues/2828
|
||||
[#2835]: https://github.com/AdguardTeam/AdGuardHome/issues/2835
|
||||
[#2838]: https://github.com/AdguardTeam/AdGuardHome/issues/2838
|
||||
[#2843]: https://github.com/AdguardTeam/AdGuardHome/issues/2843
|
||||
[#2889]: https://github.com/AdguardTeam/AdGuardHome/issues/2889
|
||||
[#2923]: https://github.com/AdguardTeam/AdGuardHome/issues/2923
|
||||
[#2927]: https://github.com/AdguardTeam/AdGuardHome/issues/2927
|
||||
[#2934]: https://github.com/AdguardTeam/AdGuardHome/issues/2934
|
||||
[#2945]: https://github.com/AdguardTeam/AdGuardHome/issues/2945
|
||||
@@ -85,6 +129,10 @@ and this project adheres to
|
||||
[#2952]: https://github.com/AdguardTeam/AdGuardHome/issues/2952
|
||||
[#2954]: https://github.com/AdguardTeam/AdGuardHome/issues/2954
|
||||
[#2961]: https://github.com/AdguardTeam/AdGuardHome/issues/2961
|
||||
[#2981]: https://github.com/AdguardTeam/AdGuardHome/issues/2981
|
||||
[#2994]: https://github.com/AdguardTeam/AdGuardHome/issues/2994
|
||||
|
||||
[doq-draft-02]: https://tools.ietf.org/html/draft-ietf-dprive-dnsoquic-02
|
||||
|
||||
|
||||
|
||||
@@ -298,11 +346,15 @@ and this project adheres to
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.106.0...HEAD
|
||||
[v0.106.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.2...v0.106.0
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.106.3...HEAD
|
||||
[v0.107.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.106.3...v0.107.0
|
||||
[v0.106.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.106.2...v0.106.3
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.2...HEAD
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.106.2...HEAD
|
||||
[v0.106.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.106.1...v0.106.2
|
||||
[v0.106.1]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.106.0...v0.106.1
|
||||
[v0.106.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.2...v0.106.0
|
||||
[v0.105.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.1...v0.105.2
|
||||
[v0.105.1]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.0...v0.105.1
|
||||
[v0.105.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...v0.105.0
|
||||
|
||||
22
Makefile
22
Makefile
@@ -7,9 +7,15 @@
|
||||
CHANNEL = development
|
||||
CLIENT_BETA_DIR = client2
|
||||
CLIENT_DIR = client
|
||||
COMMIT = $$(git rev-parse --short HEAD)
|
||||
COMMIT = $$( git rev-parse --short HEAD )
|
||||
DIST_DIR = dist
|
||||
GO = go
|
||||
# Don't name this macro "GO", because GNU Make apparenly makes it an
|
||||
# exported environment variable with the literal value of "${GO:-go}",
|
||||
# which is not what we need. Use a dot in the name to make sure that
|
||||
# users don't have an environment variable with the same name.
|
||||
#
|
||||
# See https://unix.stackexchange.com/q/646255/105635.
|
||||
GO.MACRO = $${GO:-go}
|
||||
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
|
||||
GPG_KEY = devteam@adguard.com
|
||||
GPG_KEY_PASSPHRASE = not-a-real-password
|
||||
@@ -33,9 +39,9 @@ ENV = env\
|
||||
GPG_KEY='$(GPG_KEY)'\
|
||||
GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\
|
||||
DIST_DIR='$(DIST_DIR)'\
|
||||
GO='$(GO)'\
|
||||
GO="$(GO.MACRO)"\
|
||||
GOPROXY='$(GOPROXY)'\
|
||||
PATH="$${PWD}/bin:$$($(GO) env GOPATH)/bin:$${PATH}"\
|
||||
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
|
||||
RACE='$(RACE)'\
|
||||
SIGN='$(SIGN)'\
|
||||
VERBOSE='$(VERBOSE)'\
|
||||
@@ -93,10 +99,10 @@ go-check: go-tools go-lint go-test
|
||||
# A quick check to make sure that all supported operating systems can be
|
||||
# typechecked and built successfully.
|
||||
go-os-check:
|
||||
env GOOS='darwin' go vet ./internal/...
|
||||
env GOOS='freebsd' go vet ./internal/...
|
||||
env GOOS='linux' go vet ./internal/...
|
||||
env GOOS='windows' go vet ./internal/...
|
||||
env GOOS='darwin' "$(GO.MACRO)" vet ./internal/...
|
||||
env GOOS='freebsd' "$(GO.MACRO)" vet ./internal/...
|
||||
env GOOS='linux' "$(GO.MACRO)" vet ./internal/...
|
||||
env GOOS='windows' "$(GO.MACRO)" vet ./internal/...
|
||||
|
||||
openapi-lint: ; cd ./openapi/ && $(YARN) test
|
||||
openapi-show: ; cd ./openapi/ && $(YARN) start
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
'project-key': 'AGH'
|
||||
'key': 'AGHBSNAPSPECS'
|
||||
'name': 'AdGuard Home - Build and publish release'
|
||||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:2.0'
|
||||
@@ -228,7 +229,7 @@
|
||||
- 'adg-docker': 'true'
|
||||
|
||||
'triggers':
|
||||
- 'cron': '0 30 14 * * ?'
|
||||
- 'cron': '0 30 14 ? * MON-FRI *'
|
||||
'branches':
|
||||
'create': 'manually'
|
||||
'delete':
|
||||
@@ -250,3 +251,25 @@
|
||||
'labels': []
|
||||
'other':
|
||||
'concurrent-build-plugin': 'system-default'
|
||||
|
||||
'branch-overrides':
|
||||
# beta-vX.Y branches are the branches into which the commits that are needed to
|
||||
# release a new patch version are initially cherry-picked.
|
||||
- '^beta-v[0-9]+\.[0-9]+':
|
||||
# Build betas on release branches manually.
|
||||
'triggers': []
|
||||
# Set the default release channel on the release branch to beta, as we may
|
||||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:2.0'
|
||||
# release-vX.Y.Z branches are the branches from which the actual final release
|
||||
# is built.
|
||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||
# Build final releases on release branches manually.
|
||||
'triggers': []
|
||||
# Set the default release channel on the final branch to release, as these
|
||||
# are the ones that actually get released.
|
||||
'variables':
|
||||
'channel': 'release'
|
||||
'dockerGo': 'adguard/golang-ubuntu:2.0'
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"form_error_mac_format": "Некарэктны фармат MAC",
|
||||
"form_error_client_id_format": "Няслушны фармат ID кліента",
|
||||
"form_error_server_name": "Няслушнае імя сервера",
|
||||
"form_error_subnet": "Падсетка «{{cidr}}» не ўтрымвае IP-адраса «{{ip}}»",
|
||||
"form_error_positive": "Павінна быць больш 0",
|
||||
"form_error_negative": "Павінна быць не менш 0",
|
||||
"range_end_error": "Павінен перавышаць пачатак дыяпазону",
|
||||
@@ -310,7 +311,7 @@
|
||||
"install_devices_router": "Роўтар",
|
||||
"install_devices_router_desc": "Такая наладка аўтаматычна пакрые ўсе прылады, што выкарыстоўваюць ваш хатні роўтар, і вам не трэба будзе наладжваць кожнае з іх у асобнасці.",
|
||||
"install_devices_address": "DNS-сервер AdGuard Home даступны па наступных адрасах",
|
||||
"install_devices_router_list_1": "Адкрыйце налады вашага рутара. Звычайна вы можаце адкрыць іх у вашым браўзары (напрыклад, http://192.168.0.1/ ці http://192.168.1.1/). Вас могуць папрасіць увесці пароль. Калі вы не помніце яго, пароль часта можна скінуць, націснуўшы на кнопку на самым роўтары. Некаторыя роўтары патрабуюць адмысловага дадатку, які ў гэтым выпадку павінен быць ужо ўсталявана на ваш кампутар ці тэлефон.",
|
||||
"install_devices_router_list_1": "Адкрыйце налады вашага роўтара. Звычайна вы можаце адкрыць іх у вашым браўзары, напрыклад, http://192.168.0.1/ ці http://192.168.1.1/. Вас могуць папрасіць увесці пароль. Калі вы не помніце яго, пароль часта можна скінуць, націснуўшы на кнопку на самым роўтары. Некаторыя роўтары патрабуюць адмысловага дадатку, які ў гэтым выпадку павінен быць ужо ўсталявана на ваш кампутар ці тэлефон.",
|
||||
"install_devices_router_list_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары \"DNS\" поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.",
|
||||
"install_devices_router_list_3": "Увядзіце туды адрас вашага AdGuard Home.",
|
||||
"install_devices_router_list_4": "Вы не можаце ўсталяваць уласны DNS-сервер на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе DNS-сервераў для вашай пэўнай мадэлі маршрутызатара.",
|
||||
@@ -400,6 +401,7 @@
|
||||
"ip_address": "IP-адрас",
|
||||
"client_identifier_desc": "Кліенты могуць быць ідэнтыфікаваны па IP-адрасе, CIDR ці MAC-адрасу. Звярніце ўвагу, што выкарыстанне MAC як ідэнтыфікатара магчыма, толькі калі AdGuard Home таксама з'яўляецца і <0>DHCP-серверам</0>",
|
||||
"form_enter_ip": "Увядзіце IP",
|
||||
"form_enter_subnet_ip": "Увядзіце IP-адрас у падсеткі «{{cidr}}»",
|
||||
"form_enter_mac": "Увядзіце MAC",
|
||||
"form_enter_id": "Увядзіце ідэнтыфікатар",
|
||||
"form_add_id": "Дадаць ідэнтыфікатар",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Bootstrap DNS servery",
|
||||
"bootstrap_dns_desc": "Servery Bootstrap DNS se používají k řešení IP adres DoH/DoT, které zadáváte jako upstreamy.",
|
||||
"local_ptr_title": "Soukromé DNS servery",
|
||||
"local_ptr_desc": "Servery DNS, které AdGuard Home použije pro dotazy na lokálně poskytované zdroje. Tento server bude například používán k řešení názvů hostitelů pro klienty se soukromými IP adresami. Pokud není nastaveno, AdGuard Home automaticky použije váš výchozí překladač DNS.",
|
||||
"local_ptr_desc": "Servery DNS, které AdGuard Home používá pro lokální dotazy PTR. Tyto servery se používají k rozlišení názvů hostitelů klientů se soukromými adresami IP, například \"192.168.12.34\" pomocí rDNS. Pokud není nastaveno, AdGuard Home automaticky použije výchozí řešitele vašeho OS.",
|
||||
"local_ptr_placeholder": "Zadejte jednu adresu serveru na řádek",
|
||||
"resolve_clients_title": "Povolit zpětné řešení IP adres klientů",
|
||||
"resolve_clients_desc": "Pokud je povoleno, AdGuard Home se pokusí obráceně vyřešit IP adresy klientů na jejich názvy hostitelů zasláním dotazů PTR příslušným řešitelům (soukromé DNS servery pro místní klienty, odchozí server pro klienty s veřejnou IP adresou).",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Bootstrap DNS-servere",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS-servere bruges til at fortolke IP-adresser for de DoH-/DoT-resolvere, du angiver som upstream.",
|
||||
"local_ptr_title": "Private DNS-servere",
|
||||
"local_ptr_desc": "De DNS-servere som AdGuard Home vil bruge til forespørgsler efter lokalt leverede ressourcer. For eksempel vil denne server blive brugt til at løse klienters værtsnavne for klienter med private IP-adresser. Hvis ikke indstillet, bruger AdGuard Home automatisk din standard DNS-resolver.",
|
||||
"local_ptr_desc": "De DNS-servere, som AdGuard Home bruger til lokale PTR-forespørgsler. Disse servere bruges til at opløse klientværtsnavne med private IP-adresser, f.eks. \"192.168.12.34\", vha. rDNS. Hvis ikke indstillet, bruger AdGuard Home dit operativsystems standard DNS-opløsere.",
|
||||
"local_ptr_placeholder": "Indtast en serveradresse pr. Linje",
|
||||
"resolve_clients_title": "Aktivér omvendt løsning af klienters IP-adresser",
|
||||
"resolve_clients_desc": "Hvis aktiveret, forsøger AdGuard Home automatisk at løse klienters værtsnavne fra deres IP-adresser ved at sende en PTR-forespørgsel til en tilsvarende resolver (privat DNS-server til lokale klienter, upstream-server til klienter med offentlig IP-adresse).",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Bootstrap DNS-Server starten",
|
||||
"bootstrap_dns_desc": "Bootstrap-DNS-Server werden verwendet, um IP-Adressen der DoH/DoT-Resolver aufzulösen, die Sie als Upstreams angeben.",
|
||||
"local_ptr_title": "Eigene DNS-Server",
|
||||
"local_ptr_desc": "DNS-Server, die AdGuard Home für Abfragen nach lokal bereitgestellten Ressourcen verwenden wird. Diese Server werden z. B. für die Auflösung der Hostnamen der Clients für die Clients mit privaten IP-Adressen verwendet. Wenn nicht festgelegt, verwendet AdGuard Home automatisch Ihre Standard-DNS-Auflösung.",
|
||||
"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“, mithilfe von rDNS aufzulösen. Wenn nicht festgelegt, verwendet AdGuard Home die Standard-DNS-Resolver Ihres Betriebssystems.",
|
||||
"local_ptr_placeholder": "Eine Serveradresse pro Zeile eingeben",
|
||||
"resolve_clients_title": "Hostnamenauflösung der Clients aktivieren",
|
||||
"resolve_clients_desc": "Wenn aktiviert, versucht AdGuard Home, die Hostnamen der Clients automatisch aus deren IP-Adressen aufzulösen, indem er eine PTR-Abfrage an einen entsprechenden Auflösungsdienst (privater DNS-Server für lokale Clients, Upstream-Server für Clients mit öffentlicher IP) sendet.",
|
||||
@@ -314,7 +314,7 @@
|
||||
"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.",
|
||||
"install_devices_router_list_4": "Sie können auf einigen Routern keine beliebigen DNS-Server festlegen. In diesem Fall kann es hilfreich sein, dass Sie AdGuard Home als <0>DHCP-Server</0> festlegen. Andernfalls sollten Sie nach einer Bedienungsanleitung zum Anpassen des DNS-Server für Ihr Router-Modell suchen.",
|
||||
"install_devices_router_list_4": "Bei einigen Routertypen kann kein eigener DNS-Server eingerichtet werden. In diesem Fall kann es helfen, AdGuard Home als <0>DHCP-Server</0> einzurichten. Andernfalls sollten Sie im Handbuch des Routers nachsehen, wie Sie DNS-Server auf Ihrem konkreten Router-Modell anpassen können.",
|
||||
"install_devices_windows_list_1": "Öffnen Sie die Systemsteuerung über das Startmenü oder die Windows-Suche.",
|
||||
"install_devices_windows_list_2": "Öffnen Sie die Kategorie „Netzwerk und Internet” und dann „Netzwerk- und Freigabecenter”.",
|
||||
"install_devices_windows_list_3": "Suchen Sie auf der linken Seite des Bildschirms nach „Adaptereinstellungen ändern” und klicken Sie darauf.",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Bootstrap DNS servers",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams.",
|
||||
"local_ptr_title": "Private DNS servers",
|
||||
"local_ptr_desc": "The DNS servers that AdGuard Home will use for queries for locally served resources. For instance, this server will be used for resolving clients' hostnames for the clients with private IP addresses. If not set, AdGuard Home will automatically use your default DNS resolver.",
|
||||
"local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve the hostnames of clients with private IP addresses, for example \"192.168.12.34\", using rDNS. If not set, AdGuard Home uses the default DNS resolvers of your OS.",
|
||||
"local_ptr_placeholder": "Enter one server address per line",
|
||||
"resolve_clients_title": "Enable reverse resolving of clients' IP addresses",
|
||||
"resolve_clients_desc": "If enabled, AdGuard Home will attempt to reversely resolve clients' IP addresses into their hostnames by sending PTR queries to corresponding resolvers (private DNS servers for local clients, upstream server for clients with public IP addresses).",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Servidores DNS de arranque",
|
||||
"bootstrap_dns_desc": "Los servidores DNS de arranque se utilizan para resolver las direcciones IP de los resolutores DoH/DoT que especifiques como DNS de subida.",
|
||||
"local_ptr_title": "Servidores DNS privados",
|
||||
"local_ptr_desc": "Los servidores DNS que AdGuard Home utilizará para las consultas de recursos servidos localmente. Por ejemplo, este servidor se utilizará para resolver los nombres de hosts de los clientes con direcciones IP privadas. Si no está establecido, AdGuard Home utilizará automáticamente tu resolutor DNS predeterminado.",
|
||||
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver los nombres de hosts de los clientes a direcciones IP privadas, por ejemplo \"192.168.12.34\", utilizando rDNS. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo.",
|
||||
"local_ptr_placeholder": "Ingresa una dirección de servidor por línea",
|
||||
"resolve_clients_title": "Habilitar la resolución inversa de las direcciones IP de clientes",
|
||||
"resolve_clients_desc": "Si está habilitado, AdGuard Home intentará resolver de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, servidor DNS de subida para clientes con direcciones IP públicas).",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Serveurs DNS d'amorçage",
|
||||
"bootstrap_dns_desc": "Les serveurs DNS d'amorçage sont utilisés pour résoudre les adresses IP des résolveurs DoH/DoT que vous spécifiez comme upstream.",
|
||||
"local_ptr_title": "Serveurs DNS privés",
|
||||
"local_ptr_desc": "Le serveur ou serveurs DNS qui seront utilisés par AdGuard Home pour les requêtes de ressources servies localement. Ce serveur pourra être utilisé, par exemple, pour résoudre les noms d'hôtes des clients pour les clients avec des adresses IP privées. S'il n'est pas défini, AdGuard Home utilisera votre résolveur DNS par défaut automatiquement.",
|
||||
"local_ptr_desc": "Les serveurs DNS utilisés par AdGuard Home pour les requêtes PTR servies localement. Ces serveurs sont utilisés pour résoudre les noms d'hôtes des clients pour les clients avec des adresses IP privées, par exemple \"192.168.12.34\", en utilisant rDNS. S'il n'est pas défini, AdGuard Home utilisera le résolveur DNS de votre OS par défaut automatiquement.",
|
||||
"local_ptr_placeholder": "Saisissez une adresse de serveur par ligne",
|
||||
"resolve_clients_title": "Activer la résolution inverse des adresses IP des clients",
|
||||
"resolve_clients_desc": "Lorsque activé, AdGuard Home tentera de résoudre de manière inverse les adresses IP des clients en leurs noms d'hôtes en envoyant des requêtes PTR aux résolveurs correspondants (serveurs DNS privés pour les clients locaux, serveur en amont pour les clients ayant des adresses IP publiques).",
|
||||
@@ -38,7 +38,7 @@
|
||||
"form_error_mac_format": "Format MAC invalide",
|
||||
"form_error_client_id_format": "Format d'ID client non valide",
|
||||
"form_error_server_name": "Nom de serveur invalide",
|
||||
"form_error_subnet": "Le sous-réseau \"{{cidr}}\" ne contient pas l'adresse IP \"{{ip}}\"",
|
||||
"form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} »",
|
||||
"form_error_positive": "Doit être supérieur à 0",
|
||||
"form_error_negative": "Doit être égal à 0 ou supérieur",
|
||||
"range_end_error": "Doit être supérieur au début de la gamme",
|
||||
@@ -58,16 +58,16 @@
|
||||
"dhcp_warning": "Si vous souhaitez tout de même activer le serveur DHCP, assurez-vous qu'il n'y a pas d'autre serveur DHCP actif sur votre réseau. Sinon, cela peut perturber la connexion Internet sur tous les appareils connectés au réseau !",
|
||||
"dhcp_error": "AdGuard Home ne peut pas déterminer s'il y a un autre serveur DHCP actif sur le réseau.",
|
||||
"dhcp_static_ip_error": "Pour utiliser un serveur DHCP, une adresse IP statique est requise. AdGuard Home n'a pas réussi à déterminer si cette interface réseau est configurée via une adresse IP statique. Veuillez définir une adresse IP statique manuellement.",
|
||||
"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 DHCP\".",
|
||||
"dhcp_lease_added": "\"{{key}}\" de bail statique ajoutée avec succès",
|
||||
"dhcp_lease_deleted": "\"{{key}}\" de bail statique supprimée avec succès",
|
||||
"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 avec succès",
|
||||
"dhcp_lease_deleted": "« {{key}} » de bail statique supprimée avec succès",
|
||||
"dhcp_new_static_lease": "Nouveau bail statique",
|
||||
"dhcp_static_leases_not_found": "Aucun bail statique DHCP trouvé",
|
||||
"dhcp_add_static_lease": "Ajoutez un bail statique",
|
||||
"dhcp_reset": "Voulez-vous vraiment réinitialiser votre configuration DHCP ?",
|
||||
"country": "Pays",
|
||||
"city": "Ville",
|
||||
"delete_confirm": "Voulez-vous vraiment supprimer \"{{key}}\" ?",
|
||||
"delete_confirm": "Voulez-vous vraiment supprimer « {{key}} »?",
|
||||
"form_enter_hostname": "Saisissez un nom d'hôte",
|
||||
"error_details": "Détails des erreurs",
|
||||
"response_details": "Détails de la réponse",
|
||||
@@ -201,7 +201,7 @@
|
||||
"all_lists_up_to_date_toast": "Toutes les listes sont déjà à jour",
|
||||
"updated_upstream_dns_toast": "Les serveurs DNS upstream sont mis à jour",
|
||||
"dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement",
|
||||
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur \"{{key}}\": veuillez vérifier si le nom saisi est bien correct",
|
||||
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur « {{key}} »: veuillez vérifier si le nom saisi est bien correct",
|
||||
"unblock": "Débloquer",
|
||||
"block": "Bloquer",
|
||||
"disallow_this_client": "Interdire ce client",
|
||||
@@ -227,8 +227,8 @@
|
||||
"page_table_footer_text": "Page",
|
||||
"rows_table_footer_text": "lignes",
|
||||
"updated_custom_filtering_toast": "Règles de filtrage d'utilisateur mises à jour",
|
||||
"rule_removed_from_custom_filtering_toast": "Règle retirée des règles d'utilisateur: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Règle ajoutée aux règles d'utilisateur: {{rule}}",
|
||||
"rule_removed_from_custom_filtering_toast": "Règle retirée des règles d'utilisateur : {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Règle ajoutée aux règles d'utilisateur : {{rule}}",
|
||||
"query_log_response_status": "Statut : {{value}}",
|
||||
"query_log_filtered": "Filtré par {{filter}}",
|
||||
"query_log_confirm_clear": "Êtes-vous sûr de vouloir effacer tout le journal des requêtes ?",
|
||||
@@ -272,10 +272,10 @@
|
||||
"rate_limit_desc": "Le nombre de requêtes par seconde qu’un seul client est autorisé à faire. Le réglage 0 fait illimité.",
|
||||
"blocking_ipv4_desc": "Adresse IP à renvoyer pour une demande A bloquée",
|
||||
"blocking_ipv6_desc": "Adresse IP à renvoyer pour une demande AAAA bloquée",
|
||||
"blocking_mode_default": "Par défaut : Répondre avec adresse IP zéro (0.0.0.0 pour A; :: pour AAAA) lorsque bloqué par la règle de style Adblock; répondre avec l’adresse IP spécifiée dans la règle lorsque bloquée par la règle du style /etc/hosts",
|
||||
"blocking_mode_refused": "REFUSED: Répondre avec le code REFUSED",
|
||||
"blocking_mode_default": "Par défaut : Répondre avec adresse IP zéro (0.0.0.0 pour A ; :: pour AAAA) lorsque bloqué par la règle de style Adblock ; répondre avec l’adresse IP spécifiée dans la règle lorsque bloquée par la règle du style /etc/hosts",
|
||||
"blocking_mode_refused": "REFUSED : Répondre avec le code REFUSED",
|
||||
"blocking_mode_nxdomain": "NXDOMAIN : Répondre avec le code NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP nulle : Répondre avec une adresse IP nulle (0.0.0.0 pour A; :: pour AAAA)",
|
||||
"blocking_mode_null_ip": "IP nulle : Répondre avec une adresse IP nulle (0.0.0.0 pour A ; :: pour AAAA)",
|
||||
"blocking_mode_custom_ip": "IP personnalisée : Répondre avec une adresse IP définie manuellement",
|
||||
"upstream_dns_client_desc": "Si vous laissez ce champ vide, AdGuard Home utilisera les serveurs configurés dans les <0>paramètres DNS</0>.",
|
||||
"tracker_source": "Source du traceur",
|
||||
@@ -311,7 +311,7 @@
|
||||
"install_devices_router": "Routeur",
|
||||
"install_devices_router_desc": "Cette installation impactera automatiquement tous les appareils connectés au routeur de votre maison et vous n'aurez pas besoin de configurer manuellement chaque appareil.",
|
||||
"install_devices_address": "Le serveur DNS AdGuard Home écoute sur les adresses suivantes",
|
||||
"install_devices_router_list_1": "Ouvrez les préférences de votre routeur. Habituellement, vous pouvez y accéder depuis votre navigateur web via une URL comme http://192.168.0.1/ ou http://192.168.1.1/. Vous devrez probablement saisir le mot de passe. Si vous ne vous en rappelez pas, vous pouvez normalement le réinitialiser en appuyant sur le bouton du routeur, mais attention, si vous choisissez cette voie de procéder, vous pouvez perdre vos configurations du routeur. Certains routeurs requièrent une application spécifique, qui devrait être déjà installée sur votre ordinateur/téléphone.",
|
||||
"install_devices_router_list_1": "Ouvrez les préférences de votre routeur. Normalement vous pouvez y accéder depuis votre navigateur web via une URL du type http://192.168.0.1/ ou http://192.168.1.1/. Vous devrez peut-être renseigner le mot de passe. Si vous ne vous en rappelez pas, vous pouvez normalement le réinitialiser en appuyant sur le bouton du routeur, mais attention : si vous choisissez cette procédure, toute la configuration de votre routeur sera probablement perdue. Si votre routeur requière une application spécifique, installez-la sur votre ordinateur/téléphone et utilisez-la pour accéder aux paramètres du routeur.",
|
||||
"install_devices_router_list_2": "Trouvez les paramètres DHCP/DNS. Recherchez les lettres DNS près d'une zone qui permet la saisie de 2 ou 3 blocs de chiffres, chacun composé de 4 parties de 1 à 3 chiffres.",
|
||||
"install_devices_router_list_3": "Saisissez vos adresses de serveur AdGuard Home ici.",
|
||||
"install_devices_router_list_4": "Vous ne pouvez pas définir un serveur DNS personnalisé sur certains types de routeurs. Dans ce cas, la configuration de AdGuard Home en tant que <0>serveur DHCP</0> peut aider. Sinon, vous devez rechercher le manuel sur la façon de personnaliser les serveurs DNS pour votre modèle de routeur particulier.",
|
||||
@@ -401,18 +401,18 @@
|
||||
"ip_address": "Adresse IP",
|
||||
"client_identifier_desc": "Les clients peuvent être identifiés par les adresses IP, CIDR, MAC ou un ID client spécial (qui peut être utilisé pour DoT/DoH/DoQ). Vous trouverez plus d'information sur l'identification des clients <0>ici</0> .",
|
||||
"form_enter_ip": "Saisissez l'IP",
|
||||
"form_enter_subnet_ip": "Saisissez une adresse IP dans le sous-réseau \"{{cidr}}\"",
|
||||
"form_enter_subnet_ip": "Saisissez une adresse IP dans le sous-réseau « {{cidr}} »",
|
||||
"form_enter_mac": "Saisissez MAC",
|
||||
"form_enter_id": "Entrer identifiant",
|
||||
"form_add_id": "Ajouter identifiant",
|
||||
"form_client_name": "Saisissez le nom du client",
|
||||
"name": "Nom",
|
||||
"client_global_settings": "Utiliser les paramètres généraux",
|
||||
"client_deleted": "Le client \"{{key}}\" a été supprimé avec succès",
|
||||
"client_added": "Le client \"{{key}}\" a été ajouté",
|
||||
"client_updated": "Le client \"{{key}}\" a été mis à jour",
|
||||
"client_deleted": "Le client « {{key}} » a été supprimé avec succès",
|
||||
"client_added": "Le client « {{key}} » a été ajouté",
|
||||
"client_updated": "Le client « {{key}} » a été mis à jour",
|
||||
"clients_not_found": "Aucun client trouvé",
|
||||
"client_confirm_delete": "Voulez-vous vraiment supprimer le client \"{{key}}\" ?",
|
||||
"client_confirm_delete": "Voulez-vous vraiment supprimer le client « {{key}} »?",
|
||||
"list_confirm_delete": "Voulez-vous vraiment supprimer cette liste ?",
|
||||
"auto_clients_title": "Clients (exécution)",
|
||||
"auto_clients_desc": "Les données des clients qu'utilisent AdGuard Home, mais non stockées dans la configuration",
|
||||
@@ -446,11 +446,11 @@
|
||||
"setup_dns_privacy_other_5": "Vous trouverez plus d'implémentations <0>ici</0> et <1>ici</1>.",
|
||||
"setup_dns_privacy_ioc_mac": "Configuration sur iOS et macOS",
|
||||
"setup_dns_notice": "Pour utiliser le <1>DNS-over-HTTPS</1> ou le <1>DNS-over-TLS</1>, vous devez <0>configurer le Chiffrement</0> dans les paramètres de AdGuard Home.",
|
||||
"rewrite_added": "Réécriture DNS pour \"{{key}}\" ajoutée",
|
||||
"rewrite_deleted": "Réécriture DNS pour \"{{key}}\" supprimée",
|
||||
"rewrite_added": "Réécriture DNS pour « {{key}} » ajoutée",
|
||||
"rewrite_deleted": "Réécriture DNS pour « {{key}} » supprimée",
|
||||
"rewrite_add": "Ajouter une réécriture DNS",
|
||||
"rewrite_not_found": "Aucune réécriture DNS trouvée",
|
||||
"rewrite_confirm_delete": "Voulez-vous vraiment supprimer la réécriture DNS pour \"{{key}}\" ?",
|
||||
"rewrite_confirm_delete": "Voulez-vous vraiment supprimer la réécriture DNS pour « {{key}} »?",
|
||||
"rewrite_desc": "Permet de configurer facilement la réponse DNS personnalisée pour un nom de domaine spécifique.",
|
||||
"rewrite_applied": "Règle de réécriture appliquée",
|
||||
"rewrite_hosts_applied": "Réécrit par la règle du fichier d’hôtes",
|
||||
@@ -527,7 +527,7 @@
|
||||
"disable_ipv6_desc": "Si cette fonctionnalité est activée, toutes les requêtes DNS visant des adresses IPv6 (type AAAA) seront annulées.",
|
||||
"fastest_addr": "Adresse IP la plus rapide",
|
||||
"fastest_addr_desc": "Rechercher tous les serveurs DNS et renvoyer l’adresse IP la plus rapide parmi toutes les réponses. Cela ralentit les requêtes DNS car AdGuard Home doit attendre les réponses de tous les serveurs DNS, mais la connectivité globale s'améliore.",
|
||||
"autofix_warning_text": "Si vous cliquez sur \"Réparer\", AdGuardHome configurera votre système pour utiliser le serveur DNS AdGuardHome.",
|
||||
"autofix_warning_text": "Si vous cliquez sur « Réparer », AdGuardHome configurera votre système pour utiliser le serveur DNS AdGuardHome.",
|
||||
"autofix_warning_list": "Ceci effectuera les tâches suivantes : <0>Désactiver le système DNSStubListener</0> <0>Définir l’adresse du serveur DNS à 127.0.0.1 </0> <0>Remplacer la cible du lien symbolique de /etc/resolv.conf par /run/systemd/resolve/resolv.conf</0> <0>Arrêter DNSStubListener (recharger le service résolu par systemd)</0>",
|
||||
"autofix_warning_result": "Par conséquent, toutes les demandes DNS de votre système seront traitées par AdGuardHome par défaut.",
|
||||
"tags_title": "Mots clés",
|
||||
@@ -554,10 +554,10 @@
|
||||
"static_ip": "Adresse IP statique",
|
||||
"static_ip_desc": "AdGuard Home est un serveur, il a donc besoin d’une adresse IP statique pour fonctionner correctement. Autrement, à un moment donné, votre routeur pourrait attribuer une adresse IP différente à cet appareil.",
|
||||
"set_static_ip": "Définir une adresse IP statique",
|
||||
"install_static_ok": "Bonne nouvelle! L’adresse IP statique est déjà configurée",
|
||||
"install_static_ok": "Bonne nouvelle ! L’adresse IP statique est déjà configurée",
|
||||
"install_static_error": "AdGuard Home ne peut pas le configurer automatiquement pour cette interface réseau. Veuillez rechercher une instruction sur la façon de procéder manuellement.",
|
||||
"install_static_configure": "AdGuard Home a détecté qu’une adresse IP dynamique est utilisée — <0>{{ip}}</0>. Souhaitez-vous l’utiliser comme votre adresse statique ?",
|
||||
"confirm_static_ip": "AdGuard Home configurera {{ip}} pour être votre adresse IP statique. Voulez-vous poursuivre?",
|
||||
"confirm_static_ip": "AdGuard Home configurera {{ip}} pour être votre adresse IP statique. Voulez-vous poursuivre ?",
|
||||
"list_updated": "{{count}} liste mise à jour",
|
||||
"list_updated_plural": "{{count}} listes mises à jour",
|
||||
"dnssec_enable": "Activer DNSSEC",
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"load_balancing_desc": "Egyszerre csak egy szerverről történjen lekérdezés. Az AdGuard Home egy súlyozott, véletlenszerű algoritmust fog használni a megfelelő szerver kiválasztására, így a leggyorsabb szervert gyakrabban fogja használni.",
|
||||
"bootstrap_dns": "Bootstrap DNS kiszolgálók",
|
||||
"bootstrap_dns_desc": "A Bootstrap DNS szerverek a DoH/DoT feloldók IP-címeinek feloldására szolgálnak.",
|
||||
"local_ptr_title": "Privát DNS szerverek",
|
||||
"local_ptr_desc": "Azok a DNS szerverek, amiket az AdGuard Home a helyi PTR kérésekhez használ. Ezeket a szervereket arra használjuk, hogy az rDNS segítségével fel lehessen oldani a kliensek hosztneveit. Ha nincs beállítva ilyen, akkor az AdGuard Home alapértelmezés szerint az OS nevét fogja feloldani.",
|
||||
"local_ptr_placeholder": "Adjon meg soronként egy kiszolgáló címet",
|
||||
"resolve_clients_title": "Kliensek IP címeinek fordított feloldása",
|
||||
"resolve_clients_desc": "Ha engedélyezve van, az AdGuard Home megpróbálja átfordítani a kliensek IP címeit hosztnevekre, PTR lekérdezéseket küldve a megfelelő feloldóknak (privát DNS szerverek a helyi kliensek számára, upstream szerverek a nyilvános IP címmel rendelkező ügyfelek számára).",
|
||||
"check_dhcp_servers": "DHCP szerverek keresése",
|
||||
"save_config": "Konfiguráció mentése",
|
||||
"enabled_dhcp": "DHCP szerver engedélyezve",
|
||||
@@ -33,6 +38,7 @@
|
||||
"form_error_mac_format": "Érvénytelen MAC formátum",
|
||||
"form_error_client_id_format": "Érvénytelen kliens ID formátum",
|
||||
"form_error_server_name": "Érvénytelen szervernév",
|
||||
"form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet",
|
||||
"form_error_positive": "0-nál nagyobbnak kell lennie",
|
||||
"form_error_negative": "Legalább 0-nak kell lennie",
|
||||
"range_end_error": "Nagyobbnak kell lennie, mint a tartomány kezdete",
|
||||
@@ -51,11 +57,14 @@
|
||||
"dhcp_table_expires": "Lejár",
|
||||
"dhcp_warning": "Ha engedélyezni szeretné a DHCP-kiszolgálót, ellenőrizze, hogy nincs-e más aktív DHCP-kiszolgáló a hálózaton, mert ez megszakíthatja a hálózati eszközök internetkapcsolatát.",
|
||||
"dhcp_error": "Az AdGuard Home nem tudta megállapítani, hogy van-e másik aktív DHCP-szerver a hálózaton.",
|
||||
"dhcp_static_ip_error": "A DHCP szerver használatához statikus IP-címet kell beállítani. Nem sikerült meghatározni, hogy ez a hálózati interfész statikus IP-cím használatával van-e beállítva. Állítson be kézzel egy statikus IP-címet.",
|
||||
"dhcp_dynamic_ip_found": "A rendszer dinamikus IP-cím konfigurációt használ az <0>{{interfaceName}}</0> interfészhez. A DHCP szerver használatához statikus IP-címet kell beállítani. Jelenlegi IP-címe: <0>{{ipAddress}}</0>. Automatikusan beállítjuk ezt az IP címet statikusnak, ha rányom a DHCP engedélyezése gombra.",
|
||||
"dhcp_lease_added": "Statikus bérlet \"{{key}}\" sikeresen hozzáadva",
|
||||
"dhcp_lease_deleted": "Statikus bérlet \"{{key}}\" sikeresen törölve",
|
||||
"dhcp_new_static_lease": "Új statikus bérlet",
|
||||
"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": "Biztosan visszaállítja a DHCP beállításokat?",
|
||||
"country": "Ország",
|
||||
"city": "Város",
|
||||
"delete_confirm": "Biztosan törli a \"{{key}}\" -t?",
|
||||
@@ -102,7 +111,14 @@
|
||||
"top_clients": "Legaktívabb kliensek",
|
||||
"no_clients_found": "Nem található kliens",
|
||||
"general_statistics": "Általános statisztikák",
|
||||
"number_of_dns_query_days": "Lekérdezések száma az utolsó {{count}} napban",
|
||||
"number_of_dns_query_days_plural": "Feldolgozott DNS lekérdezések száma az utolsó {{count}} napban",
|
||||
"number_of_dns_query_24_hours": "Az elmúlt 24 órában feldolgozott DNS lekérdezések száma",
|
||||
"number_of_dns_query_blocked_24_hours": "A hirdetésblokkoló szűrők és a hosztfájlok által letiltott DNS kérések száma",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Az AdGuard böngészési biztonság modulja által letiltott DNS kérések száma",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Blokkolt felnőtt tartalmak száma",
|
||||
"enforced_save_search": "Kényszerített biztonságos keresés",
|
||||
"number_of_dns_query_to_safe_search": "A biztonságos keresésre kényszerített DNS lekérdezések száma",
|
||||
"average_processing_time": "Átlagos feldolgozási idő",
|
||||
"average_processing_time_hint": "A DNS lekérdezések feldolgozásához szükséges átlagos idő milliszekundumban",
|
||||
"block_domain_use_filters_and_hosts": "Domainek blokkolása szűrők és hosztfájlok használatával",
|
||||
@@ -112,6 +128,7 @@
|
||||
"use_adguard_parental": "Használja az AdGuard szülői felügyelet webszolgáltatását",
|
||||
"use_adguard_parental_hint": "Az AdGuard Home ellenőrzi, hogy a domain tartalmaz-e felnőtteknek szóló anyagokat. Ugyanazokat az adatvédelmi API-kat használja, mint a böngésző biztonsági webszolgáltatás.",
|
||||
"enforce_safe_search": "Biztonságos keresés kényszerítése",
|
||||
"enforce_save_search_hint": "Az AdGuard Home a következő keresőmotorokban biztosíthatja a biztonságos keresést: Google, Youtube, Bing, DuckDuckGo, Yandex és Pixabay.",
|
||||
"no_servers_specified": "Nincsenek megadott kiszolgálók",
|
||||
"general_settings": "Általános beállítások",
|
||||
"dns_settings": "DNS beállítások",
|
||||
@@ -123,6 +140,7 @@
|
||||
"encryption_settings": "Titkosítási beállítások",
|
||||
"dhcp_settings": "DHCP beállítások",
|
||||
"upstream_dns": "Upstream DNS-kiszolgálók",
|
||||
"upstream_dns_help": "Adja meg a szerverek címeit soronként. <a>Tudjon meg többet</a> a DNS szerverek bekonfigurálásáról.",
|
||||
"upstream_dns_configured_in_file": "Beállítva itt: {{path}}",
|
||||
"test_upstream_btn": "Upstreamek tesztelése",
|
||||
"upstreams": "Upstream-ek",
|
||||
@@ -251,6 +269,7 @@
|
||||
"rate_limit": "Kérések korlátozása",
|
||||
"edns_enable": "EDNS kliens alhálózat engedélyezése",
|
||||
"edns_cs_desc": "Ha engedélyezve van, az AdGuard Home a kliensek alhálózatait küldi el a DNS-kiszolgálóknak.",
|
||||
"rate_limit_desc": "Maximálisan hány kérést küldhet egy kliens másodpercenkén. Ha 0-ra állítja, akkor nincs korlátozás.",
|
||||
"blocking_ipv4_desc": "A blokkolt A kéréshez visszaadandó IP-cím",
|
||||
"blocking_ipv6_desc": "A blokkolt AAAA kéréshez visszaadandó IP-cím",
|
||||
"blocking_mode_default": "Alapértelmezés: Válaszoljon nulla IP-címmel (vagyis 0.0.0.0 az A-hoz, :: pedig az AAAA-hoz), amikor a blokkolás egy adblock-stílusú szabállyal történik; illetve válaszoljon egy, a szabály által meghatározott IP címmel, amikor a blokkolás egy /etc/hosts stílusú szabállyal történik",
|
||||
@@ -273,6 +292,7 @@
|
||||
"install_settings_listen": "Figyelő felület",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Az AdGuard Home webes admin felülete elérhető a következő címe(ke)n:",
|
||||
"form_error_port": "Írja be az érvényes portszámot",
|
||||
"install_settings_dns": "DNS szerver",
|
||||
"install_settings_dns_desc": "Be kell állítania az eszközeit vagy a routerét, hogy használni tudja a DNS szervert a következő címeken:",
|
||||
"install_settings_all_interfaces": "Minden felület",
|
||||
@@ -291,8 +311,10 @@
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Ez a beállítás lefed minden eszközt, amik az Ön routeréhez csatlakoznak, így azokat nem kell külön, kézzel beállítania.",
|
||||
"install_devices_address": "Az AdGuard DNS szerver a következő címeket figyeli",
|
||||
"install_devices_router_list_1": "Nyissa meg a router beállításait. Ez általában a böngészőn keresztül történik egy URL megadásával (pl. http://192.168.0.1/ vagy http://192.168.1.1/). Ez az oldal valószínűleg felhasználónevet és jelszót fog kérni. Ha nem tudja a belépési adatokat, ellenőrizze a router dobozát, a router alján levő fehér címkét vagy a technikai dokumentációt az interneten. Végső esetben visszaállíthatja a routert, azonban ne feledje, hogyha ezt az eljárást választja, akkor valószínűleg elveszíti annak összes beállítását. Ha a router beállításához alkalmazásra van szükség, telepítse az alkalmazást a telefonjára vagy a számítógépére, és használja azt az útválasztó beállításainak eléréséhez.",
|
||||
"install_devices_router_list_2": "Keresse meg a DHCP/DNS beállításokat. Keresse a DNS szót egy olyan mező mellett, amely egy 4 csoportból álló, 1-3 számjegyű számsort vár.",
|
||||
"install_devices_router_list_3": "Adja meg az AdGuard Home szerver címét itt.",
|
||||
"install_devices_router_list_4": "Bizonyos típusú routereknél nem állíthat be egyéni DNS-kiszolgálót. Ebben az esetben segíthet, ha az AdGuard Home-t DHCP-szerverként állítja be. Ellenkező esetben keresse meg az adott router kézikönyvében a DNS-kiszolgálók testreszabását.",
|
||||
"install_devices_windows_list_1": "Nyissa meg a Vezérlőpultot a Start menün vagy a Windows keresőn keresztül.",
|
||||
"install_devices_windows_list_2": "Válassza a Hálózat és internet kategóriát, majd pedig a Hálózati és megosztási központot.",
|
||||
"install_devices_windows_list_3": "A képernyő bal oldalán keresse meg az Adapterbeállítások módosítása lehetőséget és kattintson rá.",
|
||||
@@ -318,6 +340,7 @@
|
||||
"install_saved": "Sikeres mentés",
|
||||
"encryption_title": "Titkosítás",
|
||||
"encryption_desc": "Titkosítás (HTTPS/TLS) támogatása mind a DNS, mind pedig a webes admin felület számára",
|
||||
"encryption_config_saved": "Titkosítási beállítások mentve",
|
||||
"encryption_server": "Szerver neve",
|
||||
"encryption_server_enter": "Adja meg az Ön domain címét",
|
||||
"encryption_server_desc": "A HTTPS használatához meg kell adnia a szerver nevét, amely megegyezik az SSL tanúsítvánnyal vagy a helyettesítő tanúsítvánnyal. Ha a mező nincs beállítva, akkor bármely tartományhoz elfogadja a TLS kapcsolatokat.",
|
||||
@@ -348,10 +371,13 @@
|
||||
"encryption_reset": "Biztosan visszaállítja a titkosítási beállításokat?",
|
||||
"topline_expiring_certificate": "Az SSL-tanúsítványa hamarosan lejár. Frissítse a <0>Titkosítási beállításokat</0>.",
|
||||
"topline_expired_certificate": "Az SSL-tanúsítványa lejárt. Frissítse a <0>Titkosítási beállításokat</0>.",
|
||||
"form_error_port_range": "A port értékét a 80-65535 tartományban adja meg",
|
||||
"form_error_port_unsafe": "Ez a port nem biztonságos",
|
||||
"form_error_equal": "Nem egyezhetnek",
|
||||
"form_error_password": "A jelszavak nem egyeznek",
|
||||
"reset_settings": "Beállítások visszaállítása",
|
||||
"update_announcement": "Az AdGuard Home {{version}} verziója elérhető! <0>Kattintson ide</0> további információkért.",
|
||||
"setup_guide": "Beállítási útmutató",
|
||||
"dns_addresses": "DNS címek",
|
||||
"dns_start": "A DNS szerver indul",
|
||||
"dns_status_error": "Hiba történt a DNS szerver állapotának ellenőrzésekor",
|
||||
@@ -375,6 +401,7 @@
|
||||
"ip_address": "IP cím",
|
||||
"client_identifier_desc": "A klienseket az IP-cím, a CIDR, a MAC-cím vagy egy speciális kliens azonosító alapján lehet azonosítani (ez használható DoT/DoH /DoQ esetén). <0>Itt</0> többet is megtudhat a kliensek azonosításáról.",
|
||||
"form_enter_ip": "IP-cím megadása",
|
||||
"form_enter_subnet_ip": "Adjon meg egy IP címet az alhálózatban \"{{cidr}}\"",
|
||||
"form_enter_mac": "MAC-cím megadása",
|
||||
"form_enter_id": "Azonosító megadása",
|
||||
"form_add_id": "Azonosító hozzáadása",
|
||||
@@ -396,6 +423,7 @@
|
||||
"access_disallowed_title": "Nem engedélyezett kliensek",
|
||||
"access_disallowed_desc": "A CIDR vagy IP címek listája. Ha konfigurálva van, az AdGuard Home eldobja a lekérdezéseket ezekről az IP-címekről.",
|
||||
"access_blocked_title": "Nem engedélyezett domainek",
|
||||
"access_blocked_desc": "Ne keverje össze ezt a szűrőkkel. Az AdGuard Home az összes DNS kérést el fogja dobni, ami ezekkel a domainekkel kapcsolatos. Itt megadhatja a pontos domainneveket, a helyettesítő karaktereket és az urlfilter-szabályokat, pl. 'example.org', '*.example.org' vagy '||example.org^'.",
|
||||
"access_settings_saved": "A hozzáférési beállítások sikeresen mentésre kerültek",
|
||||
"updates_checked": "A frissítések sikeresen ellenőrizve lettek",
|
||||
"updates_version_equal": "Az AdGuard Home naprakész",
|
||||
@@ -498,6 +526,7 @@
|
||||
"disable_ipv6": "IPv6 letiltása",
|
||||
"disable_ipv6_desc": "Ha ez a szolgáltatás engedélyezve van, akkor az összes IPv6-cím (AAAA típus) DNS-lekérdezése elveszik.",
|
||||
"fastest_addr": "Leggyorsabb IP-cím",
|
||||
"fastest_addr_desc": "Kérdezze le az összes DNS-kiszolgálót, és adja vissza a leggyorsabb IP-címet a válaszok közül. Ez lelassítja a DNS-lekérdezéseket, mivel az összes DNS-kiszolgáló esetében meg kell várnunk a válaszokat, de javítja az összeköttetést.",
|
||||
"autofix_warning_text": "Ha a \"Javítás\" lehetőségre kattint, az AdGuard Home megpróbálja beállítani a rendszerét, hogy használja az AdGuard Home DNS szervert.",
|
||||
"autofix_warning_list": "A következő feladatokat hajtja végre: <0>A DNSStubListener rendszer kikapcsolása</0><0>Beállítja a DNS-kiszolgáló címét 127.0.0.1-re.</0><0>Lecseréli az /etc/resolv.conf szimbolikus útvonalat erre: /run/systemd/resolve/resolv.conf</0><0>A DNSStubListener leállítása (a rendszer által feloldott szolgáltatás újratöltése)</0>",
|
||||
"autofix_warning_result": "Mindennek eredményeként az Ön rendszeréből származó összes DNS-kérést alapértelmezés szerint az AdGuard Home dolgozza fel.",
|
||||
@@ -527,6 +556,7 @@
|
||||
"set_static_ip": "Statikus IP-cím beállítása",
|
||||
"install_static_ok": "Jó hír! A statikus IP-cím már be van állítva",
|
||||
"install_static_error": "Az AdGuard Home nem tudja automatikusan konfigurálni ezt a hálózati felületet. Kérjük, nézzen utána, hogyan kell ezt manuálisan elvégezni.",
|
||||
"install_static_configure": "Úgy észleltük, hogy dinamikus IP-cím van használatban — <0>{{ip}}</0>. Szeretné ezt statikus IP-címként használni?",
|
||||
"confirm_static_ip": "Az AdGuard Home beállítja az {{ip}} IP-címet az Ön statikus IP-címének. Biztosan folytatni kívánja?",
|
||||
"list_updated": "{{count}} lista frissítve lett",
|
||||
"list_updated_plural": "{{count}} lista frissítve lett",
|
||||
@@ -564,6 +594,7 @@
|
||||
"filter_category_security_desc": "Olyan listák, amelyek a kártékony, adathalász vagy átverős oldalak tiltására vannak kifejlesztve",
|
||||
"filter_category_regional_desc": "Olyan listák, amelyek a regionális hirdetések, valamint a nyomkövető szerverek ellen vannak kifejlesztve",
|
||||
"filter_category_other_desc": "További tiltólisták",
|
||||
"setup_config_to_enable_dhcp_server": "Konfiguráció beállítása a DHCP-kiszolgáló engedélyezéséhez",
|
||||
"original_response": "Eredeti válasz",
|
||||
"click_to_view_queries": "Kattintson a lekérésekért",
|
||||
"port_53_faq_link": "Az 53-as portot gyakran a \"DNSStubListener\" vagy a \"systemd-resolved\" (rendszer által feloldott) szolgáltatások használják. Kérjük, olvassa el <0>ezt az útmutatót</0> a probléma megoldásához.",
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
{
|
||||
"client_settings": "Pengaturan klien",
|
||||
"example_upstream_reserved": "Anda dapat menetapkan DNS upstream <0>untuk domain spesifik</0>",
|
||||
"example_upstream_comment": "Anda dapat menentukan komentar",
|
||||
"upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan",
|
||||
"parallel_requests": "Permintaan paralel",
|
||||
"load_balancing": "Penyeimbang beban",
|
||||
"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": "Server Bootstrap DNS dapat digunakan untuk meresolve alamat IP pada DoH/DoT resolvers yang Anda tentukan sebagai upstreams.",
|
||||
"local_ptr_title": "Server DNS pribadi",
|
||||
"local_ptr_desc": "Server DNS yang digunakan AdGuard Home untuk kueri PTR lokal. Server ini digunakan untuk menetapkan nama host klien bersama alamat IP pribadi, sebagai contoh \"192.168.12.34\", menggunakan rDNS. Jika tidka diatur, AdGuard Home akan menggunakan resolver DNS bawaan/default OS Anda.",
|
||||
"local_ptr_placeholder": "Masukkan satu alamat server per baris",
|
||||
"resolve_clients_title": "Aktifkan resolusi hostname klien",
|
||||
"resolve_clients_desc": "Jika diaktifkan, AdGuard Home akan mencoba menyelesaikan hostname klien secara otomatis dari alamat IP mereka dengan mengirimkan kueri PTR ke penyelesai yang sesuai (server DNS pribadi untuk klien lokal, server upstream untuk klien dengan IP publik).",
|
||||
"check_dhcp_servers": "Cek untuk server DHCP",
|
||||
"save_config": "Simpan pengaturan",
|
||||
"enabled_dhcp": "Server DHCP diaktifkan",
|
||||
"disabled_dhcp": "Server DHCP dinonaktifkan",
|
||||
"unavailable_dhcp": "DHCP tidak tersedia",
|
||||
@@ -13,10 +23,12 @@
|
||||
"dhcp_description": "Jika router Anda tidak mendukung pengaturan DHCP, Anda dapat menggunakan server DHCP bawaan AdGuard.",
|
||||
"dhcp_enable": "Aktifkan server DHCP",
|
||||
"dhcp_disable": "Nonaktifkan server DHCP",
|
||||
"dhcp_not_found": "Aman untuk mengaktifkan server DHCP yang dibangun karena rumah AdGuard tidak menemukan server DHCP yang aktif pada jaringan. Namun, Anda harus memeriksa ulang secara manual sebagai penyelidikan otomatis tidak memberikan jaminan 100%.",
|
||||
"dhcp_found": "Ditemukan beberapa server DHCP aktif di dalam jaringan. Tidak aman untuk menyalakan server DHCP bawaan.",
|
||||
"dhcp_leases": "DHCP leases",
|
||||
"dhcp_static_leases": "DHCP static leases",
|
||||
"dhcp_leases_not_found": "DHCP lease tidak ditemukan",
|
||||
"dhcp_config_saved": "Pengaturan server DHCP tersimpan",
|
||||
"dhcp_ipv4_settings": "Pengaturan DHCP IPv4",
|
||||
"dhcp_ipv6_settings": "Pengaturan DHCP IPv6",
|
||||
"form_error_required": "Kolom yang harus diisi",
|
||||
@@ -25,6 +37,8 @@
|
||||
"form_error_ip_format": "Format IPv4 tidak valid",
|
||||
"form_error_mac_format": "Format MAC tidak valid",
|
||||
"form_error_client_id_format": "Format client ID tidak valid",
|
||||
"form_error_server_name": "Nama server tidak valid",
|
||||
"form_error_subnet": "Subnet \"{{cidr}}\" tidak berisi alamat IP \"{{ip}}\"",
|
||||
"form_error_positive": "Harus lebih dari 0",
|
||||
"form_error_negative": "Harus berjumlah 0 atau lebih besar dari 0",
|
||||
"range_end_error": "Harus lebih besar dari rentang awal",
|
||||
@@ -41,11 +55,16 @@
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "Nama host",
|
||||
"dhcp_table_expires": "Kadaluwarsa",
|
||||
"dhcp_warning": "Jika anda ingin mengaktifkan server DHCP bawaan, pastikan tidak ada server DHCP lain yang aktif. Jika tidak, akan memutus koneksi internet pada perangkat yang telah terhubung!",
|
||||
"dhcp_error": "AdGuard Home tidak dapat menentukan apakah ada server DHCP aktif lain pada jaringan.",
|
||||
"dhcp_static_ip_error": "Jika ingin menggunakan server DHCP, alamat IP statis harus diatur. AdGuard Home gagal menentukan jika antarmuka jaringan ini dikonfigurasi menggunakan alamat IP statis. Silakan atur alamat IP statis secara manual.",
|
||||
"dhcp_dynamic_ip_found": "Sistem Anda menggunakan konfigurasi alamat IP dinamis untuk antarmuka <0>{{interfaceName}}</0>. Untuk menggunakan server DHCP, alamat IP statis harus ditetapkan. Alamat IP Anda saat ini adalah <0>{{ipAddress}}</0>. AdGuard Home akan secara otomatis menetapkan alamat IP ini sebagai statis jika Anda menekan tombol Aktifkan DHCP.",
|
||||
"dhcp_lease_added": "Static lease \"{{key}}\" berhasil ditambahkan",
|
||||
"dhcp_lease_deleted": "Static lease \"{{key}}\" berhasil dihapus",
|
||||
"dhcp_new_static_lease": "Static lease baru",
|
||||
"dhcp_static_leases_not_found": "DHCP static lease tidak ditemukan",
|
||||
"dhcp_add_static_lease": "Tambah static lease",
|
||||
"dhcp_reset": "Apakah anda yakin ingin mengatur ulang konfigurasi DHCP anda?",
|
||||
"country": "Negara",
|
||||
"city": "Kota",
|
||||
"delete_confirm": "Apakah anda yakin ingin menghapus \"{{key}}\"?",
|
||||
@@ -92,7 +111,14 @@
|
||||
"top_clients": "Klien teratas",
|
||||
"no_clients_found": "Tidak ditemukan klien",
|
||||
"general_statistics": "Statistik umum",
|
||||
"number_of_dns_query_days": "Jumlah kueri DNS diproses selama {{value}} hari terakhir",
|
||||
"number_of_dns_query_days_plural": "Jumlah kueri DNS yang diproses selama {{count}} hari terakhir",
|
||||
"number_of_dns_query_24_hours": "Jumlah kueri DNS diproses selama 24 jam terakhir",
|
||||
"number_of_dns_query_blocked_24_hours": "Julah DNS diblokir oleh penyaring adblock dan daftar blokir hosts",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Jumlah perminataan DNS diblokir oleh modul Kemanan Penjelajahan AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Jumlah website dewasa diblokir",
|
||||
"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",
|
||||
"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",
|
||||
@@ -102,6 +128,7 @@
|
||||
"use_adguard_parental": "Gunakan layanan web kontrol orang tua AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home akan mengecek jika domain mengandung materi dewasa. Akan menggunakan API yang ramah privasi yang sama sebagai layanan web keamanan penjelajahan.",
|
||||
"enforce_safe_search": "Paksa penelusuran aman",
|
||||
"enforce_save_search_hint": "AdGuard Home dapat memaksa penelusuran aman pada mesin pencari berikut: Google, Youtube, Bing, DuckDuckGo, Yandex, dan Pixabay.",
|
||||
"no_servers_specified": "Sever tidak disebutkan",
|
||||
"general_settings": "Pengaturan umum",
|
||||
"dns_settings": "Pengaturan DNS",
|
||||
@@ -113,6 +140,7 @@
|
||||
"encryption_settings": "Pengaturan enkripsi",
|
||||
"dhcp_settings": "Pengaturan DHCP",
|
||||
"upstream_dns": "Server DNS hulu",
|
||||
"upstream_dns_help": "Masukkan alamat server per baris. <a>Pelajari lebih</a> mengenai konfigurasi upstream server DNS.",
|
||||
"upstream_dns_configured_in_file": "Diatur dalam {{path}}",
|
||||
"test_upstream_btn": "Uji hulu",
|
||||
"upstreams": "Upstream",
|
||||
@@ -226,15 +254,22 @@
|
||||
"custom_ip": "Custom IP",
|
||||
"blocking_ipv4": "Blokiran IPv4",
|
||||
"blocking_ipv6": "Blokiran IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
"client_id": "ID Klien",
|
||||
"client_id_placeholder": "Masukkan ID klien",
|
||||
"client_id_desc": "Klien yang berbeda dapat diidentifikasi dengan ID klien khusus. <a>Di sini</a> Anda dapat mempelajari lebih lanjut tentang cara mengidentifikasi klien.",
|
||||
"download_mobileconfig_doh": "Unduh .mobileconfig untuk DNS-over-HTTPS",
|
||||
"download_mobileconfig_dot": "Unduh .mobileconfig untuk DNS-over-TLS",
|
||||
"download_mobileconfig": "Unduh berkas konfigurasi",
|
||||
"plain_dns": "Plain DNS",
|
||||
"form_enter_rate_limit": "Masukkan batas nilai",
|
||||
"rate_limit": "Batas nilai",
|
||||
"edns_enable": "Aktifkan EDNS Klien Subnet",
|
||||
"edns_cs_desc": "Apabila dinyalakan, AdGuard Home akan mengirim subnet klien ke server-server DNS.",
|
||||
"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",
|
||||
"blocking_mode_default": "Default: Tanggapi dengan alamat IP nol (0.0.0.0 untuk A; :: untuk AAAA) saat diblokir oleh aturan gaya Adblock; tanggapi dengan alamat IP yang ditentukan dalam aturan ketika diblokir oleh aturan gaya host /etc/",
|
||||
@@ -276,8 +311,10 @@
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Pengaturan ini akan secara otomatis mencakup semua perangkat yang terhubung ke router rumah anda dan anda tak perlu mengkonfigurasikan secara manual.",
|
||||
"install_devices_address": "Server DNS AdGuard Home akan menggunakan alamat berikut",
|
||||
"install_devices_router_list_1": "Buka preferensi untuk router Anda. Biasanya, Anda dapat mengaksesnya dari browser Anda melalui URL, seperti http://192.168.0.1/ atau http://192.168.1.1/. Anda mungkin diminta untuk memasukkan kata sandi. Jika Anda tidak mengingatnya, Anda sering kali dapat mengatur ulang kata sandi dengan menekan tombol pada perute itu sendiri, tetapi perlu diketahui bahwa jika prosedur ini dipilih, Anda mungkin akan kehilangan seluruh konfigurasi perute. Jika router Anda memerlukan aplikasi untuk menyiapkannya, instal aplikasi tersebut di ponsel atau PC Anda dan gunakan untuk mengakses pengaturan router.",
|
||||
"install_devices_router_list_2": "Temukan pengaturan DHCP / DNS. Cari huruf DNS di sebelah bidang yang memungkinkan dua atau tiga set angka, masing-masing dipecah menjadi empat grup dengan satu hingga tiga digit.",
|
||||
"install_devices_router_list_3": "Masukkan alamat server AdGuard Home disana",
|
||||
"install_devices_router_list_4": "Anda tidak dapat menyetel server DNS kustom pada beberapa tipe router. Dalam hal ini mungkin membantu jika Anda mengatur AdGuard Home sebagai <0>server DHCP</0>. Jika tidak, Anda harus mencari petunjuk tentang cara mengkustomisasi server DNS untuk model router khusus Anda.",
|
||||
"install_devices_windows_list_1": "Buka Panel Kontrol melalui menu Start atau pencarian Windows.",
|
||||
"install_devices_windows_list_2": "Masuk ke kategori Jaringan dan Internet (Network and Internet) dan kemudian ke Pusat Jaringan dan Berbagi (Network and Sharing Center).",
|
||||
"install_devices_windows_list_3": "Di sisi kiri layar temukan Ubah pengaturan adaptor dan klik.",
|
||||
@@ -303,8 +340,10 @@
|
||||
"install_saved": "Berhasil disimpan",
|
||||
"encryption_title": "Enkripsi",
|
||||
"encryption_desc": "Enkripsi (HTTPS / TLS) untuk DNS dan antarmuka admin",
|
||||
"encryption_config_saved": "Pengaturan enkripsi telah tersimpan",
|
||||
"encryption_server": "Nama server",
|
||||
"encryption_server_enter": "Masukkan nama domain anda",
|
||||
"encryption_server_desc": "Untuk menggunakan HTTPS, Anda harus memasukkan nama server yang cocok dengan sertifikat SSL Anda. Bila ruas tak ditata, akan menerima koneksi TLS bagi domain manapun.",
|
||||
"encryption_redirect": "Alihkan ke HTTPS secara otomatis",
|
||||
"encryption_redirect_desc": "Jika dicentang, AdGuard Home akan secara otomatis mengarahkan anda dari HTTP ke alamat HTTPS.",
|
||||
"encryption_https": "Port HTTPS",
|
||||
@@ -332,10 +371,13 @@
|
||||
"encryption_reset": "Anda yakin ingin mengatur ulang pengaturan enkripsi?",
|
||||
"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",
|
||||
"form_error_port_unsafe": "Ini adalah port yang tidak aman",
|
||||
"form_error_equal": "Seharusnya tidak sama",
|
||||
"form_error_password": "Kata sandi tidak cocok",
|
||||
"reset_settings": "Setel ulang pengaturan",
|
||||
"update_announcement": "AdGuard Home {{version}} sekarang tersedia! <0>Klik di sini</0> untuk info lebih lanjut.",
|
||||
"setup_guide": "Panduan Penyiapan",
|
||||
"dns_addresses": "Alamat DNS",
|
||||
"dns_start": "Server DNS sedang dinyalakan",
|
||||
"dns_status_error": "Kesalahan dalam mendapatkan status server DNS",
|
||||
@@ -357,7 +399,9 @@
|
||||
"client_edit": "Ubah Klien",
|
||||
"client_identifier": "Identifikasi",
|
||||
"ip_address": "Alamat IP",
|
||||
"client_identifier_desc": "Klien dapat diidentifikasi oleh alamat IP, CIDR, alamat MAC atau ID klien khusus (dapat digunakan untuk DoT/DoH/DoQ). <0>Di sini</0> Anda dapat mempelajari lebih lanjut tentang bagaimana mengidentifikasi klien.",
|
||||
"form_enter_ip": "Masukkan IP",
|
||||
"form_enter_subnet_ip": "Masukkan alamat IP di subnet \"{{cidr}}\"",
|
||||
"form_enter_mac": "Masukkan MAC",
|
||||
"form_enter_id": "Masukkan pengenal",
|
||||
"form_add_id": "Tambah pengenal",
|
||||
@@ -379,6 +423,7 @@
|
||||
"access_disallowed_title": "Klien yang tidak diizinkan",
|
||||
"access_disallowed_desc": "Daftar CIDR atau alamat IP. Jika dikonfigurasi, AdGuard Home akan membatalkan permintaan dari alamat IP ini.",
|
||||
"access_blocked_title": "Domain yang diblokir",
|
||||
"access_blocked_desc": "Jangan bingung antara ini dengan filter. AdGuard Home akan membatalkan kueri DNS dengan domain ini dalam pertanyaan kueri. Di sini Anda dapat menentukan nama domain yang tepat, karakter pengganti, dan aturan filter URL, mis. \"example.org\", \"*.example.org\" atau \"||example.org^\".",
|
||||
"access_settings_saved": "Pengaturan akses berhasil disimpan",
|
||||
"updates_checked": "Pembaruan berhasil dicek",
|
||||
"updates_version_equal": "AdGuard Home sudah tebaru",
|
||||
@@ -481,6 +526,7 @@
|
||||
"disable_ipv6": "Matikan IPv6",
|
||||
"disable_ipv6_desc": "Apabila fitur ini dinyalakan, semua permintaan DNS untuk alamat-alamat IPv6 (tipe AAAA) akan diputus.",
|
||||
"fastest_addr": "Alamat IP tercepat",
|
||||
"fastest_addr_desc": "Kuiri semua server DNS dan kembalikan alamat IP tercepat diantara semua tanggapan. Ini memperlambat pencarian DNS Sebagai Rumah AdGuard harus menunggu tanggapan dari semua server DNS, tapi meningkatkan konektivitas keseluruhan.",
|
||||
"autofix_warning_text": "Apabila anda menekan \"Perbaiki\", AdGuardHome akan mengatur sistem anda untuk menggunakan server DNS AdGuardHome.",
|
||||
"autofix_warning_list": "Ini akan melakukan tugas berikut: <0>Nonaktifkan sistem DNSStubListener</0> <0> Atur alamat server DNS ke 127.0.0.1</0> <0>Ganti target tautan simbolis /etc/resolv.conf pakai /run/systemd/resolve/resolv.conf</0> <0>Hentikan DNSStubListener (muat ulang layanan sistemd-resolve service)</0>",
|
||||
"autofix_warning_result": "Hasilnya, semua permintaan DNS dari sistem anda akan diproses oleh AdGuardHome secara standar.",
|
||||
@@ -510,6 +556,7 @@
|
||||
"set_static_ip": "Atur alamat IP statik",
|
||||
"install_static_ok": "Kabar baik! Alamat IP statis sudah dikonfigurasi",
|
||||
"install_static_error": "AdGuard Home tidak dapat mengonfigurasinya secara otomatis untuk antarmuka jaringan ini. Silakan cari instruksi tentang cara melakukan ini secara manual.",
|
||||
"install_static_configure": "AdGuard Home mendeteksi alamat IP dinamis <0>{{ip}}</0> digunakan. Anda ingin menggunakannya sebagai alamat statis Anda?",
|
||||
"confirm_static_ip": "AdGuard Home akan mengonfigurasi {{ip}} menjadi alamat IP statis Anda. Anda ingin melanjutkan?",
|
||||
"list_updated": "{{count}} daftar terbarui",
|
||||
"list_updated_plural": "{{count}} daftar terbarui",
|
||||
@@ -547,6 +594,7 @@
|
||||
"filter_category_security_desc": "Daftar yang khusus pada pemblokiran malware, phishing, atau domain penipuan",
|
||||
"filter_category_regional_desc": "Daftar yang berfokus pada iklan regional dan server pelacakan",
|
||||
"filter_category_other_desc": "Daftar hitam lain",
|
||||
"setup_config_to_enable_dhcp_server": "Setel konfigurasi untuk aktifkan server DHCP",
|
||||
"original_response": "Respon asli",
|
||||
"click_to_view_queries": "Klik untuk lihat permintaan",
|
||||
"port_53_faq_link": "Port 53 sering ditempati oleh layanan \"DNSStubListener\" atau \"systemd-resolved\". Silakan baca <0>instruksi ini</0> tentang cara menyelesaikan ini.",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Server DNS bootstrap",
|
||||
"bootstrap_dns_desc": "I server DNS di bootstrap sono utilizzati per risolvere gli indirizzi IP dei risolutori DoH/DoT specificati come upstream.",
|
||||
"local_ptr_title": "Server DNS privati",
|
||||
"local_ptr_desc": "I server DNS che AdGuard Home utilizzerà per richiedere le risorse disponibili localmente. Ad esempio, questo server verrà utilizzato per risolvere i nomi host dei client con indirizzi IP privati. Se non impostato, AdGuard Home utilizzerà automaticamente il risolutore DNS predefinito.",
|
||||
"local_ptr_desc": "I server DNS che AdGuard Home utilizzerà per richiedere le risorse PTR disponibili localmente. Ad esempio, questo server verrà utilizzato per risolvere i nomi host dei client con indirizzi IP privati, comò \"192.168.12.34\", utilizzando rDNS. Se non impostato, AdGuard Home utilizzerà automaticamente il risolutore DNS predefinito del tuo sistema operativo.",
|
||||
"local_ptr_placeholder": "Inserisci un indirizzo server per riga",
|
||||
"resolve_clients_title": "Attiva la risoluzione inversa degli indirizzi IP dei client",
|
||||
"resolve_clients_desc": "Se attivo, AdGuard Home tenterà di risolvere inversamente gli indirizzi IP dei client nei relativi nomi host inviando una richiesta PTR a un risolutore corrispondente (server DNS privato per client locali, server upstream per client con IP pubblico).",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "ブートストラップDNSサーバ",
|
||||
"bootstrap_dns_desc": "ブートストラップDNSサーバは、上流として指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されます。",
|
||||
"local_ptr_title": "プライベートDNSサーバー",
|
||||
"local_ptr_desc": "AdGuard Homeがローカルに提供されるリソースのクエリに使用するDNSサーバーです。例えば、このサーバーは、プライベートIPアドレスを持つクライアントのホスト名を解決するために使用されます。設定されていない場合、AdGuard Homeはお使いのデフォルトDNSリゾルバーを自動的に使用します。",
|
||||
"local_ptr_desc": "AdGuard HomeがローカルPTRクエリに使用するDNSサーバーです。これらのサーバーは、rDNSを使ってプライベートIPアドレス(例えば\"192.168.12.34\")を持つクライアントのホスト名を解決するために使用されます。設定されていない場合、AdGuard HomeはOSのデフォルトDNSリゾルバーを自動的に使用します。",
|
||||
"local_ptr_placeholder": "1行に1つのサーバを入力してください。",
|
||||
"resolve_clients_title": "クライアントのIPアドレスの逆解決を有効にする",
|
||||
"resolve_clients_desc": "有効にすると、AdGuard Homeは、対応するリゾルバー(ローカルクライアントの場合はプライベートDNSサーバ、パブリックIPを持つクライアントの場合は上流サーバ)にPTRクエリを送信することにより、クライアントのIPアドレスをホスト名に逆解決しようとします。",
|
||||
@@ -58,7 +58,7 @@
|
||||
"dhcp_warning": "ともかくDHCPサーバを有効にしたい場合は、ネットワーク内で他に稼働中のDHCPサーバがないことを確認してください。そうでなければ、ネットワーク上デバイスでインターネット接続を壊してしまう可能性があります!",
|
||||
"dhcp_error": "ネットワーク上に別の稼働中DHCPサーバがあるかどうか、AdGuard Homeは判断できませんでした。",
|
||||
"dhcp_static_ip_error": "DHCPサーバーを使用するには、静的IPアドレスを設定する必要があります。このネットワークインターフェースが静的IPアドレスを使用するように設定されているかどうかを、AdGuard Homeは判断できませんでした。手動で静的IPアドレスを設定してください。",
|
||||
"dhcp_dynamic_ip_found": "お使いのシステムは、インターフェース<0>{{interfaceName}}</0>に動的IPアドレス構成を使用しています。DHCPサーバを使用するには、静的IPアドレスを設定する必要があります。あなたの現在のIPアドレスは<0>{{ipAddress}}</0>です。「DHCPサーバを有効にする」ボタンを押すと、AdGuard HomeはこのIPアドレスを静的IPアドレスに自動設定します。",
|
||||
"dhcp_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_new_static_lease": "新規静的割り当て",
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
"bootstrap_dns": "부트스트랩 DNS 서버",
|
||||
"bootstrap_dns_desc": "부트스트랩 DNS 서버는 업스트림으로 지정한 DoH/DoT 서버의 IP 주소를 확인하는 데 사용합니다.",
|
||||
"local_ptr_title": "프라이빗 DNS 서버",
|
||||
"local_ptr_desc": "AdGuard Home이 로컬 리소스에 대한 쿼리에 사용할 DNS 서버입니다. 예를 들어, 사설 IP 주소가 있는 클라이언트의 호스트명을 확인할 때 사용합니다. 설정하지 않으면 기본으로 설정된 DNS 서버를 사용합니다.",
|
||||
"local_ptr_desc": "AdGuard Home이 로컬 PTR 쿼리에 사용하는 DNS 서버입니다. 이러한 서버는 rDNS를 사용하여 개인 IP 주소(예: '192.168.12.34')가 있는 클라이언트의 호스트 이름을 확인하는 데 사용됩니다. 설정되지 않은 경우, AdGuard Home은 OS의 기본 DNS 리졸버를 사용합니다.",
|
||||
"local_ptr_placeholder": "한 줄에 하나씩 서버 주소 입력",
|
||||
"resolve_clients_title": "클라이언트 IP 주소에 대한 호스트명 확인 활성화",
|
||||
"resolve_clients_desc": "활성화된 경우 AdGuard Home은 PTR 쿼리를 해당 서버(로컬 클라이언트의 경우 프라이빗 DNS 서버, 공용 IP 주소가 있는 클라이언트의 경우 업스트림 서버)로 전송하여 IP 주소로부터 클라이언트의 호스트명을 역으로 확인하려고 시도합니다.",
|
||||
"check_dhcp_servers": "DHCP 서버 체크",
|
||||
"save_config": "구성 저장",
|
||||
"enabled_dhcp": "DHCP 서버 활성화됨",
|
||||
@@ -21,6 +23,7 @@
|
||||
"dhcp_description": "라우터가 DHCP 설정을 제공하지 않으면 AdGuard의 자체 기본 제공 DHCP 서버를 사용할 수 있습니다.",
|
||||
"dhcp_enable": "DHCP 서버 활성화",
|
||||
"dhcp_disable": "DHCP 서버 비활성화",
|
||||
"dhcp_not_found": "AdGuard Home이 네트워크에서 활성화된 DHCP 서버를 찾지 못했기 때문에 DHCP 서버를 활성화하는 것이 안전합니다. 하지만 자동 검색이 완전히 안전하지 않기 때문에 수동으로 다시 확인하는 걸 권장합니다.",
|
||||
"dhcp_found": "네트워크에 활성 DHCP 서버가 있습니다. 기본 제공 DHCP 서버를 활성화하는 것은 안전하지 않습니다.",
|
||||
"dhcp_leases": "DHCP 임대",
|
||||
"dhcp_static_leases": "DHCP 고정 임대",
|
||||
@@ -35,6 +38,7 @@
|
||||
"form_error_mac_format": "잘못된 MAC 형식",
|
||||
"form_error_client_id_format": "잘못된 클라이언트 ID 형식",
|
||||
"form_error_server_name": "유효하지 않은 서버 이름입니다",
|
||||
"form_error_subnet": "서브넷 \"{{cidr}}\"에 \"{{ip}}\" IP 주소가 없습니다",
|
||||
"form_error_positive": "0보다 커야 합니다",
|
||||
"form_error_negative": "반드시 0 이상이여야 합니다",
|
||||
"range_end_error": "입력 값은 범위의 시작 지점보다 큰 값 이여야 합니다.",
|
||||
@@ -51,6 +55,7 @@
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "호스트 이름",
|
||||
"dhcp_table_expires": "만료",
|
||||
"dhcp_warning": "DHCP 서버를 사용하려면 네트워크에 다른 활성화된 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 주소를 고정 IP 주소로 자동 설정합니다.",
|
||||
@@ -59,6 +64,7 @@
|
||||
"dhcp_new_static_lease": "새 고정 임대",
|
||||
"dhcp_static_leases_not_found": "DHCP 고정 임대를 찾을 수 없음",
|
||||
"dhcp_add_static_lease": "고정 임대 추가",
|
||||
"dhcp_reset": "정말로 DHCP 설정을 초기화할까요?",
|
||||
"country": "지역",
|
||||
"city": "도시",
|
||||
"delete_confirm": "\"{{key}}\"을 삭제하시겠습니까?",
|
||||
@@ -112,6 +118,7 @@
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "AdGuard 브라우징 보안 모듈에 의해 차단된 DNS 요청 수",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "차단된 성인 웹 사이트의 수",
|
||||
"enforced_save_search": "세이프 서치 강제",
|
||||
"number_of_dns_query_to_safe_search": "세이프서치가 적용된 검색 엔진에 대해 DNS 요청 수",
|
||||
"average_processing_time": "평균처리 시간",
|
||||
"average_processing_time_hint": "DNS 요청 처리시 평균 시간(밀리초)",
|
||||
"block_domain_use_filters_and_hosts": "필터 및 호스트 파일을 사용하여 도메인 차단",
|
||||
@@ -121,6 +128,7 @@
|
||||
"use_adguard_parental": "AdGuard 자녀 보호 웹 서비스 사용",
|
||||
"use_adguard_parental_hint": "AdGuard Home은 도메인에 성인 자료가 포함되어 있는지 확인합니다. 브라우징 보안 웹 서비스와 동일한 개인정보 보호 API를 사용함.",
|
||||
"enforce_safe_search": "세이프서치 강제",
|
||||
"enforce_save_search_hint": "AdGuard Home이 Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay와 같은 검색 엔진에서 안전 검색을 시행할 수 있습니다.",
|
||||
"no_servers_specified": "지정된 서버 없음",
|
||||
"general_settings": "일반 설정",
|
||||
"dns_settings": "DNS 설정",
|
||||
@@ -261,6 +269,7 @@
|
||||
"rate_limit": "한도 제한",
|
||||
"edns_enable": "EDNS 클라이언트 서브넷 활성화",
|
||||
"edns_cs_desc": "활성화되면 AdGuard Home은 클라이언트의 서브넷을 DNS 서버에 전달합니다.",
|
||||
"rate_limit_desc": "단일 클라이언트에서 허용 가능한 초 당 요청 생성 숫자 (0: 무제한)",
|
||||
"blocking_ipv4_desc": "차단된 A 요청에 대해서 반환할 IP 주소",
|
||||
"blocking_ipv6_desc": "차단된 AAAA 요청에 대해서 반환할 IP 주소",
|
||||
"blocking_mode_default": "기본: Adblock 스타일 규칙에 의해 차단되면 제로 IP 주소(A는 0.0.0.0; AAAA는 ::)로 응답합니다; /etc/hosts 스타일 규칙에 의해 차단되면 규칙에 정의된 IP 주소로 응답합니다",
|
||||
@@ -283,6 +292,7 @@
|
||||
"install_settings_listen": "네트워크 인터페이스",
|
||||
"install_settings_port": "포트",
|
||||
"install_settings_interface_link": "AdGuard Home 관리자 웹 인터페이스는 다음 주소로 제공됨:",
|
||||
"form_error_port": "유효한 포트 번호를 입력하십시오",
|
||||
"install_settings_dns": "DNS 서버",
|
||||
"install_settings_dns_desc": "다음 주소의 DNS 서버를 사용하도록 장치 또는 라우터를 구성해야 합니다.",
|
||||
"install_settings_all_interfaces": "모든 인터페이스",
|
||||
@@ -301,8 +311,10 @@
|
||||
"install_devices_router": "라우터",
|
||||
"install_devices_router_desc": "이 설정은 이제 자동으로 당신의 집의 라우터에 연결된 모든 기기에 적용될 것이기에 수동으로 각각의 기기를 설정해줄 필요가 없습니다.",
|
||||
"install_devices_address": "AdGuard Home DNS 서버는 다음의 주소를 받고 있습니다.",
|
||||
"install_devices_router_list_1": "라우터의 환경 설정을 여세요. 환경 설정은 다음의 주소(http://192.168.0.1/ 혹은 http://192.168.1.1/)를 통해 브라우저로 접근 가능합니다. 비밀번호를 입력해야 할 수 있습니다. 비밀번호를 잊었다면 라우터 기기에 있는 버튼을 눌러 비밀번호를 초기화할 수 있지만 라우터 설정이 손실될 수 있습니다. 라우터 설정에 앱이 필요한 경우, 휴대폰이나 컴퓨터에 앱을 설치하고 이를 사용하여 라우터 설정에 액세스하세요.",
|
||||
"install_devices_router_list_2": "각각 1~3자리 숫자의 네 그룹으로 분할된 두 세트의 숫자를 허용하는 필드 옆에 있는 DNS 문자를 찾으세요.",
|
||||
"install_devices_router_list_3": "AdGuard Home 서버 주소를 입력하세요",
|
||||
"install_devices_router_list_4": "일부 라우터 유형에서는 사용자 정의 DNS 서버를 설정할 수 없습니다. 이 경우에는 AdGuard Home을 <0>DHCP 서버</0>로 설정할 수 있습니다. 그렇지 않으면 특정 라우터 모델에 맞게 DNS 서버를 설정하는 방법을 찾아야 합니다.",
|
||||
"install_devices_windows_list_1": "시작 메뉴 또는 윈도우 검색을 통해 제어판을 여세요",
|
||||
"install_devices_windows_list_2": "네트워크 및 인터넷 카테고리로 이동한 다음 네트워크 및 공유 센터로 이동하세요.",
|
||||
"install_devices_windows_list_3": "화면 왼쪽에서 어댑터 설정 변경을 찾아 클릭하세요.",
|
||||
@@ -328,6 +340,7 @@
|
||||
"install_saved": "성공적으로 저장되었습니다",
|
||||
"encryption_title": "암호화",
|
||||
"encryption_desc": "DNS 및 관리자 웹 인터페이스에 대한 암호화 (HTTPS/TLS) 지원입니다.",
|
||||
"encryption_config_saved": "암호화 구성이 저장되었습니다",
|
||||
"encryption_server": "서버 이름",
|
||||
"encryption_server_enter": "도메인 이름을 입력하세요.",
|
||||
"encryption_server_desc": "HTTPS를 사용하려면 SSL 인증서와 일치하는 서버 이름을 입력해야 합니다.",
|
||||
@@ -358,10 +371,13 @@
|
||||
"encryption_reset": "암호화 설정을 재설정하시겠습니까?",
|
||||
"topline_expiring_certificate": "SSL 인증서가 곧 만료됩니다. 업데이트<0> 암호화 설정</0>.",
|
||||
"topline_expired_certificate": "SSL 인증서가 만료되었습니다. 업데이트<0> 암호화 설정</0>.",
|
||||
"form_error_port_range": "80-65535 범위의 포트 번호를 입력하십시오",
|
||||
"form_error_port_unsafe": "안전하지 않은 포트입니다",
|
||||
"form_error_equal": "동일하지 않아야 함",
|
||||
"form_error_password": "비밀번호 불일치",
|
||||
"reset_settings": "설정 초기화",
|
||||
"update_announcement": "AdGuard Home {{version}} 사용 가능합니다! <0>이곳</0>을 클릭하여 더 많은 정보를 확인하세요.",
|
||||
"setup_guide": "설치 안내",
|
||||
"dns_addresses": "DNS 주소",
|
||||
"dns_start": "DNS 서버를 시작하고 있습니다",
|
||||
"dns_status_error": "DNS 서버 상태를 가져오는 도중 오류가 발생했습니다",
|
||||
@@ -385,6 +401,7 @@
|
||||
"ip_address": "IP 주소",
|
||||
"client_identifier_desc": "클라이언트는 IP 주소, CIDR, MAC 주소 또는 특수 클라이언트 ID로 식별할 수 있습니다 (DoT/DoH/DoQ에 사용 가능). <0>여기에서</0> 클라이언트를 식별하는 방법에 대한 자세한 내용은 확인하실 수 있습니다.",
|
||||
"form_enter_ip": "IP 입력",
|
||||
"form_enter_subnet_ip": "서브넷 \"{{cidr}}\" 내의 IP 주소 입력",
|
||||
"form_enter_mac": "MAC 입력",
|
||||
"form_enter_id": "식별자 입력",
|
||||
"form_add_id": "식별자 추가",
|
||||
@@ -406,6 +423,7 @@
|
||||
"access_disallowed_title": "차단된 클라이언트",
|
||||
"access_disallowed_desc": "CIDR 또는 IP 주소 목록입니다. 구성된 경우 AdGuard Home은 이러한 IP 주소의 요청을 삭제합니다.",
|
||||
"access_blocked_title": "차단된 도메인",
|
||||
"access_blocked_desc": "이 기능을 필터와 혼동하지 마세요. AdGuard Home은 이러한 도메인에 대한 DNS 요청을 무시합니다. 여기서 특정 도메인을 지정하거나, 와일드 카드 또는 URL 필터 규칙을 설정해 보세요. 예) 'example.org', '*.example.org' 또는 '||example.org^'.",
|
||||
"access_settings_saved": "액세스 설정이 성공적으로 저장되었습니다.",
|
||||
"updates_checked": "업데이트가 성공적으로 확인되었습니다",
|
||||
"updates_version_equal": "AdGuard Home 최신 상태입니다.",
|
||||
@@ -508,6 +526,7 @@
|
||||
"disable_ipv6": "IPv6 비활성화",
|
||||
"disable_ipv6_desc": "이 기능이 활성화되면 IPv6 (타입 AAAA) 의 모든 DNS 쿼리가 드랍됩니다.",
|
||||
"fastest_addr": "가장 빠른 IP 주소",
|
||||
"fastest_addr_desc": "모든 DNS 서버에 쿼리를 수행한 다음 반응이 가장 빠른 IP주소를 반송합니다. AdGuard Home이 모든 DNS 서버의 응답을 기다려야 하기 때문에 DNS 쿼리 속도가 느려지지만 전반적인 연결이 향상됩니다.",
|
||||
"autofix_warning_text": "\"Fix\"를 클릭한다면 AdGuard Home은 시스템이 AdGuard Home의 DNS 서버를 사용하도록 설정합니다.",
|
||||
"autofix_warning_list": "다음 작업을 진행합니다: <0>DNSStubListener 시스템 비활성화</0> <0>DNS 서버 주소를 127.0.0.1로 설정</0> <0>/etc/resolv.conf의 심볼릭 링크 타겟을 /run/systemd/resolve/resolv.conf로 변경</0> <0>DNSStubListener 중지 (systemd-resolved 서비스 새로고침)</0>",
|
||||
"autofix_warning_result": "결과적으로 시스템의 모든 DNS 요청은 기본적으로 AdGuard Home에 의해 처리됩니다.",
|
||||
@@ -537,6 +556,7 @@
|
||||
"set_static_ip": "고정 IP 주소 설정",
|
||||
"install_static_ok": "좋은 소식입니다! 고정 IP 주소가 이미 설정되어있네요",
|
||||
"install_static_error": "AdGuard Home는 이 네트워크 인터페이스에서 자동 설정할 수 없습니다. 여기에서 어떻게 이걸 수동으로 할 수 있는지 확인해주세요.",
|
||||
"install_static_configure": "AdGuard Home이 동적 IP 주소를 사용하는 것을 감지했습니다 - <0>{{ip}}</0>. 정말로 이걸 고정 IP로 사용하시겠습니까?",
|
||||
"confirm_static_ip": "AdGuard Home이 {{ip}}를 고정 IP 주소로 설정하려고 합니다. 계속하시겠습니까?",
|
||||
"list_updated": "{{count}} 리스트 업데이트됨",
|
||||
"list_updated_plural": "{{count}} 리스트 업데이트됨",
|
||||
@@ -574,6 +594,7 @@
|
||||
"filter_category_security_desc": "멀웨어, 피싱 또는 사기 도메인을 차단하는 목록",
|
||||
"filter_category_regional_desc": "지역 광고 및 추적 서버에 중점을 둔 목록",
|
||||
"filter_category_other_desc": "기타 차단 목록",
|
||||
"setup_config_to_enable_dhcp_server": "DHCP 서버를 활성화하기 위한 설정 구성",
|
||||
"original_response": "원래 응답",
|
||||
"click_to_view_queries": "쿼리를 보려면 클릭합니다",
|
||||
"port_53_faq_link": "53번 포트는 보통 \"DNSStubListener\"나 \"systemd-resolved\" 서비스가 이미 사용하고 있습니다. 이 문제에 대한 해결 방법을 <0>설명</0>에서 찾아보세요.",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Serwery DNS Bootstrap",
|
||||
"bootstrap_dns_desc": "Serwery DNS Bootstrap są używane do ustalenia adresu IP serwerów DoH/DoT, które oznaczysz jako główne serwery DNS.",
|
||||
"local_ptr_title": "Prywatne serwery DNS",
|
||||
"local_ptr_desc": "Serwery DNS, z których AdGuard Home będzie korzystał przy zapytaniach o lokalnie obsługiwane zasoby. Na przykład, ten serwer będzie używany do rozwiązywania nazw hostów klientów z prywatnymi adresami IP. Jeśli nie jest ustawiony, AdGuard Home będzie automatycznie korzystał z domyślnego resolvera DNS.",
|
||||
"local_ptr_desc": "Serwery DNS, których AdGuard Home używa do lokalnych zapytań PTR. Serwery te są używane do rozwiązywania nazw hostów klientów z prywatnymi adresami IP, na przykład \"192.168.12.34\", przy użyciu rDNS. Jeśli nie jest ustawiony, AdGuard Home używa domyślnych resolwerów DNS systemu operacyjnego.",
|
||||
"local_ptr_placeholder": "Wprowadź po jednym adresie serwera w każdym wierszu",
|
||||
"resolve_clients_title": "Włącz odwrotne rozpoznawanie adresów IP klientów",
|
||||
"resolve_clients_desc": "Jeśli jest włączona, AdGuard Home spróbuje odwrócić adresy IP klientów do ich nazw hostów, wysyłając zapytania PTR do odpowiednich resolverów (prywatne serwery DNS dla klientów lokalnych, serwer nadrzędny dla klientów z publicznymi adresami IP).",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Servidores DNS de inicialização",
|
||||
"bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams.",
|
||||
"local_ptr_title": "Servidores DNS privados",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home usará para consultas de recursos servidos localmente. Por exemplo, este servidor será usado para resolver nomes de host de clientes para clientes com endereços IP privados. Se não for definido, o AdGuard Home usará automaticamente seu resolvedor DNS padrão.",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver os nomes de host de clientes com endereços IP privados, por exemplo \"192.168.12.34\", usando rDNS. Se não for definido, o AdGuard Home usa os resolvedores DNS padrão do seu sistema operacional.",
|
||||
"local_ptr_placeholder": "Insira um endereço de servidor por linha",
|
||||
"resolve_clients_title": "Ativar resolução reversa de endereços IP de clientes",
|
||||
"resolve_clients_desc": "Se ativado, o AdGuard Home tentará resolver de forma reversa os endereços IP dos clientes em seus nomes de host, enviando consultas PTR aos resolvedores correspondentes (servidores DNS privados para clientes locais, servidor DNS primário para clientes com endereços IP públicos).",
|
||||
@@ -311,6 +311,7 @@
|
||||
"install_devices_router": "Roteador",
|
||||
"install_devices_router_desc": "Esta configuração cobrirá automaticamente todos os dispositivos conectados ao seu roteador doméstico e você não irá precisar configurar cada um deles manualmente.",
|
||||
"install_devices_address": "O servidor de DNS do AdGuard Home está capturando os seguintes endereços",
|
||||
"install_devices_router_list_1": "Abra as preferências do seu roteador. Normalmente, você pode acessá-lo de seu navegador por meio de um URL, como http://192.168.0.1/ ou http://192.168.1.1/. Você pode ser solicitado a inserir uma senha. Se você não se lembrar, muitas vezes você pode redefinir a senha pressionando um botão no próprio roteador, mas esteja ciente de que se esse procedimento for escolhido, você provavelmente perderá toda a configuração do roteador. Se o seu roteador requer um aplicativo para configurá-lo, instale o aplicativo no seu telefone ou PC e use-o para acessar as configurações do roteador.",
|
||||
"install_devices_router_list_2": "Encontre as Configurações de DNS. Procure as letras DNS ao lado de um campo que permite dois ou três conjuntos de números, cada um dividido em quatro grupos de um a três números.",
|
||||
"install_devices_router_list_3": "Digite aqui seu servidor do AdGuard Home.",
|
||||
"install_devices_router_list_4": "Em alguns tipos de roteador, um servidor DNS personalizado não pode ser configurado. Nesse caso, configurar o AdGuard Home como um <0>Servidor DHCP</0> pode ajudar. Caso contrário, você deve verificar o manual do roteador sobre como personalizar os servidores DNS em seu modelo de roteador específico.",
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"bootstrap_dns": "Servidores DNS de arranque",
|
||||
"bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que especifica como upstreams.",
|
||||
"local_ptr_title": "Servidores DNS privados",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home usará para consultas de recursos servidos localmente. Por exemplo, este servidor será usado para resolver nomes de host de clientes para clientes com endereços IP privados. Se não for definido, o AdGuard Home usará automaticamente seu resolvedor DNS padrão.",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver os nomes de host de clientes com endereços IP privados, por exemplo \"192.168.12.34\", usando rDNS. Se não for definido, o AdGuard Home usa os resolvedores DNS padrão do seu sistema operacional.",
|
||||
"local_ptr_placeholder": "Insira um endereço de servidor por linha",
|
||||
"resolve_clients_title": "Activar resolução reversa de endereços IP de clientes",
|
||||
"resolve_clients_title": "Ativar resolução reversa de endereços IP de clientes",
|
||||
"resolve_clients_desc": "Se activado, o AdGuard Home tentará resolver de forma reversa os endereços IP dos clientes em seus nomes de host, enviando consultas PTR aos resolvedores correspondentes (servidores DNS privados para clientes locais, servidor DNS primário para clientes com endereços IP públicos).",
|
||||
"check_dhcp_servers": "Verificar por servidores DHCP",
|
||||
"save_config": "Guardar definição",
|
||||
@@ -21,10 +21,10 @@
|
||||
"unavailable_dhcp_desc": "O AdGuard Home não pode executar um servidor DHCP em seu sistema operacional",
|
||||
"dhcp_title": "Servidor DHCP (experimental)",
|
||||
"dhcp_description": "Se o seu router não fornecer configurações de DHCP, poderá usar o servidor DHCP integrado do AdGuard.",
|
||||
"dhcp_enable": "Activar servidor DHCP",
|
||||
"dhcp_disable": "Desactivar servidor DHCP",
|
||||
"dhcp_not_found": "É seguro activar o servidor DHCP integrado porque o AdGuard Home não encontrou nenhum servidor DHCP ativo na rede. No entanto, você deve verificar isso manualmente, pois a verificação automática atualmente não oferece 100% de garantia.",
|
||||
"dhcp_found": "Um servidor DHCP activo foi encontrado na rede. Não é seguro activar o servidor DHCP incorporado.",
|
||||
"dhcp_enable": "Ativar servidor DHCP",
|
||||
"dhcp_disable": "Desativar servidor DHCP",
|
||||
"dhcp_not_found": "É seguro ativar o servidor DHCP integrado porque o AdGuard Home não encontrou nenhum servidor DHCP ativo na rede. No entanto, você deve verificar isso manualmente, pois a verificação automática atualmente não oferece 100% de garantia.",
|
||||
"dhcp_found": "Um servidor DHCP ativo foi encontrado na rede. Não é seguro ativar o servidor DHCP incorporado.",
|
||||
"dhcp_leases": "Concessões DHCP",
|
||||
"dhcp_static_leases": "Concessões de DHCP estático",
|
||||
"dhcp_leases_not_found": "Nenhuma concessão DHCP encontrada",
|
||||
@@ -55,10 +55,10 @@
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "Nome do servidor",
|
||||
"dhcp_table_expires": "Expira",
|
||||
"dhcp_warning": "Se tu quiser activar o servidor DHCP de qualquer maneira, certifique-se de que não haja outro servidor DHCP activo em tua rede, pois isso pode quebrar a conectividade com a Internet para dispositivos na rede!",
|
||||
"dhcp_warning": "Se tu quiser ativar o servidor DHCP de qualquer maneira, certifique-se de que não haja outro servidor DHCP ativo em tua rede, pois isso pode quebrar a conectividade com a Internet para dispositivos na rede!",
|
||||
"dhcp_error": "O AdGuard Home não conseguiu determinar se há noutro servidor DHCP ativo na rede.",
|
||||
"dhcp_static_ip_error": "Para usar o servidor DHCP, deve definir um endereço IP estático. AdGuard Home não conseguiu determinar se essa interface de rede está configurada usando o endereço de IP estático. Por favor, defina um endereço IP estático manualmente.",
|
||||
"dhcp_dynamic_ip_found": "O seu sistema usa a configuração de endereço IP dinâmico para a interface <0>{{interfaceName}}</0>. Para usar o servidor DHCP, deve definir um endereço de IP estático. O seu endereço IP actual é <0> {{ipAddress}} </ 0>. AdGuard Home irá definir automaticamente este endereço IP como estático se pressionar o botão \"Activar servidor DHCP\".",
|
||||
"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 actual é <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_new_static_lease": "Nova concessão estática",
|
||||
@@ -92,10 +92,10 @@
|
||||
"homepage": "Página inicial",
|
||||
"report_an_issue": "Comunicar um problema",
|
||||
"privacy_policy": "Política de Privacidade",
|
||||
"enable_protection": "Activar protecção",
|
||||
"enabled_protection": "Activar protecção",
|
||||
"disable_protection": "Desactivar protecção",
|
||||
"disabled_protection": "Desactivar protecção",
|
||||
"enable_protection": "Ativar protecção",
|
||||
"enabled_protection": "Ativar protecção",
|
||||
"disable_protection": "Desativar protecção",
|
||||
"disabled_protection": "Desativar protecção",
|
||||
"refresh_statics": "Repor estatísticas",
|
||||
"dns_query": "Consultas de DNS",
|
||||
"blocked_by": "<0>Bloqueado por filtros</0>",
|
||||
@@ -236,7 +236,7 @@
|
||||
"query_log_updated": "O registro da consulta foi actualizado com sucesso",
|
||||
"query_log_clear": "Limpar registos de consulta",
|
||||
"query_log_retention": "Retenção de registos de consulta",
|
||||
"query_log_enable": "Activar registo",
|
||||
"query_log_enable": "Ativar registo",
|
||||
"query_log_configuration": "Definições do registo",
|
||||
"query_log_disabled": "O registo de consulta está desactivado e pode ser configurado em <0>definições</0>",
|
||||
"query_log_strict_search": "Usar aspas duplas para uma pesquisa rigorosa",
|
||||
@@ -267,7 +267,7 @@
|
||||
"plain_dns": "DNS simples",
|
||||
"form_enter_rate_limit": "Insira o limite de taxa",
|
||||
"rate_limit": "Limite de taxa",
|
||||
"edns_enable": "Activar sub-rede do cliente EDNS",
|
||||
"edns_enable": "Ativar sub-rede do cliente EDNS",
|
||||
"edns_cs_desc": "Se activado, o AdGuard Home enviará sub-redes dos clientes para os servidores DNS.",
|
||||
"rate_limit_desc": "O número de solicitações por segundo permitido por cliente. Configurando para 0 significa sem limite.",
|
||||
"blocking_ipv4_desc": "Endereço IP a ser devolvido para uma solicitação A bloqueada",
|
||||
@@ -311,6 +311,7 @@
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Esta definição cobrirá automaticamente todos os dispositivos ligados ao seu router doméstico e não irá precisar de configurar cada um deles manualmente.",
|
||||
"install_devices_address": "O servidor de DNS do AdGuard Home está a capturar os seguintes endereços",
|
||||
"install_devices_router_list_1": "Abra as preferências do seu roteador. Normalmente, tu podes acessá-lo de teu navegador por meio de um URL, como http://192.168.0.1/ ou http://192.168.1.1/. Tu podes ser solicitado a inserir uma palavra-passe. Se tu não se lembrar, muitas vezes tu podes repor a palavra-passe pressionando um botão no próprio roteador, mas esteja ciente de que se esse procedimento for escolhido, tu provavelmente perderás toda a definição do roteador. Se o teu roteador requer uma aplicação para configurá-lo, instale a aplicação no seu telefone ou PC e use-o para acessar as definições do roteador.",
|
||||
"install_devices_router_list_2": "Encontre as configurações de DNS. Procure as letras DNS ao lado de um campo que permite dois ou três conjuntos de números, cada um dividido em quatro grupos de um a três números.",
|
||||
"install_devices_router_list_3": "Insira aqui seu servidor do AdGuard Home.",
|
||||
"install_devices_router_list_4": "Em alguns tipos de roteador, um servidor DNS personalizado não pode ser configurado. Nesse caso, configurar o AdGuard Home como um <0>Servidor DHCP</0> pode ajudar. Caso contrário, tu deve verificar o manual do router sobre como personalizar os servidores DNS em seu modelo de router específico.",
|
||||
@@ -358,7 +359,7 @@
|
||||
"encryption_expire": "Expira",
|
||||
"encryption_key": "Chave privada",
|
||||
"encryption_key_input": "Copie/cole aqui a chave privada codificada em PEM para o seu certificado.",
|
||||
"encryption_enable": "Activar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
|
||||
"encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
|
||||
"encryption_enable_desc": "Se a criptografia estiver activada, a interface administrativa do AdGuard Home funcionará em HTTPS, o servidor DNS irá capturar as solicitações por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.",
|
||||
"encryption_chain_valid": "Cadeia de certificado válida",
|
||||
"encryption_chain_invalid": "A cadeia de certificado é inválida",
|
||||
@@ -494,7 +495,7 @@
|
||||
"interval_hours": "{{count}} hora",
|
||||
"interval_hours_plural": "{{count}} horas",
|
||||
"filters_configuration": "Definição dos filtros",
|
||||
"filters_enable": "Activar filtros",
|
||||
"filters_enable": "Ativar filtros",
|
||||
"filters_interval": "Intervalo de actualização de filtros",
|
||||
"disabled": "Desactivado",
|
||||
"username_label": "Nome do utilizador",
|
||||
@@ -522,12 +523,12 @@
|
||||
"rewrite_domain_name": "Nome de domínio: adicione um registro CNAME",
|
||||
"rewrite_A": "<0>A</0>: valor especial, mantenha <0>A</0> nos registros do upstream",
|
||||
"rewrite_AAAA": "<0>AAAA</0>: valor especial, mantenha <0>AAAA</0> nos registros do servidor DNS primário",
|
||||
"disable_ipv6": "Desactivar IPv6",
|
||||
"disable_ipv6": "Desativar IPv6",
|
||||
"disable_ipv6_desc": "Se este recurso estiver ativado, todas as consultas de DNS para endereços IPv6 (tipo AAAA) serão ignoradas.",
|
||||
"fastest_addr": "Endereço de IP mais rápido",
|
||||
"fastest_addr_desc": "Consulta todos os servidores DNS e retorna o endereço IP mais rápido entre todas as respostas. Isso torna as consultas DNS mais lentas, pois o AdGuard Home tem que esperar pelas respostas de todos os servidores DNS, mas melhora a conectividade geral.",
|
||||
"autofix_warning_text": "Se clicar em \"Corrigir\", o AdGuardHome irá configurar o seu sistema para utilizar o servidor DNS do AdGuardHome.",
|
||||
"autofix_warning_list": "Ele irá realizar estas tarefas: <0>Desactivar sistema DNSStubListener</0> <0>Definir endereço do servidor DNS para 127.0.0.1</0> <0>Substituir o alvo simbólico do link /etc/resolv.conf para /run/systemd/resolv.conf</0> <0>Parar DNSStubListener (recarregar serviço resolvido pelo sistema)</0>",
|
||||
"autofix_warning_list": "Irá realizar estas tarefas: <0>Desativar sistema DNSStubListener</0> <0>Definir endereço do servidor DNS para 127.0.0.1</0> <0>Substituir o alvo simbólico do link /etc/resolv.conf para /run/systemd/resolv.conf</0> <0>Parar DNSStubListener (recarregar serviço resolvido pelo sistema)</0>",
|
||||
"autofix_warning_result": "Como resultado, todos as solicitações DNS do seu sistema serão processadas pelo AdGuard Home por predefinição.",
|
||||
"tags_title": "Etiquetas",
|
||||
"tags_desc": "Tu podes seleccionar as etiquetas que correspondem ao cliente. As etiquetas podem ser incluídas nas regras de filtragem e permitir que tu as aplique com mais precisão. <0>Saiba mais</0>",
|
||||
@@ -559,7 +560,7 @@
|
||||
"confirm_static_ip": "O AdGuard Home irá configurar {{ip}} para ser seu endereço IP estático. Deseja continuar?",
|
||||
"list_updated": "{{count}} lista actualizada",
|
||||
"list_updated_plural": "{{count}} listas actualizadas",
|
||||
"dnssec_enable": "Activar DNSSEC",
|
||||
"dnssec_enable": "Ativar DNSSEC",
|
||||
"dnssec_enable_desc": "Definir a flag DNSSEC nas consultas de DNS em andamento e verificar o resultado (é necessário um resolvedor DNSSEC ativado)",
|
||||
"validated_with_dnssec": "Validado com DNSSEC",
|
||||
"all_queries": "Todas as consultas",
|
||||
@@ -593,7 +594,7 @@
|
||||
"filter_category_security_desc": "Listas especializadas em bloquear domínios de malware, phishing ou fraude",
|
||||
"filter_category_regional_desc": "Listas focadas em anúncios regionais e servidores de monitorização",
|
||||
"filter_category_other_desc": "Outras listas de bloqueio",
|
||||
"setup_config_to_enable_dhcp_server": "Defina a definição para activar o servidor DHCP",
|
||||
"setup_config_to_enable_dhcp_server": "Defina a configuração para ativar o servidor DHCP",
|
||||
"original_response": "Resposta original",
|
||||
"click_to_view_queries": "Clique para ver as consultas",
|
||||
"port_53_faq_link": "A porta 53 é frequentemente ocupada por serviços \"DNSStubListener\" ou \"systemd-resolved\". Por favor leia <0>essa instrução</0> para resolver isso.",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Bootstrap DNS-серверы",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS-серверы используются для поиска IP-адресов DoH/DoT серверов, которые вы указали.",
|
||||
"local_ptr_title": "Приватные DNS-серверы",
|
||||
"local_ptr_desc": "DNS-серверы, которые AdGuard Home будет использовать для запросов на локальные ресурсы. Например, эти серверы будут использоваться, чтобы получить доменные имена клиентов в приватных сетях. Если список пуст, AdGuard Home будет использовать системный DNS-сервер по умолчанию.",
|
||||
"local_ptr_desc": "DNS-серверы, которые AdGuard Home использует для локальных PTR-запросов. Эти серверы используются, чтобы получить доменные имена клиентов с приватными IP-адресами, например «192.168.12.34», с помощью rDNS. Если список пуст, AdGuard Home использует DNS-серверы по умолчанию вашей ОС.",
|
||||
"local_ptr_placeholder": "Введите по одному адресу на строчку",
|
||||
"resolve_clients_title": "Включить запрашивание доменных имён для IP-адресов клиентов",
|
||||
"resolve_clients_desc": "AdGuard Home будет пытаться определить доменные имена клиентов через PTR-запросы к соответствующим серверам (приватные DNS-серверы для локальных клиентов, upstream-сервер для клиентов с публичным IP-адресом).",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "Zagonski DNS strežniki",
|
||||
"bootstrap_dns_desc": "Zagonski DNS strežniki se uporabljajo za razreševanje IP naslovov DoH/DoT reševalcev, ki jih določite kot navzgornje.",
|
||||
"local_ptr_title": "Zasebni strežniki DNS",
|
||||
"local_ptr_desc": "Strežniki DNS, ki jih bo AdGuard Home uporabil za poizvedbe o lokalno oskrbovanih virih. Ta strežnik bo na primer uporabljen za razreševanje imen gostiteljev odjemalcev z zasebnimi naslovi IP. Če ni nastavljen, bo AdGuard Home samodejno uporabil vaš privzeti razreševalnik DNS.",
|
||||
"local_ptr_desc": "Strežniki DNS, ki jih AdGuard Home uporablja za lokalne poizvedbe PTR. Ti strežniki se uporabljajo za razreševanje imen gostiteljev z zasebnimi naslovi IP, na primer \"192.168.12.34\" uporablja rDNS. Če ni nastavljen, AdGuard Home uporablja privzete rešitve DNS vašega OS.",
|
||||
"local_ptr_placeholder": "V vrstico vnesite en naslov strežnika",
|
||||
"resolve_clients_title": "Omogoči obratno reševanje naslovov IP gostiteljev",
|
||||
"resolve_clients_desc": "Če je omogočeno, bo AdGuard Home poskušal samodejno razrešiti gostiteljska imena odjemalcev iz njihovih naslovov IP tako, da pošlje poizvedbo PTR ustreznemu razreševalniku (zasebni strežniki DNS za lokalne odjemalce, gorvodni strežnik za odjemalce z javnimi naslovi IP).",
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
"bootstrap_dns": "Bootstrap DNS 服务器",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS 服务器用于解析您指定为上游的 DoH / DoT 解析器的 IP 地址。",
|
||||
"local_ptr_title": "私人 DNS 服务器",
|
||||
"local_ptr_desc": "AdGuard Home 用于查询本地服务资源的 DNS 服务器。例如,该服务器将被用作具有私人 IP 地址的客户机解析客户机的主机名。如果没有设置,AdGuard Home 将自动使用您的默认 DNS 解析器",
|
||||
"local_ptr_desc": "AdGuard Home 用于查询本地服务资源的 DNS 服务器。例如,该服务器将被用于解析具有私人 IP 地址的客户机的主机名,比如 \"192.168.12.34\"。如果没有设置,AdGuard Home 将自动使用您的默认 DNS 解析器。",
|
||||
"local_ptr_placeholder": "每行输入一个服务器地址",
|
||||
"resolve_clients_title": "启用客户端主机名解析",
|
||||
"resolve_clients_desc": "如果启用,AdGuard Home 将尝试通过发送一个 PTR 查询到相应的解析器 (本地客户端的私人 DNS 服务器,公有 IP 客户端的上游服务器) 来自动解析来自其 IP 地址的客户端主机名。",
|
||||
"resolve_clients_title": "启用客户端的 IP 地址的反向解析",
|
||||
"resolve_clients_desc": "如果启用,AdGuard Home 将尝试通过发送 PTR 查询到对应的解析器 (本地客户端的私人 DNS 服务器,公有 IP 客户端的上游服务器) 将 IP 地址反向解析成其客户端主机名。",
|
||||
"check_dhcp_servers": "检查 DHCP 服务器",
|
||||
"save_config": "保存配置",
|
||||
"enabled_dhcp": "DHCP 服务器已启用",
|
||||
@@ -311,7 +311,7 @@
|
||||
"install_devices_router": "路由器",
|
||||
"install_devices_router_desc": "此设置将自动覆盖连接到您的家庭路由器的所有设备,您不需要手动配置它们。",
|
||||
"install_devices_address": "AdGuard Home DNS 服务器正在监听以下地址",
|
||||
"install_devices_router_list_1": "打开您的路由器配置界面。通常情况下,您可以通过浏览器访问地址(如 http://192.168.0.1/ 或 http://192.168.1.1 )。打开后您可能需要输入密码以进入配置界面。如果您不记得密码,通常可以通过路由器上的重置按钮来重设密码。但是,请注意,如您进行此操作,您最可能会失去所有路由器的配置。一些路由器可能需要通过特定的应用来进行这一操作,请确保您已经在计算机或手机上安装了相关应用。",
|
||||
"install_devices_router_list_1": "打开您的路由器配置界面。通常情况下,您可以通过浏览器访问地址(如 http://192.168.0.1/ 或 http://192.168.1.1 )。打开后您可能需要输入密码以进入配置界面。如果您不记得密码,通常可以通过路由器上的重置按钮来重设密码。但是,请注意,如您进行此操作,您最可能会失去所有路由器的配置。如果您的路由器需要通过特定的应用进行这一操作,请将相关应用程序安装到您的手机或计算机上并使用它设置您的路由器。",
|
||||
"install_devices_router_list_2": "找到路由器的 DHCP/DNS 设置页面。您会在 DNS 这一单词旁边找到两到三行允许输入的输入框,每一行输入框分为四组,每组允许输入一到三个数字。",
|
||||
"install_devices_router_list_3": "请在此处输入您的 AdGuard Home 服务器地址。",
|
||||
"install_devices_router_list_4": "在某些类型的路由器上无法设置自定义 DNS 服务器。在此情况下将 AdGuard Home 设置为 <0>DHCP 服务器</0>,可能会有所帮助。否则您应该查找如何根据特定路由器型号设置 DNS 服务器的使用手册。",
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"client_settings": "用戶端設定",
|
||||
"example_upstream_reserved": "您可以<0>指定網域</0>使用特定 DNS 查詢",
|
||||
"example_upstream_comment": "您可以指定註解",
|
||||
"upstream_parallel": "使用平行查詢,同時查詢所有上游伺服器來加速解析結果",
|
||||
"parallel_requests": "平行處理",
|
||||
"load_balancing": "負載平衡",
|
||||
"load_balancing_desc": "一次只查詢一個伺服器。AdGuard Home 會使用加權隨機取樣來選擇使用的查詢結果,以確保速度最快的伺服器能被充分運用。",
|
||||
"bootstrap_dns": "引導(Boostrap) DNS 伺服器",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS 伺服器用於解析您所設定的上游 DoH/DoT 解析器的 IP 地址",
|
||||
"check_dhcp_servers": "檢查 DHCP 伺服器",
|
||||
"save_config": "儲存設定",
|
||||
"enabled_dhcp": "DHCP 伺服器已啟動",
|
||||
"disabled_dhcp": "DHCP 伺服器已關閉",
|
||||
"unavailable_dhcp": "DHCP 無法使用",
|
||||
@@ -13,10 +18,12 @@
|
||||
"dhcp_description": "如果你的路由器沒有提供 DHCP 設定,您可以使用 AdGuard 內建的 DHCP 伺服器。",
|
||||
"dhcp_enable": "開啟 DHCP 伺服器",
|
||||
"dhcp_disable": "關閉 DHCP 伺服器",
|
||||
"dhcp_not_found": "您可以安全地啟用內建 DHCP 伺服器 - 在目前網路中沒有找到任何有效的 DHCP 伺服器。但我們依舊建議您手動再次檢查,因為目前我們的自動檢測並不能確定 100% 準確",
|
||||
"dhcp_found": "在目前網段中有正在運作的 DHCP 伺服器,開啟內建 DHCP 伺服器是不安全的。",
|
||||
"dhcp_leases": "DHCP 租用",
|
||||
"dhcp_static_leases": "DHCP 靜態租用",
|
||||
"dhcp_leases_not_found": "找不到 DHCP 租約",
|
||||
"dhcp_config_saved": "DHCP 設定已儲存",
|
||||
"dhcp_ipv4_settings": "DHCP IPv4 設定",
|
||||
"dhcp_ipv6_settings": "DHCP IPv6 設定",
|
||||
"form_error_required": "必要欄位",
|
||||
@@ -42,11 +49,16 @@
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "主機名稱",
|
||||
"dhcp_table_expires": "到期",
|
||||
"dhcp_warning": "如果無論如何您都想要啟動 AdGuard 內建 DHCP 伺服器,請先確保同一網路下沒有正在運作的 DHCP 伺服器,否則很有可能會破壞其他已連線至網際網路的裝置。",
|
||||
"dhcp_error": "無法偵測到同一網路下使否有其他 DHCP 伺服器。",
|
||||
"dhcp_static_ip_error": "使用 DHCP 伺服器必須先指定靜態 IP 位置給 AdGuard。無法偵測到有效的靜態 IP 設定,請先手動設定。",
|
||||
"dhcp_dynamic_ip_found": "您的網路介面 <0>{{interfaceName}}</0> 正在使用動態 IP,要使用 DHCP 伺服器必須指定靜態 IP 給 AdGuard。\n目前您的 IP 位址 <0>{{ipAddress}}</0>,啟用 DHCP 後此 IP 將自動設定為靜態 IP 位址。",
|
||||
"dhcp_lease_added": "靜態租用 \"{{key}}\" 已新增成功",
|
||||
"dhcp_lease_deleted": "靜態租用 \"{{key}}\" 已刪除成功",
|
||||
"dhcp_new_static_lease": "新增靜態租用",
|
||||
"dhcp_static_leases_not_found": "找不到 DHCP 靜態租用",
|
||||
"dhcp_add_static_lease": "新增靜態租用",
|
||||
"dhcp_reset": "您確定要重設 DHCP 設定嗎?",
|
||||
"country": "國家",
|
||||
"city": "城市",
|
||||
"delete_confirm": "您確定要刪除「{{key}}」嗎?",
|
||||
@@ -93,7 +105,14 @@
|
||||
"top_clients": "熱門用戶端排行",
|
||||
"no_clients_found": "找不到用戶端",
|
||||
"general_statistics": "一般統計資料",
|
||||
"number_of_dns_query_days": "過去 {{count}} 天內 DNS 查詢總數",
|
||||
"number_of_dns_query_days_plural": "過去 {{count}} 天內 DNS 查詢總數",
|
||||
"number_of_dns_query_24_hours": "過去 24小時內 DNS 查詢總數",
|
||||
"number_of_dns_query_blocked_24_hours": "已被廣告過濾器與主機黑名單封鎖 DNS 查詢總數",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "已被 AdGuard 瀏覽安全模組封鎖的 DNS 查詢總數",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "已封鎖成人網站總數",
|
||||
"enforced_save_search": "強制使用安全搜尋",
|
||||
"number_of_dns_query_to_safe_search": "已強制使用安全搜尋總數",
|
||||
"average_processing_time": "平均的處理時間",
|
||||
"average_processing_time_hint": "處理 DNS 請求的平均時間(毫秒)",
|
||||
"block_domain_use_filters_and_hosts": "使用過濾器與 hosts 檔案阻擋網域查詢",
|
||||
@@ -103,6 +122,7 @@
|
||||
"use_adguard_parental": "使用 AdGuard 家長監護功能",
|
||||
"use_adguard_parental_hint": "AdGuard Home 將比對查詢網域是否含有成人內容。它使用與 AdGuard 瀏覽安全一樣的尊重個人隱私的 API 來進行檢查。",
|
||||
"enforce_safe_search": "強制使用安全搜尋",
|
||||
"enforce_save_search_hint": "AdGuard Home 可在下列搜尋引擎使用強制安全搜尋:Google、YouTube、Bing、DuckDuckGo 和 Yandex。",
|
||||
"no_servers_specified": "沒有指定的伺服器",
|
||||
"general_settings": "一般設定",
|
||||
"dns_settings": "DNS 設定",
|
||||
@@ -114,6 +134,7 @@
|
||||
"encryption_settings": "加密設定",
|
||||
"dhcp_settings": "DHCP 設定",
|
||||
"upstream_dns": "上游 DNS 伺服器",
|
||||
"upstream_dns_help": "每行輸入一個伺服器位址。<a>了解更多</a>有關設定上游 DNS 伺服器的內容",
|
||||
"upstream_dns_configured_in_file": "設定在 {{path}}",
|
||||
"test_upstream_btn": "測試上游 DNS",
|
||||
"upstreams": "上游",
|
||||
@@ -242,6 +263,7 @@
|
||||
"rate_limit": "速率限制",
|
||||
"edns_enable": "啟用 EDNS Client Subnet",
|
||||
"edns_cs_desc": "開啟後 AdGuard Home 將會傳送用戶端的子網路給 DNS 伺服器。",
|
||||
"rate_limit_desc": "限制單一裝置每秒發出的查詢次數(設定為 0 即表示無限制)",
|
||||
"blocking_ipv4_desc": "回覆指定 IPv4 位址給被封鎖的網域的 A 紀錄查詢",
|
||||
"blocking_ipv6_desc": "回覆指定 IPv6 位址給被封鎖的網域的 AAAA 紀錄查詢",
|
||||
"blocking_mode_default": "預設:被 Adblock 規則封鎖時回應零值的 IP 位址(A 紀錄回應 0.0.0.0 ,AAAA 紀錄回應 ::);被 /etc/hosts 規則封鎖時回應規則中指定 IP 位址",
|
||||
@@ -254,6 +276,7 @@
|
||||
"source_label": "來源",
|
||||
"found_in_known_domain_db": "在已知網域資料庫中找到。",
|
||||
"category_label": "類別",
|
||||
"rule_label": "規則",
|
||||
"list_label": "清單",
|
||||
"unknown_filter": "未知過濾器 {{filterId}}",
|
||||
"known_tracker": "已知追蹤器",
|
||||
@@ -263,6 +286,7 @@
|
||||
"install_settings_listen": "監聽介面",
|
||||
"install_settings_port": "連接埠",
|
||||
"install_settings_interface_link": "您可以從以下 IP 位址來訪問 AdGuard Home 管理介面:",
|
||||
"form_error_port": "輸入有效的連接埠",
|
||||
"install_settings_dns": "DNS 伺服器",
|
||||
"install_settings_dns_desc": "您需要將您的裝置或路由器設定以下的 IP 位址為 DNS 伺服器:",
|
||||
"install_settings_all_interfaces": "所有介面",
|
||||
@@ -281,8 +305,10 @@
|
||||
"install_devices_router": "路由器",
|
||||
"install_devices_router_desc": "使用此設定後,所有連接家中路由器的裝置都會自動套用,無須在每台裝置上個別設定。",
|
||||
"install_devices_address": "AdGuard Home DNS 伺服器正在監聽以下位址",
|
||||
"install_devices_router_list_1": "開啟您的路由器設定。通常可透過瀏覽器開啟(http://192.168.0.1/ 或 http://192.168.1.1)。接著您可能會被要求驗證登入,如果忘記密碼可以按壓路由器的 REST 重設按鈕來重設。部分路由器可能需要安裝特定應用程式,在這種情況下應該已經安裝在您的電腦或手機上。",
|
||||
"install_devices_router_list_2": "找到 DHCP/DNS 設定。允許兩到三組數字的欄位旁邊尋找 DNS 字串,每組數字分為四組,每組一到三位數。",
|
||||
"install_devices_router_list_3": "請在那邊輸入您的 AdGuard Home 伺服器位址。",
|
||||
"install_devices_router_list_4": "您無法於某些類型的路由器上設定自訂的 DNS 伺服器。在這種情況下,如果您設置 AdGuard Home 作為 <0>DHCP</0> 伺服器,其可能有所幫助。否則,您應搜尋有關如何為您的特定路由器型號自訂 DNS 伺服器之用法說明。",
|
||||
"install_devices_windows_list_1": "在「開始列」或「Windows 搜尋」開啟控制台。",
|
||||
"install_devices_windows_list_2": "點擊「網路和網際網路」,接著點選「網路和共用中心」。",
|
||||
"install_devices_windows_list_3": "在畫面左側點擊「變更介面卡設定」。",
|
||||
@@ -308,8 +334,10 @@
|
||||
"install_saved": "成功儲存",
|
||||
"encryption_title": "加密",
|
||||
"encryption_desc": "加密(HTTPS/TLS)提供給 DNS 和「管理介面網頁介面」兩者",
|
||||
"encryption_config_saved": "加密設定已儲存",
|
||||
"encryption_server": "伺服器名稱",
|
||||
"encryption_server_enter": "輸入您的網域名稱",
|
||||
"encryption_server_desc": "要使用 HTTPS,您必須輸入與您 SSL 憑證相符的伺服器名稱。",
|
||||
"encryption_redirect": "自動重新導向到 HTTPS",
|
||||
"encryption_redirect_desc": "如果啟用,AdGuard Home 將會自動導向 HTTP 到 HTTPS。",
|
||||
"encryption_https": "HTTPS 連接埠",
|
||||
@@ -337,10 +365,13 @@
|
||||
"encryption_reset": "您確定要重設加密設定嗎?",
|
||||
"topline_expiring_certificate": "您的 SSL 憑證即將到期。請前往<0>加密設定</0>更新。",
|
||||
"topline_expired_certificate": "您的 SSL 憑證已到期。請前往<0>加密設定</0>更新。",
|
||||
"form_error_port_range": "輸入範圍 80-65535 中的值",
|
||||
"form_error_port_unsafe": "這個連接埠不安全",
|
||||
"form_error_equal": "不可相同",
|
||||
"form_error_password": "密碼不相符",
|
||||
"reset_settings": "重設設定",
|
||||
"update_announcement": "有新版的 AdGuard Home {{version}} 可供更新!詳細資訊請<0>點擊這裡</0>。",
|
||||
"setup_guide": "安裝導覽",
|
||||
"dns_addresses": "DNS 位址",
|
||||
"dns_start": "DNS 伺服器正在啟動",
|
||||
"dns_status_error": "檢查 DNS 伺服器狀態錯誤",
|
||||
@@ -362,6 +393,7 @@
|
||||
"client_edit": "編輯用戶端",
|
||||
"client_identifier": "識別碼",
|
||||
"ip_address": "IP 位址",
|
||||
"client_identifier_desc": "可通過 IP 地址、CIDR、MAC 地址來辨識使用者裝置。注意:必須使用 AdGuard Home 內建 <0>DHCP 伺服器</0> 才能偵測 MAC 地址。",
|
||||
"form_enter_ip": "輸入 IP",
|
||||
"form_enter_mac": "輸入 MAC 地址",
|
||||
"form_enter_id": "輸入識別碼",
|
||||
@@ -384,6 +416,7 @@
|
||||
"access_disallowed_title": "用戶端黑名單",
|
||||
"access_disallowed_desc": "輸入 CIDR 或 IP 位址格式的清單,設定後 AdGuard Home 將拒絕設定的 IP 位址查詢請求。",
|
||||
"access_blocked_title": "網域黑名單",
|
||||
"access_blocked_desc": "請不要與過濾器搞混,AdGuard Home 將對這些網域執行過濾檢查後丟棄 DNS 請求。您可以輸入特定網域名稱來使用設定,或使用萬用字元,例如:「example.org」、「*.example.org」或「||example.org^」。",
|
||||
"access_settings_saved": "存取設定已儲存",
|
||||
"updates_checked": "檢查更新成功",
|
||||
"updates_version_equal": "AdGuard Home 是最新的版本",
|
||||
@@ -486,6 +519,7 @@
|
||||
"disable_ipv6": "停用 IPv6",
|
||||
"disable_ipv6_desc": "開啟此功能後,將捨棄所有對 IPv6 位址(AAAA)的查詢。",
|
||||
"fastest_addr": "Fastest IP 位址",
|
||||
"fastest_addr_desc": "從所有 DNS 伺服器查詢中回應最快的 IP 位址。但這操作會等待所有 DNS 查詢結果後才能回應,導致速度有所降低,不過同時卻也改善了整體連線品質。",
|
||||
"autofix_warning_text": "如果您點擊「修復」,AdGuard Home 將更改您的系統 DNS 設定更改為 AdGuard Home DNS 伺服器",
|
||||
"autofix_warning_list": "它將執行這些任務:<0>停用系統 DNSStubListener</0> <0>將 DNS 設定為 127.0.0.1</0> <0>更換軟連結將 /etc/resolv.conf 為 /run/systemd/resolve/resolv.conf</0> <0>停止 DNSStubListener(重新載入 systemd-resolved)</0>",
|
||||
"autofix_warning_result": "就結論來說 DNS 請求預設由本機的 AdGuard Home 處理。",
|
||||
@@ -515,6 +549,7 @@
|
||||
"set_static_ip": "設定一組靜態 IP 位址",
|
||||
"install_static_ok": "好消息!靜態 IP 位址設定完成了",
|
||||
"install_static_error": "AdGuard Home 無法在這個網路介面上執行自動設定。請尋找有關如何手動更改設定的說明。",
|
||||
"install_static_configure": "我們偵測到 <0>{{ip}}</0> 動態 IP 已被使用。您想要將它當作靜態 IP 使用嗎?",
|
||||
"confirm_static_ip": "AdGuard Home 將使用 {{ip}} 作為靜態 IP。要繼續處理?",
|
||||
"list_updated": "已更新 {{count}} 個清單",
|
||||
"list_updated_plural": "已更新 {{count}} 個清單",
|
||||
@@ -552,6 +587,7 @@
|
||||
"filter_category_security_desc": "針對惡意軟體、網路釣魚或詐騙網域的封鎖清單",
|
||||
"filter_category_regional_desc": "針對地區性廣告與追蹤器伺服器的封鎖清單",
|
||||
"filter_category_other_desc": "其他封鎖清單",
|
||||
"setup_config_to_enable_dhcp_server": "建立設定檔來使用 DHCP 伺服器",
|
||||
"original_response": "原始回應",
|
||||
"click_to_view_queries": "按一下以檢視查詢結果",
|
||||
"port_53_faq_link": "連接埠 53 經常被「DNSStubListener」或「systemd-resolved」服務佔用。請閱讀下列有關解決<0>這個問題</0>的說明",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bootstrap_dns": "自我啟動(Bootstrap)DNS 伺服器",
|
||||
"bootstrap_dns_desc": "自我啟動(Bootstrap)DNS 伺服器被用於解析您明確指定作為上游的 DoH/DoT 解析器之 IP 位址。",
|
||||
"local_ptr_title": "私人 DNS 伺服器",
|
||||
"local_ptr_desc": "AdGuard Home 將用於供在本地服務的資源的查詢之 DNS 伺服器。例如,此伺服器將被用於對於有私人 IP 位址的用戶端解析其主機名稱。如果未被設定,AdGuard Home 將自動地使用您的預設 DNS 解析器。",
|
||||
"local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析含私人 IP 位址的用戶端之主機名稱,例如,\"192.168.12.34\",使用反向的 DNS(rDNS)。如果未被設定,AdGuard Home 使用您的作業系統之預設 DNS 解析器。",
|
||||
"local_ptr_placeholder": "每行輸入一個伺服器位址",
|
||||
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
|
||||
"resolve_clients_desc": "如果被啟用,透過傳送指標(PTR)查詢到對應的解析器(私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),AdGuard Home 將試圖反向地解析用戶端的 IP 位址變為它們的主機名稱。",
|
||||
|
||||
@@ -27,6 +27,7 @@ const Form = (props) => {
|
||||
component={renderInputField}
|
||||
placeholder={t('username_placeholder')}
|
||||
autoComplete="username"
|
||||
autocapitalize="none"
|
||||
disabled={processing}
|
||||
validate={[validateRequiredValue]}
|
||||
/>
|
||||
|
||||
15
go.mod
15
go.mod
@@ -3,11 +3,11 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.35.5
|
||||
github.com/AdguardTeam/dnsproxy v0.37.2
|
||||
github.com/AdguardTeam/golibs v0.4.5
|
||||
github.com/AdguardTeam/urlfilter v0.14.4
|
||||
github.com/AdguardTeam/urlfilter v0.14.5
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.0.3
|
||||
github.com/ameshkov/dnscrypt/v2 v2.1.3
|
||||
github.com/digineo/go-ipset/v2 v2.2.1
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/go-ping/ping v0.0.0-20210216210419-25d1413fb7bb
|
||||
@@ -16,15 +16,11 @@ require (
|
||||
github.com/gobuffalo/packr/v2 v2.8.1 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/google/renameio v1.0.1-0.20210406141108-81588dbe0453
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210310193751-cfd4d47082c2
|
||||
github.com/kardianos/service v1.2.0
|
||||
github.com/karrick/godirwalk v1.16.1 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.19.3
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4 // indirect
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
|
||||
github.com/lucas-clemente/quic-go v0.20.1
|
||||
github.com/mdlayher/netlink v1.4.0
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065
|
||||
github.com/miekg/dns v1.1.40
|
||||
github.com/rogpeppe/go-internal v1.7.0 // indirect
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
@@ -32,7 +28,6 @@ require (
|
||||
github.com/spf13/cobra v1.1.3 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/ti-mo/netfilter v0.4.0
|
||||
github.com/u-root/u-root v7.0.0+incompatible
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
|
||||
@@ -44,3 +39,5 @@ require (
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06
|
||||
)
|
||||
|
||||
replace github.com/insomniacslk/dhcp => github.com/AdguardTeam/dhcp v0.0.0-20210420175708-50b0efd52063
|
||||
|
||||
42
go.sum
42
go.sum
@@ -18,17 +18,18 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/AdguardTeam/dnsproxy v0.35.5 h1:SsRF0eDzuLGaSUDKABIu9Mn1joi4v4kvEU1vju2DQPQ=
|
||||
github.com/AdguardTeam/dnsproxy v0.35.5/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs=
|
||||
github.com/AdguardTeam/dhcp v0.0.0-20210420175708-50b0efd52063 h1:RBsQppxEJEqHApY6WDBkM2H0UG5wt57RcT0El2WGdp8=
|
||||
github.com/AdguardTeam/dhcp v0.0.0-20210420175708-50b0efd52063/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
|
||||
github.com/AdguardTeam/dnsproxy v0.37.2 h1:3lgizD+lZI6uqxFiQykd1/hV7Ji4vSJBMejl1rbFAXU=
|
||||
github.com/AdguardTeam/dnsproxy v0.37.2/go.mod h1:xkJWEuTr550gPDmB9azsciKZzSXjf9wMn+Ji54PQ4gE=
|
||||
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
|
||||
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/golibs v0.4.5 h1:RRA9ZsmbJEN4OllAx0BcfvSbRBxxpWluJijBYmtp13U=
|
||||
github.com/AdguardTeam/golibs v0.4.5/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
|
||||
github.com/AdguardTeam/urlfilter v0.14.4 h1:lrS7lrfxVCFh4TFB6nwPp5UE4n1XNvv3zUetduD9mZw=
|
||||
github.com/AdguardTeam/urlfilter v0.14.4/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
|
||||
github.com/AdguardTeam/urlfilter v0.14.5 h1:WyF0hg0MwKevsqNPkoaZFH8f5WRi/yuy/7qePtYt5Ts=
|
||||
github.com/AdguardTeam/urlfilter v0.14.5/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
@@ -43,9 +44,8 @@ github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyY
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.0.1/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.0.3 h1:PE6VVc8QUMYJv9dTwcDcX5cYXf58XPi1WVPHrLf8MDs=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.0.3/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.1.3 h1:DG4Uf7LSDg6XDj9sp3maxh3Ur26jeGQaP5MeYosn6v0=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.1.3/go.mod h1:+8SbPbVXpxxcUsgGi8eodkqWPo1MyNHxKYC8hDpqLSo=
|
||||
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
@@ -128,15 +128,13 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -205,9 +203,6 @@ github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210310193751-cfd4d47082c2 h1:NpTIlXznCStsY88jU+Gh1Dy5dt/jYV4z4uU8h2TUOt4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210310193751-cfd4d47082c2/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
@@ -248,8 +243,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4=
|
||||
github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8=
|
||||
github.com/lucas-clemente/quic-go v0.20.1 h1:hb5m76V8QS/8Nw/suHvXqo3BMHAozvIkcnzpJdpanSk=
|
||||
github.com/lucas-clemente/quic-go v0.20.1/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -261,11 +256,10 @@ github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2
|
||||
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
|
||||
github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4 h1:RehYMOyRW8hPVEja1KBVsFVNSm35Jj9Mvs5yNoZZ28A=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.3 h1:XEZ1xGorVy9u+lJq+WXNE+hiqRYLNvJGYmwfwKQN2gU=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -292,7 +286,6 @@ github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
@@ -438,7 +431,6 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
@@ -482,6 +474,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -578,8 +571,8 @@ golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201214095126-aec9a390925b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -590,7 +583,6 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -712,7 +704,5 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
|
||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06 h1:QDxUo/w2COstK1wIBYpzQlHX/NqaQTcf9jyz347nI58=
|
||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
||||
@@ -48,32 +48,32 @@ const maxDomainLabelLen = 63
|
||||
// See https://stackoverflow.com/a/32294443/1892060.
|
||||
const maxDomainNameLen = 253
|
||||
|
||||
const invalidCharMsg = "invalid char %q at index %d in %q"
|
||||
|
||||
// ValidateDomainNameLabel returns an error if label is not a valid label of
|
||||
// a domain name.
|
||||
func ValidateDomainNameLabel(label string) (err error) {
|
||||
defer agherr.Annotate("validating label %q: %w", &err, label)
|
||||
|
||||
l := len(label)
|
||||
if l > maxDomainLabelLen {
|
||||
return fmt.Errorf("%q is too long, max: %d", label, maxDomainLabelLen)
|
||||
return fmt.Errorf("label is too long, max: %d", maxDomainLabelLen)
|
||||
} else if l == 0 {
|
||||
return agherr.Error("label is empty")
|
||||
}
|
||||
|
||||
if r := label[0]; !IsValidHostOuterRune(rune(r)) {
|
||||
return fmt.Errorf(invalidCharMsg, r, 0, label)
|
||||
return fmt.Errorf("invalid char %q at index %d", r, 0)
|
||||
} else if l == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, r := range label[1 : l-1] {
|
||||
if !isValidHostRune(r) {
|
||||
return fmt.Errorf(invalidCharMsg, r, i+1, label)
|
||||
return fmt.Errorf("invalid char %q at index %d", r, i+1)
|
||||
}
|
||||
}
|
||||
|
||||
if r := label[l-1]; !IsValidHostOuterRune(rune(r)) {
|
||||
return fmt.Errorf(invalidCharMsg, r, l-1, label)
|
||||
return fmt.Errorf("invalid char %q at index %d", r, l-1)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -87,6 +87,8 @@ func ValidateDomainNameLabel(label string) (err error) {
|
||||
// TODO(a.garipov): After making sure that this works correctly, port this into
|
||||
// module golibs.
|
||||
func ValidateDomainName(name string) (err error) {
|
||||
defer agherr.Annotate("validating domain name %q: %w", &err, name)
|
||||
|
||||
name, err = idna.ToASCII(name)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -96,7 +98,7 @@ func ValidateDomainName(name string) (err error) {
|
||||
if l == 0 {
|
||||
return agherr.Error("domain name is empty")
|
||||
} else if l > maxDomainNameLen {
|
||||
return fmt.Errorf("%q is too long, max: %d", name, maxDomainNameLen)
|
||||
return fmt.Errorf("too long, max: %d", maxDomainNameLen)
|
||||
}
|
||||
|
||||
labels := strings.Split(name, ".")
|
||||
|
||||
@@ -95,40 +95,46 @@ func TestValidateDomainName(t *testing.T) {
|
||||
}, {
|
||||
name: "empty",
|
||||
in: "",
|
||||
wantErrMsg: `domain name is empty`,
|
||||
wantErrMsg: `validating domain name "": domain name is empty`,
|
||||
}, {
|
||||
name: "bad_symbol",
|
||||
in: "!!!",
|
||||
wantErrMsg: `invalid domain name label at index 0: ` +
|
||||
`invalid char '!' at index 0 in "!!!"`,
|
||||
wantErrMsg: `validating domain name "!!!": invalid domain name label at index 0: ` +
|
||||
`validating label "!!!": invalid char '!' at index 0`,
|
||||
}, {
|
||||
name: "bad_length",
|
||||
in: longDomainName,
|
||||
wantErrMsg: `"` + longDomainName + `" is too long, max: 253`,
|
||||
wantErrMsg: `validating domain name "` + longDomainName + `": too long, max: 253`,
|
||||
}, {
|
||||
name: "bad_label_length",
|
||||
in: longLabelDomainName,
|
||||
wantErrMsg: `invalid domain name label at index 0: "` + longLabel +
|
||||
`" is too long, max: 63`,
|
||||
wantErrMsg: `validating domain name "` + longLabelDomainName + `": ` +
|
||||
`invalid domain name label at index 0: validating label "` + longLabel +
|
||||
`": label is too long, max: 63`,
|
||||
}, {
|
||||
name: "bad_label_empty",
|
||||
in: "example..com",
|
||||
wantErrMsg: `invalid domain name label at index 1: label is empty`,
|
||||
name: "bad_label_empty",
|
||||
in: "example..com",
|
||||
wantErrMsg: `validating domain name "example..com": ` +
|
||||
`invalid domain name label at index 1: ` +
|
||||
`validating label "": label is empty`,
|
||||
}, {
|
||||
name: "bad_label_first_symbol",
|
||||
in: "example.-aa.com",
|
||||
wantErrMsg: `invalid domain name label at index 1:` +
|
||||
` invalid char '-' at index 0 in "-aa"`,
|
||||
wantErrMsg: `validating domain name "example.-aa.com": ` +
|
||||
`invalid domain name label at index 1: ` +
|
||||
`validating label "-aa": invalid char '-' at index 0`,
|
||||
}, {
|
||||
name: "bad_label_last_symbol",
|
||||
in: "example-.aa.com",
|
||||
wantErrMsg: `invalid domain name label at index 0:` +
|
||||
` invalid char '-' at index 7 in "example-"`,
|
||||
wantErrMsg: `validating domain name "example-.aa.com": ` +
|
||||
`invalid domain name label at index 0: ` +
|
||||
`validating label "example-": invalid char '-' at index 7`,
|
||||
}, {
|
||||
name: "bad_label_symbol",
|
||||
in: "example.a!!!.com",
|
||||
wantErrMsg: `invalid domain name label at index 1:` +
|
||||
` invalid char '!' at index 1 in "a!!!"`,
|
||||
wantErrMsg: `validating domain name "example.a!!!.com": ` +
|
||||
`invalid domain name label at index 1: ` +
|
||||
`validating label "a!!!": invalid char '!' at index 1`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -15,9 +15,6 @@ import (
|
||||
// implementation must be safe for concurrent use.
|
||||
type HostGenFunc func() (host string)
|
||||
|
||||
// unit is an alias for an existing map value.
|
||||
type unit = struct{}
|
||||
|
||||
// SystemResolvers helps to work with local resolvers' addresses provided by OS.
|
||||
type SystemResolvers interface {
|
||||
// Get returns the slice of local resolvers' addresses.
|
||||
@@ -29,11 +26,15 @@ type SystemResolvers interface {
|
||||
}
|
||||
|
||||
const (
|
||||
// fakeDialErr is an error which dialFunc is expected to return.
|
||||
fakeDialErr agherr.Error = "this error signals the successful dialFunc work"
|
||||
// errBadAddrPassed is returned when dialFunc can't parse an IP address.
|
||||
errBadAddrPassed agherr.Error = "the passed string is not a valid IP address"
|
||||
|
||||
// badAddrPassedErr is returned when dialFunc can't parse an IP address.
|
||||
badAddrPassedErr agherr.Error = "the passed string is not a valid IP address"
|
||||
// errFakeDial is an error which dialFunc is expected to return.
|
||||
errFakeDial agherr.Error = "this error signals the successful dialFunc work"
|
||||
|
||||
// errUnexpectedHostFormat is returned by validateDialedHost when the host has
|
||||
// more than one percent sign.
|
||||
errUnexpectedHostFormat agherr.Error = "unexpected host format"
|
||||
)
|
||||
|
||||
// refreshWithTicker refreshes the cache of sr after each tick form tickCh.
|
||||
|
||||
@@ -7,10 +7,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
)
|
||||
|
||||
// defaultHostGen is the default method of generating host for Refresh.
|
||||
@@ -24,8 +26,8 @@ type systemResolvers struct {
|
||||
resolver *net.Resolver
|
||||
hostGenFunc HostGenFunc
|
||||
|
||||
// addrs is the map that contains cached local resolvers' addresses.
|
||||
addrs map[string]unit
|
||||
// addrs is the set that contains cached local resolvers' addresses.
|
||||
addrs *aghstrings.Set
|
||||
addrsLock sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -34,7 +36,7 @@ func (sr *systemResolvers) refresh() (err error) {
|
||||
|
||||
_, err = sr.resolver.LookupHost(context.Background(), sr.hostGenFunc())
|
||||
dnserr := &net.DNSError{}
|
||||
if errors.As(err, &dnserr) && dnserr.Err == fakeDialErr.Error() {
|
||||
if errors.As(err, &dnserr) && dnserr.Err == errFakeDial.Error() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -50,47 +52,63 @@ func newSystemResolvers(refreshIvl time.Duration, hostGenFunc HostGenFunc) (sr S
|
||||
PreferGo: true,
|
||||
},
|
||||
hostGenFunc: hostGenFunc,
|
||||
addrs: make(map[string]unit),
|
||||
addrs: aghstrings.NewSet(),
|
||||
}
|
||||
s.resolver.Dial = s.dialFunc
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// validateDialedHost validated the host used by resolvers in dialFunc.
|
||||
func validateDialedHost(host string) (err error) {
|
||||
defer agherr.Annotate("parsing %q: %w", &err, host)
|
||||
|
||||
var ipStr string
|
||||
parts := strings.Split(host, "%")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
ipStr = host
|
||||
case 2:
|
||||
// Remove the zone and check the IP address part.
|
||||
ipStr = parts[0]
|
||||
default:
|
||||
return errUnexpectedHostFormat
|
||||
}
|
||||
|
||||
if net.ParseIP(ipStr) == nil {
|
||||
return errBadAddrPassed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 = SplitHost(address)
|
||||
if err != nil {
|
||||
// TODO(e.burkov): Maybe use a structured badAddrPassedErr to
|
||||
// TODO(e.burkov): Maybe use a structured errBadAddrPassed to
|
||||
// allow unwrapping of the real error.
|
||||
return nil, fmt.Errorf("%s: %w", err, badAddrPassedErr)
|
||||
return nil, fmt.Errorf("%s: %w", err, errBadAddrPassed)
|
||||
}
|
||||
|
||||
if net.ParseIP(host) == nil {
|
||||
return nil, fmt.Errorf("parsing %q: %w", host, badAddrPassedErr)
|
||||
err = validateDialedHost(host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating dialed host: %w", err)
|
||||
}
|
||||
|
||||
sr.addrsLock.Lock()
|
||||
defer sr.addrsLock.Unlock()
|
||||
|
||||
sr.addrs[host] = unit{}
|
||||
sr.addrs.Add(host)
|
||||
|
||||
return nil, fakeDialErr
|
||||
return nil, errFakeDial
|
||||
}
|
||||
|
||||
func (sr *systemResolvers) Get() (rs []string) {
|
||||
sr.addrsLock.RLock()
|
||||
defer sr.addrsLock.RUnlock()
|
||||
|
||||
addrs := sr.addrs
|
||||
rs = make([]string, len(addrs))
|
||||
var i int
|
||||
for addr := range addrs {
|
||||
rs[i] = addr
|
||||
i++
|
||||
}
|
||||
|
||||
return rs
|
||||
return sr.addrs.Values()
|
||||
}
|
||||
|
||||
@@ -46,21 +46,33 @@ func TestSystemResolvers_DialFunc(t *testing.T) {
|
||||
imp := createTestSystemResolversImp(t, 0, nil)
|
||||
|
||||
testCases := []struct {
|
||||
want error
|
||||
name string
|
||||
address string
|
||||
want error
|
||||
}{{
|
||||
want: errFakeDial,
|
||||
name: "valid",
|
||||
address: "127.0.0.1",
|
||||
want: fakeDialErr,
|
||||
}, {
|
||||
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: badAddrPassedErr,
|
||||
}, {
|
||||
want: errUnexpectedHostFormat,
|
||||
name: "invalid_ipv6_zone_port",
|
||||
address: "[::1%%lo0]:53",
|
||||
}, {
|
||||
want: errBadAddrPassed,
|
||||
name: "invalid_parse_ip",
|
||||
address: "not-ip",
|
||||
want: badAddrPassedErr,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
71
internal/aghstrings/set.go
Normal file
71
internal/aghstrings/set.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package aghstrings
|
||||
|
||||
// unit is a convenient alias for struct{}
|
||||
type unit = struct{}
|
||||
|
||||
// Set is a set of strings.
|
||||
type Set struct {
|
||||
m map[string]unit
|
||||
}
|
||||
|
||||
// NewSet returns a new string set containing strs.
|
||||
func NewSet(strs ...string) (set *Set) {
|
||||
set = &Set{
|
||||
m: make(map[string]unit, len(strs)),
|
||||
}
|
||||
|
||||
for _, s := range strs {
|
||||
set.Add(s)
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
// Add adds s to the set. Add panics if the set is a nil set, just like a nil
|
||||
// map does.
|
||||
func (set *Set) Add(s string) {
|
||||
set.m[s] = unit{}
|
||||
}
|
||||
|
||||
// Del deletes s from the set. Calling Del on a nil set has no effect, just
|
||||
// like delete on an empty map doesn't.
|
||||
func (set *Set) Del(s string) {
|
||||
if set != nil {
|
||||
delete(set.m, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Has returns true if s is in the set. Calling Has on a nil set returns false,
|
||||
// just like indexing on an empty map does.
|
||||
func (set *Set) Has(s string) (ok bool) {
|
||||
if set != nil {
|
||||
_, ok = set.m[s]
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Len returns the length of the set. A nil set has a length of zero, just like
|
||||
// an empty map.
|
||||
func (set *Set) Len() (n int) {
|
||||
if set == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(set.m)
|
||||
}
|
||||
|
||||
// Values returns all values in the set. The order of the values is undefined.
|
||||
// Values returns nil if the set is nil.
|
||||
func (set *Set) Values() (strs []string) {
|
||||
if set == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
strs = make([]string, 0, len(set.m))
|
||||
for s := range set.m {
|
||||
strs = append(strs, s)
|
||||
}
|
||||
|
||||
return strs
|
||||
}
|
||||
56
internal/aghstrings/set_test.go
Normal file
56
internal/aghstrings/set_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package aghstrings
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
const s = "a"
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var set *Set
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
set.Del(s)
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
assert.False(t, set.Has(s))
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
assert.Equal(t, 0, set.Len())
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
assert.Nil(t, set.Values())
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
set.Add(s)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("non_nil", func(t *testing.T) {
|
||||
set := NewSet()
|
||||
assert.Equal(t, 0, set.Len())
|
||||
|
||||
ok := set.Has(s)
|
||||
assert.False(t, ok)
|
||||
|
||||
set.Add(s)
|
||||
ok = set.Has(s)
|
||||
assert.True(t, ok)
|
||||
|
||||
assert.Equal(t, []string{s}, set.Values())
|
||||
|
||||
set.Del(s)
|
||||
ok = set.Has(s)
|
||||
assert.False(t, ok)
|
||||
|
||||
set = NewSet(s)
|
||||
assert.Equal(t, 1, set.Len())
|
||||
})
|
||||
}
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd/nclient4"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6/nclient6"
|
||||
"github.com/insomniacslk/dhcp/iana"
|
||||
|
||||
@@ -27,13 +27,13 @@ var webHandlersRegistered = false
|
||||
|
||||
// Lease contains the necessary information about a DHCP lease
|
||||
type Lease struct {
|
||||
// Expiry is the expiration time of the lease. The unix timestamp value
|
||||
// of 1 means that this is a static lease.
|
||||
Expiry time.Time `json:"expires"`
|
||||
|
||||
Hostname string `json:"hostname"`
|
||||
HWAddr net.HardwareAddr `json:"mac"`
|
||||
IP net.IP `json:"ip"`
|
||||
Hostname string `json:"hostname"`
|
||||
|
||||
// Lease expiration time
|
||||
// 1: static lease
|
||||
Expiry time.Time `json:"expires"`
|
||||
}
|
||||
|
||||
// IsStatic returns true if the lease is static.
|
||||
@@ -133,6 +133,7 @@ type Server struct {
|
||||
|
||||
// ServerInterface is an interface for servers.
|
||||
type ServerInterface interface {
|
||||
Enabled() (ok bool)
|
||||
Leases(flags int) []Lease
|
||||
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
|
||||
}
|
||||
@@ -207,6 +208,11 @@ func Create(conf ServerConfig) *Server {
|
||||
return s
|
||||
}
|
||||
|
||||
// Enabled returns true when the server is enabled.
|
||||
func (s *Server) Enabled() (ok bool) {
|
||||
return s.conf.Enabled
|
||||
}
|
||||
|
||||
// server calls this function after DB is updated
|
||||
func (s *Server) onNotify(flags uint32) {
|
||||
if flags == LeaseChangedDBStore {
|
||||
@@ -242,16 +248,14 @@ func (s *Server) WriteDiskConfig(c *ServerConfig) {
|
||||
}
|
||||
|
||||
// Start will listen on port 67 and serve DHCP requests.
|
||||
func (s *Server) Start() error {
|
||||
err := s.srv4.Start()
|
||||
func (s *Server) Start() (err error) {
|
||||
err = s.srv4.Start()
|
||||
if err != nil {
|
||||
log.Error("DHCPv4: start: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.srv6.Start()
|
||||
if err != nil {
|
||||
log.Error("DHCPv6: start: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -37,30 +37,34 @@ func TestDB(t *testing.T) {
|
||||
SubnetMask: net.IP{255, 255, 255, 0},
|
||||
notify: testNotify,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.srv6, err = v6Create(V6ServerConf{})
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
leases := []Lease{{
|
||||
IP: net.IP{192, 168, 10, 100},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Expiry: time.Now().Add(time.Hour),
|
||||
Expiry: time.Now().Add(time.Hour),
|
||||
Hostname: "static-1.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 100},
|
||||
}, {
|
||||
IP: net.IP{192, 168, 10, 101},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xBB},
|
||||
Hostname: "static-2.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xBB},
|
||||
IP: net.IP{192, 168, 10, 101},
|
||||
}}
|
||||
|
||||
srv4, ok := s.srv4.(*v4Server)
|
||||
require.True(t, ok)
|
||||
|
||||
err = srv4.addLease(&leases[0])
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, s.srv4.AddStaticLease(leases[1]))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.srv4.AddStaticLease(leases[1])
|
||||
require.NoError(t, err)
|
||||
|
||||
s.dbStore()
|
||||
t.Cleanup(func() {
|
||||
assert.Nil(t, os.Remove(dbFilename))
|
||||
assert.NoError(t, os.Remove(dbFilename))
|
||||
})
|
||||
s.srv4.ResetLeases(nil)
|
||||
s.dbLoad()
|
||||
|
||||
@@ -110,3 +110,8 @@ func (r *ipRange) offset(ip net.IP) (offset uint64, ok bool) {
|
||||
// construction.
|
||||
return offsetInt.Uint64(), true
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface for *ipRange.
|
||||
func (r *ipRange) String() (s string) {
|
||||
return fmt.Sprintf("%s-%s", r.start, r.end)
|
||||
}
|
||||
|
||||
@@ -1,554 +0,0 @@
|
||||
// Copyright 2018 the u-root Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
// +build go1.12
|
||||
|
||||
// Package nclient4 is a small, minimum-functionality client for DHCPv4.
|
||||
//
|
||||
// It only supports the 4-way DHCPv4 Discover-Offer-Request-Ack handshake as
|
||||
// well as the Request-Ack renewal process.
|
||||
// Originally from here: github.com/insomniacslk/dhcp/dhcpv4/nclient4
|
||||
// with the difference that this package can be built on UNIX (not just Linux),
|
||||
// because github.com/mdlayher/raw package supports it.
|
||||
package nclient4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBufferCap = 5
|
||||
|
||||
// DefaultTimeout is the default value for read-timeout if option WithTimeout is not set
|
||||
DefaultTimeout = 5 * time.Second
|
||||
|
||||
// DefaultRetries is amount of retries will be done if no answer was received within read-timeout amount of time
|
||||
DefaultRetries = 3
|
||||
|
||||
// MaxMessageSize is the value to be used for DHCP option "MaxMessageSize".
|
||||
MaxMessageSize = 1500
|
||||
|
||||
// ClientPort is the port that DHCP clients listen on.
|
||||
ClientPort = 68
|
||||
|
||||
// ServerPort is the port that DHCP servers and relay agents listen on.
|
||||
ServerPort = 67
|
||||
)
|
||||
|
||||
// DefaultServers is the address of all link-local DHCP servers and
|
||||
// relay agents.
|
||||
var DefaultServers = &net.UDPAddr{
|
||||
IP: net.IPv4bcast,
|
||||
Port: ServerPort,
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNoResponse is returned when no response packet is received.
|
||||
ErrNoResponse = errors.New("no matching response packet received")
|
||||
|
||||
// ErrNoConn is returned when NewWithConn is called with nil-value as conn.
|
||||
ErrNoConn = errors.New("conn is nil")
|
||||
|
||||
// ErrNoIfaceHWAddr is returned when NewWithConn is called with nil-value as ifaceHWAddr
|
||||
ErrNoIfaceHWAddr = errors.New("ifaceHWAddr is nil")
|
||||
)
|
||||
|
||||
// pendingCh is a channel associated with a pending TransactionID.
|
||||
type pendingCh struct {
|
||||
// SendAndRead closes done to indicate that it wishes for no more
|
||||
// messages for this particular XID.
|
||||
done <-chan struct{}
|
||||
|
||||
// ch is used by the receive loop to distribute DHCP messages.
|
||||
ch chan<- *dhcpv4.DHCPv4
|
||||
}
|
||||
|
||||
// Logger is a handler which will be used to output logging messages
|
||||
type Logger interface {
|
||||
// PrintMessage print _all_ DHCP messages
|
||||
PrintMessage(prefix string, message *dhcpv4.DHCPv4)
|
||||
|
||||
// Printf is use to print the rest debugging information
|
||||
Printf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// EmptyLogger prints nothing
|
||||
type EmptyLogger struct{}
|
||||
|
||||
// Printf is just a dummy function that does nothing
|
||||
func (e EmptyLogger) Printf(format string, v ...interface{}) {}
|
||||
|
||||
// PrintMessage is just a dummy function that does nothing
|
||||
func (e EmptyLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {}
|
||||
|
||||
// Printfer is used for actual output of the logger. For example *log.Logger is a Printfer.
|
||||
type Printfer interface {
|
||||
// Printf is the function for logging output. Arguments are handled in the manner of fmt.Printf.
|
||||
Printf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// ShortSummaryLogger is a wrapper for Printfer to implement interface Logger.
|
||||
// DHCP messages are printed in the short format.
|
||||
type ShortSummaryLogger struct {
|
||||
// Printfer is used for actual output of the logger
|
||||
Printfer
|
||||
}
|
||||
|
||||
// Printf prints a log message as-is via predefined Printfer
|
||||
func (s ShortSummaryLogger) Printf(format string, v ...interface{}) {
|
||||
s.Printfer.Printf(format, v...)
|
||||
}
|
||||
|
||||
// PrintMessage prints a DHCP message in the short format via predefined Printfer
|
||||
func (s ShortSummaryLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {
|
||||
s.Printf("%s: %s", prefix, message)
|
||||
}
|
||||
|
||||
// DebugLogger is a wrapper for Printfer to implement interface Logger.
|
||||
// DHCP messages are printed in the long format.
|
||||
type DebugLogger struct {
|
||||
// Printfer is used for actual output of the logger
|
||||
Printfer
|
||||
}
|
||||
|
||||
// Printf prints a log message as-is via predefined Printfer
|
||||
func (d DebugLogger) Printf(format string, v ...interface{}) {
|
||||
d.Printfer.Printf(format, v...)
|
||||
}
|
||||
|
||||
// PrintMessage prints a DHCP message in the long format via predefined Printfer
|
||||
func (d DebugLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {
|
||||
d.Printf("%s: %s", prefix, message.Summary())
|
||||
}
|
||||
|
||||
// Client is an IPv4 DHCP client.
|
||||
type Client struct {
|
||||
ifaceHWAddr net.HardwareAddr
|
||||
conn net.PacketConn
|
||||
timeout time.Duration
|
||||
retry int
|
||||
logger Logger
|
||||
|
||||
// bufferCap is the channel capacity for each TransactionID.
|
||||
bufferCap int
|
||||
|
||||
// serverAddr is the UDP address to send all packets to.
|
||||
//
|
||||
// This may be an actual broadcast address, or a unicast address.
|
||||
serverAddr *net.UDPAddr
|
||||
|
||||
// closed is an atomic bool set to 1 when done is closed.
|
||||
closed uint32
|
||||
|
||||
// done is closed to unblock the receive loop.
|
||||
done chan struct{}
|
||||
|
||||
// wg protects any spawned goroutines, namely the receiveLoop.
|
||||
wg sync.WaitGroup
|
||||
|
||||
pendingMu sync.Mutex
|
||||
// pending stores the distribution channels for each pending
|
||||
// TransactionID. receiveLoop uses this map to determine which channel
|
||||
// to send a new DHCP message to.
|
||||
pending map[dhcpv4.TransactionID]*pendingCh
|
||||
}
|
||||
|
||||
// New returns a client usable with an unconfigured interface.
|
||||
func New(iface string, opts ...ClientOpt) (*Client, error) {
|
||||
return new(iface, nil, nil, opts...)
|
||||
}
|
||||
|
||||
// NewWithConn creates a new DHCP client that sends and receives packets on the
|
||||
// given interface.
|
||||
func NewWithConn(conn net.PacketConn, ifaceHWAddr net.HardwareAddr, opts ...ClientOpt) (*Client, error) {
|
||||
return new(``, conn, ifaceHWAddr, opts...)
|
||||
}
|
||||
|
||||
func new(iface string, conn net.PacketConn, ifaceHWAddr net.HardwareAddr, opts ...ClientOpt) (*Client, error) {
|
||||
c := &Client{
|
||||
ifaceHWAddr: ifaceHWAddr,
|
||||
timeout: DefaultTimeout,
|
||||
retry: DefaultRetries,
|
||||
serverAddr: DefaultServers,
|
||||
bufferCap: defaultBufferCap,
|
||||
conn: conn,
|
||||
logger: EmptyLogger{},
|
||||
|
||||
done: make(chan struct{}),
|
||||
pending: make(map[dhcpv4.TransactionID]*pendingCh),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to apply option: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.ifaceHWAddr == nil {
|
||||
if iface == `` {
|
||||
return nil, ErrNoIfaceHWAddr
|
||||
}
|
||||
|
||||
i, err := net.InterfaceByName(iface)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get interface information: %w", err)
|
||||
}
|
||||
|
||||
c.ifaceHWAddr = i.HardwareAddr
|
||||
}
|
||||
|
||||
if c.conn == nil {
|
||||
var err error
|
||||
if iface == `` {
|
||||
return nil, ErrNoConn
|
||||
}
|
||||
c.conn, err = NewRawUDPConn(iface, ClientPort) // broadcast
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open a broadcasting socket: %w", err)
|
||||
}
|
||||
}
|
||||
c.wg.Add(1)
|
||||
go c.receiveLoop()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Close closes the underlying connection.
|
||||
func (c *Client) Close() error {
|
||||
// Make sure not to close done twice.
|
||||
if !atomic.CompareAndSwapUint32(&c.closed, 0, 1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := c.conn.Close()
|
||||
|
||||
// Closing c.done sets off a chain reaction:
|
||||
//
|
||||
// Any SendAndRead unblocks trying to receive more messages, which
|
||||
// means rem() gets called.
|
||||
//
|
||||
// rem() should be unblocking receiveLoop if it is blocked.
|
||||
//
|
||||
// receiveLoop should then exit gracefully.
|
||||
close(c.done)
|
||||
|
||||
// Wait for receiveLoop to stop.
|
||||
c.wg.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) isClosed() bool {
|
||||
return atomic.LoadUint32(&c.closed) != 0
|
||||
}
|
||||
|
||||
func (c *Client) receiveLoop() {
|
||||
defer c.wg.Done()
|
||||
for {
|
||||
// TODO: Clients can send a "max packet size" option in their
|
||||
// packets, IIRC. Choose a reasonable size and set it.
|
||||
b := make([]byte, MaxMessageSize)
|
||||
n, _, err := c.conn.ReadFrom(b)
|
||||
if err != nil {
|
||||
if !c.isClosed() {
|
||||
c.logger.Printf("error reading from UDP connection: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := dhcpv4.FromBytes(b[:n])
|
||||
if err != nil {
|
||||
// Not a valid DHCP packet; keep listening.
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.OpCode != dhcpv4.OpcodeBootReply {
|
||||
// Not a response message.
|
||||
continue
|
||||
}
|
||||
|
||||
// This is a somewhat non-standard check, by the looks
|
||||
// of RFC 2131. It should work as long as the DHCP
|
||||
// server is spec-compliant for the HWAddr field.
|
||||
if c.ifaceHWAddr != nil && !bytes.Equal(c.ifaceHWAddr, msg.ClientHWAddr) {
|
||||
// Not for us.
|
||||
continue
|
||||
}
|
||||
|
||||
c.pendingMu.Lock()
|
||||
p, ok := c.pending[msg.TransactionID]
|
||||
if ok {
|
||||
select {
|
||||
case <-p.done:
|
||||
close(p.ch)
|
||||
delete(c.pending, msg.TransactionID)
|
||||
|
||||
// This send may block.
|
||||
case p.ch <- msg:
|
||||
}
|
||||
}
|
||||
c.pendingMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOpt is a function that configures the Client.
|
||||
type ClientOpt func(c *Client) error
|
||||
|
||||
// WithTimeout configures the retransmission timeout.
|
||||
//
|
||||
// Default is 5 seconds.
|
||||
func WithTimeout(d time.Duration) ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
c.timeout = d
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger set the logger (see interface Logger).
|
||||
func WithLogger(newLogger Logger) ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
c.logger = newLogger
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithUnicast forces client to send messages as unicast frames.
|
||||
// By default client sends messages as broadcast frames even if server address is defined.
|
||||
//
|
||||
// srcAddr is both:
|
||||
// * The source address of outgoing frames.
|
||||
// * The address to be listened for incoming frames.
|
||||
func WithUnicast(srcAddr *net.UDPAddr) ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
if srcAddr == nil {
|
||||
srcAddr = &net.UDPAddr{Port: ServerPort}
|
||||
}
|
||||
c.conn, err = net.ListenUDP("udp4", srcAddr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to start listening UDP port: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithHWAddr tells to the Client to receive messages destinated to selected
|
||||
// hardware address
|
||||
func WithHWAddr(hwAddr net.HardwareAddr) ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
c.ifaceHWAddr = hwAddr
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithRetry configures the number of retransmissions to attempt.
|
||||
//
|
||||
// Default is 3.
|
||||
func WithRetry(r int) ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
c.retry = r
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithServerAddr configures the address to send messages to.
|
||||
func WithServerAddr(n *net.UDPAddr) ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
c.serverAddr = n
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Matcher matches DHCP packets.
|
||||
type Matcher func(*dhcpv4.DHCPv4) bool
|
||||
|
||||
// IsMessageType returns a matcher that checks for the message type.
|
||||
//
|
||||
// If t is MessageTypeNone, all packets are matched.
|
||||
func IsMessageType(t dhcpv4.MessageType) Matcher {
|
||||
return func(p *dhcpv4.DHCPv4) bool {
|
||||
return p.MessageType() == t || t == dhcpv4.MessageTypeNone
|
||||
}
|
||||
}
|
||||
|
||||
// DiscoverOffer sends a DHCPDiscover message and returns the first valid offer
|
||||
// received.
|
||||
func (c *Client) DiscoverOffer(ctx context.Context, modifiers ...dhcpv4.Modifier) (offer *dhcpv4.DHCPv4, err error) {
|
||||
// RFC 2131, Section 4.4.1, Table 5 details what a DISCOVER packet should
|
||||
// contain.
|
||||
discover, err := dhcpv4.NewDiscovery(c.ifaceHWAddr, dhcpv4.PrependModifiers(modifiers,
|
||||
dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create a discovery request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
offer, err = c.SendAndRead(ctx, c.serverAddr, discover, IsMessageType(dhcpv4.MessageTypeOffer))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("got an error while the discovery request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Request completes the 4-way Discover-Offer-Request-Ack handshake.
|
||||
//
|
||||
// Note that modifiers will be applied *both* to Discover and Request packets.
|
||||
func (c *Client) Request(ctx context.Context, modifiers ...dhcpv4.Modifier) (offer, ack *dhcpv4.DHCPv4, err error) {
|
||||
offer, err = c.DiscoverOffer(ctx, modifiers...)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to receive an offer: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(chrisko): should this be unicast to the server?
|
||||
request, err := dhcpv4.NewRequestFromOffer(offer, dhcpv4.PrependModifiers(modifiers,
|
||||
dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create a request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
ack, err = c.SendAndRead(ctx, c.serverAddr, request, nil)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("got an error while processing the request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ErrTransactionIDInUse is returned if there were an attempt to send a message
|
||||
// with the same TransactionID as we are already waiting an answer for.
|
||||
type ErrTransactionIDInUse struct {
|
||||
// TransactionID is the transaction ID of the message which the error is related to.
|
||||
TransactionID dhcpv4.TransactionID
|
||||
}
|
||||
|
||||
// Error is just the method to comply interface "error".
|
||||
func (err *ErrTransactionIDInUse) Error() string {
|
||||
return fmt.Sprintf("transaction ID %s already in use", err.TransactionID)
|
||||
}
|
||||
|
||||
// send sends p to destination and returns a response channel.
|
||||
//
|
||||
// Responses will be matched by transaction ID and ClientHWAddr.
|
||||
//
|
||||
// The returned lambda function must be called after all desired responses have
|
||||
// been received in order to return the Transaction ID to the usable pool.
|
||||
func (c *Client) send(dest *net.UDPAddr, msg *dhcpv4.DHCPv4) (resp <-chan *dhcpv4.DHCPv4, cancel func(), err error) {
|
||||
c.pendingMu.Lock()
|
||||
if _, ok := c.pending[msg.TransactionID]; ok {
|
||||
c.pendingMu.Unlock()
|
||||
return nil, nil, &ErrTransactionIDInUse{msg.TransactionID}
|
||||
}
|
||||
|
||||
ch := make(chan *dhcpv4.DHCPv4, c.bufferCap)
|
||||
done := make(chan struct{})
|
||||
c.pending[msg.TransactionID] = &pendingCh{done: done, ch: ch}
|
||||
c.pendingMu.Unlock()
|
||||
|
||||
cancel = func() {
|
||||
// Why can't we just close ch here?
|
||||
//
|
||||
// Because receiveLoop may potentially be blocked trying to
|
||||
// send on ch. We gotta unblock it first, and then we can take
|
||||
// the lock and remove the XID from the pending transaction
|
||||
// map.
|
||||
close(done)
|
||||
|
||||
c.pendingMu.Lock()
|
||||
if p, ok := c.pending[msg.TransactionID]; ok {
|
||||
close(p.ch)
|
||||
delete(c.pending, msg.TransactionID)
|
||||
}
|
||||
c.pendingMu.Unlock()
|
||||
}
|
||||
|
||||
if _, err = c.conn.WriteTo(msg.ToBytes(), dest); err != nil {
|
||||
cancel()
|
||||
return nil, nil, fmt.Errorf("error writing packet to connection: %w", err)
|
||||
}
|
||||
return ch, cancel, nil
|
||||
}
|
||||
|
||||
// This error should never be visible to users.
|
||||
// It is used only to increase the timeout in retryFn.
|
||||
var errDeadlineExceeded = errors.New("INTERNAL ERROR: deadline exceeded")
|
||||
|
||||
// SendAndRead sends a packet p to a destination dest and waits for the first
|
||||
// response matching `match` as well as its Transaction ID and ClientHWAddr.
|
||||
//
|
||||
// If match is nil, the first packet matching the Transaction ID and
|
||||
// ClientHWAddr is returned.
|
||||
func (c *Client) SendAndRead(ctx context.Context, dest *net.UDPAddr, p *dhcpv4.DHCPv4, match Matcher) (*dhcpv4.DHCPv4, error) {
|
||||
var response *dhcpv4.DHCPv4
|
||||
err := c.retryFn(func(timeout time.Duration) error {
|
||||
ch, rem, err := c.send(dest, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.logger.PrintMessage("sent message", p)
|
||||
defer rem()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.done:
|
||||
return ErrNoResponse
|
||||
|
||||
case <-time.After(timeout):
|
||||
return errDeadlineExceeded
|
||||
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
|
||||
case packet := <-ch:
|
||||
if match == nil || match(packet) {
|
||||
c.logger.PrintMessage("received message", packet)
|
||||
response = packet
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if err == errDeadlineExceeded {
|
||||
return nil, ErrNoResponse
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) retryFn(fn func(timeout time.Duration) error) error {
|
||||
timeout := c.timeout
|
||||
|
||||
// Each retry takes the amount of timeout at worst.
|
||||
for i := 0; i < c.retry || c.retry < 0; i++ { // TODO: why is this called "retry" if this is "tries" ("retries"+1)?
|
||||
switch err := fn(timeout); err {
|
||||
case nil:
|
||||
// Got it!
|
||||
return nil
|
||||
|
||||
case errDeadlineExceeded:
|
||||
// Double timeout, then retry.
|
||||
timeout *= 2
|
||||
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return errDeadlineExceeded
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
// Copyright 2018 the u-root Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
// github.com/hugelgupf/socketpair is Linux-only
|
||||
// +build go1.12
|
||||
|
||||
package nclient4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/hugelgupf/socketpair"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
aghtest.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
mu sync.Mutex
|
||||
received []*dhcpv4.DHCPv4
|
||||
|
||||
// Each received packet can have more than one response (in theory,
|
||||
// from different servers sending different Advertise, for example).
|
||||
responses [][]*dhcpv4.DHCPv4
|
||||
}
|
||||
|
||||
func (h *handler) handle(conn net.PacketConn, peer net.Addr, m *dhcpv4.DHCPv4) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
h.received = append(h.received, m)
|
||||
|
||||
if len(h.responses) > 0 {
|
||||
for _, resp := range h.responses[0] {
|
||||
_, _ = conn.WriteTo(resp.ToBytes(), peer)
|
||||
}
|
||||
h.responses = h.responses[1:]
|
||||
}
|
||||
}
|
||||
|
||||
func serveAndClient(ctx context.Context, responses [][]*dhcpv4.DHCPv4, opts ...ClientOpt) (*Client, net.PacketConn) {
|
||||
// Fake PacketConn connection.
|
||||
clientRawConn, serverRawConn, err := socketpair.PacketSocketPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
clientConn := NewBroadcastUDPConn(clientRawConn, &net.UDPAddr{Port: ClientPort})
|
||||
serverConn := NewBroadcastUDPConn(serverRawConn, &net.UDPAddr{Port: ServerPort})
|
||||
|
||||
o := []ClientOpt{WithRetry(1), WithTimeout(2 * time.Second)}
|
||||
o = append(o, opts...)
|
||||
mc, err := NewWithConn(clientConn, net.HardwareAddr{0xa, 0xb, 0xc, 0xd, 0xe, 0xf}, o...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
h := &handler{responses: responses}
|
||||
s, err := server4.NewServer("", nil, h.handle, server4.WithConn(serverConn))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go func() {
|
||||
_ = s.Serve()
|
||||
}()
|
||||
|
||||
return mc, serverConn
|
||||
}
|
||||
|
||||
func ComparePacket(got, want *dhcpv4.DHCPv4) error {
|
||||
if got == nil && got == want {
|
||||
return nil
|
||||
}
|
||||
if (want == nil || got == nil) && (got != want) {
|
||||
return fmt.Errorf("packet got %v, want %v", got, want)
|
||||
}
|
||||
if !bytes.Equal(got.ToBytes(), want.ToBytes()) {
|
||||
return fmt.Errorf("packet got %v, want %v", got, want)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pktsExpected(got, want []*dhcpv4.DHCPv4) error {
|
||||
if len(got) != len(want) {
|
||||
return fmt.Errorf("got %d packets, want %d packets", len(got), len(want))
|
||||
}
|
||||
|
||||
for i := range got {
|
||||
if err := ComparePacket(got[i], want[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newPacketWeirdHWAddr(op dhcpv4.OpcodeType, xid dhcpv4.TransactionID) *dhcpv4.DHCPv4 {
|
||||
p, err := dhcpv4.New()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("newpacket: %v", err))
|
||||
}
|
||||
p.OpCode = op
|
||||
p.TransactionID = xid
|
||||
p.ClientHWAddr = net.HardwareAddr{0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 1, 2, 3, 4, 5, 6}
|
||||
return p
|
||||
}
|
||||
|
||||
func newPacket(op dhcpv4.OpcodeType, xid dhcpv4.TransactionID) *dhcpv4.DHCPv4 {
|
||||
p, err := dhcpv4.New()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("newpacket: %v", err))
|
||||
}
|
||||
p.OpCode = op
|
||||
p.TransactionID = xid
|
||||
p.ClientHWAddr = net.HardwareAddr{0xa, 0xb, 0xc, 0xd, 0xe, 0xf}
|
||||
return p
|
||||
}
|
||||
|
||||
func withBufferCap(n int) ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
c.bufferCap = n
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendAndRead(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
desc string
|
||||
send *dhcpv4.DHCPv4
|
||||
server []*dhcpv4.DHCPv4
|
||||
|
||||
// If want is nil, we assume server[0] contains what is wanted.
|
||||
want *dhcpv4.DHCPv4
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
desc: "two response packets",
|
||||
send: newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
server: []*dhcpv4.DHCPv4{
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
},
|
||||
want: newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
},
|
||||
{
|
||||
desc: "one response packet",
|
||||
send: newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
server: []*dhcpv4.DHCPv4{
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
},
|
||||
want: newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
},
|
||||
{
|
||||
desc: "one response packet, one invalid XID, one invalid opcode, one invalid hwaddr",
|
||||
send: newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
server: []*dhcpv4.DHCPv4{
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x77, 0x33, 0x33, 0x33}),
|
||||
newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
newPacketWeirdHWAddr(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
},
|
||||
want: newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
},
|
||||
{
|
||||
desc: "discard wrong XID",
|
||||
send: newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
server: []*dhcpv4.DHCPv4{
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0, 0, 0, 0}),
|
||||
},
|
||||
want: nil, // Explicitly empty.
|
||||
wantErr: ErrNoResponse,
|
||||
},
|
||||
{
|
||||
desc: "no response, timeout",
|
||||
send: newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
wantErr: ErrNoResponse,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
// Both server and client only get 2 seconds.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mc, _ := serveAndClient(ctx, [][]*dhcpv4.DHCPv4{tt.server},
|
||||
// Use an unbuffered channel to make sure we
|
||||
// have no deadlocks.
|
||||
withBufferCap(0))
|
||||
defer mc.Close()
|
||||
|
||||
rcvd, err := mc.SendAndRead(context.Background(), DefaultServers, tt.send, nil)
|
||||
if err != tt.wantErr {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err = ComparePacket(rcvd, tt.want); err != nil {
|
||||
t.Errorf("got unexpected packets: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParallelSendAndRead(t *testing.T) {
|
||||
pkt := newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33})
|
||||
|
||||
// Both the server and client only get 2 seconds.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mc, _ := serveAndClient(ctx, [][]*dhcpv4.DHCPv4{},
|
||||
WithTimeout(10*time.Second),
|
||||
// Use an unbuffered channel to make sure nothing blocks.
|
||||
withBufferCap(0))
|
||||
defer mc.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if _, err := mc.SendAndRead(context.Background(), DefaultServers, pkt, nil); err != ErrNoResponse {
|
||||
t.Errorf("SendAndRead(%v) = %v, want %v", pkt, err, ErrNoResponse)
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
time.Sleep(4 * time.Second)
|
||||
|
||||
if err := mc.Close(); err != nil {
|
||||
t.Errorf("closing failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestReuseXID(t *testing.T) {
|
||||
pkt := newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33})
|
||||
|
||||
// Both the server and client only get 2 seconds.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mc, _ := serveAndClient(ctx, [][]*dhcpv4.DHCPv4{})
|
||||
defer mc.Close()
|
||||
|
||||
if _, err := mc.SendAndRead(context.Background(), DefaultServers, pkt, nil); err != ErrNoResponse {
|
||||
t.Errorf("SendAndRead(%v) = %v, want %v", pkt, err, ErrNoResponse)
|
||||
}
|
||||
|
||||
if _, err := mc.SendAndRead(context.Background(), DefaultServers, pkt, nil); err != ErrNoResponse {
|
||||
t.Errorf("SendAndRead(%v) = %v, want %v", pkt, err, ErrNoResponse)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleSendAndReadDiscardGarbage(t *testing.T) {
|
||||
pkt := newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33})
|
||||
|
||||
responses := newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33})
|
||||
|
||||
// Both the server and client only get 2 seconds.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mc, udpConn := serveAndClient(ctx, [][]*dhcpv4.DHCPv4{{responses}})
|
||||
defer mc.Close()
|
||||
|
||||
// Too short for valid DHCPv4 packet.
|
||||
_, _ = udpConn.WriteTo([]byte{0x01}, nil)
|
||||
_, _ = udpConn.WriteTo([]byte{0x01, 0x2}, nil)
|
||||
|
||||
rcvd, err := mc.SendAndRead(ctx, DefaultServers, pkt, nil)
|
||||
if err != nil {
|
||||
t.Errorf("SendAndRead(%v) = %v, want nil", pkt, err)
|
||||
}
|
||||
|
||||
if err = ComparePacket(rcvd, responses); err != nil {
|
||||
t.Errorf("got unexpected packets: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleSendAndRead(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
desc string
|
||||
send []*dhcpv4.DHCPv4
|
||||
server [][]*dhcpv4.DHCPv4
|
||||
wantErr []error
|
||||
}{
|
||||
{
|
||||
desc: "two requests, two responses",
|
||||
send: []*dhcpv4.DHCPv4{
|
||||
newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x44, 0x44, 0x44, 0x44}),
|
||||
},
|
||||
server: [][]*dhcpv4.DHCPv4{
|
||||
{ // Response for first packet.
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||
},
|
||||
{ // Response for second packet.
|
||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x44, 0x44, 0x44, 0x44}),
|
||||
},
|
||||
},
|
||||
wantErr: []error{
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
} {
|
||||
// Both server and client only get 2 seconds.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mc, _ := serveAndClient(ctx, tt.server)
|
||||
defer mc.Close()
|
||||
|
||||
for i, send := range tt.send {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
rcvd, err := mc.SendAndRead(ctx, DefaultServers, send, nil)
|
||||
|
||||
if wantErr := tt.wantErr[i]; err != wantErr {
|
||||
t.Errorf("SendAndReadOne(%v): got %v, want %v", send, err, wantErr)
|
||||
}
|
||||
if err = pktsExpected([]*dhcpv4.DHCPv4{rcvd}, tt.server[i]); err != nil {
|
||||
t.Errorf("got unexpected packets: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
// Copyright 2018 the u-root Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
// +build go1.12
|
||||
|
||||
package nclient4
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/mdlayher/ethernet"
|
||||
"github.com/mdlayher/raw"
|
||||
"github.com/u-root/u-root/pkg/uio"
|
||||
)
|
||||
|
||||
// BroadcastMac is the broadcast MAC address.
|
||||
//
|
||||
// Any UDP packet sent to this address is broadcast on the subnet.
|
||||
var BroadcastMac = net.HardwareAddr([]byte{255, 255, 255, 255, 255, 255})
|
||||
|
||||
// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr".
|
||||
var ErrUDPAddrIsRequired = errors.New("must supply UDPAddr")
|
||||
|
||||
// NewRawUDPConn returns a UDP connection bound to the interface and port
|
||||
// given based on a raw packet socket. All packets are broadcasted.
|
||||
//
|
||||
// The interface can be completely unconfigured.
|
||||
func NewRawUDPConn(iface string, port int) (net.PacketConn, error) {
|
||||
ifc, err := net.InterfaceByName(iface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawConn, err := raw.ListenPacket(ifc, uint16(ethernet.EtherTypeIPv4), &raw.Config{LinuxSockDGRAM: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewBroadcastUDPConn(rawConn, &net.UDPAddr{Port: port}), nil
|
||||
}
|
||||
|
||||
// BroadcastRawUDPConn uses a raw socket to send UDP packets to the broadcast
|
||||
// MAC address.
|
||||
type BroadcastRawUDPConn struct {
|
||||
// PacketConn is a raw DGRAM socket.
|
||||
net.PacketConn
|
||||
|
||||
// boundAddr is the address this RawUDPConn is "bound" to.
|
||||
//
|
||||
// Calls to ReadFrom will only return packets destined to this address.
|
||||
boundAddr *net.UDPAddr
|
||||
}
|
||||
|
||||
// NewBroadcastUDPConn returns a PacketConn that marshals and unmarshals UDP
|
||||
// packets, sending them to the broadcast MAC at on rawPacketConn.
|
||||
//
|
||||
// Calls to ReadFrom will only return packets destined to boundAddr.
|
||||
func NewBroadcastUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr) net.PacketConn {
|
||||
return &BroadcastRawUDPConn{
|
||||
PacketConn: rawPacketConn,
|
||||
boundAddr: boundAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func udpMatch(addr, bound *net.UDPAddr) bool {
|
||||
if bound == nil {
|
||||
return true
|
||||
}
|
||||
if bound.IP != nil && !bound.IP.Equal(addr.IP) {
|
||||
return false
|
||||
}
|
||||
return bound.Port == addr.Port
|
||||
}
|
||||
|
||||
// ReadFrom implements net.PacketConn.ReadFrom.
|
||||
//
|
||||
// ReadFrom reads raw IP packets and will try to match them against
|
||||
// upc.boundAddr. Any matching packets are returned via the given buffer.
|
||||
func (upc *BroadcastRawUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
ipHdrMaxLen := IPv4MaximumHeaderSize
|
||||
udpHdrLen := UDPMinimumSize
|
||||
|
||||
for {
|
||||
pkt := make([]byte, ipHdrMaxLen+udpHdrLen+len(b))
|
||||
n, _, err := upc.PacketConn.ReadFrom(pkt)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, nil, io.EOF
|
||||
}
|
||||
pkt = pkt[:n]
|
||||
buf := uio.NewBigEndianBuffer(pkt)
|
||||
|
||||
// To read the header length, access data directly.
|
||||
ipHdr := IPv4(buf.Data())
|
||||
ipHdr = IPv4(buf.Consume(int(ipHdr.HeaderLength())))
|
||||
|
||||
if ipHdr.TransportProtocol() != UDPProtocolNumber {
|
||||
continue
|
||||
}
|
||||
udpHdr := UDP(buf.Consume(udpHdrLen))
|
||||
|
||||
addr := &net.UDPAddr{
|
||||
IP: ipHdr.DestinationAddress(),
|
||||
Port: int(udpHdr.DestinationPort()),
|
||||
}
|
||||
if !udpMatch(addr, upc.boundAddr) {
|
||||
continue
|
||||
}
|
||||
srcAddr := &net.UDPAddr{
|
||||
IP: ipHdr.SourceAddress(),
|
||||
Port: int(udpHdr.SourcePort()),
|
||||
}
|
||||
// Extra padding after end of IP packet should be ignored,
|
||||
// if not dhcp option parsing will fail.
|
||||
dhcpLen := int(ipHdr.PayloadLength()) - udpHdrLen
|
||||
return copy(b, buf.Consume(dhcpLen)), srcAddr, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo implements net.PacketConn.WriteTo and broadcasts all packets at the
|
||||
// raw socket level.
|
||||
//
|
||||
// WriteTo wraps the given packet in the appropriate UDP and IP header before
|
||||
// sending it on the packet conn.
|
||||
func (upc *BroadcastRawUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
udpAddr, ok := addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
return 0, ErrUDPAddrIsRequired
|
||||
}
|
||||
|
||||
// Using the boundAddr is not quite right here, but it works.
|
||||
packet := udp4pkt(b, udpAddr, upc.boundAddr)
|
||||
|
||||
// Broadcasting is not always right, but hell, what the ARP do I know.
|
||||
return upc.PacketConn.WriteTo(packet, &raw.Addr{HardwareAddr: BroadcastMac})
|
||||
}
|
||||
@@ -1,377 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file contains code taken from gVisor.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
// +build go1.12
|
||||
|
||||
package nclient4
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/u-root/u-root/pkg/uio"
|
||||
)
|
||||
|
||||
const (
|
||||
versIHL = 0
|
||||
tos = 1
|
||||
totalLen = 2
|
||||
id = 4
|
||||
flagsFO = 6
|
||||
ttl = 8
|
||||
protocol = 9
|
||||
checksum = 10
|
||||
srcAddr = 12
|
||||
dstAddr = 16
|
||||
)
|
||||
|
||||
// TransportProtocolNumber is the number of a transport protocol.
|
||||
type TransportProtocolNumber uint32
|
||||
|
||||
// IPv4Fields contains the fields of an IPv4 packet. It is used to describe the
|
||||
// fields of a packet that needs to be encoded.
|
||||
type IPv4Fields struct {
|
||||
// IHL is the "internet header length" field of an IPv4 packet.
|
||||
IHL uint8
|
||||
|
||||
// TOS is the "type of service" field of an IPv4 packet.
|
||||
TOS uint8
|
||||
|
||||
// TotalLength is the "total length" field of an IPv4 packet.
|
||||
TotalLength uint16
|
||||
|
||||
// ID is the "identification" field of an IPv4 packet.
|
||||
ID uint16
|
||||
|
||||
// Flags is the "flags" field of an IPv4 packet.
|
||||
Flags uint8
|
||||
|
||||
// FragmentOffset is the "fragment offset" field of an IPv4 packet.
|
||||
FragmentOffset uint16
|
||||
|
||||
// TTL is the "time to live" field of an IPv4 packet.
|
||||
TTL uint8
|
||||
|
||||
// Protocol is the "protocol" field of an IPv4 packet.
|
||||
Protocol uint8
|
||||
|
||||
// Checksum is the "checksum" field of an IPv4 packet.
|
||||
Checksum uint16
|
||||
|
||||
// SrcAddr is the "source ip address" of an IPv4 packet.
|
||||
SrcAddr net.IP
|
||||
|
||||
// DstAddr is the "destination ip address" of an IPv4 packet.
|
||||
DstAddr net.IP
|
||||
}
|
||||
|
||||
// IPv4 represents an ipv4 header stored in a byte array.
|
||||
// Most of the methods of IPv4 access to the underlying slice without
|
||||
// checking the boundaries and could panic because of 'index out of range'.
|
||||
// Always call IsValid() to validate an instance of IPv4 before using other methods.
|
||||
type IPv4 []byte
|
||||
|
||||
const (
|
||||
// IPv4MinimumSize is the minimum size of a valid IPv4 packet.
|
||||
IPv4MinimumSize = 20
|
||||
|
||||
// IPv4MaximumHeaderSize is the maximum size of an IPv4 header. Given
|
||||
// that there are only 4 bits to represents the header length in 32-bit
|
||||
// units, the header cannot exceed 15*4 = 60 bytes.
|
||||
IPv4MaximumHeaderSize = 60
|
||||
|
||||
// IPv4AddressSize is the size, in bytes, of an IPv4 address.
|
||||
IPv4AddressSize = 4
|
||||
|
||||
// IPv4Version is the version of the ipv4 protocol.
|
||||
IPv4Version = 4
|
||||
)
|
||||
|
||||
var (
|
||||
// IPv4Broadcast is the broadcast address of the IPv4 protocol.
|
||||
IPv4Broadcast = net.IP{0xff, 0xff, 0xff, 0xff}
|
||||
|
||||
// IPv4Any is the non-routable IPv4 "any" meta address.
|
||||
IPv4Any = net.IP{0, 0, 0, 0}
|
||||
)
|
||||
|
||||
// Flags that may be set in an IPv4 packet.
|
||||
const (
|
||||
IPv4FlagMoreFragments = 1 << iota
|
||||
IPv4FlagDontFragment
|
||||
)
|
||||
|
||||
// HeaderLength returns the value of the "header length" field of the ipv4
|
||||
// header.
|
||||
func (b IPv4) HeaderLength() uint8 {
|
||||
return (b[versIHL] & 0xf) * 4
|
||||
}
|
||||
|
||||
// Protocol returns the value of the protocol field of the ipv4 header.
|
||||
func (b IPv4) Protocol() uint8 {
|
||||
return b[protocol]
|
||||
}
|
||||
|
||||
// SourceAddress returns the "source address" field of the ipv4 header.
|
||||
func (b IPv4) SourceAddress() net.IP {
|
||||
return net.IP(b[srcAddr : srcAddr+IPv4AddressSize])
|
||||
}
|
||||
|
||||
// DestinationAddress returns the "destination address" field of the ipv4
|
||||
// header.
|
||||
func (b IPv4) DestinationAddress() net.IP {
|
||||
return net.IP(b[dstAddr : dstAddr+IPv4AddressSize])
|
||||
}
|
||||
|
||||
// TransportProtocol implements Network.TransportProtocol.
|
||||
func (b IPv4) TransportProtocol() TransportProtocolNumber {
|
||||
return TransportProtocolNumber(b.Protocol())
|
||||
}
|
||||
|
||||
// Payload implements Network.Payload.
|
||||
func (b IPv4) Payload() []byte {
|
||||
return b[b.HeaderLength():][:b.PayloadLength()]
|
||||
}
|
||||
|
||||
// PayloadLength returns the length of the payload portion of the ipv4 packet.
|
||||
func (b IPv4) PayloadLength() uint16 {
|
||||
return b.TotalLength() - uint16(b.HeaderLength())
|
||||
}
|
||||
|
||||
// TotalLength returns the "total length" field of the ipv4 header.
|
||||
func (b IPv4) TotalLength() uint16 {
|
||||
return binary.BigEndian.Uint16(b[totalLen:])
|
||||
}
|
||||
|
||||
// SetTotalLength sets the "total length" field of the ipv4 header.
|
||||
func (b IPv4) SetTotalLength(totalLength uint16) {
|
||||
binary.BigEndian.PutUint16(b[totalLen:], totalLength)
|
||||
}
|
||||
|
||||
// SetChecksum sets the checksum field of the ipv4 header.
|
||||
func (b IPv4) SetChecksum(v uint16) {
|
||||
binary.BigEndian.PutUint16(b[checksum:], v)
|
||||
}
|
||||
|
||||
// SetFlagsFragmentOffset sets the "flags" and "fragment offset" fields of the
|
||||
// ipv4 header.
|
||||
func (b IPv4) SetFlagsFragmentOffset(flags uint8, offset uint16) {
|
||||
v := (uint16(flags) << 13) | (offset >> 3)
|
||||
binary.BigEndian.PutUint16(b[flagsFO:], v)
|
||||
}
|
||||
|
||||
// SetSourceAddress sets the "source address" field of the ipv4 header.
|
||||
func (b IPv4) SetSourceAddress(addr net.IP) {
|
||||
copy(b[srcAddr:srcAddr+IPv4AddressSize], addr.To4())
|
||||
}
|
||||
|
||||
// SetDestinationAddress sets the "destination address" field of the ipv4
|
||||
// header.
|
||||
func (b IPv4) SetDestinationAddress(addr net.IP) {
|
||||
copy(b[dstAddr:dstAddr+IPv4AddressSize], addr.To4())
|
||||
}
|
||||
|
||||
// CalculateChecksum calculates the checksum of the ipv4 header.
|
||||
func (b IPv4) CalculateChecksum() uint16 {
|
||||
return Checksum(b[:b.HeaderLength()], 0)
|
||||
}
|
||||
|
||||
// Encode encodes all the fields of the ipv4 header.
|
||||
func (b IPv4) Encode(i *IPv4Fields) {
|
||||
b[versIHL] = (4 << 4) | ((i.IHL / 4) & 0xf)
|
||||
b[tos] = i.TOS
|
||||
b.SetTotalLength(i.TotalLength)
|
||||
binary.BigEndian.PutUint16(b[id:], i.ID)
|
||||
b.SetFlagsFragmentOffset(i.Flags, i.FragmentOffset)
|
||||
b[ttl] = i.TTL
|
||||
b[protocol] = i.Protocol
|
||||
b.SetChecksum(i.Checksum)
|
||||
copy(b[srcAddr:srcAddr+IPv4AddressSize], i.SrcAddr)
|
||||
copy(b[dstAddr:dstAddr+IPv4AddressSize], i.DstAddr)
|
||||
}
|
||||
|
||||
const (
|
||||
udpSrcPort = 0
|
||||
udpDstPort = 2
|
||||
udpLength = 4
|
||||
udpChecksum = 6
|
||||
)
|
||||
|
||||
// UDPFields contains the fields of a UDP packet. It is used to describe the
|
||||
// fields of a packet that needs to be encoded.
|
||||
type UDPFields struct {
|
||||
// SrcPort is the "source port" field of a UDP packet.
|
||||
SrcPort uint16
|
||||
|
||||
// DstPort is the "destination port" field of a UDP packet.
|
||||
DstPort uint16
|
||||
|
||||
// Length is the "length" field of a UDP packet.
|
||||
Length uint16
|
||||
|
||||
// Checksum is the "checksum" field of a UDP packet.
|
||||
Checksum uint16
|
||||
}
|
||||
|
||||
// UDP represents a UDP header stored in a byte array.
|
||||
type UDP []byte
|
||||
|
||||
const (
|
||||
// UDPMinimumSize is the minimum size of a valid UDP packet.
|
||||
UDPMinimumSize = 8
|
||||
|
||||
// UDPProtocolNumber is UDP's transport protocol number.
|
||||
UDPProtocolNumber TransportProtocolNumber = 17
|
||||
)
|
||||
|
||||
// SourcePort returns the "source port" field of the udp header.
|
||||
func (b UDP) SourcePort() uint16 {
|
||||
return binary.BigEndian.Uint16(b[udpSrcPort:])
|
||||
}
|
||||
|
||||
// DestinationPort returns the "destination port" field of the udp header.
|
||||
func (b UDP) DestinationPort() uint16 {
|
||||
return binary.BigEndian.Uint16(b[udpDstPort:])
|
||||
}
|
||||
|
||||
// Length returns the "length" field of the udp header.
|
||||
func (b UDP) Length() uint16 {
|
||||
return binary.BigEndian.Uint16(b[udpLength:])
|
||||
}
|
||||
|
||||
// SetSourcePort sets the "source port" field of the udp header.
|
||||
func (b UDP) SetSourcePort(port uint16) {
|
||||
binary.BigEndian.PutUint16(b[udpSrcPort:], port)
|
||||
}
|
||||
|
||||
// SetDestinationPort sets the "destination port" field of the udp header.
|
||||
func (b UDP) SetDestinationPort(port uint16) {
|
||||
binary.BigEndian.PutUint16(b[udpDstPort:], port)
|
||||
}
|
||||
|
||||
// SetChecksum sets the "checksum" field of the udp header.
|
||||
func (b UDP) SetChecksum(checksum uint16) {
|
||||
binary.BigEndian.PutUint16(b[udpChecksum:], checksum)
|
||||
}
|
||||
|
||||
// Payload returns the data contained in the UDP datagram.
|
||||
func (b UDP) Payload() []byte {
|
||||
return b[UDPMinimumSize:]
|
||||
}
|
||||
|
||||
// Checksum returns the "checksum" field of the udp header.
|
||||
func (b UDP) Checksum() uint16 {
|
||||
return binary.BigEndian.Uint16(b[udpChecksum:])
|
||||
}
|
||||
|
||||
// CalculateChecksum calculates the checksum of the udp packet, given the total
|
||||
// length of the packet and the checksum of the network-layer pseudo-header
|
||||
// (excluding the total length) and the checksum of the payload.
|
||||
func (b UDP) CalculateChecksum(partialChecksum, totalLen uint16) uint16 {
|
||||
// Add the length portion of the checksum to the pseudo-checksum.
|
||||
tmp := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(tmp, totalLen)
|
||||
checksum := Checksum(tmp, partialChecksum)
|
||||
|
||||
// Calculate the rest of the checksum.
|
||||
return Checksum(b[:UDPMinimumSize], checksum)
|
||||
}
|
||||
|
||||
// Encode encodes all the fields of the udp header.
|
||||
func (b UDP) Encode(u *UDPFields) {
|
||||
binary.BigEndian.PutUint16(b[udpSrcPort:], u.SrcPort)
|
||||
binary.BigEndian.PutUint16(b[udpDstPort:], u.DstPort)
|
||||
binary.BigEndian.PutUint16(b[udpLength:], u.Length)
|
||||
binary.BigEndian.PutUint16(b[udpChecksum:], u.Checksum)
|
||||
}
|
||||
|
||||
func calculateChecksum(buf []byte, initial uint32) uint16 {
|
||||
v := initial
|
||||
|
||||
l := len(buf)
|
||||
if l&1 != 0 {
|
||||
l--
|
||||
v += uint32(buf[l]) << 8
|
||||
}
|
||||
|
||||
for i := 0; i < l; i += 2 {
|
||||
v += (uint32(buf[i]) << 8) + uint32(buf[i+1])
|
||||
}
|
||||
|
||||
return ChecksumCombine(uint16(v), uint16(v>>16))
|
||||
}
|
||||
|
||||
// Checksum calculates the checksum (as defined in RFC 1071) of the bytes in the
|
||||
// given byte array.
|
||||
//
|
||||
// The initial checksum must have been computed on an even number of bytes.
|
||||
func Checksum(buf []byte, initial uint16) uint16 {
|
||||
return calculateChecksum(buf, uint32(initial))
|
||||
}
|
||||
|
||||
// ChecksumCombine combines the two uint16 to form their checksum. This is done
|
||||
// by adding them and the carry.
|
||||
//
|
||||
// Note that checksum a must have been computed on an even number of bytes.
|
||||
func ChecksumCombine(a, b uint16) uint16 {
|
||||
v := uint32(a) + uint32(b)
|
||||
return uint16(v + v>>16)
|
||||
}
|
||||
|
||||
// PseudoHeaderChecksum calculates the pseudo-header checksum for the
|
||||
// given destination protocol and network address, ignoring the length
|
||||
// field. Pseudo-headers are needed by transport layers when calculating
|
||||
// their own checksum.
|
||||
func PseudoHeaderChecksum(protocol TransportProtocolNumber, srcAddr, dstAddr net.IP) uint16 {
|
||||
xsum := Checksum([]byte(srcAddr), 0)
|
||||
xsum = Checksum([]byte(dstAddr), xsum)
|
||||
return Checksum([]byte{0, uint8(protocol)}, xsum)
|
||||
}
|
||||
|
||||
func udp4pkt(packet []byte, dest, src *net.UDPAddr) []byte {
|
||||
ipLen := IPv4MinimumSize
|
||||
udpLen := UDPMinimumSize
|
||||
|
||||
h := make([]byte, 0, ipLen+udpLen+len(packet))
|
||||
hdr := uio.NewBigEndianBuffer(h)
|
||||
|
||||
ipv4fields := &IPv4Fields{
|
||||
IHL: IPv4MinimumSize,
|
||||
TotalLength: uint16(ipLen + udpLen + len(packet)),
|
||||
TTL: 64, // Per RFC 1700's recommendation for IP time to live
|
||||
Protocol: uint8(UDPProtocolNumber),
|
||||
SrcAddr: src.IP.To4(),
|
||||
DstAddr: dest.IP.To4(),
|
||||
}
|
||||
ipv4hdr := IPv4(hdr.WriteN(ipLen))
|
||||
ipv4hdr.Encode(ipv4fields)
|
||||
ipv4hdr.SetChecksum(^ipv4hdr.CalculateChecksum())
|
||||
|
||||
udphdr := UDP(hdr.WriteN(udpLen))
|
||||
udphdr.Encode(&UDPFields{
|
||||
SrcPort: uint16(src.Port),
|
||||
DstPort: uint16(dest.Port),
|
||||
Length: uint16(udpLen + len(packet)),
|
||||
})
|
||||
|
||||
xsum := Checksum(packet, PseudoHeaderChecksum(
|
||||
ipv4hdr.TransportProtocol(), ipv4fields.SrcAddr, ipv4fields.DstAddr))
|
||||
udphdr.SetChecksum(^udphdr.CalculateChecksum(xsum, udphdr.Length()))
|
||||
|
||||
hdr.WriteBytes(packet)
|
||||
return hdr.Data()
|
||||
}
|
||||
@@ -64,9 +64,12 @@ type V4ServerConf struct {
|
||||
|
||||
leaseTime time.Duration // the time during which a dynamic lease is considered valid
|
||||
dnsIPAddrs []net.IP // IPv4 addresses to return to DHCP clients as DNS server addresses
|
||||
routerIP net.IP // value for Option Router
|
||||
subnetMask net.IPMask // value for Option SubnetMask
|
||||
options []dhcpOption
|
||||
|
||||
// subnet contains the DHCP server's subnet. The IP is the IP of the
|
||||
// gateway.
|
||||
subnet *net.IPNet
|
||||
|
||||
options []dhcpOption
|
||||
|
||||
// notify is a way to signal to other components that leases have
|
||||
// change. notify must be called outside of locked sections, since the
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/go-ping/ping"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
@@ -29,10 +30,13 @@ type v4Server struct {
|
||||
// leased.
|
||||
leasedOffsets *bitSet
|
||||
|
||||
// leaseHosts is the set of all hostnames of all known DHCP clients.
|
||||
leaseHosts *aghstrings.Set
|
||||
|
||||
// leases contains all dynamic and static leases.
|
||||
leases []*Lease
|
||||
|
||||
// leasesLock protects leases and leasedOffsets.
|
||||
// leasesLock protects leases, leaseHosts, and leasedOffsets.
|
||||
leasesLock sync.Mutex
|
||||
}
|
||||
|
||||
@@ -45,26 +49,99 @@ func (s *v4Server) WriteDiskConfig4(c *V4ServerConf) {
|
||||
func (s *v4Server) WriteDiskConfig6(c *V6ServerConf) {
|
||||
}
|
||||
|
||||
// normalizeHostname normalizes a hostname sent by the client. If err is not
|
||||
// nil, norm is an empty string.
|
||||
func normalizeHostname(hostname string) (norm string, err error) {
|
||||
defer agherr.Annotate("normalizing %q: %w", &err, hostname)
|
||||
|
||||
if hostname == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
norm = strings.ToLower(hostname)
|
||||
parts := strings.FieldsFunc(norm, func(c rune) (ok bool) {
|
||||
return c != '.' && !aghnet.IsValidHostOuterRune(c)
|
||||
})
|
||||
|
||||
if len(parts) == 0 {
|
||||
return "", fmt.Errorf("no valid parts")
|
||||
}
|
||||
|
||||
norm = strings.Join(parts, "-")
|
||||
norm = strings.TrimSuffix(norm, "-")
|
||||
|
||||
return norm, nil
|
||||
}
|
||||
|
||||
// validHostnameForClient accepts the hostname sent by the client and returns
|
||||
// either a normalized version of that hostname or a new hostname generated from
|
||||
// the client's IP address. If this new hostname is different from the provided
|
||||
// previous hostname, additional uniqueness check is performed.
|
||||
//
|
||||
// hostname is always a non-empty valid hostname. If err is not nil, it
|
||||
// describes the issues encountered when normalizing cliHostname.
|
||||
func (s *v4Server) validHostnameForClient(
|
||||
cliHostname string,
|
||||
prevHostname string,
|
||||
ip net.IP,
|
||||
) (hostname string, err error) {
|
||||
hostname, err = normalizeHostname(cliHostname)
|
||||
if err == nil && hostname != "" {
|
||||
err = aghnet.ValidateDomainName(hostname)
|
||||
if err != nil {
|
||||
// Go on and assign a hostname made from the IP below,
|
||||
// returning the error that we've got.
|
||||
hostname = ""
|
||||
} else if hostname != prevHostname && s.leaseHosts.Has(hostname) {
|
||||
// Go on and assign a unique hostname made from the IP
|
||||
// below, returning the error about uniqueness.
|
||||
err = agherr.Error("hostname exists")
|
||||
hostname = ""
|
||||
}
|
||||
}
|
||||
|
||||
if hostname == "" {
|
||||
hostname = aghnet.GenerateHostname(ip)
|
||||
}
|
||||
|
||||
if hostname != cliHostname {
|
||||
log.Info("dhcpv4: normalized hostname %q into %q", cliHostname, hostname)
|
||||
}
|
||||
|
||||
return hostname, err
|
||||
}
|
||||
|
||||
// ResetLeases - reset leases
|
||||
func (s *v4Server) ResetLeases(leases []*Lease) {
|
||||
var err error
|
||||
|
||||
if !s.conf.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
s.leasedOffsets = newBitSet()
|
||||
s.leaseHosts = aghstrings.NewSet()
|
||||
s.leases = nil
|
||||
|
||||
r := s.conf.ipRange
|
||||
for _, l := range leases {
|
||||
if !l.IsStatic() && !r.contains(l.IP) {
|
||||
log.Debug(
|
||||
"dhcpv4: skipping lease %s (%s): not within current ip range",
|
||||
l.IP,
|
||||
l.HWAddr,
|
||||
l.Hostname, err = s.validHostnameForClient(l.Hostname, l.Hostname, l.IP)
|
||||
if err != nil {
|
||||
log.Info(
|
||||
"dhcpv4: warning: previous hostname %q is invalid: %s",
|
||||
l.Hostname,
|
||||
err,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
err := s.addLease(l)
|
||||
err = s.addLease(l)
|
||||
if err != nil {
|
||||
// TODO(a.garipov): Better error handling.
|
||||
log.Error("dhcpv4: adding a lease for %s (%s): %s", l.IP, l.HWAddr, err)
|
||||
// TODO(a.garipov): Wrap and bubble up the error.
|
||||
log.Error(
|
||||
"dhcpv4: reset: re-adding a lease for %s (%s): %s",
|
||||
l.IP,
|
||||
l.HWAddr,
|
||||
err,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -174,17 +251,14 @@ func (s *v4Server) rmLeaseByIndex(i int) {
|
||||
l := s.leases[i]
|
||||
s.leases = append(s.leases[:i], s.leases[i+1:]...)
|
||||
|
||||
n = len(s.leases)
|
||||
if n > 0 {
|
||||
s.leases = s.leases[:n-1]
|
||||
}
|
||||
|
||||
r := s.conf.ipRange
|
||||
offset, ok := r.offset(l.IP)
|
||||
if ok {
|
||||
s.leasedOffsets.set(offset, false)
|
||||
}
|
||||
|
||||
s.leaseHosts.Del(l.Hostname)
|
||||
|
||||
log.Debug("dhcpv4: removed lease %s (%s)", l.IP, l.HWAddr)
|
||||
}
|
||||
|
||||
@@ -196,7 +270,7 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
||||
|
||||
if bytes.Equal(l.HWAddr, lease.HWAddr) {
|
||||
if l.IsStatic() {
|
||||
return fmt.Errorf("static lease already exists")
|
||||
return agherr.Error("static lease already exists")
|
||||
}
|
||||
|
||||
s.rmLeaseByIndex(i)
|
||||
@@ -207,9 +281,9 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
||||
l = s.leases[i]
|
||||
}
|
||||
|
||||
if net.IP.Equal(l.IP, lease.IP) {
|
||||
if l.IP.Equal(lease.IP) {
|
||||
if l.IsStatic() {
|
||||
return fmt.Errorf("static lease already exists")
|
||||
return agherr.Error("static lease already exists")
|
||||
}
|
||||
|
||||
s.rmLeaseByIndex(i)
|
||||
@@ -219,51 +293,31 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *v4Server) addStaticLease(l *Lease) (err error) {
|
||||
subnet := &net.IPNet{
|
||||
IP: s.conf.routerIP,
|
||||
Mask: s.conf.subnetMask,
|
||||
}
|
||||
|
||||
if !subnet.Contains(l.IP) {
|
||||
return fmt.Errorf("subnet %s does not contain the ip %q", subnet, l.IP)
|
||||
}
|
||||
|
||||
s.leases = append(s.leases, l)
|
||||
|
||||
// addLease adds a dynamic or static lease.
|
||||
func (s *v4Server) addLease(l *Lease) (err error) {
|
||||
r := s.conf.ipRange
|
||||
offset, ok := r.offset(l.IP)
|
||||
if ok {
|
||||
s.leasedOffsets.set(offset, true)
|
||||
}
|
||||
offset, inOffset := r.offset(l.IP)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *v4Server) addDynamicLease(l *Lease) (err error) {
|
||||
r := s.conf.ipRange
|
||||
offset, ok := r.offset(l.IP)
|
||||
if !ok {
|
||||
if l.IsStatic() {
|
||||
if sn := s.conf.subnet; !sn.Contains(l.IP) {
|
||||
return fmt.Errorf("subnet %s does not contain the ip %q", sn, l.IP)
|
||||
}
|
||||
} else if !inOffset {
|
||||
return fmt.Errorf("lease %s (%s) out of range, not adding", l.IP, l.HWAddr)
|
||||
}
|
||||
|
||||
s.leases = append(s.leases, l)
|
||||
s.leasedOffsets.set(offset, true)
|
||||
|
||||
if l.Hostname != "" {
|
||||
s.leaseHosts.Add(l.Hostname)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addLease adds a dynamic or static lease.
|
||||
func (s *v4Server) addLease(l *Lease) (err error) {
|
||||
if l.IsStatic() {
|
||||
return s.addStaticLease(l)
|
||||
}
|
||||
|
||||
return s.addDynamicLease(l)
|
||||
}
|
||||
|
||||
// Remove a lease with the same properties
|
||||
func (s *v4Server) rmLease(lease Lease) error {
|
||||
// rmLease removes a lease with the same properties.
|
||||
func (s *v4Server) rmLease(lease Lease) (err error) {
|
||||
if len(s.leases) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -285,18 +339,35 @@ func (s *v4Server) rmLease(lease Lease) error {
|
||||
|
||||
// AddStaticLease adds a static lease. It is safe for concurrent use.
|
||||
func (s *v4Server) AddStaticLease(l Lease) (err error) {
|
||||
defer agherr.Annotate("dhcpv4: %w", &err)
|
||||
defer agherr.Annotate("dhcpv4: adding static lease: %w", &err)
|
||||
|
||||
if ip4 := l.IP.To4(); ip4 == nil {
|
||||
return fmt.Errorf("invalid ip %q, only ipv4 is supported", l.IP)
|
||||
}
|
||||
|
||||
l.Expiry = time.Unix(leaseExpireStatic, 0)
|
||||
|
||||
err = aghnet.ValidateHardwareAddress(l.HWAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating lease: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
l.Expiry = time.Unix(leaseExpireStatic, 0)
|
||||
var hostname string
|
||||
hostname, err = normalizeHostname(l.Hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = aghnet.ValidateDomainName(hostname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating hostname: %w", err)
|
||||
}
|
||||
|
||||
if s.leaseHosts.Has(hostname) {
|
||||
return agherr.Error("hostname exists")
|
||||
}
|
||||
|
||||
l.Hostname = hostname
|
||||
|
||||
// Perform the following actions in an anonymous function to make sure
|
||||
// that the lock gets unlocked before the notification step.
|
||||
@@ -361,16 +432,19 @@ func (s *v4Server) RemoveStaticLease(l Lease) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send ICMP to the specified machine
|
||||
// Return TRUE if it doesn't reply, which probably means that the IP is available
|
||||
func (s *v4Server) addrAvailable(target net.IP) bool {
|
||||
// addrAvailable sends an ICP request to the specified IP address. It returns
|
||||
// true if the remote host doesn't reply, which probably means that the IP
|
||||
// address is available.
|
||||
//
|
||||
// TODO(a.garipov): I'm not sure that this is the best way to do this.
|
||||
func (s *v4Server) addrAvailable(target net.IP) (avail bool) {
|
||||
if s.conf.ICMPTimeout == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
pinger, err := ping.NewPinger(target.String())
|
||||
if err != nil {
|
||||
log.Error("ping.NewPinger(): %v", err)
|
||||
log.Error("dhcpv4: ping.NewPinger(): %s", err)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -382,20 +456,24 @@ func (s *v4Server) addrAvailable(target net.IP) bool {
|
||||
pinger.OnRecv = func(_ *ping.Packet) {
|
||||
reply = true
|
||||
}
|
||||
log.Debug("dhcpv4: Sending ICMP Echo to %v", target)
|
||||
|
||||
log.Debug("dhcpv4: sending icmp echo to %s", target)
|
||||
|
||||
err = pinger.Run()
|
||||
if err != nil {
|
||||
log.Error("pinger.Run(): %v", err)
|
||||
log.Error("dhcpv4: pinger.Run(): %s", err)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if reply {
|
||||
log.Info("dhcpv4: IP conflict: %v is already used by another device", target)
|
||||
log.Info("dhcpv4: ip conflict: %s is already used by another device", target)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
log.Debug("dhcpv4: ICMP procedure is complete: %v", target)
|
||||
log.Debug("dhcpv4: icmp procedure is complete: %q", target)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -470,58 +548,69 @@ func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) {
|
||||
func (s *v4Server) commitLease(l *Lease) {
|
||||
l.Expiry = time.Now().Add(s.conf.leaseTime)
|
||||
|
||||
s.leasesLock.Lock()
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
s.leasesLock.Unlock()
|
||||
func() {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
s.leaseHosts.Add(l.Hostname)
|
||||
}()
|
||||
|
||||
s.conf.notify(LeaseChangedAdded)
|
||||
}
|
||||
|
||||
// Process Discover request and return lease
|
||||
// processDiscover is the handler for the DHCP Discover request.
|
||||
func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err error) {
|
||||
mac := req.ClientHWAddr
|
||||
|
||||
err = aghnet.ValidateHardwareAddress(mac)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
// TODO(a.garipov): Refactor this mess.
|
||||
l = s.findLease(mac)
|
||||
if l == nil {
|
||||
toStore := false
|
||||
for l == nil {
|
||||
l, err = s.reserveLease(mac)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reserving a lease: %w", err)
|
||||
}
|
||||
|
||||
if l == nil {
|
||||
log.Debug("dhcpv4: no more ip addresses")
|
||||
if toStore {
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Return a special error?
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
toStore = true
|
||||
|
||||
if !s.addrAvailable(l.IP) {
|
||||
s.blocklistLease(l)
|
||||
l = nil
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
} else {
|
||||
if l != nil {
|
||||
reqIP := req.RequestedIPAddress()
|
||||
if len(reqIP) != 0 && !reqIP.Equal(l.IP) {
|
||||
log.Debug("dhcpv4: different RequestedIP: %s != %s", reqIP, l.IP)
|
||||
}
|
||||
|
||||
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
needsUpdate := false
|
||||
defer func() {
|
||||
if needsUpdate {
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
}
|
||||
}()
|
||||
|
||||
leaseReady := false
|
||||
for !leaseReady {
|
||||
l, err = s.reserveLease(mac)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reserving a lease: %w", err)
|
||||
}
|
||||
|
||||
if l == nil {
|
||||
log.Debug("dhcpv4: no more ip addresses")
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
needsUpdate = true
|
||||
|
||||
if s.addrAvailable(l.IP) {
|
||||
leaseReady = true
|
||||
} else {
|
||||
s.blocklistLease(l)
|
||||
l = nil
|
||||
}
|
||||
}
|
||||
|
||||
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
|
||||
@@ -558,51 +647,14 @@ func (o *optFQDN) ToBytes() []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
// normalizeHostname normalizes a hostname sent by the client.
|
||||
func normalizeHostname(name string) (norm string, err error) {
|
||||
if name == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parts := strings.FieldsFunc(name, func(c rune) (ok bool) {
|
||||
return c != '.' && !aghnet.IsValidHostOuterRune(c)
|
||||
})
|
||||
|
||||
if len(parts) == 0 {
|
||||
return "", fmt.Errorf("normalizing hostname %q: no valid parts", name)
|
||||
}
|
||||
|
||||
norm = strings.Join(parts, "-")
|
||||
norm = strings.TrimSuffix(norm, "-")
|
||||
|
||||
return norm, nil
|
||||
}
|
||||
|
||||
// validateHostname validates a hostname sent by the client.
|
||||
func (s *v4Server) validateHostname(name string) (err error) {
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = aghnet.ValidateDomainName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating hostname: %w", err)
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Add client hostname uniqueness validation either
|
||||
// here or into method processRequest. This is not as easy as it might
|
||||
// look like, because the process of adding and releasing a lease is
|
||||
// currently non-straightforward.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process Request request and return lease
|
||||
// Return false if we don't need to reply
|
||||
func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, ok bool) {
|
||||
var err error
|
||||
|
||||
// processDiscover is the handler for the DHCP Request request.
|
||||
func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) {
|
||||
mac := req.ClientHWAddr
|
||||
err := aghnet.ValidateHardwareAddress(mac)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
reqIP := req.RequestedIPAddress()
|
||||
if reqIP == nil {
|
||||
reqIP = req.ClientIPAddr
|
||||
@@ -621,56 +673,51 @@ func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, ok bo
|
||||
return nil, false
|
||||
}
|
||||
|
||||
s.leasesLock.Lock()
|
||||
for _, l := range s.leases {
|
||||
if bytes.Equal(l.HWAddr, mac) {
|
||||
if !l.IP.Equal(reqIP) {
|
||||
s.leasesLock.Unlock()
|
||||
log.Debug("dhcpv4: mismatched OptionRequestedIPAddress in request message for %s", mac)
|
||||
mismatch := false
|
||||
func() {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
return nil, true
|
||||
for _, l := range s.leases {
|
||||
if !bytes.Equal(l.HWAddr, mac) {
|
||||
continue
|
||||
}
|
||||
|
||||
lease = l
|
||||
if l.IP.Equal(reqIP) {
|
||||
lease = l
|
||||
} else {
|
||||
log.Debug(
|
||||
`dhcpv4: mismatched OptionRequestedIPAddress `+
|
||||
`in request message for %s`,
|
||||
mac,
|
||||
)
|
||||
mismatch = true
|
||||
}
|
||||
|
||||
break
|
||||
return
|
||||
}
|
||||
}()
|
||||
if mismatch {
|
||||
return nil, true
|
||||
}
|
||||
s.leasesLock.Unlock()
|
||||
|
||||
if lease == nil {
|
||||
log.Debug("dhcpv4: no lease for %s", mac)
|
||||
log.Debug("dhcpv4: no reserved lease for %s", mac)
|
||||
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if !lease.IsStatic() {
|
||||
cliHostname := req.HostName()
|
||||
|
||||
var hostname string
|
||||
hostname, err = normalizeHostname(cliHostname)
|
||||
lease.Hostname, err = s.validHostnameForClient(cliHostname, lease.Hostname, reqIP)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: cannot normalize hostname for %s: %s", mac, err)
|
||||
|
||||
// Go on and assign a hostname made from the IP.
|
||||
log.Info(
|
||||
"dhcpv4: warning: client hostname %q is invalid: %s",
|
||||
cliHostname,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
if hostname != "" && cliHostname != hostname {
|
||||
log.Debug("dhcpv4: normalized hostname %q into %q", cliHostname, hostname)
|
||||
}
|
||||
|
||||
err = s.validateHostname(hostname)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: validating hostname for %s: %s", mac, err)
|
||||
|
||||
// Go on and assign a hostname made from the IP.
|
||||
}
|
||||
|
||||
if hostname == "" {
|
||||
hostname = aghnet.GenerateHostname(reqIP)
|
||||
}
|
||||
|
||||
lease.Hostname = hostname
|
||||
s.commitLease(lease)
|
||||
} else if len(lease.Hostname) != 0 {
|
||||
o := &optFQDN{
|
||||
@@ -726,8 +773,8 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
||||
copy(resp.YourIPAddr, l.IP)
|
||||
|
||||
resp.UpdateOption(dhcpv4.OptIPAddressLeaseTime(s.conf.leaseTime))
|
||||
resp.UpdateOption(dhcpv4.OptRouter(s.conf.routerIP))
|
||||
resp.UpdateOption(dhcpv4.OptSubnetMask(s.conf.subnetMask))
|
||||
resp.UpdateOption(dhcpv4.OptRouter(s.conf.subnet.IP))
|
||||
resp.UpdateOption(dhcpv4.OptSubnetMask(s.conf.subnet.Mask))
|
||||
resp.UpdateOption(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
||||
|
||||
for _, opt := range s.conf.options {
|
||||
@@ -784,7 +831,9 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
||||
}
|
||||
|
||||
// Start starts the IPv4 DHCP server.
|
||||
func (s *v4Server) Start() error {
|
||||
func (s *v4Server) Start() (err error) {
|
||||
defer agherr.Annotate("dhcpv4: %w", &err)
|
||||
|
||||
if !s.conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
@@ -792,14 +841,14 @@ func (s *v4Server) Start() error {
|
||||
ifaceName := s.conf.InterfaceName
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv4: finding interface %s by name: %w", ifaceName, err)
|
||||
return fmt.Errorf("finding interface %s by name: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
log.Debug("dhcpv4: starting...")
|
||||
|
||||
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion4, defaultMaxAttempts, defaultBackoff)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv4: interface %s: %w", ifaceName, err)
|
||||
return fmt.Errorf("interface %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
if len(dnsIPAddrs) == 0 {
|
||||
@@ -821,8 +870,18 @@ func (s *v4Server) Start() error {
|
||||
log.Info("dhcpv4: listening")
|
||||
|
||||
go func() {
|
||||
err = s.srv.Serve()
|
||||
log.Debug("dhcpv4: srv.Serve: %s", err)
|
||||
serr := s.srv.Serve()
|
||||
// TODO(a.garipov): Uncomment in Go 1.16.
|
||||
//
|
||||
// if errors.Is(serr, net.ErrClosed) {
|
||||
// log.Info("dhcpv4: server is closed")
|
||||
//
|
||||
// return
|
||||
// }
|
||||
|
||||
if serr != nil {
|
||||
log.Error("dhcpv4: srv.Serve: %s", serr)
|
||||
}
|
||||
}()
|
||||
|
||||
// Signal to the clients containers in packages home and dnsforward that
|
||||
@@ -855,6 +914,7 @@ func (s *v4Server) Stop() {
|
||||
func v4Create(conf V4ServerConf) (srv DHCPServer, err error) {
|
||||
s := &v4Server{}
|
||||
s.conf = conf
|
||||
s.leaseHosts = aghstrings.NewSet()
|
||||
|
||||
// TODO(a.garipov): Don't use a disabled server in other places or just
|
||||
// use an interface.
|
||||
@@ -862,7 +922,8 @@ func v4Create(conf V4ServerConf) (srv DHCPServer, err error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
s.conf.routerIP, err = tryTo4(s.conf.GatewayIP)
|
||||
var routerIP net.IP
|
||||
routerIP, err = tryTo4(s.conf.GatewayIP)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("dhcpv4: %w", err)
|
||||
}
|
||||
@@ -870,8 +931,14 @@ func v4Create(conf V4ServerConf) (srv DHCPServer, err error) {
|
||||
if s.conf.SubnetMask == nil {
|
||||
return s, fmt.Errorf("dhcpv4: invalid subnet mask: %v", s.conf.SubnetMask)
|
||||
}
|
||||
s.conf.subnetMask = make([]byte, 4)
|
||||
copy(s.conf.subnetMask, s.conf.SubnetMask.To4())
|
||||
|
||||
subnetMask := make([]byte, 4)
|
||||
copy(subnetMask, s.conf.SubnetMask.To4())
|
||||
|
||||
s.conf.subnet = &net.IPNet{
|
||||
IP: routerIP,
|
||||
Mask: subnetMask,
|
||||
}
|
||||
|
||||
s.conf.ipRange, err = newIPRange(conf.RangeStart, conf.RangeEnd)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,8 +30,9 @@ func TestV4_AddRemove_static(t *testing.T) {
|
||||
|
||||
// Add static lease.
|
||||
l := Lease{
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Hostname: "static-1.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
}
|
||||
|
||||
err = s.AddStaticLease(l)
|
||||
@@ -76,11 +77,13 @@ func TestV4_AddReplace(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
|
||||
dynLeases := []Lease{{
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Hostname: "dynamic-1.local",
|
||||
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
}, {
|
||||
IP: net.IP{192, 168, 10, 151},
|
||||
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Hostname: "dynamic-2.local",
|
||||
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 151},
|
||||
}}
|
||||
|
||||
for i := range dynLeases {
|
||||
@@ -89,11 +92,13 @@ func TestV4_AddReplace(t *testing.T) {
|
||||
}
|
||||
|
||||
stLeases := []Lease{{
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Hostname: "static-1.local",
|
||||
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
}, {
|
||||
IP: net.IP{192, 168, 10, 152},
|
||||
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Hostname: "static-2.local",
|
||||
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 152},
|
||||
}}
|
||||
|
||||
for _, l := range stLeases {
|
||||
@@ -129,8 +134,9 @@ func TestV4StaticLease_Get(t *testing.T) {
|
||||
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
||||
|
||||
l := Lease{
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Hostname: "static-1.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
}
|
||||
err = s.AddStaticLease(l)
|
||||
require.NoError(t, err)
|
||||
@@ -157,7 +163,7 @@ func TestV4StaticLease_Get(t *testing.T) {
|
||||
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
})
|
||||
|
||||
@@ -179,7 +185,7 @@ func TestV4StaticLease_Get(t *testing.T) {
|
||||
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
})
|
||||
|
||||
@@ -246,7 +252,7 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
||||
|
||||
assert.Equal(t, s.conf.GatewayIP, router[0])
|
||||
|
||||
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)])
|
||||
|
||||
@@ -269,9 +275,14 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
||||
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
||||
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||
assert.True(t, s.conf.RangeStart.Equal(resp.YourIPAddr))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||
|
||||
router := resp.Router()
|
||||
require.Len(t, router, 1)
|
||||
|
||||
assert.Equal(t, s.conf.GatewayIP, router[0])
|
||||
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.subnet.Mask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
})
|
||||
|
||||
@@ -329,12 +340,12 @@ func TestNormalizeHostname(t *testing.T) {
|
||||
}, {
|
||||
name: "error",
|
||||
hostname: "!!!",
|
||||
wantErrMsg: `normalizing hostname "!!!": no valid parts`,
|
||||
wantErrMsg: `normalizing "!!!": no valid parts`,
|
||||
want: "",
|
||||
}, {
|
||||
name: "error_spaces",
|
||||
hostname: "! ! !",
|
||||
wantErrMsg: `normalizing hostname "! ! !": no valid parts`,
|
||||
wantErrMsg: `normalizing "! ! !": no valid parts`,
|
||||
want: "",
|
||||
}}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -477,10 +478,10 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
|
||||
res.Reason = Rewritten
|
||||
}
|
||||
|
||||
cnames := map[string]bool{}
|
||||
cnames := aghstrings.NewSet()
|
||||
origHost := host
|
||||
for len(rr) != 0 && rr[0].Type == dns.TypeCNAME {
|
||||
log.Debug("Rewrite: CNAME for %s is %s", host, rr[0].Answer)
|
||||
log.Debug("rewrite: CNAME for %s is %s", host, rr[0].Answer)
|
||||
|
||||
if host == rr[0].Answer { // "host == CNAME" is an exception
|
||||
res.Reason = NotFilteredNotFound
|
||||
@@ -489,12 +490,13 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
|
||||
}
|
||||
|
||||
host = rr[0].Answer
|
||||
_, ok := cnames[host]
|
||||
if ok {
|
||||
log.Info("Rewrite: breaking CNAME redirection loop: %s. Question: %s", host, origHost)
|
||||
if cnames.Has(host) {
|
||||
log.Info("rewrite: breaking CNAME redirection loop: %s. Question: %s", host, origHost)
|
||||
|
||||
return res
|
||||
}
|
||||
cnames[host] = false
|
||||
|
||||
cnames.Add(host)
|
||||
res.CanonName = rr[0].Answer
|
||||
rr = findRewrites(d.Rewrites, host)
|
||||
}
|
||||
@@ -509,7 +511,7 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
|
||||
}
|
||||
|
||||
res.IPList = append(res.IPList, r.IP)
|
||||
log.Debug("Rewrite: A/AAAA for %s is %s", host, r.IP)
|
||||
log.Debug("rewrite: A/AAAA for %s is %s", host, r.IP)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,11 @@ import (
|
||||
type accessCtx struct {
|
||||
lock sync.Mutex
|
||||
|
||||
allowedClients map[string]bool // IP addresses of whitelist clients
|
||||
disallowedClients map[string]bool // IP addresses of clients that should be blocked
|
||||
// allowedClients are the IP addresses of clients in the allowlist.
|
||||
allowedClients *aghstrings.Set
|
||||
|
||||
// disallowedClients are the IP addresses of clients in the blocklist.
|
||||
disallowedClients *aghstrings.Set
|
||||
|
||||
allowedClientsIPNet []net.IPNet // CIDRs of whitelist clients
|
||||
disallowedClientsIPNet []net.IPNet // CIDRs of clients that should be blocked
|
||||
@@ -26,15 +29,20 @@ type accessCtx struct {
|
||||
blockedHostsEngine *urlfilter.DNSEngine // finds hosts that should be blocked
|
||||
}
|
||||
|
||||
func (a *accessCtx) Init(allowedClients, disallowedClients, blockedHosts []string) error {
|
||||
err := processIPCIDRArray(&a.allowedClients, &a.allowedClientsIPNet, allowedClients)
|
||||
if err != nil {
|
||||
return err
|
||||
func newAccessCtx(allowedClients, disallowedClients, blockedHosts []string) (a *accessCtx, err error) {
|
||||
a = &accessCtx{
|
||||
allowedClients: aghstrings.NewSet(),
|
||||
disallowedClients: aghstrings.NewSet(),
|
||||
}
|
||||
|
||||
err = processIPCIDRArray(&a.disallowedClients, &a.disallowedClientsIPNet, disallowedClients)
|
||||
err = processIPCIDRArray(a.allowedClients, &a.allowedClientsIPNet, allowedClients)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, fmt.Errorf("processing allowed clients: %w", err)
|
||||
}
|
||||
|
||||
err = processIPCIDRArray(a.disallowedClients, &a.disallowedClientsIPNet, disallowedClients)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("processing disallowed clients: %w", err)
|
||||
}
|
||||
|
||||
b := &strings.Builder{}
|
||||
@@ -51,21 +59,20 @@ func (a *accessCtx) Init(allowedClients, disallowedClients, blockedHosts []strin
|
||||
listArray = append(listArray, list)
|
||||
rulesStorage, err := filterlist.NewRuleStorage(listArray)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filterlist.NewRuleStorage(): %w", err)
|
||||
return nil, fmt.Errorf("filterlist.NewRuleStorage(): %w", err)
|
||||
}
|
||||
a.blockedHostsEngine = urlfilter.NewDNSEngine(rulesStorage)
|
||||
|
||||
return nil
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Split array of IP or CIDR into 2 containers for fast search
|
||||
func processIPCIDRArray(dst *map[string]bool, dstIPNet *[]net.IPNet, src []string) error {
|
||||
*dst = make(map[string]bool)
|
||||
|
||||
func processIPCIDRArray(dst *aghstrings.Set, dstIPNet *[]net.IPNet, src []string) error {
|
||||
for _, s := range src {
|
||||
ip := net.ParseIP(s)
|
||||
if ip != nil {
|
||||
(*dst)[s] = true
|
||||
dst.Add(s)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -73,6 +80,7 @@ func processIPCIDRArray(dst *map[string]bool, dstIPNet *[]net.IPNet, src []strin
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dstIPNet = append(*dstIPNet, *ipnet)
|
||||
}
|
||||
|
||||
@@ -89,9 +97,8 @@ func (a *accessCtx) IsBlockedIP(ip net.IP) (bool, string) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
if len(a.allowedClients) != 0 || len(a.allowedClientsIPNet) != 0 {
|
||||
_, ok := a.allowedClients[ipStr]
|
||||
if ok {
|
||||
if a.allowedClients.Len() != 0 || len(a.allowedClientsIPNet) != 0 {
|
||||
if a.allowedClients.Has(ipStr) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
@@ -106,8 +113,7 @@ func (a *accessCtx) IsBlockedIP(ip net.IP) (bool, string) {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
_, ok := a.disallowedClients[ipStr]
|
||||
if ok {
|
||||
if a.disallowedClients.Has(ipStr) {
|
||||
return true, ipStr
|
||||
}
|
||||
|
||||
@@ -186,10 +192,11 @@ func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
a := &accessCtx{}
|
||||
err = a.Init(j.AllowedClients, j.DisallowedClients, j.BlockedHosts)
|
||||
var a *accessCtx
|
||||
a, err = newAccessCtx(j.AllowedClients, j.DisallowedClients, j.BlockedHosts)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "access.Init: %s", err)
|
||||
httpError(r, w, http.StatusBadRequest, "creating access ctx: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ func TestIsBlockedIP(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run(prefix+tc.name, func(t *testing.T) {
|
||||
aCtx := &accessCtx{}
|
||||
allowedRules := rules
|
||||
var disallowedRules []string
|
||||
|
||||
@@ -90,7 +89,8 @@ func TestIsBlockedIP(t *testing.T) {
|
||||
allowedRules, disallowedRules = disallowedRules, allowedRules
|
||||
}
|
||||
|
||||
require.Nil(t, aCtx.Init(allowedRules, disallowedRules, nil))
|
||||
aCtx, err := newAccessCtx(allowedRules, disallowedRules, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
disallowed, rule := aCtx.IsBlockedIP(tc.ip)
|
||||
assert.Equal(t, tc.wantDis, disallowed)
|
||||
@@ -100,12 +100,12 @@ func TestIsBlockedIP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsBlockedDomain(t *testing.T) {
|
||||
aCtx := &accessCtx{}
|
||||
require.Nil(t, aCtx.Init(nil, nil, []string{
|
||||
aCtx, err := newAccessCtx(nil, nil, []string{
|
||||
"host1",
|
||||
"*.host.com",
|
||||
"||host3.com^",
|
||||
}))
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
||||
@@ -2,6 +2,7 @@ package dnsforward
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -15,7 +16,8 @@ import (
|
||||
func ValidateClientID(clientID string) (err error) {
|
||||
err = aghnet.ValidateDomainNameLabel(clientID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid client id: %w", err)
|
||||
// Replace the domain name label wrapper with our own.
|
||||
return fmt.Errorf("invalid client id %q: %w", clientID, errors.Unwrap(err))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -142,7 +144,7 @@ func processClientID(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
cliSrvName = qs.ConnectionState().ServerName
|
||||
cliSrvName = qs.ConnectionState().TLS.ServerName
|
||||
}
|
||||
|
||||
clientID, err := clientIDFromClientServerName(hostSrvName, cliSrvName, srvConf.StrictSNICheck)
|
||||
|
||||
@@ -40,7 +40,7 @@ type testQUICSession struct {
|
||||
|
||||
// ConnectionState implements the quicSession interface for testQUICSession.
|
||||
func (c testQUICSession) ConnectionState() (cs quic.ConnectionState) {
|
||||
cs.ServerName = c.serverName
|
||||
cs.TLS.ServerName = c.serverName
|
||||
|
||||
return cs
|
||||
}
|
||||
@@ -117,8 +117,8 @@ func TestProcessClientID(t *testing.T) {
|
||||
hostSrvName: "example.com",
|
||||
cliSrvName: "!!!.example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `client id check: invalid client id: invalid char '!' ` +
|
||||
`at index 0 in "!!!"`,
|
||||
wantErrMsg: `client id check: invalid client id "!!!": ` +
|
||||
`invalid char '!' at index 0`,
|
||||
wantRes: resultCodeError,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
@@ -128,9 +128,9 @@ func TestProcessClientID(t *testing.T) {
|
||||
cliSrvName: `abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmno` +
|
||||
`pqrstuvwxyz0123456789.example.com`,
|
||||
wantClientID: "",
|
||||
wantErrMsg: `client id check: invalid client id: "abcdefghijklmno` +
|
||||
`pqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789" ` +
|
||||
`is too long, max: 63`,
|
||||
wantErrMsg: `client id check: invalid client id "abcdefghijklmno` +
|
||||
`pqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789": ` +
|
||||
`label is too long, max: 63`,
|
||||
wantRes: resultCodeError,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
@@ -238,8 +238,8 @@ func TestProcessClientID_https(t *testing.T) {
|
||||
name: "invalid_client_id",
|
||||
path: "/dns-query/!!!",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `client id check: invalid client id: invalid char '!' ` +
|
||||
`at index 0 in "!!!"`,
|
||||
wantErrMsg: `client id check: invalid client id "!!!": ` +
|
||||
`invalid char '!' at index 0`,
|
||||
wantRes: resultCodeError,
|
||||
}}
|
||||
|
||||
|
||||
@@ -249,6 +249,10 @@ func (s *Server) hostToIP(host string) (ip net.IP, ok bool) {
|
||||
//
|
||||
// TODO(a.garipov): Adapt to AAAA as well.
|
||||
func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
|
||||
if !s.dhcpServer.Enabled() {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
req := dctx.proxyCtx.Req
|
||||
q := req.Question[0]
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := &Server{
|
||||
dhcpServer: &testDHCP{},
|
||||
localDomainSuffix: defaultLocalDomainSuffix,
|
||||
tableHostToIP: hostToIPTable{
|
||||
"example": knownIP,
|
||||
@@ -201,6 +202,7 @@ func TestServer_ProcessInternalHosts(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := &Server{
|
||||
dhcpServer: &testDHCP{},
|
||||
localDomainSuffix: tc.suffix,
|
||||
tableHostToIP: hostToIPTable{
|
||||
"example": knownIP,
|
||||
@@ -318,7 +320,7 @@ func TestLocalRestriction(t *testing.T) {
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err = s.handleDNSRequest(nil, pctx)
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pctx.Res)
|
||||
require.Len(t, pctx.Res.Answer, tc.wantLen)
|
||||
if tc.wantLen > 0 {
|
||||
|
||||
@@ -340,19 +340,6 @@ func (s *Server) collectDNSIPAddrs() (addrs []string, err error) {
|
||||
return addrs[:i], nil
|
||||
}
|
||||
|
||||
// unit is used to show the presence of a value in a set.
|
||||
type unit = struct{}
|
||||
|
||||
// sliceToSet converts a slice of strings into a string set.
|
||||
func sliceToSet(strs []string) (set map[string]unit) {
|
||||
set = make(map[string]unit, len(strs))
|
||||
for _, s := range strs {
|
||||
set[s] = unit{}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
// setupResolvers initializes the resolvers for local addresses. For internal
|
||||
// use only.
|
||||
func (s *Server) setupResolvers(localAddrs []string) (err error) {
|
||||
@@ -377,16 +364,14 @@ func (s *Server) setupResolvers(localAddrs []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
ourAddrsSet := sliceToSet(ourAddrs)
|
||||
ourAddrsSet := aghstrings.NewSet(ourAddrs...)
|
||||
|
||||
// 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.
|
||||
localAddrs = aghstrings.FilterOut(localAddrs, func(s string) (ok bool) {
|
||||
_, ok = ourAddrsSet[s]
|
||||
|
||||
return ok
|
||||
return ourAddrsSet.Has(s)
|
||||
})
|
||||
|
||||
var upsConfig proxy.UpstreamConfig
|
||||
@@ -442,7 +427,7 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
||||
//
|
||||
// TODO(a.garipov): The Snap problem can probably be solved if
|
||||
// we add the netlink-connector interface plug.
|
||||
log.Error("cannot initialize ipset: %s", err)
|
||||
log.Info("warning: cannot initialize ipset: %s", err)
|
||||
}
|
||||
|
||||
// Prepare DNS servers settings
|
||||
@@ -464,10 +449,7 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
||||
// --
|
||||
s.prepareIntlProxy()
|
||||
|
||||
// Initialize DNS access module
|
||||
// --
|
||||
s.access = &accessCtx{}
|
||||
err = s.access.Init(s.conf.AllowedClients, s.conf.DisallowedClients, s.conf.BlockedHosts)
|
||||
s.access, err = newAccessCtx(s.conf.AllowedClients, s.conf.DisallowedClients, s.conf.BlockedHosts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ func createTestServer(
|
||||
require.NotNil(t, snd)
|
||||
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DHCPServer: &testDHCP{},
|
||||
DNSFilter: f,
|
||||
SubnetDetector: snd,
|
||||
})
|
||||
@@ -334,7 +335,7 @@ func TestDoTServer(t *testing.T) {
|
||||
|
||||
func TestDoQServer(t *testing.T) {
|
||||
s, _ := createTestTLS(t, TLSConfig{
|
||||
QUICListenAddrs: []*net.UDPAddr{{}},
|
||||
QUICListenAddrs: []*net.UDPAddr{{IP: net.IP{127, 0, 0, 1}}},
|
||||
})
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.TestUpstream{
|
||||
@@ -736,6 +737,7 @@ func TestBlockedCustomIP(t *testing.T) {
|
||||
|
||||
var s *Server
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DHCPServer: &testDHCP{},
|
||||
DNSFilter: dnsfilter.New(&dnsfilter.Config{}, filters),
|
||||
SubnetDetector: snd,
|
||||
})
|
||||
@@ -873,6 +875,7 @@ func TestRewrite(t *testing.T) {
|
||||
|
||||
var s *Server
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DHCPServer: &testDHCP{},
|
||||
DNSFilter: f,
|
||||
SubnetDetector: snd,
|
||||
})
|
||||
@@ -1016,11 +1019,13 @@ func TestMatchDNSName(t *testing.T) {
|
||||
|
||||
type testDHCP struct{}
|
||||
|
||||
func (d *testDHCP) Enabled() (ok bool) { return true }
|
||||
|
||||
func (d *testDHCP) Leases(flags int) []dhcpd.Lease {
|
||||
l := dhcpd.Lease{
|
||||
IP: net.IP{127, 0, 0, 1},
|
||||
IP: net.IP{192, 168, 12, 34},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Hostname: "localhost",
|
||||
Hostname: "myhost",
|
||||
}
|
||||
|
||||
return []dhcpd.Lease{l}
|
||||
@@ -1056,7 +1061,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
})
|
||||
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
req := createTestMessageWithType("1.0.0.127.in-addr.arpa.", dns.TypePTR)
|
||||
req := createTestMessageWithType("34.12.168.192.in-addr.arpa.", dns.TypePTR)
|
||||
|
||||
resp, err := dns.Exchange(req, addr.String())
|
||||
require.NoError(t, err)
|
||||
@@ -1064,11 +1069,11 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
require.Len(t, resp.Answer, 1)
|
||||
|
||||
assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype)
|
||||
assert.Equal(t, "1.0.0.127.in-addr.arpa.", resp.Answer[0].Header().Name)
|
||||
assert.Equal(t, "34.12.168.192.in-addr.arpa.", resp.Answer[0].Header().Name)
|
||||
|
||||
ptr, ok := resp.Answer[0].(*dns.PTR)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "localhost.", ptr.Ptr)
|
||||
assert.Equal(t, "myhost.", ptr.Ptr)
|
||||
}
|
||||
|
||||
func TestPTRResponseFromHosts(t *testing.T) {
|
||||
@@ -1098,6 +1103,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
||||
|
||||
var s *Server
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DHCPServer: &testDHCP{},
|
||||
DNSFilter: dnsfilter.New(&c, nil),
|
||||
SubnetDetector: snd,
|
||||
})
|
||||
@@ -1160,8 +1166,9 @@ func TestNewServer(t *testing.T) {
|
||||
in: DNSCreateParams{
|
||||
LocalDomain: "!!!",
|
||||
},
|
||||
wantErrMsg: `local domain: invalid domain name label at index 0: ` +
|
||||
`invalid char '!' at index 0 in "!!!"`,
|
||||
wantErrMsg: `local domain: validating domain name "!!!": ` +
|
||||
`invalid domain name label at index 0: ` +
|
||||
`validating label "!!!": invalid char '!' at index 0`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -62,6 +63,7 @@ func (s *session) deserialize(data []byte) bool {
|
||||
// Auth - global object
|
||||
type Auth struct {
|
||||
db *bbolt.DB
|
||||
blocker *authRateLimiter
|
||||
sessions map[string]*session
|
||||
users []User
|
||||
lock sync.Mutex
|
||||
@@ -75,12 +77,15 @@ type User struct {
|
||||
}
|
||||
|
||||
// InitAuth - create a global object
|
||||
func InitAuth(dbFilename string, users []User, sessionTTL uint32) *Auth {
|
||||
func InitAuth(dbFilename string, users []User, sessionTTL uint32, blocker *authRateLimiter) *Auth {
|
||||
log.Info("Initializing auth module: %s", dbFilename)
|
||||
|
||||
a := Auth{}
|
||||
a.sessionTTL = sessionTTL
|
||||
a.sessions = make(map[string]*session)
|
||||
a := &Auth{
|
||||
sessionTTL: sessionTTL,
|
||||
blocker: blocker,
|
||||
sessions: make(map[string]*session),
|
||||
users: users,
|
||||
}
|
||||
var err error
|
||||
a.db, err = bbolt.Open(dbFilename, 0o644, nil)
|
||||
if err != nil {
|
||||
@@ -92,10 +97,9 @@ func InitAuth(dbFilename string, users []User, sessionTTL uint32) *Auth {
|
||||
return nil
|
||||
}
|
||||
a.loadSessions()
|
||||
a.users = users
|
||||
log.Info("auth: initialized. users:%d sessions:%d", len(a.users), len(a.sessions))
|
||||
|
||||
return &a
|
||||
return a
|
||||
}
|
||||
|
||||
// Close - close module
|
||||
@@ -330,13 +334,23 @@ func cookieExpiryFormat(exp time.Time) (formatted string) {
|
||||
return exp.Format(cookieTimeFormat)
|
||||
}
|
||||
|
||||
func (a *Auth) httpCookie(req loginJSON) (string, error) {
|
||||
func (a *Auth) httpCookie(req loginJSON, addr string) (cookie string, err error) {
|
||||
blocker := a.blocker
|
||||
u := a.UserFind(req.Name, req.Password)
|
||||
if len(u.Name) == 0 {
|
||||
return "", nil
|
||||
if blocker != nil {
|
||||
blocker.inc(addr)
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
sess, err := newSessionToken()
|
||||
if blocker != nil {
|
||||
blocker.remove(addr)
|
||||
}
|
||||
|
||||
var sess []byte
|
||||
sess, err = newSessionToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -404,10 +418,38 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "json decode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cookie, err := Context.auth.httpCookie(req)
|
||||
var remoteAddr string
|
||||
// The realIP couldn't be used here due to security issues.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2799.
|
||||
//
|
||||
// TODO(e.burkov): Use realIP when the issue will be fixed.
|
||||
if remoteAddr, err = aghnet.SplitHost(r.RemoteAddr); err != nil {
|
||||
httpError(w, http.StatusBadRequest, "auth: getting remote address: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if blocker := Context.auth.blocker; blocker != nil {
|
||||
if left := blocker.check(remoteAddr); left > 0 {
|
||||
w.Header().Set("Retry-After", strconv.Itoa(int(left.Seconds())))
|
||||
httpError(
|
||||
w,
|
||||
http.StatusTooManyRequests,
|
||||
"auth: blocked for %s",
|
||||
left,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var cookie string
|
||||
cookie, err = Context.auth.httpCookie(req, remoteAddr)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "crypto rand reader: %s", err)
|
||||
|
||||
@@ -425,7 +467,6 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
log.Info("auth: failed to login user %q from ip %q", req.Name, ip)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
http.Error(w, "invalid username or password", http.StatusBadRequest)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -22,21 +21,6 @@ func TestMain(m *testing.M) {
|
||||
aghtest.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
func prepareTestDir(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
const dir = "./agh-test"
|
||||
|
||||
require.Nil(t, os.RemoveAll(dir))
|
||||
// TODO(e.burkov): Replace with testing.TempDir after updating Go
|
||||
// version to 1.16.
|
||||
require.Nil(t, os.MkdirAll(dir, 0o755))
|
||||
|
||||
t.Cleanup(func() { require.Nil(t, os.RemoveAll(dir)) })
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
func TestNewSessionToken(t *testing.T) {
|
||||
// Successful case.
|
||||
token, err := newSessionToken()
|
||||
@@ -57,14 +41,14 @@ func TestNewSessionToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
dir := prepareTestDir(t)
|
||||
dir := t.TempDir()
|
||||
fn := filepath.Join(dir, "sessions.db")
|
||||
|
||||
users := []User{{
|
||||
Name: "name",
|
||||
PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2",
|
||||
}}
|
||||
a := InitAuth(fn, nil, 60)
|
||||
a := InitAuth(fn, nil, 60, nil)
|
||||
s := session{}
|
||||
|
||||
user := User{Name: "name"}
|
||||
@@ -92,7 +76,7 @@ func TestAuth(t *testing.T) {
|
||||
a.Close()
|
||||
|
||||
// load saved session
|
||||
a = InitAuth(fn, users, 60)
|
||||
a = InitAuth(fn, users, 60, nil)
|
||||
|
||||
// the session is still alive
|
||||
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
|
||||
@@ -107,7 +91,7 @@ func TestAuth(t *testing.T) {
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// load and remove expired sessions
|
||||
a = InitAuth(fn, users, 60)
|
||||
a = InitAuth(fn, users, 60, nil)
|
||||
assert.Equal(t, checkSessionNotFound, a.checkSession(sessStr))
|
||||
|
||||
a.Close()
|
||||
@@ -132,13 +116,13 @@ func (w *testResponseWriter) WriteHeader(statusCode int) {
|
||||
}
|
||||
|
||||
func TestAuthHTTP(t *testing.T) {
|
||||
dir := prepareTestDir(t)
|
||||
dir := t.TempDir()
|
||||
fn := filepath.Join(dir, "sessions.db")
|
||||
|
||||
users := []User{
|
||||
{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||
}
|
||||
Context.auth = InitAuth(fn, users, 60)
|
||||
Context.auth = InitAuth(fn, users, 60, nil)
|
||||
|
||||
handlerCalled := false
|
||||
handler := func(_ http.ResponseWriter, _ *http.Request) {
|
||||
@@ -167,7 +151,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||
assert.True(t, handlerCalled)
|
||||
|
||||
// perform login
|
||||
cookie, err := Context.auth.httpCookie(loginJSON{Name: "name", Password: "password"})
|
||||
cookie, err := Context.auth.httpCookie(loginJSON{Name: "name", Password: "password"}, "")
|
||||
assert.Nil(t, err)
|
||||
assert.NotEmpty(t, cookie)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestAuthGL(t *testing.T) {
|
||||
dir := prepareTestDir(t)
|
||||
dir := t.TempDir()
|
||||
|
||||
GLMode = true
|
||||
t.Cleanup(func() {
|
||||
|
||||
109
internal/home/authratelimiter.go
Normal file
109
internal/home/authratelimiter.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// failedAuthTTL is the period of time for which the failed attempt will stay in
|
||||
// cache.
|
||||
const failedAuthTTL = 1 * time.Minute
|
||||
|
||||
// failedAuth is an entry of authRateLimiter's cache.
|
||||
type failedAuth struct {
|
||||
until time.Time
|
||||
num uint
|
||||
}
|
||||
|
||||
// authRateLimiter used to cache failed authentication attempts.
|
||||
type authRateLimiter struct {
|
||||
failedAuths map[string]failedAuth
|
||||
// failedAuthsLock protects failedAuths.
|
||||
failedAuthsLock sync.Mutex
|
||||
blockDur time.Duration
|
||||
maxAttempts uint
|
||||
}
|
||||
|
||||
// newAuthRateLimiter returns properly initialized *authRateLimiter.
|
||||
func newAuthRateLimiter(blockDur time.Duration, maxAttempts uint) (ab *authRateLimiter) {
|
||||
return &authRateLimiter{
|
||||
failedAuths: make(map[string]failedAuth),
|
||||
blockDur: blockDur,
|
||||
maxAttempts: maxAttempts,
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupLocked checks each blocked users removing ones with expired TTL. For
|
||||
// internal use only.
|
||||
func (ab *authRateLimiter) cleanupLocked(now time.Time) {
|
||||
for k, v := range ab.failedAuths {
|
||||
if now.After(v.until) {
|
||||
delete(ab.failedAuths, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkLocked checks the attempter for it's state. For internal use only.
|
||||
func (ab *authRateLimiter) checkLocked(usrID string, now time.Time) (left time.Duration) {
|
||||
a, ok := ab.failedAuths[usrID]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
if a.num < ab.maxAttempts {
|
||||
return 0
|
||||
}
|
||||
|
||||
return a.until.Sub(now)
|
||||
}
|
||||
|
||||
// check returns the time left until unblocking. The nonpositive result should
|
||||
// be interpreted as not blocked attempter.
|
||||
func (ab *authRateLimiter) check(usrID string) (left time.Duration) {
|
||||
now := time.Now()
|
||||
|
||||
ab.failedAuthsLock.Lock()
|
||||
defer ab.failedAuthsLock.Unlock()
|
||||
|
||||
ab.cleanupLocked(now)
|
||||
return ab.checkLocked(usrID, now)
|
||||
}
|
||||
|
||||
// incLocked increments the number of unsuccessful attempts for attempter with
|
||||
// ip and updates it's blocking moment if needed. For internal use only.
|
||||
func (ab *authRateLimiter) incLocked(usrID string, now time.Time) {
|
||||
var until time.Time = now.Add(failedAuthTTL)
|
||||
var attNum uint = 1
|
||||
|
||||
a, ok := ab.failedAuths[usrID]
|
||||
if ok {
|
||||
until = a.until
|
||||
attNum = a.num + 1
|
||||
}
|
||||
if attNum >= ab.maxAttempts {
|
||||
until = now.Add(ab.blockDur)
|
||||
}
|
||||
|
||||
ab.failedAuths[usrID] = failedAuth{
|
||||
num: attNum,
|
||||
until: until,
|
||||
}
|
||||
}
|
||||
|
||||
// inc updates the failed attempt in cache.
|
||||
func (ab *authRateLimiter) inc(usrID string) {
|
||||
now := time.Now()
|
||||
|
||||
ab.failedAuthsLock.Lock()
|
||||
defer ab.failedAuthsLock.Unlock()
|
||||
|
||||
ab.incLocked(usrID, now)
|
||||
}
|
||||
|
||||
// remove stops any tracking and any blocking of the user.
|
||||
func (ab *authRateLimiter) remove(usrID string) {
|
||||
ab.failedAuthsLock.Lock()
|
||||
defer ab.failedAuthsLock.Unlock()
|
||||
|
||||
delete(ab.failedAuths, usrID)
|
||||
}
|
||||
207
internal/home/authratelimiter_test.go
Normal file
207
internal/home/authratelimiter_test.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAuthRateLimiter_Cleanup(t *testing.T) {
|
||||
const key = "some-key"
|
||||
now := time.Now()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
att failedAuth
|
||||
wantExp bool
|
||||
}{{
|
||||
name: "expired",
|
||||
att: failedAuth{
|
||||
until: now.Add(-100 * time.Hour),
|
||||
},
|
||||
wantExp: true,
|
||||
}, {
|
||||
name: "nope_yet",
|
||||
att: failedAuth{
|
||||
until: now.Add(failedAuthTTL / 2),
|
||||
},
|
||||
wantExp: false,
|
||||
}, {
|
||||
name: "blocked",
|
||||
att: failedAuth{
|
||||
until: now.Add(100 * time.Hour),
|
||||
},
|
||||
wantExp: false,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
ab := &authRateLimiter{
|
||||
failedAuths: map[string]failedAuth{
|
||||
key: tc.att,
|
||||
},
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ab.cleanupLocked(now)
|
||||
if tc.wantExp {
|
||||
assert.Empty(t, ab.failedAuths)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.Len(t, ab.failedAuths, 1)
|
||||
|
||||
_, ok := ab.failedAuths[key]
|
||||
require.True(t, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthRateLimiter_Check(t *testing.T) {
|
||||
key := string(net.IP{127, 0, 0, 1})
|
||||
const maxAtt = 1
|
||||
now := time.Now()
|
||||
|
||||
testCases := []struct {
|
||||
until time.Time
|
||||
name string
|
||||
num uint
|
||||
wantExp bool
|
||||
}{{
|
||||
until: now.Add(-100 * time.Hour),
|
||||
name: "expired",
|
||||
num: 0,
|
||||
wantExp: true,
|
||||
}, {
|
||||
until: now.Add(failedAuthTTL),
|
||||
name: "not_blocked_but_tracked",
|
||||
num: 0,
|
||||
wantExp: true,
|
||||
}, {
|
||||
until: now,
|
||||
name: "expired_but_stayed",
|
||||
num: 2,
|
||||
wantExp: true,
|
||||
}, {
|
||||
until: now.Add(100 * time.Hour),
|
||||
name: "blocked",
|
||||
num: 2,
|
||||
wantExp: false,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
failedAuths := map[string]failedAuth{
|
||||
key: {
|
||||
num: tc.num,
|
||||
until: tc.until,
|
||||
},
|
||||
}
|
||||
ab := &authRateLimiter{
|
||||
maxAttempts: maxAtt,
|
||||
failedAuths: failedAuths,
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
until := ab.check(key)
|
||||
|
||||
if tc.wantExp {
|
||||
assert.LessOrEqual(t, until, time.Duration(0))
|
||||
} else {
|
||||
assert.Greater(t, until, time.Duration(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("non-existent", func(t *testing.T) {
|
||||
ab := &authRateLimiter{
|
||||
failedAuths: map[string]failedAuth{
|
||||
key + "smthng": {},
|
||||
},
|
||||
}
|
||||
|
||||
until := ab.check(key)
|
||||
|
||||
assert.Zero(t, until)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthRateLimiter_Inc(t *testing.T) {
|
||||
ip := net.IP{127, 0, 0, 1}
|
||||
key := string(ip)
|
||||
now := time.Now()
|
||||
const maxAtt = 2
|
||||
const blockDur = 15 * time.Minute
|
||||
|
||||
testCases := []struct {
|
||||
until time.Time
|
||||
wantUntil time.Time
|
||||
name string
|
||||
num uint
|
||||
wantNum uint
|
||||
}{{
|
||||
name: "only_inc",
|
||||
until: now,
|
||||
wantUntil: now,
|
||||
num: maxAtt - 1,
|
||||
wantNum: maxAtt,
|
||||
}, {
|
||||
name: "inc_and_block",
|
||||
until: now,
|
||||
wantUntil: now.Add(failedAuthTTL),
|
||||
num: maxAtt,
|
||||
wantNum: maxAtt + 1,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
failedAuths := map[string]failedAuth{
|
||||
key: {
|
||||
num: tc.num,
|
||||
until: tc.until,
|
||||
},
|
||||
}
|
||||
ab := &authRateLimiter{
|
||||
blockDur: blockDur,
|
||||
maxAttempts: maxAtt,
|
||||
failedAuths: failedAuths,
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ab.inc(key)
|
||||
|
||||
a, ok := ab.failedAuths[key]
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, tc.wantNum, a.num)
|
||||
assert.LessOrEqual(t, tc.wantUntil.Unix(), a.until.Unix())
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("non-existent", func(t *testing.T) {
|
||||
ab := &authRateLimiter{
|
||||
blockDur: blockDur,
|
||||
maxAttempts: maxAtt,
|
||||
failedAuths: map[string]failedAuth{},
|
||||
}
|
||||
|
||||
ab.inc(key)
|
||||
|
||||
a, ok := ab.failedAuths[key]
|
||||
require.True(t, ok)
|
||||
assert.EqualValues(t, 1, a.num)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthRateLimiter_Remove(t *testing.T) {
|
||||
const key = "some-key"
|
||||
|
||||
failedAuths := map[string]failedAuth{
|
||||
key: {},
|
||||
}
|
||||
ab := &authRateLimiter{
|
||||
failedAuths: failedAuths,
|
||||
}
|
||||
|
||||
ab.remove(key)
|
||||
|
||||
assert.Empty(t, ab.failedAuths)
|
||||
}
|
||||
@@ -29,25 +29,25 @@ var webHandlersRegistered = false
|
||||
|
||||
// Client contains information about persistent clients.
|
||||
type Client struct {
|
||||
IDs []string
|
||||
Tags []string
|
||||
Name string
|
||||
UseOwnSettings bool // false: use global settings
|
||||
FilteringEnabled bool
|
||||
SafeSearchEnabled bool
|
||||
SafeBrowsingEnabled bool
|
||||
ParentalEnabled bool
|
||||
|
||||
UseOwnBlockedServices bool // false: use global settings
|
||||
BlockedServices []string
|
||||
|
||||
Upstreams []string // list of upstream servers to be used for the client's requests
|
||||
|
||||
// Custom upstream config for this client
|
||||
// nil: not yet initialized
|
||||
// not nil, but empty: initialized, no good upstreams
|
||||
// not nil, not empty: Upstreams ready to be used
|
||||
// upstreamConfig is the custom upstream config for this client. If
|
||||
// it's nil, it has not been initialized yet. If it's non-nil and
|
||||
// empty, there are no valid upstreams. If it's non-nil and non-empty,
|
||||
// these upstream must be used.
|
||||
upstreamConfig *proxy.UpstreamConfig
|
||||
|
||||
Name string
|
||||
|
||||
IDs []string
|
||||
Tags []string
|
||||
BlockedServices []string
|
||||
Upstreams []string
|
||||
|
||||
UseOwnSettings bool
|
||||
FilteringEnabled bool
|
||||
SafeSearchEnabled bool
|
||||
SafeBrowsingEnabled bool
|
||||
ParentalEnabled bool
|
||||
UseOwnBlockedServices bool
|
||||
}
|
||||
|
||||
type clientSource uint
|
||||
@@ -63,9 +63,9 @@ const (
|
||||
|
||||
// RuntimeClient information
|
||||
type RuntimeClient struct {
|
||||
WhoisInfo *RuntimeClientWhoisInfo
|
||||
Host string
|
||||
Source clientSource
|
||||
WhoisInfo *RuntimeClientWhoisInfo
|
||||
}
|
||||
|
||||
// RuntimeClientWhoisInfo is the filtered WHOIS data for a runtime client.
|
||||
@@ -83,7 +83,7 @@ type clientsContainer struct {
|
||||
ipToRC map[string]*RuntimeClient // IP -> runtime client
|
||||
lock sync.Mutex
|
||||
|
||||
allTags map[string]bool
|
||||
allTags *aghstrings.Set
|
||||
|
||||
// dhcpServer is used for looking up clients IP addresses by MAC addresses
|
||||
dhcpServer *dhcpd.Server
|
||||
@@ -111,10 +111,7 @@ func (clients *clientsContainer) Init(
|
||||
clients.idIndex = make(map[string]*Client)
|
||||
clients.ipToRC = make(map[string]*RuntimeClient)
|
||||
|
||||
clients.allTags = make(map[string]bool)
|
||||
for _, t := range clientTags {
|
||||
clients.allTags[t] = false
|
||||
}
|
||||
clients.allTags = aghstrings.NewSet(clientTags...)
|
||||
|
||||
clients.dhcpServer = dhcpServer
|
||||
clients.etcHosts = etcHosts
|
||||
@@ -163,9 +160,8 @@ type clientObject struct {
|
||||
Upstreams []string `yaml:"upstreams"`
|
||||
}
|
||||
|
||||
func (clients *clientsContainer) tagKnown(tag string) bool {
|
||||
_, ok := clients.allTags[tag]
|
||||
return ok
|
||||
func (clients *clientsContainer) tagKnown(tag string) (ok bool) {
|
||||
return clients.allTags.Has(tag)
|
||||
}
|
||||
|
||||
func (clients *clientsContainer) addFromConfig(objects []clientObject) {
|
||||
|
||||
@@ -7,31 +7,38 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// clientJSON is a common structure used by several handlers to deal with
|
||||
// clients. Some of the fields are only necessary in one or two handlers and
|
||||
// are thus made pointers with an omitempty tag.
|
||||
//
|
||||
// TODO(a.garipov): Consider using nullbool and an optional string here? Or
|
||||
// split into several structs?
|
||||
type clientJSON struct {
|
||||
IDs []string `json:"ids"`
|
||||
Tags []string `json:"tags"`
|
||||
Name string `json:"name"`
|
||||
UseGlobalSettings bool `json:"use_global_settings"`
|
||||
FilteringEnabled bool `json:"filtering_enabled"`
|
||||
ParentalEnabled bool `json:"parental_enabled"`
|
||||
SafeSearchEnabled bool `json:"safesearch_enabled"`
|
||||
SafeBrowsingEnabled bool `json:"safebrowsing_enabled"`
|
||||
// Disallowed, if non-nil and false, means that the client's IP is
|
||||
// allowed. Otherwise, the IP is blocked.
|
||||
Disallowed *bool `json:"disallowed,omitempty"`
|
||||
|
||||
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
||||
BlockedServices []string `json:"blocked_services"`
|
||||
// DisallowedRule is the rule due to which the client is disallowed.
|
||||
// If Disallowed is true and this string is empty, the client IP is
|
||||
// disallowed by the "allowed IP list", that is it is not included in
|
||||
// the allowlist.
|
||||
DisallowedRule *string `json:"disallowed_rule,omitempty"`
|
||||
|
||||
Upstreams []string `json:"upstreams"`
|
||||
WhoisInfo *RuntimeClientWhoisInfo `json:"whois_info,omitempty"`
|
||||
|
||||
WhoisInfo *RuntimeClientWhoisInfo `json:"whois_info"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Disallowed - if true -- client's IP is not disallowed
|
||||
// Otherwise, it is blocked.
|
||||
Disallowed bool `json:"disallowed"`
|
||||
BlockedServices []string `json:"blocked_services"`
|
||||
IDs []string `json:"ids"`
|
||||
Tags []string `json:"tags"`
|
||||
Upstreams []string `json:"upstreams"`
|
||||
|
||||
// DisallowedRule - the rule due to which the client is disallowed
|
||||
// If Disallowed is true, and this string is empty - it means that the client IP
|
||||
// is disallowed by the "allowed IP list", i.e. it is not included in allowed.
|
||||
DisallowedRule string `json:"disallowed_rule"`
|
||||
FilteringEnabled bool `json:"filtering_enabled"`
|
||||
ParentalEnabled bool `json:"parental_enabled"`
|
||||
SafeBrowsingEnabled bool `json:"safebrowsing_enabled"`
|
||||
SafeSearchEnabled bool `json:"safesearch_enabled"`
|
||||
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
||||
UseGlobalSettings bool `json:"use_global_settings"`
|
||||
}
|
||||
|
||||
type runtimeClientJSON struct {
|
||||
@@ -126,8 +133,6 @@ func clientToJSON(c *Client) clientJSON {
|
||||
BlockedServices: c.BlockedServices,
|
||||
|
||||
Upstreams: c.Upstreams,
|
||||
|
||||
WhoisInfo: &RuntimeClientWhoisInfo{},
|
||||
}
|
||||
|
||||
return cj
|
||||
@@ -243,7 +248,8 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
|
||||
}
|
||||
} else {
|
||||
cj = clientToJSON(c)
|
||||
cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip)
|
||||
disallowed, rule := clients.dnsServer.IsBlockedIP(ip)
|
||||
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
||||
}
|
||||
|
||||
data = append(data, map[string]clientJSON{
|
||||
@@ -279,8 +285,8 @@ func (clients *clientsContainer) findRuntime(ip net.IP, idStr string) (cj client
|
||||
|
||||
cj = clientJSON{
|
||||
IDs: []string{idStr},
|
||||
Disallowed: disallowed,
|
||||
DisallowedRule: rule,
|
||||
Disallowed: &disallowed,
|
||||
DisallowedRule: &rule,
|
||||
WhoisInfo: &RuntimeClientWhoisInfo{},
|
||||
}
|
||||
|
||||
@@ -288,7 +294,8 @@ func (clients *clientsContainer) findRuntime(ip net.IP, idStr string) (cj client
|
||||
}
|
||||
|
||||
cj = runtimeClientToJSON(idStr, rc)
|
||||
cj.Disallowed, cj.DisallowedRule = clients.dnsServer.IsBlockedIP(ip)
|
||||
disallowed, rule := clients.dnsServer.IsBlockedIP(ip)
|
||||
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
||||
|
||||
return cj, true
|
||||
}
|
||||
|
||||
@@ -47,10 +47,16 @@ type configuration struct {
|
||||
BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server
|
||||
BetaBindPort int `yaml:"beta_bind_port"` // BetaBindPort is the port for new client
|
||||
Users []User `yaml:"users"` // Users that can access HTTP server
|
||||
ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client
|
||||
Language string `yaml:"language"` // two-letter ISO 639-1 language code
|
||||
RlimitNoFile uint `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default)
|
||||
DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060
|
||||
// AuthAttempts is the maximum number of failed login attempts a user
|
||||
// can do before being blocked.
|
||||
AuthAttempts uint `yaml:"auth_attempts"`
|
||||
// AuthBlockMin is the duration, in minutes, of the block of new login
|
||||
// attempts after AuthAttempts unsuccessful login attempts.
|
||||
AuthBlockMin uint `yaml:"block_auth_min"`
|
||||
ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client
|
||||
Language string `yaml:"language"` // two-letter ISO 639-1 language code
|
||||
RlimitNoFile uint `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default)
|
||||
DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060
|
||||
|
||||
// TTL for a web session (in hours)
|
||||
// An active session is automatically refreshed once a day.
|
||||
@@ -137,6 +143,8 @@ var config = configuration{
|
||||
BindPort: 3000,
|
||||
BetaBindPort: 0,
|
||||
BindHost: net.IP{0, 0, 0, 0},
|
||||
AuthAttempts: 5,
|
||||
AuthBlockMin: 15,
|
||||
DNS: dnsConfig{
|
||||
BindHosts: []net.IP{{0, 0, 0, 0}},
|
||||
Port: 53,
|
||||
|
||||
@@ -43,7 +43,7 @@ func testStartFilterListener(t *testing.T) net.Listener {
|
||||
|
||||
func TestFilters(t *testing.T) {
|
||||
l := testStartFilterListener(t)
|
||||
dir := prepareTestDir(t)
|
||||
dir := t.TempDir()
|
||||
|
||||
Context = homeContext{
|
||||
workDir: dir,
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
@@ -139,8 +138,8 @@ func setupContext(args options) {
|
||||
|
||||
initConfig()
|
||||
|
||||
Context.tlsRoots = util.LoadSystemRootCAs()
|
||||
Context.tlsCiphers = util.InitTLSCiphers()
|
||||
Context.tlsRoots = LoadSystemRootCAs()
|
||||
Context.tlsCiphers = InitTLSCiphers()
|
||||
Context.transport = &http.Transport{
|
||||
DialContext: customDialContext,
|
||||
Proxy: getHTTPProxy,
|
||||
@@ -185,6 +184,10 @@ func setupConfig(args options) {
|
||||
|
||||
Context.dhcpServer = dhcpd.Create(config.DHCP)
|
||||
if Context.dhcpServer == nil {
|
||||
// TODO(a.garipov): There are a lot of places in the code right
|
||||
// now which assume that the DHCP server can be nil despite this
|
||||
// condition. Inspect them and perhaps rewrite them to use
|
||||
// Enabled() instead.
|
||||
log.Fatalf("can't initialize dhcp module")
|
||||
}
|
||||
|
||||
@@ -283,7 +286,21 @@ func run(args options) {
|
||||
|
||||
sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
|
||||
GLMode = args.glinetMode
|
||||
Context.auth = InitAuth(sessFilename, config.Users, config.WebSessionTTLHours*60*60)
|
||||
var arl *authRateLimiter
|
||||
if config.AuthAttempts > 0 && config.AuthBlockMin > 0 {
|
||||
arl = newAuthRateLimiter(
|
||||
time.Duration(config.AuthBlockMin)*time.Minute,
|
||||
config.AuthAttempts,
|
||||
)
|
||||
} else {
|
||||
log.Info("authratelimiter is disabled")
|
||||
}
|
||||
Context.auth = InitAuth(
|
||||
sessFilename,
|
||||
config.Users,
|
||||
config.WebSessionTTLHours*60*60,
|
||||
arl,
|
||||
)
|
||||
if Context.auth == nil {
|
||||
log.Fatalf("Couldn't initialize Auth module")
|
||||
}
|
||||
@@ -332,7 +349,10 @@ func run(args options) {
|
||||
}()
|
||||
|
||||
if Context.dhcpServer != nil {
|
||||
_ = Context.dhcpServer.Start()
|
||||
err = Context.dhcpServer.Start()
|
||||
if err != nil {
|
||||
log.Error("starting dhcp server: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,7 +658,9 @@ func detectFirstRun() bool {
|
||||
return errors.Is(err, os.ErrNotExist)
|
||||
}
|
||||
|
||||
// Connect to a remote server resolving hostname using our own DNS server
|
||||
// Connect to a remote server resolving hostname using our own DNS server.
|
||||
//
|
||||
// TODO(e.burkov): This messy logic should be decomposed and clarified.
|
||||
func customDialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
|
||||
log.Tracef("network:%v addr:%v", network, addr)
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ func TestHome(t *testing.T) {
|
||||
// Init new context
|
||||
Context = homeContext{}
|
||||
|
||||
dir := prepareTestDir(t)
|
||||
dir := t.TempDir()
|
||||
fn := filepath.Join(dir, "AdGuardHome.yaml")
|
||||
|
||||
// Prepare the test config
|
||||
|
||||
@@ -6,52 +6,46 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// --------------------
|
||||
// internationalization
|
||||
// --------------------
|
||||
var allowedLanguages = map[string]bool{
|
||||
"be": true,
|
||||
"bg": true,
|
||||
"cs": true,
|
||||
"da": true,
|
||||
"de": true,
|
||||
"en": true,
|
||||
"es": true,
|
||||
"fa": true,
|
||||
"fr": true,
|
||||
"hr": true,
|
||||
"hu": true,
|
||||
"id": true,
|
||||
"it": true,
|
||||
"ja": true,
|
||||
"ko": true,
|
||||
"nl": true,
|
||||
"no": true,
|
||||
"pl": true,
|
||||
"pt-br": true,
|
||||
"pt-pt": true,
|
||||
"ro": true,
|
||||
"ru": true,
|
||||
"si-lk": true,
|
||||
"sk": true,
|
||||
"sl": true,
|
||||
"sr-cs": true,
|
||||
"sv": true,
|
||||
"th": true,
|
||||
"tr": true,
|
||||
"vi": true,
|
||||
"zh-cn": true,
|
||||
"zh-hk": true,
|
||||
"zh-tw": true,
|
||||
}
|
||||
|
||||
func isLanguageAllowed(language string) bool {
|
||||
l := strings.ToLower(language)
|
||||
return allowedLanguages[l]
|
||||
}
|
||||
// TODO(a.garipov): Get rid of a global variable?
|
||||
var allowedLanguages = aghstrings.NewSet(
|
||||
"be",
|
||||
"bg",
|
||||
"cs",
|
||||
"da",
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fa",
|
||||
"fr",
|
||||
"hr",
|
||||
"hu",
|
||||
"id",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"nl",
|
||||
"no",
|
||||
"pl",
|
||||
"pt-br",
|
||||
"pt-pt",
|
||||
"ro",
|
||||
"ru",
|
||||
"si-lk",
|
||||
"sk",
|
||||
"sl",
|
||||
"sr-cs",
|
||||
"sv",
|
||||
"th",
|
||||
"tr",
|
||||
"vi",
|
||||
"zh-cn",
|
||||
"zh-hk",
|
||||
"zh-tw",
|
||||
)
|
||||
|
||||
func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
@@ -80,12 +74,15 @@ func handleI18nChangeLanguage(w http.ResponseWriter, r *http.Request) {
|
||||
msg := "empty language specified"
|
||||
log.Println(msg)
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
if !isLanguageAllowed(language) {
|
||||
|
||||
if !allowedLanguages.Has(language) {
|
||||
msg := fmt.Sprintf("unknown language specified: %s", language)
|
||||
log.Println(msg)
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
@@ -82,7 +83,7 @@ func TestRDNS_Begin(t *testing.T) {
|
||||
list: map[string]*Client{},
|
||||
idIndex: tc.cliIDIndex,
|
||||
ipToRC: map[string]*RuntimeClient{},
|
||||
allTags: map[string]bool{},
|
||||
allTags: aghstrings.NewSet(),
|
||||
},
|
||||
}
|
||||
ipCache.Clear()
|
||||
@@ -172,7 +173,7 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||
list: map[string]*Client{},
|
||||
idIndex: map[string]*Client{},
|
||||
ipToRC: map[string]*RuntimeClient{},
|
||||
allTags: map[string]bool{},
|
||||
allTags: aghstrings.NewSet(),
|
||||
}
|
||||
ch := make(chan net.IP)
|
||||
rdns := &RDNS{
|
||||
|
||||
@@ -15,12 +15,15 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
var tlsWebHandlersRegistered = false
|
||||
@@ -551,3 +554,96 @@ func (t *TLSMod) registerWebHandlers() {
|
||||
httpRegister(http.MethodPost, "/control/tls/configure", t.handleTLSConfigure)
|
||||
httpRegister(http.MethodPost, "/control/tls/validate", t.handleTLSValidate)
|
||||
}
|
||||
|
||||
// LoadSystemRootCAs tries to load root certificates from the operating system.
|
||||
// It returns nil in case nothing is found so that that Go.crypto will use it's
|
||||
// default algorithm to find system root CA list.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/internal/issues/1311.
|
||||
func LoadSystemRootCAs() (roots *x509.CertPool) {
|
||||
// TODO(e.burkov): Use build tags instead.
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Directories with the system root certificates, which aren't supported
|
||||
// by Go.crypto.
|
||||
dirs := []string{
|
||||
// Entware.
|
||||
"/opt/etc/ssl/certs",
|
||||
}
|
||||
roots = x509.NewCertPool()
|
||||
for _, dir := range dirs {
|
||||
fis, err := ioutil.ReadDir(dir)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("opening directory: %q: %s", dir, err)
|
||||
}
|
||||
|
||||
var rootsAdded bool
|
||||
for _, fi := range fis {
|
||||
var certData []byte
|
||||
certData, err = ioutil.ReadFile(filepath.Join(dir, fi.Name()))
|
||||
if err == nil && roots.AppendCertsFromPEM(certData) {
|
||||
rootsAdded = true
|
||||
}
|
||||
}
|
||||
|
||||
if rootsAdded {
|
||||
return roots
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitTLSCiphers performs the same work as initDefaultCipherSuites() from
|
||||
// crypto/tls/common.go but don't uses lots of other default ciphers.
|
||||
func InitTLSCiphers() (ciphers []uint16) {
|
||||
// Check the cpu flags for each platform that has optimized GCM
|
||||
// implementations. The worst case is when all these variables are
|
||||
// false.
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES &&
|
||||
cpu.S390X.HasAESCBC &&
|
||||
cpu.S390X.HasAESCTR &&
|
||||
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
|
||||
hasGCMAsm = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
|
||||
)
|
||||
|
||||
if hasGCMAsm {
|
||||
// If AES-GCM hardware is provided then prioritize AES-GCM
|
||||
// cipher suites.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
} else {
|
||||
// Without AES-GCM hardware, we put the ChaCha20-Poly1305 cipher
|
||||
// suites first.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
}
|
||||
|
||||
return append(
|
||||
ciphers,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@ package home
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/google/renameio/maybe"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -14,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
// currentSchemaVersion is the current schema version.
|
||||
const currentSchemaVersion = 9
|
||||
const currentSchemaVersion = 10
|
||||
|
||||
// These aliases are provided for convenience.
|
||||
type (
|
||||
@@ -75,6 +79,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
|
||||
upgradeSchema6to7,
|
||||
upgradeSchema7to8,
|
||||
upgradeSchema8to9,
|
||||
upgradeSchema9to10,
|
||||
}
|
||||
|
||||
n := 0
|
||||
@@ -456,7 +461,7 @@ func upgradeSchema7to8(diskConf yobj) (err error) {
|
||||
bindHostVal := dns["bind_host"]
|
||||
bindHost, ok := bindHostVal.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("undexpected type of dns.bind_host: %T", bindHostVal)
|
||||
return fmt.Errorf("unexpected type of dns.bind_host: %T", bindHostVal)
|
||||
}
|
||||
|
||||
delete(dns, "bind_host")
|
||||
@@ -490,10 +495,19 @@ func upgradeSchema8to9(diskConf yobj) (err error) {
|
||||
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
|
||||
}
|
||||
|
||||
autohostTLDVal := dns["autohost_tld"]
|
||||
autohostTLDVal, ok := dns["autohost_tld"]
|
||||
if !ok {
|
||||
// This happens when upgrading directly from v0.105.2, because
|
||||
// dns.autohost_tld was never set to any value. Go on and leave
|
||||
// it that way.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2988.
|
||||
return nil
|
||||
}
|
||||
|
||||
autohostTLD, ok := autohostTLDVal.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("undexpected type of dns.autohost_tld: %T", autohostTLDVal)
|
||||
return fmt.Errorf("unexpected type of dns.autohost_tld: %T", autohostTLDVal)
|
||||
}
|
||||
|
||||
delete(dns, "autohost_tld")
|
||||
@@ -502,6 +516,102 @@ func upgradeSchema8to9(diskConf yobj) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// addQUICPort inserts a port into QUIC upstream's hostname if it is missing.
|
||||
func addQUICPort(ups string, port int) (withPort string) {
|
||||
if ups == "" || ups[0] == '#' {
|
||||
return ups
|
||||
}
|
||||
|
||||
var doms string
|
||||
withPort = ups
|
||||
if strings.HasPrefix(ups, "[/") {
|
||||
domsAndUps := strings.Split(strings.TrimPrefix(ups, "[/"), "/]")
|
||||
if len(domsAndUps) != 2 {
|
||||
return ups
|
||||
}
|
||||
|
||||
doms, withPort = "[/"+domsAndUps[0]+"/]", domsAndUps[1]
|
||||
}
|
||||
|
||||
if !strings.Contains(withPort, "://") {
|
||||
return ups
|
||||
}
|
||||
|
||||
upsURL, err := url.Parse(withPort)
|
||||
if err != nil || upsURL.Scheme != "quic" {
|
||||
return ups
|
||||
}
|
||||
|
||||
var host string
|
||||
host, err = aghnet.SplitHost(upsURL.Host)
|
||||
if err != nil || host != upsURL.Host {
|
||||
return ups
|
||||
}
|
||||
|
||||
upsURL.Host = strings.Join([]string{host, strconv.Itoa(port)}, ":")
|
||||
|
||||
return doms + upsURL.String()
|
||||
}
|
||||
|
||||
// upgradeSchema9to10 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
// 'dns':
|
||||
// 'upstream_dns':
|
||||
// - 'quic://some-upstream.com'
|
||||
//
|
||||
// # AFTER:
|
||||
// 'dns':
|
||||
// 'upstream_dns':
|
||||
// - 'quic://some-upstream.com:784'
|
||||
//
|
||||
func upgradeSchema9to10(diskConf yobj) (err error) {
|
||||
log.Printf("Upgrade yaml: 9 to 10")
|
||||
|
||||
diskConf["schema_version"] = 10
|
||||
|
||||
dnsVal, ok := diskConf["dns"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var dns yobj
|
||||
dns, ok = dnsVal.(yobj)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
|
||||
}
|
||||
|
||||
const quicPort = 784
|
||||
for _, upsField := range []string{
|
||||
"upstream_dns",
|
||||
"local_ptr_upstreams",
|
||||
} {
|
||||
var upsVal any
|
||||
upsVal, ok = dns[upsField]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var ups yarr
|
||||
ups, ok = upsVal.(yarr)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of dns.%s: %T", upsField, upsVal)
|
||||
}
|
||||
|
||||
var u string
|
||||
for i, uVal := range ups {
|
||||
u, ok = uVal.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of upstream field: %T", uVal)
|
||||
}
|
||||
|
||||
ups[i] = addQUICPort(u, quicPort)
|
||||
}
|
||||
dns[upsField] = ups
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Replace with log.Output when we port it to our logging
|
||||
// package.
|
||||
func funcName() string {
|
||||
|
||||
@@ -92,28 +92,54 @@ func TestUpgradeSchema7to8(t *testing.T) {
|
||||
|
||||
func TestUpgradeSchema8to9(t *testing.T) {
|
||||
const tld = "foo"
|
||||
oldConf := yobj{
|
||||
"dns": yobj{
|
||||
"autohost_tld": tld,
|
||||
},
|
||||
"schema_version": 8,
|
||||
}
|
||||
|
||||
err := upgradeSchema8to9(oldConf)
|
||||
require.NoError(t, err)
|
||||
t.Run("with_autohost_tld", func(t *testing.T) {
|
||||
oldConf := yobj{
|
||||
"dns": yobj{
|
||||
"autohost_tld": tld,
|
||||
},
|
||||
"schema_version": 8,
|
||||
}
|
||||
|
||||
require.Equal(t, oldConf["schema_version"], 9)
|
||||
err := upgradeSchema8to9(oldConf)
|
||||
require.NoError(t, err)
|
||||
|
||||
dnsVal, ok := oldConf["dns"]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, oldConf["schema_version"], 9)
|
||||
|
||||
newDNSConf, ok := dnsVal.(yobj)
|
||||
require.True(t, ok)
|
||||
dnsVal, ok := oldConf["dns"]
|
||||
require.True(t, ok)
|
||||
|
||||
localDomainName, ok := newDNSConf["local_domain_name"].(string)
|
||||
require.True(t, ok)
|
||||
newDNSConf, ok := dnsVal.(yobj)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, tld, localDomainName)
|
||||
localDomainName, ok := newDNSConf["local_domain_name"].(string)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, tld, localDomainName)
|
||||
})
|
||||
|
||||
t.Run("without_autohost_tld", func(t *testing.T) {
|
||||
oldConf := yobj{
|
||||
"dns": yobj{},
|
||||
"schema_version": 8,
|
||||
}
|
||||
|
||||
err := upgradeSchema8to9(oldConf)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, oldConf["schema_version"], 9)
|
||||
|
||||
dnsVal, ok := oldConf["dns"]
|
||||
require.True(t, ok)
|
||||
|
||||
newDNSConf, ok := dnsVal.(yobj)
|
||||
require.True(t, ok)
|
||||
|
||||
// Should be nil in order to be set to the default value by the
|
||||
// following config rewrite.
|
||||
_, ok = newDNSConf["local_domain_name"]
|
||||
require.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
// assertEqualExcept removes entries from configs and compares them.
|
||||
@@ -188,3 +214,157 @@ func testDNSConf(schemaVersion int) (dnsConf yobj) {
|
||||
|
||||
return dnsConf
|
||||
}
|
||||
|
||||
func TestAddQUICPort(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
ups string
|
||||
want string
|
||||
}{{
|
||||
name: "simple_ip",
|
||||
ups: "8.8.8.8",
|
||||
want: "8.8.8.8",
|
||||
}, {
|
||||
name: "url_ipv4",
|
||||
ups: "quic://8.8.8.8",
|
||||
want: "quic://8.8.8.8:784",
|
||||
}, {
|
||||
name: "url_ipv4_with_port",
|
||||
ups: "quic://8.8.8.8:25565",
|
||||
want: "quic://8.8.8.8:25565",
|
||||
}, {
|
||||
name: "url_ipv6",
|
||||
ups: "quic://[::1]",
|
||||
want: "quic://[::1]:784",
|
||||
}, {
|
||||
name: "url_ipv6_invalid",
|
||||
ups: "quic://::1",
|
||||
want: "quic://::1",
|
||||
}, {
|
||||
name: "url_ipv6_with_port",
|
||||
ups: "quic://[::1]:25565",
|
||||
want: "quic://[::1]:25565",
|
||||
}, {
|
||||
name: "url_hostname",
|
||||
ups: "quic://example.com",
|
||||
want: "quic://example.com:784",
|
||||
}, {
|
||||
name: "url_hostname_with_port",
|
||||
ups: "quic://example.com:25565",
|
||||
want: "quic://example.com:25565",
|
||||
}, {
|
||||
name: "url_hostname_with_endpoint",
|
||||
ups: "quic://example.com/some-endpoint",
|
||||
want: "quic://example.com:784/some-endpoint",
|
||||
}, {
|
||||
name: "url_hostname_with_port_endpoint",
|
||||
ups: "quic://example.com:25565/some-endpoint",
|
||||
want: "quic://example.com:25565/some-endpoint",
|
||||
}, {
|
||||
name: "non-quic_proto",
|
||||
ups: "tls://example.com",
|
||||
want: "tls://example.com",
|
||||
}, {
|
||||
name: "comment",
|
||||
ups: "# comment",
|
||||
want: "# comment",
|
||||
}, {
|
||||
name: "blank",
|
||||
ups: "",
|
||||
want: "",
|
||||
}, {
|
||||
name: "with_domain_ip",
|
||||
ups: "[/example.domain/]8.8.8.8",
|
||||
want: "[/example.domain/]8.8.8.8",
|
||||
}, {
|
||||
name: "with_domain_url",
|
||||
ups: "[/example.domain/]quic://example.com",
|
||||
want: "[/example.domain/]quic://example.com:784",
|
||||
}, {
|
||||
name: "invalid_domain",
|
||||
ups: "[/exmaple.domain]quic://example.com",
|
||||
want: "[/exmaple.domain]quic://example.com",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
withPort := addQUICPort(tc.ups, 784)
|
||||
|
||||
assert.Equal(t, tc.want, withPort)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgradeSchema9to10(t *testing.T) {
|
||||
const ultimateAns = 42
|
||||
|
||||
testCases := []struct {
|
||||
ups any
|
||||
want any
|
||||
wantErr string
|
||||
name string
|
||||
}{{
|
||||
ups: yarr{"quic://8.8.8.8"},
|
||||
want: yarr{"quic://8.8.8.8:784"},
|
||||
wantErr: "",
|
||||
name: "success",
|
||||
}, {
|
||||
ups: ultimateAns,
|
||||
want: nil,
|
||||
wantErr: "unexpected type of dns.upstream_dns: int",
|
||||
name: "bad_yarr_type",
|
||||
}, {
|
||||
ups: yarr{ultimateAns},
|
||||
want: nil,
|
||||
wantErr: "unexpected type of upstream field: int",
|
||||
name: "bad_upstream_type",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
conf := yobj{
|
||||
"dns": yobj{
|
||||
"upstream_dns": tc.ups,
|
||||
},
|
||||
"schema_version": 9,
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := upgradeSchema9to10(conf)
|
||||
|
||||
if tc.wantErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.wantErr, err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, conf["schema_version"], 10)
|
||||
|
||||
dnsVal, ok := conf["dns"]
|
||||
require.True(t, ok)
|
||||
|
||||
newDNSConf, ok := dnsVal.(yobj)
|
||||
require.True(t, ok)
|
||||
|
||||
fixedUps, ok := newDNSConf["upstream_dns"].(yarr)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, tc.want, fixedUps)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("no_dns", func(t *testing.T) {
|
||||
err := upgradeSchema9to10(yobj{})
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("bad_dns", func(t *testing.T) {
|
||||
err := upgradeSchema9to10(yobj{
|
||||
"dns": ultimateAns,
|
||||
})
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "unexpected type of dns: int", err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ type Whois struct {
|
||||
clients *clientsContainer
|
||||
ipChan chan net.IP
|
||||
|
||||
// dialContext specifies the dial function for creating unencrypted TCP
|
||||
// connections.
|
||||
dialContext func(ctx context.Context, network, addr string) (conn net.Conn, err error)
|
||||
|
||||
// Contains IP addresses of clients
|
||||
// An active IP address is resolved once again after it expires.
|
||||
// If IP address couldn't be resolved, it stays here for some time to prevent further attempts to resolve the same IP.
|
||||
@@ -45,7 +49,8 @@ func initWhois(clients *clientsContainer) *Whois {
|
||||
EnableLRU: true,
|
||||
MaxCount: 10000,
|
||||
}),
|
||||
ipChan: make(chan net.IP, 255),
|
||||
dialContext: customDialContext,
|
||||
ipChan: make(chan net.IP, 255),
|
||||
}
|
||||
|
||||
go w.workerLoop()
|
||||
@@ -124,7 +129,7 @@ func (w *Whois) query(ctx context.Context, target, serverAddr string) (string, e
|
||||
if addr == "whois.arin.net" {
|
||||
target = "n + " + target
|
||||
}
|
||||
conn, err := customDialContext(ctx, "tcp", serverAddr)
|
||||
conn, err := w.dialContext(ctx, "tcp", serverAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -2,44 +2,77 @@ package home
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func prepareTestDNSServer(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
config.DNS.Port = 1234
|
||||
|
||||
var err error
|
||||
Context.dnsServer, err = dnsforward.NewServer(dnsforward.DNSCreateParams{})
|
||||
require.NoError(t, err)
|
||||
|
||||
conf := &dnsforward.ServerConfig{}
|
||||
conf.UpstreamDNS = []string{"8.8.8.8"}
|
||||
|
||||
err = Context.dnsServer.Prepare(conf)
|
||||
require.NoError(t, err)
|
||||
// fakeConn is a mock implementation of net.Conn to simplify testing.
|
||||
//
|
||||
// TODO(e.burkov): Search for other places in code where it may be used. Move
|
||||
// into aghtest then.
|
||||
type fakeConn struct {
|
||||
// Conn is embedded here simply to make *fakeConn a net.Conn without
|
||||
// actually implementing all methods.
|
||||
net.Conn
|
||||
data []byte
|
||||
}
|
||||
|
||||
// TODO(e.burkov): It's kind of complicated to get rid of network access in this
|
||||
// test. The thing is that *Whois creates new *net.Dialer each time it requests
|
||||
// the server, so it becomes hard to simulate handling of request from test even
|
||||
// with substituted upstream. However, it must be done.
|
||||
func TestWhois(t *testing.T) {
|
||||
prepareTestDNSServer(t)
|
||||
// Write implements net.Conn interface for *fakeConn. It always returns 0 and a
|
||||
// nil error without mutating the slice.
|
||||
func (c *fakeConn) Write(_ []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
w := Whois{timeoutMsec: 5000}
|
||||
resp, err := w.queryAll(context.Background(), "8.8.8.8")
|
||||
// Read implements net.Conn interface for *fakeConn. It puts the content of
|
||||
// c.data field into b up to the b's capacity.
|
||||
func (c *fakeConn) Read(b []byte) (n int, err error) {
|
||||
return copy(b, c.data), io.EOF
|
||||
}
|
||||
|
||||
// Close implements net.Conn interface for *fakeConn. It always returns nil.
|
||||
func (c *fakeConn) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline implements net.Conn interface for *fakeConn. It always
|
||||
// returns nil.
|
||||
func (c *fakeConn) SetReadDeadline(_ time.Time) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// fakeDial is a mock implementation of customDialContext to simplify testing.
|
||||
func (c *fakeConn) fakeDial(ctx context.Context, network, addr string) (conn net.Conn, err error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func TestWhois(t *testing.T) {
|
||||
const (
|
||||
nl = "\n"
|
||||
data = `OrgName: FakeOrg LLC` + nl +
|
||||
`City: Nonreal` + nl +
|
||||
`Country: Imagiland` + nl
|
||||
)
|
||||
|
||||
fc := &fakeConn{
|
||||
data: []byte(data),
|
||||
}
|
||||
|
||||
w := Whois{
|
||||
timeoutMsec: 5000,
|
||||
dialContext: fc.fakeDial,
|
||||
}
|
||||
resp, err := w.queryAll(context.Background(), "1.2.3.4")
|
||||
assert.NoError(t, err)
|
||||
|
||||
m := whoisParse(resp)
|
||||
require.NotEmpty(t, m)
|
||||
|
||||
assert.Equal(t, "Google LLC", m["orgname"])
|
||||
assert.Equal(t, "US", m["country"])
|
||||
assert.Equal(t, "Mountain View", m["city"])
|
||||
assert.Equal(t, "FakeOrg LLC", m["orgname"])
|
||||
assert.Equal(t, "Imagiland", m["country"])
|
||||
assert.Equal(t, "Nonreal", m["city"])
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
// Package util contains various utilities.
|
||||
//
|
||||
// TODO(a.garipov): Such packages are widely considered an antipattern. Remove
|
||||
// this when we refactor our project structure.
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
// LoadSystemRootCAs - load root CAs from the system
|
||||
// Return the x509 certificate pool object
|
||||
// Return nil if nothing has been found.
|
||||
// This means that Go.crypto will use its default algorithm to find system root CA list.
|
||||
// https://github.com/AdguardTeam/AdGuardHome/internal/issues/1311
|
||||
func LoadSystemRootCAs() *x509.CertPool {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Directories with the system root certificates, that aren't supported by Go.crypto
|
||||
dirs := []string{
|
||||
"/opt/etc/ssl/certs", // Entware
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
for _, dir := range dirs {
|
||||
fis, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
log.Error("opening directory: %q: %s", dir, err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
rootsAdded := false
|
||||
for _, fi := range fis {
|
||||
var certData []byte
|
||||
certData, err = ioutil.ReadFile(dir + "/" + fi.Name())
|
||||
if err == nil && roots.AppendCertsFromPEM(certData) {
|
||||
rootsAdded = true
|
||||
}
|
||||
}
|
||||
|
||||
if rootsAdded {
|
||||
return roots
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitTLSCiphers - the same as initDefaultCipherSuites() from src/crypto/tls/common.go
|
||||
// but with the difference that we don't use so many other default ciphers.
|
||||
func InitTLSCiphers() []uint16 {
|
||||
var ciphers []uint16
|
||||
|
||||
// Check the cpu flags for each platform that has optimized GCM implementations.
|
||||
// Worst case, these variables will just all be false.
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
|
||||
hasGCMAsm = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
|
||||
)
|
||||
|
||||
if hasGCMAsm {
|
||||
// If AES-GCM hardware is provided then prioritise AES-GCM
|
||||
// cipher suites.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
} else {
|
||||
// Without AES-GCM hardware, we put the ChaCha20-Poly1305
|
||||
// cipher suites first.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
}
|
||||
|
||||
otherCiphers := []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
}
|
||||
ciphers = append(ciphers, otherCiphers...)
|
||||
return ciphers
|
||||
}
|
||||
@@ -4,7 +4,21 @@
|
||||
|
||||
## v0.106: API changes
|
||||
|
||||
## New `"private_upstream"` field in `POST /test_upstream_dns`
|
||||
### The field `"supported_tags"` in `GET /control/clients`
|
||||
|
||||
* Prefiously undocumented field `"supported_tags"` in the response is now
|
||||
documented.
|
||||
|
||||
### The field `"whois_info"` in `GET /control/clients`
|
||||
|
||||
* Objects in the `"auto_clients"` array now have the `"whois_info"` field.
|
||||
|
||||
### New response code in `POST /control/login`
|
||||
|
||||
* `429` is returned when user is out of login attempts. It adds the
|
||||
`Retry-After` header with the number of seconds of block left in it.
|
||||
|
||||
### New `"private_upstream"` field in `POST /test_upstream_dns`
|
||||
|
||||
* The new optional field `"private_upstream"` of `UpstreamConfig` contains the
|
||||
upstream servers for resolving locally-served ip addresses to be checked.
|
||||
|
||||
@@ -1078,6 +1078,12 @@
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK.'
|
||||
'400':
|
||||
'description': >
|
||||
Invalid username or password.
|
||||
'429':
|
||||
'description': >
|
||||
Out of login attempts.
|
||||
'/logout':
|
||||
'get':
|
||||
'tags':
|
||||
@@ -2228,6 +2234,10 @@
|
||||
'type': 'array'
|
||||
'items':
|
||||
'type': 'string'
|
||||
'tags':
|
||||
'items':
|
||||
'type': 'string'
|
||||
'type': 'array'
|
||||
'ClientAuto':
|
||||
'type': 'object'
|
||||
'description': 'Auto-Client information'
|
||||
@@ -2244,6 +2254,8 @@
|
||||
'type': 'string'
|
||||
'description': 'The source of this information'
|
||||
'example': 'etc/hosts'
|
||||
'whois_info':
|
||||
'$ref': '#/components/schemas/WhoisInfo'
|
||||
'ClientUpdate':
|
||||
'type': 'object'
|
||||
'description': 'Client update request'
|
||||
@@ -2378,6 +2390,10 @@
|
||||
'$ref': '#/components/schemas/ClientsArray'
|
||||
'auto_clients':
|
||||
'$ref': '#/components/schemas/ClientsAutoArray'
|
||||
'supported_tags':
|
||||
'items':
|
||||
'type': 'string'
|
||||
'type': 'array'
|
||||
'ClientsArray':
|
||||
'type': 'array'
|
||||
'items':
|
||||
|
||||
@@ -27,7 +27,7 @@ set -f -u
|
||||
|
||||
# Deferred Helpers
|
||||
|
||||
not_found_msg='
|
||||
readonly not_found_msg='
|
||||
looks like a binary not found error.
|
||||
make sure you have installed the linter binaries using:
|
||||
|
||||
@@ -49,6 +49,29 @@ trap not_found EXIT
|
||||
|
||||
|
||||
|
||||
# Warnings
|
||||
|
||||
readonly go_min_version='go1.15'
|
||||
readonly go_min_version_prefix="go version ${go_min_version}"
|
||||
readonly go_version_msg="
|
||||
warning: your go version is different from the recommended minimal one (${go_min_version}).
|
||||
if you have the version installed, please set the GO environment variable.
|
||||
for example:
|
||||
|
||||
export GO='${go_min_version}'
|
||||
"
|
||||
case "$( "$GO" version )"
|
||||
in
|
||||
("$go_min_version_prefix"*)
|
||||
# Go on.
|
||||
;;
|
||||
(*)
|
||||
echo "$go_version_msg" 1>&2
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
|
||||
# Simple Analyzers
|
||||
|
||||
# blocklist_imports is a simple check against unwanted packages.
|
||||
@@ -99,7 +122,7 @@ exit_on_output() (
|
||||
cmd="$1"
|
||||
shift
|
||||
|
||||
output="$("$cmd" "$@" 2>&1)"
|
||||
output="$( "$cmd" "$@" 2>&1 )"
|
||||
exitcode="$?"
|
||||
if [ "$exitcode" != '0' ]
|
||||
then
|
||||
|
||||
Reference in New Issue
Block a user