Compare commits
24 Commits
3389-query
...
v0.108.0-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97af062f7b | ||
|
|
8004b135b1 | ||
|
|
4b9264531b | ||
|
|
9a506d3755 | ||
|
|
e320eb29c2 | ||
|
|
282f11a7c2 | ||
|
|
91f3e29c08 | ||
|
|
1fd6cf1a2f | ||
|
|
efed23701a | ||
|
|
ad1bf5cf6e | ||
|
|
ee8eb1d8a6 | ||
|
|
39f5c50acd | ||
|
|
d4a4bda645 | ||
|
|
2069eddf98 | ||
|
|
c652653ea4 | ||
|
|
6889837785 | ||
|
|
cf7c12c97b | ||
|
|
d88181343c | ||
|
|
e7e638443f | ||
|
|
2f515e8d8f | ||
|
|
e54fc9b1e9 | ||
|
|
66345e855e | ||
|
|
37e046acc4 | ||
|
|
f40ef76c79 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -16,10 +16,13 @@
|
||||
/dist/
|
||||
/filtering/tests/filtering.TestLotsOfRules*.pprof
|
||||
/filtering/tests/top-1m.csv
|
||||
/internal/next/AdGuardHome.yaml
|
||||
/launchpad_credentials
|
||||
/querylog.json*
|
||||
/snapcraft_login
|
||||
AdGuardHome*
|
||||
AdGuardHome
|
||||
AdGuardHome.exe
|
||||
AdGuardHome.yaml*
|
||||
coverage.txt
|
||||
node_modules/
|
||||
|
||||
|
||||
113
CHANGELOG.md
113
CHANGELOG.md
@@ -14,29 +14,94 @@ and this project adheres to
|
||||
<!--
|
||||
## [v0.108.0] - TBA
|
||||
|
||||
## [v0.107.33] - 2023-06-28 (APPROX.)
|
||||
## [v0.107.34] - 2023-07-26 (APPROX.)
|
||||
|
||||
See also the [v0.107.33 GitHub milestone][ms-v0.107.33].
|
||||
See also the [v0.107.34 GitHub milestone][ms-v0.107.34].
|
||||
|
||||
[ms-v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/milestone/68?closed=1
|
||||
[ms-v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/milestone/69?closed=1
|
||||
|
||||
NOTE: Add new changes BELOW THIS COMMENT.
|
||||
-->
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
## [v0.107.33] - 2023-07-03
|
||||
|
||||
See also the [v0.107.33 GitHub milestone][ms-v0.107.33].
|
||||
|
||||
### Added
|
||||
|
||||
- The ability to set inactivity periods for filtering blocked services in the
|
||||
configuration file ([#951]). The UI changes are coming in the upcoming
|
||||
releases.
|
||||
- The new command-line flag `--web-addr` is the address to serve the web UI on,
|
||||
in the host:port format.
|
||||
- The ability to set inactivity periods for filtering blocked services, both
|
||||
globally and per client, in the configuration file ([#951]). The UI changes
|
||||
are coming in the upcoming releases.
|
||||
- The ability to edit rewrite rules via `PUT /control/rewrite/update` HTTP API
|
||||
([#1577]).
|
||||
and the Web UI ([#1577]).
|
||||
|
||||
### Changed
|
||||
|
||||
#### Configuration Changes
|
||||
|
||||
In this release, the schema version has changed from 20 to 21.
|
||||
In this release, the schema version has changed from 20 to 23.
|
||||
|
||||
- Properties `bind_host`, `bind_port`, and `web_session_ttl` which used to setup
|
||||
web UI binding configuration, are now moved to a new object `http` containing
|
||||
new properties `address` and `session_ttl`:
|
||||
|
||||
```yaml
|
||||
# BEFORE:
|
||||
'bind_host': '1.2.3.4'
|
||||
'bind_port': 8080
|
||||
'web_session_ttl': 720
|
||||
|
||||
# AFTER:
|
||||
'http':
|
||||
'address': '1.2.3.4:8080'
|
||||
'session_ttl': '720h'
|
||||
```
|
||||
|
||||
Note that the new `http.session_ttl` property is now a duration string. To
|
||||
rollback this change, remove the new object `http`, set back `bind_host`,
|
||||
`bind_port`, `web_session_ttl`, and change the `schema_version` back to `22`.
|
||||
- Property `clients.persistent.blocked_services`, which in schema versions 21
|
||||
and earlier used to be a list containing ids of blocked services, is now an
|
||||
object containing ids and schedule for blocked services:
|
||||
|
||||
```yaml
|
||||
# BEFORE:
|
||||
'clients':
|
||||
'persistent':
|
||||
- 'name': 'client-name'
|
||||
'blocked_services':
|
||||
- id_1
|
||||
- id_2
|
||||
|
||||
# AFTER:
|
||||
'clients':
|
||||
'persistent':
|
||||
- 'name': client-name
|
||||
'blocked_services':
|
||||
'ids':
|
||||
- id_1
|
||||
- id_2
|
||||
'schedule':
|
||||
'time_zone': 'Local'
|
||||
'sun':
|
||||
'start': '0s'
|
||||
'end': '24h'
|
||||
'mon':
|
||||
'start': '1h'
|
||||
'end': '23h'
|
||||
```
|
||||
|
||||
To rollback this change, replace `clients.persistent.blocked_services` object
|
||||
with the list of ids of blocked services and change the `schema_version` back
|
||||
to `21`.
|
||||
- Property `dns.blocked_services`, which in schema versions 20 and earlier used
|
||||
to be a list containing ids of blocked services, is now an object containing
|
||||
ids and schedule for blocked services:
|
||||
@@ -80,8 +145,23 @@ In this release, the schema version has changed from 20 to 21.
|
||||
To rollback this change, replace `dns.blocked_services` object with the list
|
||||
of ids of blocked services and change the `schema_version` back to `20`.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `HEALTHCHECK` and `ENTRYPOINT` sections in `Dockerfile` ([#5939]). They cause
|
||||
a lot of issues, especially with tools like `docker-compose` and `podman`, and
|
||||
will be removed in a future release.
|
||||
- Flags `-h`, `--host`, `-p`, `--port` have been deprecated. The `-h` flag
|
||||
will work as an alias for `--help`, instead of the deprecated `--host` in the
|
||||
future releases.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ignoring of `/etc/hosts` file when resolving the hostnames of upstream DNS
|
||||
servers ([#5902]).
|
||||
- Excessive error logging when using DNS-over-QUIC ([#5285]).
|
||||
- Inability to set `bind_host` in `AdGuardHome.yaml` in Docker ([#4231],
|
||||
[#4235]).
|
||||
- The blocklists can now be deleted properly ([#5700]).
|
||||
- Queries with the question-section target `.`, for example `NS .`, are now
|
||||
counted in the statistics and correctly shown in the query log ([#5910]).
|
||||
- Safe Search not working with `AAAA` queries for domains that don't have `AAAA`
|
||||
@@ -89,12 +169,16 @@ In this release, the schema version has changed from 20 to 21.
|
||||
|
||||
[#951]: https://github.com/AdguardTeam/AdGuardHome/issues/951
|
||||
[#1577]: https://github.com/AdguardTeam/AdGuardHome/issues/1577
|
||||
[#4231]: https://github.com/AdguardTeam/AdGuardHome/issues/4231
|
||||
[#4235]: https://github.com/AdguardTeam/AdGuardHome/pull/4235
|
||||
[#5285]: https://github.com/AdguardTeam/AdGuardHome/issues/5285
|
||||
[#5700]: https://github.com/AdguardTeam/AdGuardHome/issues/5700
|
||||
[#5902]: https://github.com/AdguardTeam/AdGuardHome/issues/5902
|
||||
[#5910]: https://github.com/AdguardTeam/AdGuardHome/issues/5910
|
||||
[#5913]: https://github.com/AdguardTeam/AdGuardHome/issues/5913
|
||||
[#5939]: https://github.com/AdguardTeam/AdGuardHome/discussions/5939
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
[ms-v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/milestone/68?closed=1
|
||||
|
||||
|
||||
|
||||
@@ -2076,11 +2160,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...HEAD
|
||||
[v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.34...HEAD
|
||||
[v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...v0.107.34
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...HEAD
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...HEAD
|
||||
[v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33
|
||||
[v0.107.32]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.31...v0.107.32
|
||||
[v0.107.31]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.30...v0.107.31
|
||||
[v0.107.30]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.29...v0.107.30
|
||||
|
||||
@@ -475,7 +475,9 @@
|
||||
"setup_dns_notice": "Каб выкарыстоўваць <1>DNS-over-HTTPS</1> ці <1>DNS-over-TLS</1>, вам патрэбна <0>наладзіць шыфраванне</0> у наладах AdGuard Home.",
|
||||
"rewrite_added": "Правіла перанакіравання DNS для «{{key}}» паспяхова дададзена",
|
||||
"rewrite_deleted": "Правіла перанакіравання DNS для «{{key}}» паспяхова выдалена",
|
||||
"rewrite_updated": "Перазапіс DNS паспяхова абноўлены",
|
||||
"rewrite_add": "Дадаць правіла перанакіравання DNS",
|
||||
"rewrite_edit": "Рэдагаваць перазапіс DNS",
|
||||
"rewrite_not_found": "Не знойдзена правілаў перанакіравання DNS",
|
||||
"rewrite_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць правіла перанакіравання DNS для «{{key}}»?",
|
||||
"rewrite_desc": "Дазваляе лёгка наладзіць карыстацкі DNS-адказ для пэўнага дамена.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Pro použití <1>DNS skrze HTTPS</1> nebo <1>DNS skrze TLS</1> potřebujete v nastaveních AdGuard Home <0>nakonfigurovat šifrování</0>.",
|
||||
"rewrite_added": "Přesměrování DNS pro „{{key}}“ úspěšně přidáno",
|
||||
"rewrite_deleted": "Přesměrování DNS pro „{{key}}“ úspěšně smazáno",
|
||||
"rewrite_updated": "Přesměrování DNS bylo úspěšně aktualizováno",
|
||||
"rewrite_add": "Přidat přesměrování DNS",
|
||||
"rewrite_edit": "Upravit přesměrování DNS",
|
||||
"rewrite_not_found": "Přesměrování DNS nenalezeny",
|
||||
"rewrite_confirm_delete": "Jste si jisti, že chcete smazat přesměrování DNS pro „{{key}}“?",
|
||||
"rewrite_desc": "Umožňuje snadno nakonfigurovat vlastní DNS odezvy pro konkrétní název domény.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "For at kunne bruge <1>DNS-over-HTTPS</1> eller <1>DNS-over-TLS</1>, skal du <0>opsætte Krypteringen</0> i AdGuard Homes indstillinger.",
|
||||
"rewrite_added": "DNS-omskrivning for \"{{key}}\" blev tilføjet",
|
||||
"rewrite_deleted": "DNS-omskrivning for \"{{key}}\" blev slettet",
|
||||
"rewrite_updated": "DNS-omskrivning hermed opdateret",
|
||||
"rewrite_add": "Tilføj DNS-omskrivning",
|
||||
"rewrite_edit": "Redigér DNS-omskrivning",
|
||||
"rewrite_not_found": "Ingen DNS-omskrivninger fundet",
|
||||
"rewrite_confirm_delete": "Sikker på, at du vil slette DNS-omskrivning for \"{{key}}\"?",
|
||||
"rewrite_desc": "Gør det nemt at opsætte det tilpassede DNS-svar for et specifikt domænenavn.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Um <1>DNS-over-HTTTPS</1> oder <1>DNS-over-TLS</1> verwenden zu können, müssen Sie in den AdGuard Home Einstellungen die <0>Verschlüsselung konfigurieren</0>.",
|
||||
"rewrite_added": "DNS-Umschreibung für „{{key}}“ erfolgreich hinzugefügt",
|
||||
"rewrite_deleted": "DNS-Umschreibung für „{{key}}“ erfolgreich entfernt",
|
||||
"rewrite_updated": "DNS-Rewrite erfolgreich aktualisiert",
|
||||
"rewrite_add": "DNS-Umschreibung hinzufügen",
|
||||
"rewrite_edit": "DNS-Rewrite bearbeiten",
|
||||
"rewrite_not_found": "Keine DNS-Umschreibungen gefunden",
|
||||
"rewrite_confirm_delete": "Möchten Sie die DNS-Umschreibung für „{{key}}“ wirklich entfernen?",
|
||||
"rewrite_desc": "Ermöglicht die einfache Konfiguration der benutzerdefinierten DNS-Antwort für einen bestimmten Domainnamen.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "In order to use <1>DNS-over-HTTPS</1> or <1>DNS-over-TLS</1>, you need to <0>configure Encryption</0> in AdGuard Home settings.",
|
||||
"rewrite_added": "DNS rewrite for \"{{key}}\" successfully added",
|
||||
"rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted",
|
||||
"rewrite_updated": "DNS rewrite successfully updated",
|
||||
"rewrite_add": "Add DNS rewrite",
|
||||
"rewrite_edit": "Edit DNS rewrite",
|
||||
"rewrite_not_found": "No DNS rewrites found",
|
||||
"rewrite_confirm_delete": "Are you sure you want to delete DNS rewrite for \"{{key}}\"?",
|
||||
"rewrite_desc": "Allows to easily configure custom DNS response for a specific domain name.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Para utilizar <1>DNS mediante HTTPS</1> o <1>DNS mediante TLS</1>, debes <0>configurar el cifrado</0> en la configuración de AdGuard Home.",
|
||||
"rewrite_added": "Reescritura DNS para \"{{key}}\" añadido correctamente",
|
||||
"rewrite_deleted": "Reescritura DNS para \"{{key}}\" eliminado correctamente",
|
||||
"rewrite_updated": "Reconfiguración de DNS actualizada correctamente",
|
||||
"rewrite_add": "Añadir reescritura DNS",
|
||||
"rewrite_edit": "Editar reconfiguración de DNS",
|
||||
"rewrite_not_found": "No se han encontrado reescrituras DNS",
|
||||
"rewrite_confirm_delete": "¿Estás seguro de que deseas eliminar la reescritura DNS para \"{{key}}\"?",
|
||||
"rewrite_desc": "Permite configurar fácilmente la respuesta DNS personalizada para un nombre de dominio específico.",
|
||||
|
||||
@@ -440,7 +440,9 @@
|
||||
"setup_dns_notice": "به منظور استفاده از <1>DNS-over-HTTPS</1> یا <1>DNS-over-TLS</1>، شما نیاز به <0>پیکربندی رمزگذاری</0> در تنظیمات AdGuard Home دارید.",
|
||||
"rewrite_added": "بازنویسی DNS برای \"{{key}}\" با موفقیت اضافه شد",
|
||||
"rewrite_deleted": "بازنویسی DNS برای \"{{key}}\" با موفقیت حذف شد",
|
||||
"rewrite_updated": "بازنویسی DNS با موفقیت به روز شد",
|
||||
"rewrite_add": "افزودن بازنویسی DNS",
|
||||
"rewrite_edit": "بازنویسی DNS را ویرایش کنید",
|
||||
"rewrite_not_found": "بازنویسی DNS یافت نشد",
|
||||
"rewrite_confirm_delete": "آیا واقعا میخواهید بازنویسی DNS برای \"{{key}}\" را حذف کنید؟",
|
||||
"rewrite_desc": "به آسانی اجازه پیکربندی پاسخ DNS دستی برای یک نام دامنه خاص را می دهد.",
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
"all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla",
|
||||
"updated_upstream_dns_toast": "Ylävirtojen palvelimet tallennettiin",
|
||||
"dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
|
||||
"dns_test_not_ok_toast": "Palvelin \"{{key}}\": ei voitu käyttää, tarkista sen oikeinkirjoitus",
|
||||
"dns_test_not_ok_toast": "Palvelin \"{{key}}\": Ei voitu käyttää, tarkista oikeinkirjoitus",
|
||||
"dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla",
|
||||
"unblock": "Salli",
|
||||
"block": "Estä",
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "<1>DNS-over-HTTPS</1> tai <1>DNS-over-TLS</1> -toteutuksia varten, on AdGuard Homen <0>Salausasetukset</0> määritettävä.",
|
||||
"rewrite_added": "Kohteen \"{{key}}\" DNS-uudelleenohjaus lisättiin",
|
||||
"rewrite_deleted": "Kohteen \"{{key}}\" DNS-uudelleenohjaus poistettiin",
|
||||
"rewrite_updated": "DNS-uudelleenohjaukset päivitettiin",
|
||||
"rewrite_add": "Lisää DNS-uudelleenohjaus",
|
||||
"rewrite_edit": "Muokkaa DNS-uudelleenohjausta",
|
||||
"rewrite_not_found": "DNS-uudelleenohjauksia ei löytynyt",
|
||||
"rewrite_confirm_delete": "Haluatko varmasti poistaa DNS-uudelleenohjauksen kohteelle \"{{key}}\"?",
|
||||
"rewrite_desc": "Mahdollistaa oman DNS-vastauksen helpon määrityksen tietylle verkkotunnukselle.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"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_updated": "Réécriture DNS mise à jour",
|
||||
"rewrite_add": "Ajouter une réécriture DNS",
|
||||
"rewrite_edit": "Modifier la 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_desc": "Permet de configurer facilement la réponse DNS personnalisée pour un nom de domaine spécifique.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Da biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, morate <0>postaviti šifriranje</0> u AdGuard Home postavkama.",
|
||||
"rewrite_added": "DNS prijepis za \"{{key}}\" je uspješno dodan",
|
||||
"rewrite_deleted": "DNS prijepis za \"{{key}}\" je uspješno uklonjen",
|
||||
"rewrite_updated": "Prepisivanje DNS-a uspješno ažurirano",
|
||||
"rewrite_add": "Dodaj DNS prijepis",
|
||||
"rewrite_edit": "Uredite prepisivanje DNS-a",
|
||||
"rewrite_not_found": "Nema DNS prijepisa",
|
||||
"rewrite_confirm_delete": "Jeste li sigurni da želite ukloniti DNS prijepis za \"{{key}}\" klijenta?",
|
||||
"rewrite_desc": "Omogućuje jednostavno postavljanje prilagođenog DNS odgovora za određenu domenu.",
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"enabled_parental_toast": "Szülői felügyelet engedélyezve",
|
||||
"disabled_safe_search_toast": "Biztonságos keresés letiltva",
|
||||
"enabled_save_search_toast": "Biztonságos keresés engedélyezve",
|
||||
"updated_save_search_toast": "A Biztonságos keresés beállításai frissítve",
|
||||
"enabled_table_header": "Engedélyezve",
|
||||
"name_table_header": "Név",
|
||||
"list_url_table_header": "Lista URL-je",
|
||||
@@ -290,6 +291,8 @@
|
||||
"rate_limit": "Kérések korlátozása",
|
||||
"edns_enable": "EDNS kliens alhálózat engedélyezése",
|
||||
"edns_cs_desc": "Adja hozzá az EDNS Client Subnet beállítást (ECS) a felfelé irányuló kérésekhez, és naplózza a kliensek által küldött értékeket a lekérdezési naplóban.",
|
||||
"edns_use_custom_ip": "Használjon egyéni IP-címet az EDNS-hez",
|
||||
"edns_use_custom_ip_desc": "Engedélyezze az egyéni IP-cím használatát az EDNS-hez",
|
||||
"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",
|
||||
@@ -475,7 +478,9 @@
|
||||
"setup_dns_notice": "Ahhoz, hogy a <1>DNS-over-HTTPS</1> vagy a <1>DNS-over-TLS</1> valamelyikét használja, muszáj <0>beállítania a titkosítást</0> az AdGuard Home beállításaiban.",
|
||||
"rewrite_added": "DNS átírás a(z) \"{{key}}\" kulcshoz sikeresen hozzáadva",
|
||||
"rewrite_deleted": "DNS átírás a(z) \"{{key}}\" kulcshoz sikeresen törölve",
|
||||
"rewrite_updated": "A DNS újraírása sikeresen frissítve",
|
||||
"rewrite_add": "DNS átírás hozzáadása",
|
||||
"rewrite_edit": "DNS újraírás szerkesztése",
|
||||
"rewrite_not_found": "Nem találhatók DNS átírások",
|
||||
"rewrite_confirm_delete": "Biztosan törölni szeretné a DNS átírást ehhez: \"{{key}}\"?",
|
||||
"rewrite_desc": "Lehetővé teszi, hogy egyszerűen beállítson egyéni DNS választ egy adott domain névhez.",
|
||||
@@ -523,6 +528,10 @@
|
||||
"statistics_retention_confirm": "Biztos benne, hogy megváltoztatja a statisztika megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
|
||||
"statistics_cleared": "A statisztikák sikeresen vissza lettek állítva",
|
||||
"statistics_enable": "Statisztikák engedélyezése",
|
||||
"ignore_domains": "Figyelmen kívül hagyott domainek (újsorral elválasztva)",
|
||||
"ignore_domains_title": "Figyelmen kívül hagyott domainek",
|
||||
"ignore_domains_desc_stats": "Az ezekre a tartományokra vonatkozó lekérdezések nem kerülnek a statisztikákba",
|
||||
"ignore_domains_desc_query": "Az ezekhez a tartományokhoz tartozó lekérdezések nem kerülnek a lekérdezési naplóba",
|
||||
"interval_hours": "{{count}} óra",
|
||||
"interval_hours_plural": "{{count}} óra",
|
||||
"filters_configuration": "Szűrők beállításai",
|
||||
@@ -643,5 +652,29 @@
|
||||
"confirm_dns_cache_clear": "Biztos benne, hogy törölni szeretné a DNS-gyorsítótárat?",
|
||||
"cache_cleared": "A DNS gyorsítótár sikeresen törlődött",
|
||||
"clear_cache": "Gyorsítótár törlése",
|
||||
"protection_section_label": "Védelem"
|
||||
"make_static": "Statikussá tétel",
|
||||
"theme_auto_desc": "Automatikus (az eszköz színsémájától függően)",
|
||||
"theme_dark_desc": "Sötét téma",
|
||||
"theme_light_desc": "Világos téma",
|
||||
"disable_for_seconds": "{{count}} másodpercig",
|
||||
"disable_for_seconds_plural": "{{count}} másodpercig",
|
||||
"disable_for_minutes": "{{count}} percig",
|
||||
"disable_for_minutes_plural": "{{count}} percig",
|
||||
"disable_for_hours": "{{count}} óráig",
|
||||
"disable_for_hours_plural": "{{count}} óráig",
|
||||
"disable_until_tomorrow": "Holnapig",
|
||||
"disable_notify_for_seconds": "Kapcsolja ki a védelmet {{count}} másodpercre",
|
||||
"disable_notify_for_seconds_plural": "Kapcsolja ki a védelmet {{count}} másodpercre",
|
||||
"disable_notify_for_minutes": "Kapcsolja ki a védelmet {{count}} percre",
|
||||
"disable_notify_for_minutes_plural": "Kapcsolja ki a védelmet {{count}} percre",
|
||||
"disable_notify_for_hours": "Kapcsolja ki a védelmet {{count}} órára",
|
||||
"disable_notify_for_hours_plural": "Kapcsolja ki a védelmet {{count}} órára",
|
||||
"disable_notify_until_tomorrow": "Holnapig kapcsolja ki a védelmet",
|
||||
"enable_protection_timer": "A védelem {{time}}-kor aktiválódik",
|
||||
"custom_retention_input": "Adja meg a megőrzést órákban",
|
||||
"custom_rotation_input": "Írja be a forgatást órákban",
|
||||
"protection_section_label": "Védelem",
|
||||
"log_and_stats_section_label": "Lekérdezési napló és statisztikák",
|
||||
"ignore_query_log": "Figyelmen kívül hagyja ezt az ügyfelet a lekérdezési naplóban",
|
||||
"ignore_statistics": "Hagyja figyelmen kívül ezt az ügyfelet a statisztikákban"
|
||||
}
|
||||
|
||||
@@ -474,7 +474,9 @@
|
||||
"setup_dns_notice": "Jikalau ingin menggunakan <1>DNS-over-HTTPS</1> atau <1>DNS-over-TLS</1>, Anda perlu <0>mengatur Enkripsi</0> pada pengaturan AdGuard Home.",
|
||||
"rewrite_added": "DNS rewrite untuk \"{{key}}\" berhasil ditambahkan",
|
||||
"rewrite_deleted": "DNS rewrite untuk \"{{key}}\" berhasil dihapus",
|
||||
"rewrite_updated": "Penulisan ulang DNS berhasil diperbarui",
|
||||
"rewrite_add": "Tambah DNS rewrite",
|
||||
"rewrite_edit": "Edit penulisan ulang DNS",
|
||||
"rewrite_not_found": "Tidak ada DNS rewrite ditemukan",
|
||||
"rewrite_confirm_delete": "Apakah anda yakin ingin menghapus DNS rewrite untuk \"{{key}}\"?",
|
||||
"rewrite_desc": "Memungkinkan untuk dengan mudah mengkonfigurasi respons DNS kustom untuk nama domain tertentu.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Per utilizzare <1>DNS su HTTPS</1> o <1>DNS su TLS</1>, è necessario <0>configurare la crittografia</0> nelle impostazioni di AdGuard Home.",
|
||||
"rewrite_added": "Riscrittura DNS per \"{{key}}\" aggiunta correttamente",
|
||||
"rewrite_deleted": "La riscrittura DNS per \"{{key}}\" è stata eliminata correttamente",
|
||||
"rewrite_updated": "Riscrittura DNS aggiornata correttamente",
|
||||
"rewrite_add": "Aggiungi la riscrittura DNS",
|
||||
"rewrite_edit": "Modifica della riscrittura DNS",
|
||||
"rewrite_not_found": "Nessuna riscrittura DNS trovata",
|
||||
"rewrite_confirm_delete": "Sei sicuro di voler cancellare la riscrittura DNS per \"{{key}}\"?",
|
||||
"rewrite_desc": "Consente di configurare facilmente la risposta DNS personalizzata per un nome di dominio specifico.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "<1>DNS-over-HTTPS</1>または<1>DNS-over-TLS</1>を使用するには、AdGuard Home 設定の<0>暗号化設定</0>が必要です。",
|
||||
"rewrite_added": "\"{{key}}\" のDNS書き換え情報を追加完了しました",
|
||||
"rewrite_deleted": "\"{{key}}\" のDNS書き換え情報を削除完了しました",
|
||||
"rewrite_updated": "DNS rewrite を更新完了しました。",
|
||||
"rewrite_add": "DNS書き換え情報を追加する",
|
||||
"rewrite_edit": "DNS rewrite を編集する",
|
||||
"rewrite_not_found": "DNS書き換え情報はありません",
|
||||
"rewrite_confirm_delete": "\"{{key}}\" のDNS書き換え情報を削除してもよろしいですか?",
|
||||
"rewrite_desc": "特定のドメイン名に対するDNS応答を簡単にカスタマイズすることを可能にします。",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "<1>DNS-over-HTTPS</1> 또는 <1>DNS-over-TLS를</1> 사용하려면 AdGuard Home 설정에서 <0>암호화를 구성해야 합니다.</0>",
|
||||
"rewrite_added": "'{{key}}'에 대한 DNS 수정 정보를 성공적으로 추가 됩니다",
|
||||
"rewrite_deleted": "'{{key}}'에 대한 DNS 수정 정보를 성공적으로 삭제 됩니다",
|
||||
"rewrite_updated": "DNS 다시 쓰기 업데이트 완료",
|
||||
"rewrite_add": "DNS 변환 정보를 추가합니다",
|
||||
"rewrite_edit": "DNS 다시 쓰기 편집",
|
||||
"rewrite_not_found": "DNS 변경 정보를 찾을 수 없습니다",
|
||||
"rewrite_confirm_delete": "'{{key}}'에 대한 DNS 변경 정보를 삭제하시겠습니까?",
|
||||
"rewrite_desc": "특정 도메인 이름에 대한 사용자 지정 DNS 응답을 쉽게 구성할 수 있습니다.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Om <1>DNS-via-HTTPS</1> of <1>DNS-via-TLS</1> te gebruiken, moet je <0>Versleuteling configureren</0> in de AdGuard Home instellingen.",
|
||||
"rewrite_added": "DNS-herschrijving voor \"{{key}}\" met succes toegevoegd",
|
||||
"rewrite_deleted": "DNS-herschrijving voor \"{{key}}\" met succes verwijderd",
|
||||
"rewrite_updated": "DNS-herschrijven succesvol bijgewerkt",
|
||||
"rewrite_add": "DNS-herschrijving toevoegen",
|
||||
"rewrite_edit": "DNS-herschrijven bewerken",
|
||||
"rewrite_not_found": "Geen DNS-herschrijving gevonden",
|
||||
"rewrite_confirm_delete": "Bent u zeker dat u DNS-herschrijving \"{{key}}\" wilt verwijderen?",
|
||||
"rewrite_desc": "Hiermee kunt u eenvoudig aangepaste DNS-antwoorden configureren voor een specifieke domeinnaam.",
|
||||
|
||||
@@ -457,7 +457,9 @@
|
||||
"setup_dns_notice": "For å benytte <1>DNS-over-HTTPS</1> eller <1>DNS-over-TLS</1>, må du <0>sette opp Kryptering</0> i AdGuard Home-innstillingene.",
|
||||
"rewrite_added": "DNS-omdirigeringen for «{{key}}» ble vellykket lagt til",
|
||||
"rewrite_deleted": "DNS-omdirigeringen for «{{key}}» ble vellykket slettet",
|
||||
"rewrite_updated": "DNS-omskriving ble oppdatert",
|
||||
"rewrite_add": "Legg til DNS-omdirigering",
|
||||
"rewrite_edit": "Rediger DNS-omskriving",
|
||||
"rewrite_not_found": "Ingen DNS-omdirigeringer ble funnet",
|
||||
"rewrite_confirm_delete": "Er du sikker på at du vil slette DNS-omdirigeringen for «{{key}}»?",
|
||||
"rewrite_desc": "Lar deg enkelt konfigurere selvvalgte DNS-tilbakemeldinger for et spesifikt domenenavn.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Aby skorzystać z <1>DNS-over-HTTPS</1> lub <1>DNS-over-TLS</1>, musisz w ustawieniach AdGuard Home <0>skonfigurować szyfrowanie</0>.",
|
||||
"rewrite_added": "Pomyślnie dodano przepisanie DNS dla „{{key}}”",
|
||||
"rewrite_deleted": "Przepisanie DNS dla „{{key}}” zostało pomyślnie usunięte",
|
||||
"rewrite_updated": "Pomyślnie zaktualizowano przepisywanie DNS",
|
||||
"rewrite_add": "Dodaj przepisywanie DNS",
|
||||
"rewrite_edit": "Edytuj przepisywanie DNS",
|
||||
"rewrite_not_found": "Nie znaleziono przepisywania DNS",
|
||||
"rewrite_confirm_delete": "Czy na pewno chcesz usunąć przepisywanie DNS dla „{{key}}”?",
|
||||
"rewrite_desc": "Pozwala łatwo skonfigurować niestandardową odpowiedź DNS dla określonej nazwy domeny.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS</1> ou <1>DNS-sobre-TLS</1>, você precisa <0>configurar a criptografia</0> nas configurações do AdGuard Home.",
|
||||
"rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso",
|
||||
"rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso",
|
||||
"rewrite_updated": "Reconfiguração de DNS atualizada com êxito",
|
||||
"rewrite_add": "Adicionar reescrita de DNS",
|
||||
"rewrite_edit": "Editar reconfiguração de DNS",
|
||||
"rewrite_not_found": "Nenhuma reescrita de DNS foi encontrada",
|
||||
"rewrite_confirm_delete": "Você tem certeza de que deseja excluir a reescrita de DNS para \"{{key}}\"?",
|
||||
"rewrite_desc": "Permite configurar uma resposta personalizada do DNS para um nome de domínio específico.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS</1> ou <1>DNS-sobre-TLS</1>, precisa de <0>configurar a criptografia</0> nas configurações do AdGuard Home.",
|
||||
"rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso",
|
||||
"rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso",
|
||||
"rewrite_updated": "Reedição de DNS atualizada com sucesso",
|
||||
"rewrite_add": "Adicionar reescrita de DNS",
|
||||
"rewrite_edit": "Editar reedição de DNS",
|
||||
"rewrite_not_found": "Nenhuma reescrita de DNS foi encontrada",
|
||||
"rewrite_confirm_delete": "Tem a certeza de que deseja excluir a reescrita de DNS para \"{{key}}\"?",
|
||||
"rewrite_desc": "Permite configurar uma resposta personalizada do DNS para um nome de domínio específico.",
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"enabled_parental_toast": "Control Parental activat",
|
||||
"disabled_safe_search_toast": "Căutare protejată dezactivată",
|
||||
"enabled_save_search_toast": "Căutare protejată activată",
|
||||
"updated_save_search_toast": "Setări Căutare sigură actualizate",
|
||||
"enabled_table_header": "Activat",
|
||||
"name_table_header": "Nume",
|
||||
"list_url_table_header": "Lista URL",
|
||||
@@ -256,12 +257,12 @@
|
||||
"query_log_cleared": "Jurnalul de interogare a fost șters cu succes",
|
||||
"query_log_updated": "Jurnalul de solicitări a fost actualizat cu succes",
|
||||
"query_log_clear": "Curăță jurnalele",
|
||||
"query_log_retention": "Retenție jurnale interogare",
|
||||
"query_log_retention": "Interogarea jurnalelor de rotație",
|
||||
"query_log_enable": "Activați jurnal",
|
||||
"query_log_configuration": "Configurația jurnalelor",
|
||||
"query_log_disabled": "Jurnalul de interogare este dezactivat și poate fi configurat în <0>setări</0>",
|
||||
"query_log_strict_search": "Utilizați ghilimele duble pentru căutare strictă",
|
||||
"query_log_retention_confirm": "Sunteți sigur că doriți să schimbați retenția jurnalului de interogare? Reducând valoarea intervalului, unele date vor fi pierdute",
|
||||
"query_log_retention_confirm": "Sigur doriți să modificați rotația jurnalului de interogări? Dacă micșorați valoarea intervalului, unele date se vor pierde",
|
||||
"anonymize_client_ip": "Anonimizare client IP",
|
||||
"anonymize_client_ip_desc": "Nu salvați adresa IP completă a clientului în jurnale și statistici",
|
||||
"dns_config": "Configurația serverului DNS",
|
||||
@@ -290,6 +291,8 @@
|
||||
"rate_limit": "Limita ratei",
|
||||
"edns_enable": "Activați subrețeaua de clienți EDNS",
|
||||
"edns_cs_desc": "Adaugă opțiunea EDNS Client Subnet (ECS) la solicitările în amonte și înregistrează valorile trimise de clienți în jurnalul de interogare.",
|
||||
"edns_use_custom_ip": "Utilizați IP personalizat pentru EDNS",
|
||||
"edns_use_custom_ip_desc": "Permiteți utilizarea IP-ului personalizat pentru EDNS",
|
||||
"rate_limit_desc": "Numărul de interogări pe secundă permise pe client. Setarea la 0 înseamnă că nu există limită.",
|
||||
"blocking_ipv4_desc": "Adresa IP de returnat pentru o cerere A de blocare",
|
||||
"blocking_ipv6_desc": "Adresa IP de returnat pentru o cerere AAAA de blocare",
|
||||
@@ -475,7 +478,9 @@
|
||||
"setup_dns_notice": "Pentru a utiliza <1>DNS-over-HTTPS</1> sau <1>DNS-over-TLS</1>, trebuie să <0>configurați Criptarea</0> în setările AdGuard Home.",
|
||||
"rewrite_added": "Rescriere DNS pentru \"{{key}}\" adăugată cu succes",
|
||||
"rewrite_deleted": "Rescriere DNS pentru \"{{key}}\" ștearsă cu succes",
|
||||
"rewrite_updated": "DNS rescrie actualizat cu succes",
|
||||
"rewrite_add": "Adăugați rescriere DNS",
|
||||
"rewrite_edit": "Editați rescrierea DNS",
|
||||
"rewrite_not_found": "Nu s-au găsit rescrieri DNS",
|
||||
"rewrite_confirm_delete": "Sunteți sigur că doriți să ștergeți rescrierea DNS pentru \"{{key}}\"?",
|
||||
"rewrite_desc": "Permite configurarea cu ușurință a răspunsului personalizat DNS pentru un nume de domeniu specific.",
|
||||
@@ -523,6 +528,10 @@
|
||||
"statistics_retention_confirm": "Sunteți sigur că doriți să schimbați păstrarea statisticilor? Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
|
||||
"statistics_cleared": "Statisticile au fost șterse cu succes",
|
||||
"statistics_enable": "Activați statisticile",
|
||||
"ignore_domains": "Domenii ignorate (separate prin linie nouă)",
|
||||
"ignore_domains_title": "Domenii ignorate",
|
||||
"ignore_domains_desc_stats": "Interogările pentru aceste domenii nu sunt scrise în statistici",
|
||||
"ignore_domains_desc_query": "Interogările pentru aceste domenii nu sunt scrise în jurnalul de interogări",
|
||||
"interval_hours": "{{count}} oră",
|
||||
"interval_hours_plural": "{{count}} ore",
|
||||
"filters_configuration": "Configurația filtrelor",
|
||||
@@ -643,5 +652,29 @@
|
||||
"confirm_dns_cache_clear": "Sunteți sigur că doriți să ștergeți memoria cache DNS?",
|
||||
"cache_cleared": "Cache-ul DNS a fost golit cu succes",
|
||||
"clear_cache": "Goliți memoria cache",
|
||||
"protection_section_label": "Protecție"
|
||||
"make_static": "Faceți static",
|
||||
"theme_auto_desc": "Auto (pe baza schemei de culori a dispozitivului dvs.)",
|
||||
"theme_dark_desc": "Temă întunecată",
|
||||
"theme_light_desc": "Temă luminoasă",
|
||||
"disable_for_seconds": "Timp de {{count}} secundă",
|
||||
"disable_for_seconds_plural": "Timp de {{count}} secunde",
|
||||
"disable_for_minutes": "Timp de {{count}} minut",
|
||||
"disable_for_minutes_plural": "Timp de {{count}} minute",
|
||||
"disable_for_hours": "Timp de {{count}} oră",
|
||||
"disable_for_hours_plural": "Timp de {{count}} ore",
|
||||
"disable_until_tomorrow": "Până mâine",
|
||||
"disable_notify_for_seconds": "Dezactivați protecția timp de {{count}} secundă",
|
||||
"disable_notify_for_seconds_plural": "Dezactivați protecția timp de {{count}} secunde",
|
||||
"disable_notify_for_minutes": "Dezactivați protecția timp de {{count}} minut",
|
||||
"disable_notify_for_minutes_plural": "Dezactivați protecția timp de {{count}} minute",
|
||||
"disable_notify_for_hours": "Dezactivează protecția timp de {{count}} oră",
|
||||
"disable_notify_for_hours_plural": "Dezactivați protecția timp de {{count}} ore",
|
||||
"disable_notify_until_tomorrow": "Dezactivează protecția până mâine",
|
||||
"enable_protection_timer": "Protecția va fi activată în {{time}}",
|
||||
"custom_retention_input": "Introduceți reținerea în ore",
|
||||
"custom_rotation_input": "Introduceți rotația în ore",
|
||||
"protection_section_label": "Protecție",
|
||||
"log_and_stats_section_label": "Jurnal de interogări și statistici",
|
||||
"ignore_query_log": "Ignorați acest client în jurnalul de interogări",
|
||||
"ignore_statistics": "Ignorați acest client în statistici"
|
||||
}
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Чтобы использовать <1>DNS-over-HTTPS</1> или <1>DNS-over-TLS</1>, вам нужно <0>настроить шифрование</0> в настройках AdGuard Home.",
|
||||
"rewrite_added": "Правило перезаписи DNS-запросов для «{{key}}» успешно добавлено",
|
||||
"rewrite_deleted": "Правило перезаписи DNS-запросов для «{{key}}» успешно удалено",
|
||||
"rewrite_updated": "Правило перезаписи DNS-запросов успешно обновлено",
|
||||
"rewrite_add": "Добавить правило перезаписи DNS-запросов",
|
||||
"rewrite_edit": "Редактировать правило перезаписи DNS-запросов",
|
||||
"rewrite_not_found": "Не найдено правил перезаписи DNS-запросов",
|
||||
"rewrite_confirm_delete": "Вы уверены, что хотите удалить правило перезаписи DNS-запросов для «{{key}}»?",
|
||||
"rewrite_desc": "Позволяет легко настроить пользовательский DNS-ответ для определеннного домена.",
|
||||
|
||||
@@ -153,6 +153,7 @@
|
||||
"enabled_parental_toast": "දෙමාපිය පාලනය සබල කෙරිණි",
|
||||
"disabled_safe_search_toast": "ආරක්ෂිත සෙවුම අබල කෙරිණි",
|
||||
"enabled_save_search_toast": "ආරක්ෂිත සෙවුම සබල කෙරිණි",
|
||||
"updated_save_search_toast": "ආරක්ෂිත සෙවුමේ සැකසුම් යාවත්කාල විය",
|
||||
"enabled_table_header": "සබලයි",
|
||||
"name_table_header": "නම",
|
||||
"list_url_table_header": "ඒ.ස.නි.(URL) ලැයිස්තුව",
|
||||
@@ -237,12 +238,12 @@
|
||||
"query_log_cleared": "විමසුම් සටහන සාර්ථකව හිස් කර ඇත",
|
||||
"query_log_updated": "විමසුම් සටහන සාර්ථකව යාවත්කාල කෙරිණි",
|
||||
"query_log_clear": "විමසුම් සටහන් හිස් කරන්න",
|
||||
"query_log_retention": "විමසුම් සටහන් රඳවා තබා ගැනීම",
|
||||
"query_log_retention": "විමසුම් සටහන් රැඳවීම",
|
||||
"query_log_enable": "සටහන සබල කරන්න",
|
||||
"query_log_configuration": "සටහන් වින්යාසය",
|
||||
"query_log_disabled": "විමසුම් සටහන අබල කර ඇති අතර එය <0>සැකසුම්</0> තුළ වින්යාසගත කළ හැකිය",
|
||||
"query_log_strict_search": "ඉතා නිවැරදිව සෙවීමට ද්විත්ව උද්ධෘතය භාවිතා කරන්න",
|
||||
"query_log_retention_confirm": "විමසුම් සටහන රඳවා තබා ගැනීම වෙනස් කිරීමට ඇවැසි බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
|
||||
"query_log_retention_confirm": "විමසුම් සටහන රඳවා තබා ගැනීම වෙනස් කිරීමට වුවමනා ද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
|
||||
"anonymize_client_ip": "අනුග්රාහකයෙහි අ.ජා.කෙ. (IP) නිර්නාමික කරන්න",
|
||||
"anonymize_client_ip_desc": "සටහන් සහ සංඛ්යාලේඛන තුළ අනුග්රාහකයේ පූර්ණ අ.ජා.කෙ. ලිපිනය සුරකින්න එපා",
|
||||
"dns_config": "ව.නා.ප. සේවාදායක වින්යාසය",
|
||||
@@ -270,6 +271,8 @@
|
||||
"form_enter_rate_limit": "අනුපාත සීමාව ඇතුල් කරන්න",
|
||||
"rate_limit": "අනුපාත සීමාව",
|
||||
"edns_enable": "EDNS අනුග්රාහක අනුජාලය සබල කරන්න",
|
||||
"edns_use_custom_ip": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. යොදාගන්න",
|
||||
"edns_use_custom_ip_desc": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. භාවිතයට ඉඩදෙන්න",
|
||||
"rate_limit_desc": "එක් අනුග්රාහකයකට ඉඩ දී ඇති තත්පරයට ඉල්ලීම් ගණන. එය 0 ලෙස සැකසීම යනුවෙන් අදහස් කරන්නේ සීමාවක් නැති බවයි.",
|
||||
"blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
|
||||
"blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
|
||||
@@ -278,6 +281,9 @@
|
||||
"blocking_mode_nxdomain": "නොපවතින වසම: NXDOMAIN කේතය සමඟ ප්රතිචාර දක්වයි",
|
||||
"blocking_mode_null_ip": "අභිශූන්යය අ.ජා.කෙ.: ශුන්ය අ.ජා.කෙ. ලිපිනය සමඟ ප්රතිචාර දක්වයි (A සඳහා 0.0.0.0; AAAA සඳහා ::)",
|
||||
"blocking_mode_custom_ip": "අභිරුචි අන්තර්ජාල කෙටුම්පත: අතින් සැකසූ අ.ජා. කෙ. ලිපිනයක් සමඟ ප්රතිචාර දක්වයි",
|
||||
"theme_auto": "ස්වයං",
|
||||
"theme_light": "දීප්ත",
|
||||
"theme_dark": "අඳුරු",
|
||||
"upstream_dns_client_desc": "ඔබ මෙම ක්ෂේත්රය හිස්ව තබා ගන්නේ නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් <0>ව.නා.ප. සැකසුම්</0> හි වින්යාසගත කර ඇති සේවාදායක භාවිතා කරනු ඇත.",
|
||||
"tracker_source": "ලුහුබැඳීම් මූලාශ්රය",
|
||||
"source_label": "මූලාශ්රය",
|
||||
@@ -370,6 +376,7 @@
|
||||
"encryption_issuer": "නිකුත් කරන්නා",
|
||||
"encryption_hostnames": "ධාරක නාම",
|
||||
"encryption_reset": "සංකේතාංකන සැකසුම් යළි පිහිටුවීමට අවශ්ය බව ඔබට විශ්වාස ද?",
|
||||
"encryption_warning": "අවවාදයයි",
|
||||
"topline_expiring_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත්වීමට ආසන්න වී ඇත. <0>සංකේතන සැකසුම්</0> යාවත්කාල කරන්න.",
|
||||
"topline_expired_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත් වී ඇත. <0>සංකේතන සැකසුම්</0> යාවත්කාල කරන්න.",
|
||||
"form_error_port_range": "80-65535 පරාසය හි තොටක අගයක් ඇතුල් කරන්න",
|
||||
@@ -490,8 +497,10 @@
|
||||
"statistics_clear": "සංඛ්යාලේඛන හිස් කරන්න",
|
||||
"statistics_clear_confirm": "සංඛ්යාලේඛන ඉවත් කිරීමට වුවමනා ද?",
|
||||
"statistics_retention_confirm": "සංඛ්යාලේඛන රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
|
||||
"statistics_cleared": "සංඛ්යාලේඛන සාර්ථකව ඉවත් කෙරිණි",
|
||||
"statistics_cleared": "සංඛ්යාලේඛන සාර්ථකව හිස් කෙරිණි",
|
||||
"statistics_enable": "සංඛ්යාලේඛන සබල කරන්න",
|
||||
"ignore_domains": "නොසලකන වසම් (පේළියකට එක බැගින්)",
|
||||
"ignore_domains_title": "නොසලකන වසම්",
|
||||
"interval_hours": "පැය {{count}}",
|
||||
"interval_hours_plural": "පැය {{count}}",
|
||||
"filters_configuration": "පෙරහන් වින්යාසය",
|
||||
@@ -601,5 +610,31 @@
|
||||
"parental_control": "දෙමාපිය පාලනය",
|
||||
"safe_browsing": "ආරක්ෂිත පිරික්සුම",
|
||||
"served_from_cache": "{{value}} <i>(නිහිතයෙන් ගැනිණි)</i>",
|
||||
"form_error_password_length": "මුරපදය අවම වශයෙන් අකුරු {{value}} ක් දිගු විය යුතුමයි"
|
||||
"form_error_password_length": "මුරපදය අවම වශයෙන් අකුරු {{value}} ක් දිගු විය යුතුමයි",
|
||||
"cache_cleared": "ව.නා.ප. නිහිතය හිස් කෙරිණි",
|
||||
"clear_cache": "නිහිතය මකන්න",
|
||||
"make_static": "ස්ථිතික කරන්න",
|
||||
"theme_dark_desc": "අඳුරු තේමාව",
|
||||
"theme_light_desc": "දීප්ත තේමාව",
|
||||
"disable_for_seconds": "තත්පර {{count}} ක්",
|
||||
"disable_for_seconds_plural": "තත්පර {{count}} ක්",
|
||||
"disable_for_minutes": "විනාඩි {{count}} ක්",
|
||||
"disable_for_minutes_plural": "විනාඩි {{count}} ක්",
|
||||
"disable_for_hours": "පැය {{count}} ක්",
|
||||
"disable_for_hours_plural": "පැය {{count}} ක්",
|
||||
"disable_until_tomorrow": "හෙට වනතුරු",
|
||||
"disable_notify_for_seconds": "තත්. {{count}} කට රැකවරණය අබල කරන්න",
|
||||
"disable_notify_for_seconds_plural": "තත්. {{count}} කට රැකවරණය අබල කරන්න",
|
||||
"disable_notify_for_minutes": "විනාඩි {{count}} කට රැකවරණය අබල කරන්න",
|
||||
"disable_notify_for_minutes_plural": "විනාඩි {{count}} කට රැකවරණය අබල කරන්න",
|
||||
"disable_notify_for_hours": "පැය {{count}} කට රැකවරණය අබල කරන්න",
|
||||
"disable_notify_for_hours_plural": "පැය {{count}} කට රැකවරණය අබල කරන්න",
|
||||
"disable_notify_until_tomorrow": "හෙට වනතුරු රැකවරණය අබල කරන්න",
|
||||
"enable_protection_timer": "{{time}} න් රැකවරණය සබල කෙරේ",
|
||||
"custom_retention_input": "රඳවා ගැනීම පැය වලින්",
|
||||
"custom_rotation_input": "රඳවා ගැනීම පැය වලින්",
|
||||
"protection_section_label": "රැකවරණය",
|
||||
"log_and_stats_section_label": "විමසුම් සටහන හා සංඛ්යාලේඛන",
|
||||
"ignore_query_log": "සටහනෙහි අනුග්රාහකය නොසලකන්න",
|
||||
"ignore_statistics": "සංඛ්යාලේඛනයට අනුග්රාහකය නොසලකන්න"
|
||||
}
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Pre použitie <1>DNS-over-HTTPS</1> alebo <1>DNS-over-TLS</1>, potrebujete v nastaveniach AdGuard Home <0>nakonfigurovať šifrovanie</0>.",
|
||||
"rewrite_added": "DNS prepísanie pre \"{{key}}\" bolo úspešne pridané",
|
||||
"rewrite_deleted": "DNS prepísanie pre \"{{key}}\" bolo úspešne vymazané",
|
||||
"rewrite_updated": "Prepísanie DNS bolo úspešne aktualizované",
|
||||
"rewrite_add": "Pridať DNS prepísanie",
|
||||
"rewrite_edit": "Upraviť prepísanie DNS",
|
||||
"rewrite_not_found": "Neboli nájdené žiadne DNS prepísania",
|
||||
"rewrite_confirm_delete": "Naozaj chcete odstrániť prepísanie DNS pre \"{{key}}\"?",
|
||||
"rewrite_desc": "Umožňuje ľahko nakonfigurovať vlastnú odpoveď DNS pre konkrétne meno domény.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Za uporabo <1>DNS-prek-HTTPS</1> ali <1>DNS-prek-TLS</1>, morate <0>konfigurirati šifriranje</0> v nastavitvah AdGuard Home.",
|
||||
"rewrite_added": "Uspešno je dodano DNS prepisovanje za \"{{key}}\"",
|
||||
"rewrite_deleted": "Uspešno je izbrisano DNS prepisovanje za \"{{key}}\"",
|
||||
"rewrite_updated": "DNS prepisovanje uspešno posodobljen",
|
||||
"rewrite_add": "Dodaj prepisovanje DNS",
|
||||
"rewrite_edit": "Urejanje prepisa DNS",
|
||||
"rewrite_not_found": "Ni bilo najdenih prepisovanj DNS",
|
||||
"rewrite_confirm_delete": "Ali ste prepričani, da želite izbrisati prepisovanje DNS za \"{{key}}\"?",
|
||||
"rewrite_desc": "Omogoča enostavno konfiguriranje odgovora DNS po meri za določeno ime domene.",
|
||||
|
||||
@@ -475,7 +475,9 @@
|
||||
"setup_dns_notice": "Kako biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, potrebno je da <0>konfigurišete šifrovanje</0> u AdGuard Home postavkama.",
|
||||
"rewrite_added": "DNS prepisivanje za \"{{key}}\" je uspešno dodato",
|
||||
"rewrite_deleted": "DNS prepisivanje za \"{{key}}\" uspešno izbrisano",
|
||||
"rewrite_updated": "DNS ponovo napisao uspešno ažuriran",
|
||||
"rewrite_add": "Dodaj DNS prepisivanje",
|
||||
"rewrite_edit": "Uređivanje DNS prepravke",
|
||||
"rewrite_not_found": "DNS prepisivanja nisu pronađena",
|
||||
"rewrite_confirm_delete": "Jeste li sigurni da želite da izbrišete DNS prepisivanje za \"{{key}}\"?",
|
||||
"rewrite_desc": "Dozvoljava da jednostavno konfigurišete prilagođeni DNS odgovor za određeni domen.",
|
||||
|
||||
@@ -475,7 +475,9 @@
|
||||
"setup_dns_notice": "För att kunna använda <1>DNS-över-HTTPS</1> eller <1>DNS-över-TLS</1>, behöver du <0>konfigurera Kryptering</0> i AdGuard Home-inställningar.",
|
||||
"rewrite_added": "DNS-omskrivning för \"{{key}}\" lyckad",
|
||||
"rewrite_deleted": "DNS-omskrivning för \"{{key}}\" har tagits bort",
|
||||
"rewrite_updated": "DNS-omskrivning har uppdaterats",
|
||||
"rewrite_add": "Lägg till DNS omskrivning",
|
||||
"rewrite_edit": "Redigera DNS-omskrivning",
|
||||
"rewrite_not_found": "Inga DNS omskrivningar hittades",
|
||||
"rewrite_confirm_delete": "Är du säker på att du vill ta bort DNS-omskrivningen för \"{{key}}\"?",
|
||||
"rewrite_desc": "Gör det enkelt att konfigurera anpassat DNS svar för ett specifikt domännamn.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "<1>DNS-over-HTTPS</1> veya <1>DNS-over-TLS</1> protokolünü kullanmak için AdGuard Home üzerinde <0>Şifreleme ayarları</0> bölümünden ayarları yapmanız gerekir.",
|
||||
"rewrite_added": "\"{{key}}\" için DNS yeniden yazımı başarıyla eklendi",
|
||||
"rewrite_deleted": "\"{{key}}\" için DNS yeniden yazımı başarıyla silindi",
|
||||
"rewrite_updated": "DNS yeniden yazma başarıyla güncellendi",
|
||||
"rewrite_add": "DNS yeniden yazımı ekle",
|
||||
"rewrite_edit": "DNS yeniden yazmayı düzenle",
|
||||
"rewrite_not_found": "DNS yeniden yazımı bulunamadı",
|
||||
"rewrite_confirm_delete": "\"{{key}}\" için DNS yeniden yazımını silmek istediğinize emin misiniz?",
|
||||
"rewrite_desc": "Belirli bir alan adı için özel DNS yanıtını kolayca yapılandırmanızı sağlar.",
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "Для використання <1>DNS-over-HTTPS</1> або <1>DNS-over-TLS</1>, вам потрібно <0>налаштувати Шифрування</0> в налаштуваннях AdGuard Home.",
|
||||
"rewrite_added": "Перезапис DNS для «{{key}}» успішно додано",
|
||||
"rewrite_deleted": "Перезапис DNS для «{{key}}» успішно видалено",
|
||||
"rewrite_updated": "Перезапис DNS успішно оновлено",
|
||||
"rewrite_add": "Додати перезапис DNS",
|
||||
"rewrite_edit": "Редагувати перезапис DNS",
|
||||
"rewrite_not_found": "Перезаписів DNS не знайдено",
|
||||
"rewrite_confirm_delete": "Ви впевнені, що хочете видалити перезапис DNS для «{{key}}»?",
|
||||
"rewrite_desc": "Дозволяє легко налаштувати власну відповідь DNS для певного доменного імені.",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"client_settings": "Cài đặt máy khách",
|
||||
"client_settings": "Cài đặt thiết bị",
|
||||
"example_upstream_reserved": "ngược dòng <0>cho các miền cụ thể</0>;",
|
||||
"example_upstream_comment": "một lời bình luận.",
|
||||
"upstream_parallel": "Sử dụng truy vấn song song để tăng tốc độ giải quyết bằng cách truy vấn đồng thời tất cả các máy chủ ngược tuyến",
|
||||
@@ -167,6 +167,7 @@
|
||||
"enabled_parental_toast": "Đã bật quản lý của phụ huynh",
|
||||
"disabled_safe_search_toast": "Đã tắt tìm kiếm an toàn",
|
||||
"enabled_save_search_toast": "Đã bật tìm kiếm an toàn",
|
||||
"updated_save_search_toast": "Cài đặt Tìm kiếm an toàn đã được cập nhật",
|
||||
"enabled_table_header": "Kích hoạt",
|
||||
"name_table_header": "Tên",
|
||||
"list_url_table_header": "URL bộ lọc",
|
||||
@@ -256,12 +257,12 @@
|
||||
"query_log_cleared": "Nhật ký truy vấn đã được xóa thành công",
|
||||
"query_log_updated": "Cập nhật thành công nhật kí truy xuất",
|
||||
"query_log_clear": "Xóa nhật ký truy vấn",
|
||||
"query_log_retention": "Lưu giữ nhật ký truy vấn",
|
||||
"query_log_retention": "Xoay vòng nhật ký truy vấn",
|
||||
"query_log_enable": "Bật nhật ký",
|
||||
"query_log_configuration": "Cấu hình nhật ký",
|
||||
"query_log_disabled": "Nhật ký truy vấn bị vô hiệu hóa và có thể được định cấu hình trong <0>cài đặt</ 0>",
|
||||
"query_log_strict_search": "Sử dụng dấu ngoặc kép để tìm kiếm nghiêm ngặt",
|
||||
"query_log_retention_confirm": "Bạn có chắc chắn muốn thay đổi lưu giữ nhật ký truy vấn? Nếu bạn giảm giá trị khoảng, một số dữ liệu sẽ bị mất",
|
||||
"query_log_retention_confirm": "Bạn có chắc chắn muốn thay đổi xoay vòng nhật ký truy vấn không? Nếu bạn giảm giá trị khoảng thời gian, một số dữ liệu sẽ bị mất",
|
||||
"anonymize_client_ip": "Ẩn danh IP khách",
|
||||
"anonymize_client_ip_desc": "Không lưu địa chỉ IP đầy đủ của khách hàng trong nhật ký và thống kê",
|
||||
"dns_config": "Thiết lập máy chủ DNS",
|
||||
@@ -290,6 +291,8 @@
|
||||
"rate_limit": "Giới hạn yêu cầu",
|
||||
"edns_enable": "Bật mạng con EDNS Client",
|
||||
"edns_cs_desc": "Thêm tùy chọn EDNS Client Subnet (ECS) vào các yêu cầu ngược dòng và ghi lại các giá trị được gửi bởi các máy khách trong nhật ký truy vấn.",
|
||||
"edns_use_custom_ip": "Sử dụng địa chỉ IP tùy chỉnh cho EDNS",
|
||||
"edns_use_custom_ip_desc": "Cho phép sử dụng địa chỉ IP tùy chỉnh cho EDNS",
|
||||
"rate_limit_desc": "Số lượng yêu cầu mỗi giây mà một khách hàng được phép thực hiện (0: không giới hạn)",
|
||||
"blocking_ipv4_desc": "Địa chỉ IP được trả lại cho một yêu cầu A bị chặn",
|
||||
"blocking_ipv6_desc": "Địa chỉ IP được trả lại cho một yêu cầu AAA bị chặn",
|
||||
@@ -475,7 +478,9 @@
|
||||
"setup_dns_notice": "Để sử dụng <1>DNS-over-HTTPS</1> hoặc <1>DNS-over-TLS</1>, bạn cần <0>định cấu hình Mã hóa</0> trong cài đặt AdGuard Home.",
|
||||
"rewrite_added": "DNS viết lại cho \"{{key}}\" đã thêm thành công",
|
||||
"rewrite_deleted": "DNS viết lại cho \"{{key}}\" đã xóa thành công",
|
||||
"rewrite_updated": "Viết lại DNS được cập nhật thành công",
|
||||
"rewrite_add": "Thêm DNS viết lại",
|
||||
"rewrite_edit": "Chỉnh sửa viết lại DNS",
|
||||
"rewrite_not_found": "Không tìm thấy DNS viết lại",
|
||||
"rewrite_confirm_delete": "Bạn có chắc chắn muốn xóa DNS viết lại cho \"{{key}}\" không?",
|
||||
"rewrite_desc": "Cho phép dễ dàng định cấu hình tùy chỉnh DNS phản hồi cho một tên miền cụ thể.",
|
||||
@@ -523,6 +528,10 @@
|
||||
"statistics_retention_confirm": "Bạn có chắc chắn muốn thay đổi lưu giữ số liệu thống kê? Nếu bạn giảm giá trị khoảng, một số dữ liệu sẽ bị mất",
|
||||
"statistics_cleared": "Xoá thống kê thành công",
|
||||
"statistics_enable": "Bật thống kê",
|
||||
"ignore_domains": "Các miền bị bỏ qua (cách nhau bởi dòng mới)",
|
||||
"ignore_domains_title": "Các miền bị bỏ qua",
|
||||
"ignore_domains_desc_stats": "Các truy vấn cho các miền này sẽ không được ghi vào thống kê",
|
||||
"ignore_domains_desc_query": "Các truy vấn cho các miền này sẽ không được ghi vào nhật ký truy vấn",
|
||||
"interval_hours": "{{count}} giờ",
|
||||
"interval_hours_plural": "{{count}} giờ",
|
||||
"filters_configuration": "Cấu hình bộ lọc",
|
||||
@@ -643,5 +652,29 @@
|
||||
"confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?",
|
||||
"cache_cleared": "Đã xóa thành công bộ đệm DNS",
|
||||
"clear_cache": "Xóa bộ nhớ cache",
|
||||
"protection_section_label": "Sự bảo vệ"
|
||||
"make_static": "Chuyển sang tĩnh",
|
||||
"theme_auto_desc": "Tự động (dựa trên chủ đề màu của thiết bị của bạn)",
|
||||
"theme_dark_desc": "Chủ đề tối",
|
||||
"theme_light_desc": "Chủ đề sáng",
|
||||
"disable_for_seconds": "Trong {{count}} giây",
|
||||
"disable_for_seconds_plural": "Trong {{count}} giây",
|
||||
"disable_for_minutes": "Trong {{count}} phút",
|
||||
"disable_for_minutes_plural": "Trong {{count}} phút",
|
||||
"disable_for_hours": "Trong {{count}} giờ",
|
||||
"disable_for_hours_plural": "Trong {{count}} giờ",
|
||||
"disable_until_tomorrow": "Cho đến ngày mai",
|
||||
"disable_notify_for_seconds": "Tắt bảo vệ trong {{count}} giây",
|
||||
"disable_notify_for_seconds_plural": "Tắt bảo vệ trong {{count}} giây",
|
||||
"disable_notify_for_minutes": "Tắt bảo vệ trong {{count}} phút",
|
||||
"disable_notify_for_minutes_plural": "Tắt bảo vệ trong {{count}} phút",
|
||||
"disable_notify_for_hours": "Tắt bảo vệ trong {{count}} giờ",
|
||||
"disable_notify_for_hours_plural": "Tắt bảo vệ trong {{count}} giờ",
|
||||
"disable_notify_until_tomorrow": "Vô hiệu hóa bảo vệ cho đến ngày mai",
|
||||
"enable_protection_timer": "Bảo vệ sẽ được bật trong {{time}}",
|
||||
"custom_retention_input": "Nhập thời gian giữ lại theo giờ",
|
||||
"custom_rotation_input": "Nhập chu kỳ theo giờ",
|
||||
"protection_section_label": "Sự bảo vệ",
|
||||
"log_and_stats_section_label": "Nhật ký truy vấn và thống kê",
|
||||
"ignore_query_log": "Bỏ qua máy khách này trong nhật ký truy vấn",
|
||||
"ignore_statistics": "Bỏ qua máy khách này trong thống kê"
|
||||
}
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "为了使用 <1>DNS-over-HTTPS</1> 或者 <1>DNS-over-TLS</1> ,您需要在 AdGuard Home 设置中 <0>配置加密</0> 。",
|
||||
"rewrite_added": "已成功添加 \"{{key}}\" 的 DNS 重写",
|
||||
"rewrite_deleted": "已成功删除 \"{{key}}\" 的 DNS 重写",
|
||||
"rewrite_updated": "DNS 重写已成功更新",
|
||||
"rewrite_add": "添加 DNS 重写",
|
||||
"rewrite_edit": "编辑 DNS 重写",
|
||||
"rewrite_not_found": "未找到 DNS 重写",
|
||||
"rewrite_confirm_delete": "您确定要删除 \"{{key}}\" 的 DNS 重写?",
|
||||
"rewrite_desc": "可以轻松地为特定域名配置自定义 DNS 响应。",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"out_of_range_error": "必須介於 \"{{start}}\" - \"{{end}}\" 範圍之外",
|
||||
"lower_range_start_error": "必須小於起始值",
|
||||
"greater_range_start_error": "必須大於起始值",
|
||||
"gateway_or_subnet_invalid": "無效子網路",
|
||||
"dhcp_form_gateway_input": "閘道 IP 位址",
|
||||
"dhcp_form_subnet_input": "子網路遮罩",
|
||||
"dhcp_form_range_title": "IP 位址範圍",
|
||||
@@ -195,6 +196,7 @@
|
||||
"form_error_url_or_path_format": "列表中含有的 URL 網址或絕對路徑",
|
||||
"custom_filter_rules": "自訂過濾規則",
|
||||
"custom_filter_rules_hint": "一行一條規則。您可以使用「adblock」語法或「hosts檔案」的語法。",
|
||||
"system_host_files": "系統 hosts 檔案",
|
||||
"examples_title": "範例",
|
||||
"example_meaning_filter_block": "封鎖對 example.org 網域及其所有子網域的存取",
|
||||
"example_meaning_filter_whitelist": "解除對 example.org 網域及其所有子網域存取封鎖",
|
||||
@@ -279,6 +281,8 @@
|
||||
"rate_limit": "速率限制",
|
||||
"edns_enable": "啟用 EDNS Client Subnet",
|
||||
"edns_cs_desc": "傳送用戶端的子網路給 DNS 伺服器。",
|
||||
"edns_use_custom_ip": "使用自訂 EDNS IP",
|
||||
"edns_use_custom_ip_desc": "允許使用自訂 EDNS IP",
|
||||
"rate_limit_desc": "限制單一裝置每秒發出的查詢次數(設定為 0 即表示無限制)",
|
||||
"blocking_ipv4_desc": "回覆指定 IPv4 位址給被封鎖的網域的 A 紀錄查詢",
|
||||
"blocking_ipv6_desc": "回覆指定 IPv6 位址給被封鎖的網域的 AAAA 紀錄查詢",
|
||||
@@ -287,6 +291,9 @@
|
||||
"blocking_mode_nxdomain": "NXDOMAIN:回應 NXDOMAIN 狀態碼",
|
||||
"blocking_mode_null_ip": "Null IP:回應零值的 IP 位址(A 紀錄回應 0.0.0.0 ,AAAA 紀錄回應 ::)",
|
||||
"blocking_mode_custom_ip": "自訂 IP 位址:回應一個自訂的 IP 位址",
|
||||
"theme_auto": "自動",
|
||||
"theme_light": "明亮",
|
||||
"theme_dark": "深色",
|
||||
"upstream_dns_client_desc": "如果您將此欄位留白,AdGuard Home 將使用 <0>DNS 設定</0> 內的設定的 DNS 伺服器。",
|
||||
"tracker_source": "追蹤器來源",
|
||||
"source_label": "來源",
|
||||
@@ -397,6 +404,7 @@
|
||||
"dns_providers": "下列為常見的<0> DNS 伺服器</0>。",
|
||||
"update_now": "立即更新",
|
||||
"update_failed": "自動更新發生錯誤。請嘗試依照<a>以下步驟</a> 來手動更新。",
|
||||
"manual_update": "請嘗試依照<a>下列步驟</a>來手動更新。",
|
||||
"processing_update": "請稍候,AdGuard Home 正在更新",
|
||||
"clients_title": "用戶端",
|
||||
"clients_desc": "對已連接到 AdGuard Home 的裝置進行設定",
|
||||
@@ -505,6 +513,7 @@
|
||||
"statistics_clear_confirm": "您確定要清除統計資料嗎?",
|
||||
"statistics_retention_confirm": "您確定要更改統計資料保存時間嗎?如果您縮短期限部分資料可能將會遺失",
|
||||
"statistics_cleared": "已清除統計資料",
|
||||
"statistics_enable": "啟用統計數據",
|
||||
"interval_hours": "{{count}} 小時",
|
||||
"interval_hours_plural": "{{count}} 小時",
|
||||
"filters_configuration": "過濾器設定",
|
||||
@@ -613,5 +622,22 @@
|
||||
"original_response": "原始回應",
|
||||
"click_to_view_queries": "按一下以檢視查詢結果",
|
||||
"port_53_faq_link": "連接埠 53 經常被「DNSStubListener」或「systemd-resolved」服務佔用。請閱讀下列有關解決<0>這個問題</0>的說明",
|
||||
"adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。"
|
||||
"adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。",
|
||||
"safe_browsing": "安全瀏覽",
|
||||
"served_from_cache": "{{value}} <i>(由快取回應)</i>",
|
||||
"form_error_password_length": "密碼必須至少 {{value}} 個字元長度",
|
||||
"theme_dark_desc": "深色主題",
|
||||
"theme_light_desc": "淺色主題",
|
||||
"disable_for_seconds": "{{count}} 秒",
|
||||
"disable_for_seconds_plural": "{{count}} 秒",
|
||||
"disable_for_minutes": "{{count}} 分鐘",
|
||||
"disable_for_minutes_plural": "{{count}} 分鐘",
|
||||
"disable_for_hours": "{{count}} 小時",
|
||||
"disable_for_hours_plural": "{{count}} 小時",
|
||||
"disable_until_tomorrow": "直到明天",
|
||||
"disable_notify_for_seconds": "暫停防護 {{count}} 秒",
|
||||
"disable_notify_for_seconds_plural": "暫停防護 {{count}} 秒",
|
||||
"disable_notify_for_minutes": "暫停防護 {{count}} 分鐘",
|
||||
"disable_notify_for_minutes_plural": "暫停防護 {{count}} 分鐘",
|
||||
"disable_notify_for_hours": "暫停防護 {{count}} 小時"
|
||||
}
|
||||
|
||||
@@ -478,7 +478,9 @@
|
||||
"setup_dns_notice": "為了使用 <1>DNS-over-HTTPS</1> 或 <1>DNS-over-TLS</1>,您需要在 AdGuard Home 設定裡<0>配置加密</0>。",
|
||||
"rewrite_added": "對於 \"{{key}}\" 之 DNS 改寫被成功地加入",
|
||||
"rewrite_deleted": "對於 \"{{key}}\" 之 DNS 改寫被成功地刪除",
|
||||
"rewrite_updated": "DNS 重寫已成功更新",
|
||||
"rewrite_add": "新增 DNS 改寫",
|
||||
"rewrite_edit": "編輯 DNS 重寫",
|
||||
"rewrite_not_found": "無已發現之 DNS 改寫",
|
||||
"rewrite_confirm_delete": "您確定您想要刪除對於 \"{{key}}\" 之 DNS 改寫嗎?",
|
||||
"rewrite_desc": "允許輕易地配置自訂的 DNS 回應供特定的域名。",
|
||||
|
||||
@@ -38,6 +38,29 @@ export const addRewrite = (config) => async (dispatch) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateRewriteRequest = createAction('UPDATE_REWRITE_REQUEST');
|
||||
export const updateRewriteFailure = createAction('UPDATE_REWRITE_FAILURE');
|
||||
export const updateRewriteSuccess = createAction('UPDATE_REWRITE_SUCCESS');
|
||||
|
||||
/**
|
||||
* @param {Object} config
|
||||
* @param {string} config.target - current DNS rewrite value
|
||||
* @param {string} config.update - updated DNS rewrite value
|
||||
*/
|
||||
export const updateRewrite = (config) => async (dispatch) => {
|
||||
dispatch(updateRewriteRequest());
|
||||
try {
|
||||
await apiClient.updateRewrite(config);
|
||||
dispatch(updateRewriteSuccess());
|
||||
dispatch(toggleRewritesModal());
|
||||
dispatch(getRewritesList());
|
||||
dispatch(addSuccessToast(i18next.t('rewrite_updated', { key: config.domain })));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(updateRewriteFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteRewriteRequest = createAction('DELETE_REWRITE_REQUEST');
|
||||
export const deleteRewriteFailure = createAction('DELETE_REWRITE_FAILURE');
|
||||
export const deleteRewriteSuccess = createAction('DELETE_REWRITE_SUCCESS');
|
||||
|
||||
@@ -455,6 +455,8 @@ class Api {
|
||||
|
||||
REWRITE_ADD = { path: 'rewrite/add', method: 'POST' };
|
||||
|
||||
REWRITE_UPDATE = { path: 'rewrite/update', method: 'PUT' };
|
||||
|
||||
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' };
|
||||
|
||||
getRewritesList() {
|
||||
@@ -470,6 +472,14 @@ class Api {
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
updateRewrite(config) {
|
||||
const { path, method } = this.REWRITE_UPDATE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
deleteRewrite(config) {
|
||||
const { path, method } = this.REWRITE_DELETE;
|
||||
const parameters = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { shallowEqual, useSelector } from 'react-redux';
|
||||
import Card from '../ui/Card';
|
||||
import { formatNumber } from '../../helpers/helpers';
|
||||
import LogsSearchLink from '../ui/LogsSearchLink';
|
||||
import { RESPONSE_FILTER } from '../../helpers/constants';
|
||||
import { RESPONSE_FILTER, DAY } from '../../helpers/constants';
|
||||
import Tooltip from '../ui/Tooltip';
|
||||
|
||||
const Row = ({
|
||||
@@ -54,12 +54,12 @@ const Counters = ({ refreshButton, subtitle }) => {
|
||||
avgProcessingTime,
|
||||
} = useSelector((state) => state.stats, shallowEqual);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const days = interval / DAY;
|
||||
const rows = [
|
||||
{
|
||||
label: 'dns_query',
|
||||
count: numDnsQueries,
|
||||
tooltipTitle: interval === 1 ? 'number_of_dns_query_24_hours' : t('number_of_dns_query_days', { count: interval }),
|
||||
tooltipTitle: days === 1 ? 'number_of_dns_query_24_hours' : t('number_of_dns_query_days', { count: days }),
|
||||
response_status: RESPONSE_FILTER.ALL.QUERY,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -105,6 +105,7 @@ Form.propTypes = {
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
processingAdd: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
initialValues: PropTypes.object,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import { MODAL_TYPE } from '../../../helpers/constants';
|
||||
import Form from './Form';
|
||||
|
||||
const Modal = (props) => {
|
||||
@@ -12,6 +13,8 @@ const Modal = (props) => {
|
||||
toggleRewritesModal,
|
||||
processingAdd,
|
||||
processingDelete,
|
||||
modalType,
|
||||
currentRewrite,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -24,13 +27,18 @@ const Modal = (props) => {
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
<Trans>rewrite_add</Trans>
|
||||
{modalType === MODAL_TYPE.EDIT_REWRITE ? (
|
||||
<Trans>rewrite_edit</Trans>
|
||||
) : (
|
||||
<Trans>rewrite_add</Trans>
|
||||
)}
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={() => toggleRewritesModal()}>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
<Form
|
||||
initialValues={{ ...currentRewrite }}
|
||||
onSubmit={handleSubmit}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
processingAdd={processingAdd}
|
||||
@@ -47,6 +55,8 @@ Modal.propTypes = {
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
processingAdd: PropTypes.bool.isRequired,
|
||||
processingDelete: PropTypes.bool.isRequired,
|
||||
modalType: PropTypes.string.isRequired,
|
||||
currentRewrite: PropTypes.object,
|
||||
};
|
||||
|
||||
export default withTranslation()(Modal);
|
||||
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { sortIp } from '../../../helpers/helpers';
|
||||
import { MODAL_TYPE } from '../../../helpers/constants';
|
||||
|
||||
class Table extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
@@ -31,24 +32,44 @@ class Table extends Component {
|
||||
maxWidth: 100,
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
Cell: (value) => (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm"
|
||||
onClick={() => this.props.handleDelete({
|
||||
answer: value.row.answer,
|
||||
domain: value.row.domain,
|
||||
})
|
||||
}
|
||||
title={this.props.t('delete_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
Cell: (value) => {
|
||||
const currentRewrite = {
|
||||
answer: value.row.answer,
|
||||
domain: value.row.domain,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm mr-2"
|
||||
onClick={() => {
|
||||
this.props.toggleRewritesModal({
|
||||
type: MODAL_TYPE.EDIT_REWRITE,
|
||||
currentRewrite,
|
||||
});
|
||||
}}
|
||||
disabled={this.props.processingUpdate}
|
||||
title={this.props.t('edit_table_action')}
|
||||
>
|
||||
<svg className="icons icon12">
|
||||
<use xlinkHref="#edit" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
onClick={() => this.props.handleDelete(currentRewrite)}
|
||||
title={this.props.t('delete_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -84,7 +105,9 @@ Table.propTypes = {
|
||||
processing: PropTypes.bool.isRequired,
|
||||
processingAdd: PropTypes.bool.isRequired,
|
||||
processingDelete: PropTypes.bool.isRequired,
|
||||
processingUpdate: PropTypes.bool.isRequired,
|
||||
handleDelete: PropTypes.func.isRequired,
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withTranslation()(Table);
|
||||
|
||||
@@ -6,16 +6,13 @@ import Table from './Table';
|
||||
import Modal from './Modal';
|
||||
import Card from '../../ui/Card';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import { MODAL_TYPE } from '../../../helpers/constants';
|
||||
|
||||
class Rewrites extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getRewritesList();
|
||||
}
|
||||
|
||||
handleSubmit = (values) => {
|
||||
this.props.addRewrite(values);
|
||||
};
|
||||
|
||||
handleDelete = (values) => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) {
|
||||
@@ -23,6 +20,19 @@ class Rewrites extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
handleSubmit = (values) => {
|
||||
const { modalType, currentRewrite } = this.props.rewrites;
|
||||
|
||||
if (modalType === MODAL_TYPE.EDIT_REWRITE && currentRewrite) {
|
||||
this.props.updateRewrite({
|
||||
target: currentRewrite,
|
||||
update: values,
|
||||
});
|
||||
} else {
|
||||
this.props.addRewrite(values);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
@@ -36,6 +46,9 @@ class Rewrites extends Component {
|
||||
processing,
|
||||
processingAdd,
|
||||
processingDelete,
|
||||
processingUpdate,
|
||||
modalType,
|
||||
currentRewrite,
|
||||
} = rewrites;
|
||||
|
||||
return (
|
||||
@@ -54,13 +67,15 @@ class Rewrites extends Component {
|
||||
processing={processing}
|
||||
processingAdd={processingAdd}
|
||||
processingDelete={processingDelete}
|
||||
processingUpdate={processingUpdate}
|
||||
handleDelete={this.handleDelete}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => toggleRewritesModal()}
|
||||
onClick={() => toggleRewritesModal({ type: MODAL_TYPE.ADD_REWRITE })}
|
||||
disabled={processingAdd}
|
||||
>
|
||||
<Trans>rewrite_add</Trans>
|
||||
@@ -68,10 +83,13 @@ class Rewrites extends Component {
|
||||
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
modalType={modalType}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
handleSubmit={this.handleSubmit}
|
||||
processingAdd={processingAdd}
|
||||
processingDelete={processingDelete}
|
||||
processingUpdate={processingUpdate}
|
||||
currentRewrite={currentRewrite}
|
||||
/>
|
||||
</Fragment>
|
||||
</Card>
|
||||
@@ -86,6 +104,7 @@ Rewrites.propTypes = {
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
addRewrite: PropTypes.func.isRequired,
|
||||
deleteRewrite: PropTypes.func.isRequired,
|
||||
updateRewrite: PropTypes.func.isRequired,
|
||||
rewrites: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ class Table extends Component {
|
||||
Header: <Trans>list_url_table_header</Trans>,
|
||||
accessor: 'url',
|
||||
minWidth: 180,
|
||||
// eslint-disable-next-line react/prop-types
|
||||
Cell: ({ value }) => (
|
||||
<div className="logs__row">
|
||||
{isValidAbsolutePath(value) ? value
|
||||
|
||||
@@ -32,6 +32,8 @@ const ProtectionTimer = ({
|
||||
};
|
||||
|
||||
ProtectionTimer.propTypes = {
|
||||
protectionDisabledDuration: PropTypes.number,
|
||||
toggleProtectionSuccess: PropTypes.func.isRequired,
|
||||
setProtectionTimerTime: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
} from '../../../helpers/constants';
|
||||
import '../FormButton.css';
|
||||
|
||||
|
||||
const getIntervalTitle = (interval, t) => {
|
||||
switch (interval) {
|
||||
case RETENTION_CUSTOM:
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Trans, withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import {
|
||||
renderRadioField,
|
||||
toNumber,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
import React from 'react';
|
||||
|
||||
import './Icons.css';
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
getRewritesList,
|
||||
addRewrite,
|
||||
deleteRewrite,
|
||||
updateRewrite,
|
||||
toggleRewritesModal,
|
||||
} from '../actions/rewrites';
|
||||
import Rewrites from '../components/Filters/Rewrites';
|
||||
@@ -17,6 +18,7 @@ const mapDispatchToProps = {
|
||||
getRewritesList,
|
||||
addRewrite,
|
||||
deleteRewrite,
|
||||
updateRewrite,
|
||||
toggleRewritesModal,
|
||||
};
|
||||
|
||||
|
||||
@@ -173,6 +173,8 @@ export const MODAL_TYPE = {
|
||||
ADD_FILTERS: 'ADD_FILTERS',
|
||||
EDIT_FILTERS: 'EDIT_FILTERS',
|
||||
CHOOSE_FILTERING_LIST: 'CHOOSE_FILTERING_LIST',
|
||||
ADD_REWRITE: 'ADD_REWRITE',
|
||||
EDIT_REWRITE: 'EDIT_REWRITE',
|
||||
};
|
||||
|
||||
export const CLIENT_ID = {
|
||||
|
||||
@@ -845,7 +845,6 @@ export const sortIp = (a, b) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} filterId
|
||||
* @returns {string}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,27 @@ const rewrites = handleActions(
|
||||
[actions.deleteRewriteFailure]: (state) => ({ ...state, processingDelete: false }),
|
||||
[actions.deleteRewriteSuccess]: (state) => ({ ...state, processingDelete: false }),
|
||||
|
||||
[actions.toggleRewritesModal]: (state) => {
|
||||
[actions.updateRewriteRequest]: (state) => ({ ...state, processingUpdate: true }),
|
||||
[actions.updateRewriteFailure]: (state) => ({ ...state, processingUpdate: false }),
|
||||
[actions.updateRewriteSuccess]: (state) => {
|
||||
const newState = {
|
||||
...state,
|
||||
processingUpdate: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.toggleRewritesModal]: (state, { payload }) => {
|
||||
if (payload) {
|
||||
const newState = {
|
||||
...state,
|
||||
modalType: payload.type || '',
|
||||
isModalOpen: !state.isModalOpen,
|
||||
currentRewrite: payload.currentRewrite,
|
||||
};
|
||||
return newState;
|
||||
}
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
isModalOpen: !state.isModalOpen,
|
||||
@@ -42,7 +62,10 @@ const rewrites = handleActions(
|
||||
processing: true,
|
||||
processingAdd: false,
|
||||
processingDelete: false,
|
||||
processingUpdate: false,
|
||||
isModalOpen: false,
|
||||
modalType: '',
|
||||
currentRewrite: {},
|
||||
list: [],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -82,6 +82,5 @@ CMD [ \
|
||||
"/opt/adguardhome/AdGuardHome", \
|
||||
"--no-check-update", \
|
||||
"-c", "/opt/adguardhome/conf/AdGuardHome.yaml", \
|
||||
"-h", "0.0.0.0", \
|
||||
"-w", "/opt/adguardhome/work" \
|
||||
]
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# Don't consider the HTTPS hostname since the enforced HTTPS redirection should
|
||||
# work if the SSL check skipped. See file docker/healthcheck.sh.
|
||||
/^bind_host:/ { host = $2 }
|
||||
/^[^[:space:]]/ { is_http = /^http:/ }
|
||||
|
||||
/^bind_port:/ { port = $2 }
|
||||
|
||||
END {
|
||||
if (match(host, ":")) {
|
||||
print "http://[" host "]:" port
|
||||
} else {
|
||||
print "http://" host ":" port
|
||||
}
|
||||
}
|
||||
/^[[:space:]]+address:/ { if (is_http) print "http://" $2 }
|
||||
|
||||
19
go.mod
19
go.mod
@@ -3,7 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.50.2
|
||||
// TODO(a.garipov): Update to a tagged version when it's released.
|
||||
github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768
|
||||
github.com/AdguardTeam/golibs v0.13.3
|
||||
github.com/AdguardTeam/urlfilter v0.16.1
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
@@ -17,7 +18,7 @@ require (
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/renameio v1.0.1
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
|
||||
github.com/kardianos/service v1.2.2
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
|
||||
@@ -26,13 +27,13 @@ require (
|
||||
// TODO(a.garipov): This package is deprecated; find a new one or use our
|
||||
// own code for that. Perhaps, use gopacket.
|
||||
github.com/mdlayher/raw v0.1.0
|
||||
github.com/miekg/dns v1.1.54
|
||||
github.com/miekg/dns v1.1.55
|
||||
github.com/quic-go/quic-go v0.35.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/ti-mo/netfilter v0.5.0
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
|
||||
golang.org/x/net v0.11.0
|
||||
golang.org/x/sys v0.9.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
@@ -50,17 +51,17 @@ require (
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.10.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
)
|
||||
|
||||
38
go.sum
38
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/AdguardTeam/dnsproxy v0.50.2 h1:p1471SsMZ6SMo7T51Olw4aNluahvMwSLMorwxYV18ts=
|
||||
github.com/AdguardTeam/dnsproxy v0.50.2/go.mod h1:CQhZTkqC8X0ID6glrtyaxgqRRdiYfn1gJulC1cZ5Dn8=
|
||||
github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768 h1:5Ia6wA+tqAlTyzuaOVGSlHmb0osLWXeJUs3NxCuC4gA=
|
||||
github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768/go.mod h1:CQhZTkqC8X0ID6glrtyaxgqRRdiYfn1gJulC1cZ5Dn8=
|
||||
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
|
||||
github.com/AdguardTeam/golibs v0.13.3 h1:RT3QbzThtaLiFLkIUDS6/hlGEXrh0zYvdf4bd7UWpGo=
|
||||
@@ -58,8 +58,8 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb h1:6fDKEAXwe3rsfS4khW3EZ8kEqmSiV9szhMPcDrD+Y7Q=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df h1:pF1MMIzEJzJ/MyI4bXYXVYyN8CJgoQ2PPKT2z3O/Cl4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
@@ -85,18 +85,18 @@ github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
|
||||
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
|
||||
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
||||
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -136,13 +136,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -156,8 +156,8 @@ golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -191,8 +191,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
|
||||
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -56,15 +56,20 @@ func (rm *requestMatcher) MatchRequest(
|
||||
) (res *urlfilter.DNSResult, ok bool) {
|
||||
switch req.DNSType {
|
||||
case dns.TypeA, dns.TypeAAAA, dns.TypePTR:
|
||||
log.Debug("%s: handling the request for %s", hostsContainerPrefix, req.Hostname)
|
||||
log.Debug(
|
||||
"%s: handling %s request for %s",
|
||||
hostsContainerPrefix,
|
||||
dns.Type(req.DNSType),
|
||||
req.Hostname,
|
||||
)
|
||||
|
||||
rm.stateLock.RLock()
|
||||
defer rm.stateLock.RUnlock()
|
||||
|
||||
return rm.engine.MatchRequest(req)
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
rm.stateLock.RLock()
|
||||
defer rm.stateLock.RUnlock()
|
||||
|
||||
return rm.engine.MatchRequest(req)
|
||||
}
|
||||
|
||||
// Translate returns the source hosts-syntax rule for the generated dnsrewrite
|
||||
@@ -96,6 +101,8 @@ const hostsContainerPrefix = "hosts container"
|
||||
|
||||
// HostsContainer stores the relevant hosts database provided by the OS and
|
||||
// processes both A/AAAA and PTR DNS requests for those.
|
||||
//
|
||||
// TODO(e.burkov): Improve API and move to golibs.
|
||||
type HostsContainer struct {
|
||||
// requestMatcher matches the requests and translates the rules. It's
|
||||
// embedded to implement MatchRequest and Translate for *HostsContainer.
|
||||
|
||||
@@ -25,11 +25,8 @@ func (s *bitSet) isSet(n uint64) (ok bool) {
|
||||
|
||||
var word uint64
|
||||
word, ok = s.words[wordIdx]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return word&(1<<bitIdx) != 0
|
||||
return ok && word&(1<<bitIdx) != 0
|
||||
}
|
||||
|
||||
// set sets or unsets a bit.
|
||||
|
||||
@@ -249,31 +249,30 @@ func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []b
|
||||
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
|
||||
switch giaddr, ciaddr, mtype := req.GatewayIPAddr, req.ClientIPAddr, resp.MessageType(); {
|
||||
case giaddr != nil && !giaddr.IsUnspecified():
|
||||
// Send any return messages to the server port on the BOOTP
|
||||
// relay agent whose address appears in giaddr.
|
||||
// Send any return messages to the server port on the BOOTP relay agent
|
||||
// whose address appears in giaddr.
|
||||
peer = &net.UDPAddr{
|
||||
IP: giaddr,
|
||||
Port: dhcpv4.ServerPort,
|
||||
}
|
||||
if mtype == dhcpv4.MessageTypeNak {
|
||||
// Set the broadcast bit in the DHCPNAK, so that the relay agent
|
||||
// broadcasts it to the client, because the client may not have
|
||||
// a correct network address or subnet mask, and the client may not
|
||||
// be answering ARP requests.
|
||||
// broadcasts it to the client, because the client may not have a
|
||||
// correct network address or subnet mask, and the client may not be
|
||||
// answering ARP requests.
|
||||
resp.SetBroadcast()
|
||||
}
|
||||
case mtype == dhcpv4.MessageTypeNak:
|
||||
// Broadcast any DHCPNAK messages to 0xffffffff.
|
||||
case ciaddr != nil && !ciaddr.IsUnspecified():
|
||||
// Unicast DHCPOFFER and DHCPACK messages to the address in
|
||||
// ciaddr.
|
||||
// Unicast DHCPOFFER and DHCPACK messages to the address in ciaddr.
|
||||
peer = &net.UDPAddr{
|
||||
IP: ciaddr,
|
||||
Port: dhcpv4.ClientPort,
|
||||
}
|
||||
case !req.IsBroadcast() && req.ClientHWAddr != nil:
|
||||
// Unicast DHCPOFFER and DHCPACK messages to the client's
|
||||
// hardware address and yiaddr.
|
||||
// Unicast DHCPOFFER and DHCPACK messages to the client's hardware
|
||||
// address and yiaddr.
|
||||
peer = &dhcpUnicastAddr{
|
||||
Addr: raw.Addr{HardwareAddr: req.ClientHWAddr},
|
||||
yiaddr: resp.YourIPAddr,
|
||||
|
||||
@@ -247,31 +247,30 @@ func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []b
|
||||
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
|
||||
switch giaddr, ciaddr, mtype := req.GatewayIPAddr, req.ClientIPAddr, resp.MessageType(); {
|
||||
case giaddr != nil && !giaddr.IsUnspecified():
|
||||
// Send any return messages to the server port on the BOOTP
|
||||
// relay agent whose address appears in giaddr.
|
||||
// Send any return messages to the server port on the BOOTP relay agent
|
||||
// whose address appears in giaddr.
|
||||
peer = &net.UDPAddr{
|
||||
IP: giaddr,
|
||||
Port: dhcpv4.ServerPort,
|
||||
}
|
||||
if mtype == dhcpv4.MessageTypeNak {
|
||||
// Set the broadcast bit in the DHCPNAK, so that the relay agent
|
||||
// broadcasts it to the client, because the client may not have
|
||||
// a correct network address or subnet mask, and the client may not
|
||||
// be answering ARP requests.
|
||||
// broadcasts it to the client, because the client may not have a
|
||||
// correct network address or subnet mask, and the client may not be
|
||||
// answering ARP requests.
|
||||
resp.SetBroadcast()
|
||||
}
|
||||
case mtype == dhcpv4.MessageTypeNak:
|
||||
// Broadcast any DHCPNAK messages to 0xffffffff.
|
||||
case ciaddr != nil && !ciaddr.IsUnspecified():
|
||||
// Unicast DHCPOFFER and DHCPACK messages to the address in
|
||||
// ciaddr.
|
||||
// Unicast DHCPOFFER and DHCPACK messages to the address in ciaddr.
|
||||
peer = &net.UDPAddr{
|
||||
IP: ciaddr,
|
||||
Port: dhcpv4.ClientPort,
|
||||
}
|
||||
case !req.IsBroadcast() && req.ClientHWAddr != nil:
|
||||
// Unicast DHCPOFFER and DHCPACK messages to the client's
|
||||
// hardware address and yiaddr.
|
||||
// Unicast DHCPOFFER and DHCPACK messages to the client's hardware
|
||||
// address and yiaddr.
|
||||
peer = &dhcpUnicastAddr{
|
||||
Addr: packet.Addr{HardwareAddr: req.ClientHWAddr},
|
||||
yiaddr: resp.YourIPAddr,
|
||||
|
||||
@@ -28,8 +28,9 @@ const (
|
||||
defaultBackoff time.Duration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
// Lease contains the necessary information about a DHCP lease. It's used in
|
||||
// various places. So don't change it without good reason.
|
||||
// Lease contains the necessary information about a DHCP lease. It's used as is
|
||||
// in the database, so don't change it until it's absolutely necessary, see
|
||||
// [dataVersion].
|
||||
type Lease struct {
|
||||
// Expiry is the expiration time of the lease.
|
||||
Expiry time.Time `json:"expires"`
|
||||
@@ -41,8 +42,6 @@ type Lease struct {
|
||||
HWAddr net.HardwareAddr `json:"mac"`
|
||||
|
||||
// IP is the IP address leased to the client.
|
||||
//
|
||||
// TODO(a.garipov): Migrate leases.db.
|
||||
IP netip.Addr `json:"ip"`
|
||||
|
||||
// IsStatic defines if the lease is static.
|
||||
|
||||
@@ -51,6 +51,9 @@ func migrateDB(conf *ServerConfig) (err error) {
|
||||
oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename)
|
||||
dataDirPath := filepath.Join(conf.DataDir, dataFilename)
|
||||
|
||||
// #nosec G304 -- Trust this path, since it's taken from the old file name
|
||||
// relative to the working directory and should generally be considered
|
||||
// safe.
|
||||
file, err := os.Open(oldLeasesPath)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// Nothing to migrate.
|
||||
|
||||
@@ -200,7 +200,7 @@ func createICMPv6RAPacket(params icmpv6RA) (data []byte, err error) {
|
||||
func (ra *raCtx) Init() (err error) {
|
||||
ra.stop.Store(0)
|
||||
ra.conn = nil
|
||||
if !(ra.raAllowSLAAC || ra.raSLAACOnly) {
|
||||
if !ra.raAllowSLAAC && !ra.raSLAACOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
86
internal/dhcpsvc/config.go
Normal file
86
internal/dhcpsvc/config.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// Config is the configuration for the DHCP service.
|
||||
type Config struct {
|
||||
// Interfaces stores configurations of DHCP server specific for the network
|
||||
// interface identified by its name.
|
||||
Interfaces map[string]*InterfaceConfig
|
||||
|
||||
// LocalDomainName is the top-level domain name to use for resolving DHCP
|
||||
// clients' hostnames.
|
||||
LocalDomainName string
|
||||
|
||||
// ICMPTimeout is the timeout for checking another DHCP server's presence.
|
||||
ICMPTimeout time.Duration
|
||||
|
||||
// Enabled is the state of the service, whether it is enabled or not.
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// InterfaceConfig is the configuration of a single DHCP interface.
|
||||
type InterfaceConfig struct {
|
||||
// IPv4 is the configuration of DHCP protocol for IPv4.
|
||||
IPv4 *IPv4Config
|
||||
|
||||
// IPv6 is the configuration of DHCP protocol for IPv6.
|
||||
IPv6 *IPv6Config
|
||||
}
|
||||
|
||||
// IPv4Config is the interface-specific configuration for DHCPv4.
|
||||
type IPv4Config struct {
|
||||
// GatewayIP is the IPv4 address of the network's gateway. It is used as
|
||||
// the default gateway for DHCP clients and also used in calculating the
|
||||
// network-specific broadcast address.
|
||||
GatewayIP netip.Addr
|
||||
|
||||
// SubnetMask is the IPv4 subnet mask of the network. It should be a valid
|
||||
// IPv4 subnet mask (i.e. all 1s followed by all 0s).
|
||||
SubnetMask netip.Addr
|
||||
|
||||
// RangeStart is the first address in the range to assign to DHCP clients.
|
||||
RangeStart netip.Addr
|
||||
|
||||
// RangeEnd is the last address in the range to assign to DHCP clients.
|
||||
RangeEnd netip.Addr
|
||||
|
||||
// Options is the list of DHCP options to send to DHCP clients.
|
||||
Options layers.DHCPOptions
|
||||
|
||||
// LeaseDuration is the TTL of a DHCP lease.
|
||||
LeaseDuration time.Duration
|
||||
|
||||
// Enabled is the state of the DHCPv4 service, whether it is enabled or not
|
||||
// on the specific interface.
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// IPv6Config is the interface-specific configuration for DHCPv6.
|
||||
type IPv6Config struct {
|
||||
// RangeStart is the first address in the range to assign to DHCP clients.
|
||||
RangeStart netip.Addr
|
||||
|
||||
// Options is the list of DHCP options to send to DHCP clients.
|
||||
Options layers.DHCPOptions
|
||||
|
||||
// LeaseDuration is the TTL of a DHCP lease.
|
||||
LeaseDuration time.Duration
|
||||
|
||||
// RASlaacOnly defines whether the DHCP clients should only use SLAAC for
|
||||
// address assignment.
|
||||
RASLAACOnly bool
|
||||
|
||||
// RAAllowSlaac defines whether the DHCP clients may use SLAAC for address
|
||||
// assignment.
|
||||
RAAllowSLAAC bool
|
||||
|
||||
// Enabled is the state of the DHCPv6 service, whether it is enabled or not
|
||||
// on the specific interface.
|
||||
Enabled bool
|
||||
}
|
||||
120
internal/dhcpsvc/dhcpsvc.go
Normal file
120
internal/dhcpsvc/dhcpsvc.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// Package dhcpsvc contains the AdGuard Home DHCP service.
|
||||
//
|
||||
// TODO(e.burkov): Add tests.
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
)
|
||||
|
||||
// Lease is a DHCP lease.
|
||||
//
|
||||
// TODO(e.burkov): Consider it to [agh], since it also may be needed in
|
||||
// [websvc]. Also think of implementing iterating methods with appropriate
|
||||
// signatures.
|
||||
type Lease struct {
|
||||
// IP is the IP address leased to the client.
|
||||
IP netip.Addr
|
||||
|
||||
// Expiry is the expiration time of the lease.
|
||||
Expiry time.Time
|
||||
|
||||
// Hostname of the client.
|
||||
Hostname string
|
||||
|
||||
// HWAddr is the physical hardware address (MAC address).
|
||||
HWAddr net.HardwareAddr
|
||||
|
||||
// IsStatic defines if the lease is static.
|
||||
IsStatic bool
|
||||
}
|
||||
|
||||
type Interface interface {
|
||||
agh.ServiceWithConfig[*Config]
|
||||
|
||||
// Enabled returns true if DHCP provides information about clients.
|
||||
Enabled() (ok bool)
|
||||
|
||||
// HostByIP returns the hostname of the DHCP client with the given IP
|
||||
// address. The address will be netip.Addr{} if there is no such client,
|
||||
// due to an assumption that a DHCP client must always have an IP address.
|
||||
HostByIP(ip netip.Addr) (host string)
|
||||
|
||||
// MACByIP returns the MAC address for the given IP address leased. It
|
||||
// returns nil if there is no such client, due to an assumption that a DHCP
|
||||
// client must always have a MAC address.
|
||||
MACByIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
|
||||
// IPByHost returns the IP address of the DHCP client with the given
|
||||
// hostname. The hostname will be an empty string if there is no such
|
||||
// client, due to an assumption that a DHCP client must always have a
|
||||
// hostname, either set by the client or assigned automatically.
|
||||
IPByHost(host string) (ip netip.Addr)
|
||||
|
||||
// Leases returns all the DHCP leases.
|
||||
Leases() (leases []*Lease)
|
||||
|
||||
// AddLease adds a new DHCP lease. It returns an error if the lease is
|
||||
// invalid or already exists.
|
||||
AddLease(l *Lease) (err error)
|
||||
|
||||
// EditLease changes an existing DHCP lease. It returns an error if there
|
||||
// is no lease equal to old or if new is invalid or already exists.
|
||||
EditLease(old, new *Lease) (err error)
|
||||
|
||||
// RemoveLease removes an existing DHCP lease. It returns an error if there
|
||||
// is no lease equal to l.
|
||||
RemoveLease(l *Lease) (err error)
|
||||
|
||||
// Reset removes all the DHCP leases.
|
||||
Reset() (err error)
|
||||
}
|
||||
|
||||
// Empty is an [Interface] implementation that does nothing.
|
||||
type Empty struct{}
|
||||
|
||||
// type check
|
||||
var _ Interface = Empty{}
|
||||
|
||||
// Start implements the [Service] interface for Empty.
|
||||
func (Empty) Start() (err error) { return nil }
|
||||
|
||||
// Shutdown implements the [Service] interface for Empty.
|
||||
func (Empty) Shutdown(_ context.Context) (err error) { return nil }
|
||||
|
||||
var _ agh.ServiceWithConfig[*Config] = Empty{}
|
||||
|
||||
// Config implements the [ServiceWithConfig] interface for Empty.
|
||||
func (Empty) Config() (conf *Config) { return nil }
|
||||
|
||||
// Enabled implements the [Interface] interface for Empty.
|
||||
func (Empty) Enabled() (ok bool) { return false }
|
||||
|
||||
// HostByIP implements the [Interface] interface for Empty.
|
||||
func (Empty) HostByIP(_ netip.Addr) (host string) { return "" }
|
||||
|
||||
// MACByIP implements the [Interface] interface for Empty.
|
||||
func (Empty) MACByIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
|
||||
|
||||
// IPByHost implements the [Interface] interface for Empty.
|
||||
func (Empty) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} }
|
||||
|
||||
// Leases implements the [Interface] interface for Empty.
|
||||
func (Empty) Leases() (leases []*Lease) { return nil }
|
||||
|
||||
// AddLease implements the [Interface] interface for Empty.
|
||||
func (Empty) AddLease(_ *Lease) (err error) { return nil }
|
||||
|
||||
// EditLease implements the [Interface] interface for Empty.
|
||||
func (Empty) EditLease(_, _ *Lease) (err error) { return nil }
|
||||
|
||||
// RemoveLease implements the [Interface] interface for Empty.
|
||||
func (Empty) RemoveLease(_ *Lease) (err error) { return nil }
|
||||
|
||||
// Reset implements the [Interface] interface for Empty.
|
||||
func (Empty) Reset() (err error) { return nil }
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
@@ -436,102 +435,6 @@ func (s *Server) initDefaultSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
|
||||
// depending on configuration.
|
||||
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
|
||||
if !http3 {
|
||||
return upstream.DefaultHTTPVersions
|
||||
}
|
||||
|
||||
return []upstream.HTTPVersion{
|
||||
upstream.HTTPVersion3,
|
||||
upstream.HTTPVersion2,
|
||||
upstream.HTTPVersion11,
|
||||
}
|
||||
}
|
||||
|
||||
// prepareUpstreamSettings - prepares upstream DNS server settings
|
||||
func (s *Server) prepareUpstreamSettings() error {
|
||||
// We're setting a customized set of RootCAs. The reason is that Go default
|
||||
// mechanism of loading TLS roots does not always work properly on some
|
||||
// routers so we're loading roots manually and pass it here.
|
||||
//
|
||||
// See [aghtls.SystemRootCAs].
|
||||
upstream.RootCAs = s.conf.TLSv12Roots
|
||||
upstream.CipherSuites = s.conf.TLSCiphers
|
||||
|
||||
// Load upstreams either from the file, or from the settings
|
||||
var upstreams []string
|
||||
if s.conf.UpstreamDNSFileName != "" {
|
||||
data, err := os.ReadFile(s.conf.UpstreamDNSFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading upstream from file: %w", err)
|
||||
}
|
||||
|
||||
upstreams = stringutil.SplitTrimmed(string(data), "\n")
|
||||
|
||||
log.Debug("dns: using %d upstream servers from file %s", len(upstreams), s.conf.UpstreamDNSFileName)
|
||||
} else {
|
||||
upstreams = s.conf.UpstreamDNS
|
||||
}
|
||||
|
||||
httpVersions := UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams)
|
||||
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
|
||||
upstreamConfig, err := proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: s.conf.BootstrapDNS,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
HTTPVersions: httpVersions,
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing upstream config: %w", err)
|
||||
}
|
||||
|
||||
if len(upstreamConfig.Upstreams) == 0 {
|
||||
log.Info("warning: no default upstream servers specified, using %v", defaultDNS)
|
||||
var uc *proxy.UpstreamConfig
|
||||
uc, err = proxy.ParseUpstreamsConfig(
|
||||
defaultDNS,
|
||||
&upstream.Options{
|
||||
Bootstrap: s.conf.BootstrapDNS,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
HTTPVersions: httpVersions,
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing default upstreams: %w", err)
|
||||
}
|
||||
|
||||
upstreamConfig.Upstreams = uc.Upstreams
|
||||
}
|
||||
|
||||
s.conf.UpstreamConfig = upstreamConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setProxyUpstreamMode sets the upstream mode and related settings in conf
|
||||
// based on provided parameters.
|
||||
func setProxyUpstreamMode(
|
||||
conf *proxy.Config,
|
||||
allServers bool,
|
||||
fastestAddr bool,
|
||||
fastestTimeout time.Duration,
|
||||
) {
|
||||
if allServers {
|
||||
conf.UpstreamMode = proxy.UModeParallel
|
||||
} else if fastestAddr {
|
||||
conf.UpstreamMode = proxy.UModeFastestAddr
|
||||
conf.FastestPingTimeout = fastestTimeout
|
||||
} else {
|
||||
conf.UpstreamMode = proxy.UModeLoadBalance
|
||||
}
|
||||
}
|
||||
|
||||
// prepareIpsetListSettings reads and prepares the ipset configuration either
|
||||
// from a file or from the data in the configuration file.
|
||||
func (s *Server) prepareIpsetListSettings() (err error) {
|
||||
@@ -540,6 +443,7 @@ func (s *Server) prepareIpsetListSettings() (err error) {
|
||||
return s.ipset.init(s.conf.IpsetList)
|
||||
}
|
||||
|
||||
// #nosec G304 -- Trust the path explicitly given by the user.
|
||||
data, err := os.ReadFile(fn)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -145,10 +145,13 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, pctx *proxy.DNSContext) error
|
||||
// processRecursion checks the incoming request and halts its handling by
|
||||
// answering NXDOMAIN if s has tried to resolve it recently.
|
||||
func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing recursion")
|
||||
defer log.Debug("dnsforward: finished processing recursion")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
|
||||
if msg := pctx.Req; msg != nil && s.recDetector.check(*msg) {
|
||||
log.Debug("recursion detected resolving %q", msg.Question[0].Name)
|
||||
log.Debug("dnsforward: recursion detected resolving %q", msg.Question[0].Name)
|
||||
pctx.Res = s.genNXDomain(pctx.Req)
|
||||
|
||||
return resultCodeFinish
|
||||
@@ -158,10 +161,13 @@ func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
|
||||
}
|
||||
|
||||
// processInitial terminates the following processing for some requests if
|
||||
// needed and enriches the ctx with some client-specific information.
|
||||
// needed and enriches dctx with some client-specific information.
|
||||
//
|
||||
// TODO(e.burkov): Decompose into less general processors.
|
||||
func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing initial")
|
||||
defer log.Debug("dnsforward: finished processing initial")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
q := pctx.Req.Question[0]
|
||||
qt := q.Qtype
|
||||
@@ -282,6 +288,9 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||
//
|
||||
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
|
||||
func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing ddr")
|
||||
defer log.Debug("dnsforward: finished processing ddr")
|
||||
|
||||
if !s.conf.HandleDDR {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
@@ -375,6 +384,9 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||
// processDetermineLocal determines if the client's IP address is from locally
|
||||
// served network and saves the result into the context.
|
||||
func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing local detection")
|
||||
defer log.Debug("dnsforward: finished processing local detection")
|
||||
|
||||
rc = resultCodeSuccess
|
||||
|
||||
var ip net.IP
|
||||
@@ -405,6 +417,9 @@ func (s *Server) dhcpHostToIP(host string) (ip netip.Addr, ok bool) {
|
||||
//
|
||||
// TODO(a.garipov): Adapt to AAAA as well.
|
||||
func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing dhcp hosts")
|
||||
defer log.Debug("dnsforward: finished processing dhcp hosts")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
@@ -544,6 +559,9 @@ func extractARPASubnet(domain string) (pref netip.Prefix, err error) {
|
||||
// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
|
||||
// in locally served network from external clients.
|
||||
func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing local restriction")
|
||||
defer log.Debug("dnsforward: finished processing local restriction")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
@@ -613,6 +631,9 @@ func (s *Server) ipToDHCPHost(ip netip.Addr) (host string, ok bool) {
|
||||
// processDHCPAddrs responds to PTR requests if the target IP is leased by the
|
||||
// DHCP server.
|
||||
func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing dhcp addrs")
|
||||
defer log.Debug("dnsforward: finished processing dhcp addrs")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
if pctx.Res != nil {
|
||||
return resultCodeSuccess
|
||||
@@ -658,6 +679,9 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
|
||||
// processLocalPTR responds to PTR requests if the target IP is detected to be
|
||||
// inside the local network and the query was not answered from DHCP.
|
||||
func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing local ptr")
|
||||
defer log.Debug("dnsforward: finished processing local ptr")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
if pctx.Res != nil {
|
||||
return resultCodeSuccess
|
||||
@@ -692,6 +716,9 @@ func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
|
||||
|
||||
// Apply filtering logic
|
||||
func (s *Server) processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing filtering before req")
|
||||
defer log.Debug("dnsforward: finished processing filtering before req")
|
||||
|
||||
if ctx.proxyCtx.Res != nil {
|
||||
// Go on since the response is already set.
|
||||
return resultCodeSuccess
|
||||
@@ -725,6 +752,9 @@ func ipStringFromAddr(addr net.Addr) (ipStr string) {
|
||||
|
||||
// processUpstream passes request to upstream servers and handles the response.
|
||||
func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing upstream")
|
||||
defer log.Debug("dnsforward: finished processing upstream")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
@@ -871,6 +901,9 @@ func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
|
||||
|
||||
// Apply filtering logic after we have received response from upstream servers
|
||||
func (s *Server) processFilteringAfterResponse(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing filtering after resp")
|
||||
defer log.Debug("dnsforward: finished processing filtering after resp")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
switch res := dctx.result; res.Reason {
|
||||
case filtering.NotFilteredAllowList:
|
||||
|
||||
@@ -48,12 +48,33 @@ var webRegistered bool
|
||||
|
||||
// hostToIPTable is a convenient type alias for tables of host names to an IP
|
||||
// address.
|
||||
//
|
||||
// TODO(e.burkov): Use the [DHCP] interface instead.
|
||||
type hostToIPTable = map[string]netip.Addr
|
||||
|
||||
// ipToHostTable is a convenient type alias for tables of IP addresses to their
|
||||
// host names. For example, for use with PTR queries.
|
||||
//
|
||||
// TODO(e.burkov): Use the [DHCP] interface instead.
|
||||
type ipToHostTable = map[netip.Addr]string
|
||||
|
||||
// DHCP is an interface for accessing DHCP lease data needed in this package.
|
||||
type DHCP interface {
|
||||
// HostByIP returns the hostname of the DHCP client with the given IP
|
||||
// address. The address will be netip.Addr{} if there is no such client,
|
||||
// due to an assumption that a DHCP client must always have an IP address.
|
||||
HostByIP(ip netip.Addr) (host string)
|
||||
|
||||
// IPByHost returns the IP address of the DHCP client with the given
|
||||
// hostname. The hostname will be an empty string if there is no such
|
||||
// client, due to an assumption that a DHCP client must always have a
|
||||
// hostname, either set by the client or assigned automatically.
|
||||
IPByHost(host string) (ip netip.Addr)
|
||||
|
||||
// Enabled returns true if DHCP provides information about clients.
|
||||
Enabled() (ok bool)
|
||||
}
|
||||
|
||||
// Server is the main way to start a DNS server.
|
||||
//
|
||||
// Example:
|
||||
@@ -215,7 +236,7 @@ func (s *Server) Close() {
|
||||
s.dnsProxy = nil
|
||||
|
||||
if err := s.ipset.close(); err != nil {
|
||||
log.Error("closing ipset: %s", err)
|
||||
log.Error("dnsforward: closing ipset: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,21 +464,17 @@ func (s *Server) setupResolvers(localAddrs []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("upstreams to resolve PTR for local addresses: %v", localAddrs)
|
||||
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", localAddrs)
|
||||
|
||||
var upsConfig *proxy.UpstreamConfig
|
||||
upsConfig, err = proxy.ParseUpstreamsConfig(
|
||||
localAddrs,
|
||||
&upstream.Options{
|
||||
Bootstrap: bootstraps,
|
||||
Timeout: defaultLocalTimeout,
|
||||
// TODO(e.burkov): Should we verify server's certificates?
|
||||
upsConfig, err := s.prepareUpstreamConfig(localAddrs, nil, &upstream.Options{
|
||||
Bootstrap: bootstraps,
|
||||
Timeout: defaultLocalTimeout,
|
||||
// TODO(e.burkov): Should we verify server's certificates?
|
||||
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
},
|
||||
)
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing upstreams: %w", err)
|
||||
return fmt.Errorf("parsing private upstreams: %w", err)
|
||||
}
|
||||
|
||||
s.localResolvers = &proxy.Proxy{
|
||||
@@ -489,7 +506,8 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
|
||||
err = s.prepareUpstreamSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing upstream settings: %w", err)
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
var proxyConfig proxy.Config
|
||||
@@ -656,7 +674,9 @@ func (s *Server) Reconfigure(conf *ServerConfig) error {
|
||||
s.serverLock.Lock()
|
||||
defer s.serverLock.Unlock()
|
||||
|
||||
log.Print("Start reconfiguring the server")
|
||||
log.Info("dnsforward: starting reconfiguring server")
|
||||
defer log.Info("dnsforward: finished reconfiguring server")
|
||||
|
||||
err := s.stopLocked()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not reconfigure the server: %w", err)
|
||||
@@ -708,13 +728,13 @@ func (s *Server) IsBlockedClient(ip netip.Addr, clientID string) (blocked bool,
|
||||
// Allow if at least one of the checks allows in allowlist mode, but block
|
||||
// if at least one of the checks blocks in blocklist mode.
|
||||
if allowlistMode && blockedByIP && blockedByClientID {
|
||||
log.Debug("client %v (id %q) is not in access allowlist", ip, clientID)
|
||||
log.Debug("dnsforward: client %v (id %q) is not in access allowlist", ip, clientID)
|
||||
|
||||
// Return now without substituting the empty rule for the
|
||||
// clientID because the rule can't be empty here.
|
||||
return true, rule
|
||||
} else if !allowlistMode && (blockedByIP || blockedByClientID) {
|
||||
log.Debug("client %v (id %q) is in access blocklist", ip, clientID)
|
||||
log.Debug("dnsforward: client %v (id %q) is in access blocklist", ip, clientID)
|
||||
|
||||
blocked = true
|
||||
}
|
||||
|
||||
@@ -633,61 +633,70 @@ func (err domainSpecificTestError) Error() (msg string) {
|
||||
return fmt.Sprintf("WARNING: %s", err.error)
|
||||
}
|
||||
|
||||
// checkDNS checks the upstream server defined by upstreamConfigStr using
|
||||
// healthCheck for actually exchange messages. It uses bootstrap to resolve the
|
||||
// upstream's address.
|
||||
func checkDNS(
|
||||
upstreamConfigStr string,
|
||||
bootstrap []string,
|
||||
bootstrapPrefIPv6 bool,
|
||||
timeout time.Duration,
|
||||
healthCheck healthCheckFunc,
|
||||
) (err error) {
|
||||
if IsCommentOrEmpty(upstreamConfigStr) {
|
||||
return nil
|
||||
// parseUpstreamLine parses line and creates the [upstream.Upstream] using opts
|
||||
// and information from [s.dnsFilter.EtcHosts]. It returns an error if the line
|
||||
// is not a valid upstream line, see [upstream.AddressToUpstream]. It's a
|
||||
// caller's responsibility to close u.
|
||||
func (s *Server) parseUpstreamLine(
|
||||
line string,
|
||||
opts *upstream.Options,
|
||||
) (u upstream.Upstream, specific bool, err error) {
|
||||
// Separate upstream from domains list.
|
||||
upstreamAddr, domains, err := separateUpstream(line)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("wrong upstream format: %w", err)
|
||||
}
|
||||
|
||||
// Separate upstream from domains list.
|
||||
upstreamAddr, domains, err := separateUpstream(upstreamConfigStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrong upstream format: %w", err)
|
||||
}
|
||||
specific = len(domains) > 0
|
||||
|
||||
useDefault, err := validateUpstream(upstreamAddr, domains)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrong upstream format: %w", err)
|
||||
return nil, specific, fmt.Errorf("wrong upstream format: %w", err)
|
||||
} else if useDefault {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(bootstrap) == 0 {
|
||||
bootstrap = defaultBootstrap
|
||||
return nil, specific, nil
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: checking if upstream %q works", upstreamAddr)
|
||||
|
||||
u, err := upstream.AddressToUpstream(upstreamAddr, &upstream.Options{
|
||||
Bootstrap: bootstrap,
|
||||
Timeout: timeout,
|
||||
PreferIPv6: bootstrapPrefIPv6,
|
||||
})
|
||||
opts = &upstream.Options{
|
||||
Bootstrap: opts.Bootstrap,
|
||||
Timeout: opts.Timeout,
|
||||
PreferIPv6: opts.PreferIPv6,
|
||||
}
|
||||
|
||||
if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil {
|
||||
resolved := s.resolveUpstreamHost(extractUpstreamHost(upstreamAddr))
|
||||
sortNetIPAddrs(resolved, opts.PreferIPv6)
|
||||
opts.ServerIPAddrs = resolved
|
||||
}
|
||||
u, err = upstream.AddressToUpstream(upstreamAddr, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to choose upstream for %q: %w", upstreamAddr, err)
|
||||
return nil, specific, fmt.Errorf("creating upstream for %q: %w", upstreamAddr, err)
|
||||
}
|
||||
|
||||
return u, specific, nil
|
||||
}
|
||||
|
||||
func (s *Server) checkDNS(line string, opts *upstream.Options, check healthCheckFunc) (err error) {
|
||||
if IsCommentOrEmpty(line) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var u upstream.Upstream
|
||||
var specific bool
|
||||
defer func() {
|
||||
if err != nil && specific {
|
||||
err = domainSpecificTestError{error: err}
|
||||
}
|
||||
}()
|
||||
|
||||
u, specific, err = s.parseUpstreamLine(line, opts)
|
||||
if err != nil || u == nil {
|
||||
return err
|
||||
}
|
||||
defer func() { err = errors.WithDeferred(err, u.Close()) }()
|
||||
|
||||
if err = healthCheck(u); err != nil {
|
||||
err = fmt.Errorf("upstream %q fails to exchange: %w", upstreamAddr, err)
|
||||
if domains != nil {
|
||||
return domainSpecificTestError{error: err}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: upstream %q is ok", upstreamAddr)
|
||||
|
||||
return nil
|
||||
return check(u)
|
||||
}
|
||||
|
||||
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -699,47 +708,54 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
bootstraps := req.BootstrapDNS
|
||||
bootstrapPrefIPv6 := s.conf.BootstrapPreferIPv6
|
||||
timeout := s.conf.UpstreamTimeout
|
||||
opts := &upstream.Options{
|
||||
Bootstrap: req.BootstrapDNS,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
}
|
||||
if len(opts.Bootstrap) == 0 {
|
||||
opts.Bootstrap = defaultBootstrap
|
||||
}
|
||||
|
||||
type upsCheckResult = struct {
|
||||
res string
|
||||
err error
|
||||
host string
|
||||
}
|
||||
|
||||
req.Upstreams = stringutil.FilterOut(req.Upstreams, IsCommentOrEmpty)
|
||||
req.PrivateUpstreams = stringutil.FilterOut(req.PrivateUpstreams, IsCommentOrEmpty)
|
||||
|
||||
upsNum := len(req.Upstreams) + len(req.PrivateUpstreams)
|
||||
result := make(map[string]string, upsNum)
|
||||
resCh := make(chan upsCheckResult, upsNum)
|
||||
|
||||
checkUps := func(ups string, healthCheck healthCheckFunc) {
|
||||
res := upsCheckResult{
|
||||
host: ups,
|
||||
}
|
||||
defer func() { resCh <- res }()
|
||||
|
||||
checkErr := checkDNS(ups, bootstraps, bootstrapPrefIPv6, timeout, healthCheck)
|
||||
if checkErr != nil {
|
||||
res.res = checkErr.Error()
|
||||
} else {
|
||||
res.res = "OK"
|
||||
}
|
||||
}
|
||||
|
||||
for _, ups := range req.Upstreams {
|
||||
go checkUps(ups, checkDNSUpstreamExc)
|
||||
go func(ups string) {
|
||||
resCh <- upsCheckResult{
|
||||
host: ups,
|
||||
err: s.checkDNS(ups, opts, checkDNSUpstreamExc),
|
||||
}
|
||||
}(ups)
|
||||
}
|
||||
for _, ups := range req.PrivateUpstreams {
|
||||
go checkUps(ups, checkPrivateUpstreamExc)
|
||||
go func(ups string) {
|
||||
resCh <- upsCheckResult{
|
||||
host: ups,
|
||||
err: s.checkDNS(ups, opts, checkPrivateUpstreamExc),
|
||||
}
|
||||
}(ups)
|
||||
}
|
||||
|
||||
for i := 0; i < upsNum; i++ {
|
||||
pair := <-resCh
|
||||
// TODO(e.burkov): The upstreams used for both common and private
|
||||
// resolving should be reported separately.
|
||||
result[pair.host] = pair.res
|
||||
pair := <-resCh
|
||||
if pair.err != nil {
|
||||
result[pair.host] = pair.err.Error()
|
||||
} else {
|
||||
result[pair.host] = "OK"
|
||||
}
|
||||
}
|
||||
close(resCh)
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, result)
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/golibs/httphdr"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
@@ -280,6 +282,10 @@ func TestIsCommentOrEmpty(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateUpstreams(t *testing.T) {
|
||||
const sdnsStamp = `sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_J` +
|
||||
`S3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczE` +
|
||||
`uYWRndWFyZC5jb20`
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErr string
|
||||
@@ -300,7 +306,7 @@ func TestValidateUpstreams(t *testing.T) {
|
||||
"[//]tls://1.1.1.1",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
"[/host/]" + sdnsStamp,
|
||||
},
|
||||
}, {
|
||||
name: "with_default",
|
||||
@@ -310,7 +316,7 @@ func TestValidateUpstreams(t *testing.T) {
|
||||
"[//]tls://1.1.1.1",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
"[/host/]" + sdnsStamp,
|
||||
"8.8.8.8",
|
||||
},
|
||||
}, {
|
||||
@@ -326,9 +332,10 @@ func TestValidateUpstreams(t *testing.T) {
|
||||
wantErr: `validating upstream "123.3.7m": not an ip:port`,
|
||||
set: []string{"123.3.7m"},
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": missing separator`,
|
||||
set: []string{"[/host.com]tls://dns.adguard.com"},
|
||||
name: "invalid",
|
||||
wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": ` +
|
||||
`missing separator`,
|
||||
set: []string{"[/host.com]tls://dns.adguard.com"},
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErr: `validating upstream "[host.ru]#": not an ip:port`,
|
||||
@@ -340,14 +347,14 @@ func TestValidateUpstreams(t *testing.T) {
|
||||
"1.1.1.1",
|
||||
"tls://1.1.1.1",
|
||||
"https://dns.adguard.com/dns-query",
|
||||
"sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
sdnsStamp,
|
||||
"udp://dns.google",
|
||||
"udp://8.8.8.8",
|
||||
"[/host.com/]1.1.1.1",
|
||||
"[//]tls://1.1.1.1",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
"[/host/]" + sdnsStamp,
|
||||
"[/пример.рф/]8.8.8.8",
|
||||
},
|
||||
}, {
|
||||
@@ -418,27 +425,28 @@ func TestValidateUpstreamsPrivate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newLocalUpstreamListener(t *testing.T, port int, handler dns.Handler) (real net.Addr) {
|
||||
func newLocalUpstreamListener(t *testing.T, port uint16, handler dns.Handler) (real netip.AddrPort) {
|
||||
t.Helper()
|
||||
|
||||
startCh := make(chan struct{})
|
||||
upsSrv := &dns.Server{
|
||||
Addr: netip.AddrPortFrom(netutil.IPv4Localhost(), uint16(port)).String(),
|
||||
Addr: netip.AddrPortFrom(netutil.IPv4Localhost(), port).String(),
|
||||
Net: "tcp",
|
||||
Handler: handler,
|
||||
NotifyStartedFunc: func() { close(startCh) },
|
||||
}
|
||||
go func() {
|
||||
t := testutil.PanicT{}
|
||||
|
||||
err := upsSrv.ListenAndServe()
|
||||
require.NoError(t, err)
|
||||
require.NoError(testutil.PanicT{}, err)
|
||||
}()
|
||||
|
||||
<-startCh
|
||||
testutil.CleanupAndRequireSuccess(t, upsSrv.Shutdown)
|
||||
|
||||
return upsSrv.Listener.Addr()
|
||||
return testutil.RequireTypeAssert[*net.TCPAddr](t, upsSrv.Listener.Addr()).AddrPort()
|
||||
}
|
||||
|
||||
func TestServer_handleTestUpstreaDNS(t *testing.T) {
|
||||
func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
||||
goodHandler := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
err := w.WriteMsg(new(dns.Msg).SetReply(m))
|
||||
require.NoError(testutil.PanicT{}, err)
|
||||
@@ -457,9 +465,38 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
|
||||
Host: newLocalUpstreamListener(t, 0, badHandler).String(),
|
||||
}).String()
|
||||
|
||||
const upsTimeout = 100 * time.Millisecond
|
||||
const (
|
||||
upsTimeout = 100 * time.Millisecond
|
||||
|
||||
srv := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
hostsFileName = "hosts"
|
||||
upstreamHost = "custom.localhost"
|
||||
)
|
||||
|
||||
hostsListener := newLocalUpstreamListener(t, 0, goodHandler)
|
||||
hostsUps := (&url.URL{
|
||||
Scheme: "tcp",
|
||||
Host: netutil.JoinHostPort(upstreamHost, int(hostsListener.Port())),
|
||||
}).String()
|
||||
|
||||
hc, err := aghnet.NewHostsContainer(
|
||||
filtering.SysHostsListID,
|
||||
fstest.MapFS{
|
||||
hostsFileName: &fstest.MapFile{
|
||||
Data: []byte(hostsListener.Addr().String() + " " + upstreamHost),
|
||||
},
|
||||
},
|
||||
&aghtest.FSWatcher{
|
||||
OnEvents: func() (e <-chan struct{}) { return nil },
|
||||
OnAdd: func(_ string) (err error) { return nil },
|
||||
OnClose: func() (err error) { return nil },
|
||||
},
|
||||
hostsFileName,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := createTestServer(t, &filtering.Config{
|
||||
EtcHosts: hc,
|
||||
}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
UpstreamTimeout: upsTimeout,
|
||||
@@ -486,8 +523,7 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
|
||||
"upstream_dns": []string{badUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
badUps: `upstream "` + badUps + `" fails to exchange: ` +
|
||||
`couldn't communicate with upstream: exchanging with ` +
|
||||
badUps: `couldn't communicate with upstream: exchanging with ` +
|
||||
badUps + ` over tcp: dns: id mismatch`,
|
||||
},
|
||||
name: "broken",
|
||||
@@ -497,20 +533,40 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
goodUps: "OK",
|
||||
badUps: `upstream "` + badUps + `" fails to exchange: ` +
|
||||
`couldn't communicate with upstream: exchanging with ` +
|
||||
badUps: `couldn't communicate with upstream: exchanging with ` +
|
||||
badUps + ` over tcp: dns: id mismatch`,
|
||||
},
|
||||
name: "both",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{"[/domain.example/]" + badUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
"[/domain.example/]" + badUps: `WARNING: couldn't communicate ` +
|
||||
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
|
||||
`dns: id mismatch`,
|
||||
},
|
||||
name: "domain_specific_error",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{hostsUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
hostsUps: "OK",
|
||||
},
|
||||
name: "etc_hosts",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
reqBody, err := json.Marshal(tc.body)
|
||||
var reqBody []byte
|
||||
reqBody, err = json.Marshal(tc.body)
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
|
||||
require.NoError(t, err)
|
||||
|
||||
srv.handleTestUpstreamDNS(w, r)
|
||||
@@ -538,11 +594,15 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
|
||||
req := map[string]any{
|
||||
"upstream_dns": []string{sleepyUps},
|
||||
}
|
||||
reqBody, err := json.Marshal(req)
|
||||
|
||||
var reqBody []byte
|
||||
reqBody, err = json.Marshal(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
|
||||
require.NoError(t, err)
|
||||
|
||||
srv.handleTestUpstreamDNS(w, r)
|
||||
|
||||
@@ -110,6 +110,9 @@ func ipsFromAnswer(ans []dns.RR) (ip4s, ip6s []net.IP) {
|
||||
|
||||
// process adds the resolved IP addresses to the domain's ipsets, if any.
|
||||
func (c *ipsetCtx) process(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: ipset: started processing")
|
||||
defer log.Debug("dnsforward: ipset: finished processing")
|
||||
|
||||
if c.skipIpsetProcessing(dctx) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
@@ -125,12 +128,12 @@ func (c *ipsetCtx) process(dctx *dnsContext) (rc resultCode) {
|
||||
n, err := c.ipsetMgr.Add(host, ip4s, ip6s)
|
||||
if err != nil {
|
||||
// Consider ipset errors non-critical to the request.
|
||||
log.Error("ipset: adding host ips: %s", err)
|
||||
log.Error("dnsforward: ipset: adding host ips: %s", err)
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
log.Debug("ipset: added %d new ipset entries", n)
|
||||
log.Debug("dnsforward: ipset: added %d new ipset entries", n)
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
@@ -17,60 +17,78 @@ import (
|
||||
|
||||
// Write Stats data and logs
|
||||
func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing querylog and stats")
|
||||
defer log.Debug("dnsforward: finished processing querylog and stats")
|
||||
|
||||
elapsed := time.Since(dctx.startTime)
|
||||
pctx := dctx.proxyCtx
|
||||
|
||||
shouldLog := true
|
||||
msg := pctx.Req
|
||||
q := msg.Question[0]
|
||||
q := pctx.Req.Question[0]
|
||||
host := strings.ToLower(strings.TrimSuffix(q.Name, "."))
|
||||
|
||||
// don't log ANY request if refuseAny is enabled
|
||||
if q.Qtype == dns.TypeANY && s.conf.RefuseAny {
|
||||
shouldLog = false
|
||||
}
|
||||
|
||||
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
|
||||
ip = slices.Clone(ip)
|
||||
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
s.anonymizer.Load()(ip)
|
||||
|
||||
log.Debug("client ip: %s", ip)
|
||||
log.Debug("dnsforward: client ip for stats and querylog: %s", ip)
|
||||
|
||||
ipStr := ip.String()
|
||||
ids := []string{ipStr, dctx.clientID}
|
||||
qt, cl := q.Qtype, q.Qclass
|
||||
|
||||
// Synchronize access to s.queryLog and s.stats so they won't be suddenly
|
||||
// uninitialized while in use. This can happen after proxy server has been
|
||||
// stopped, but its workers haven't yet exited.
|
||||
if shouldLog &&
|
||||
s.queryLog != nil &&
|
||||
// TODO(s.chzhen): Use dnsforward.dnsContext when it will start
|
||||
// containing persistent client.
|
||||
s.queryLog.ShouldLog(host, q.Qtype, q.Qclass, ids) {
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
if s.shouldLog(host, qt, cl, ids) {
|
||||
s.logQuery(dctx, pctx, elapsed, ip)
|
||||
} else {
|
||||
log.Debug(
|
||||
"dnsforward: request %s %s from %s ignored; not logging",
|
||||
dns.Type(q.Qtype),
|
||||
"dnsforward: request %s %s %q from %s ignored; not adding to querylog",
|
||||
dns.Class(cl),
|
||||
dns.Type(qt),
|
||||
host,
|
||||
ip,
|
||||
)
|
||||
}
|
||||
|
||||
if s.stats != nil &&
|
||||
// TODO(s.chzhen): Use dnsforward.dnsContext when it will start
|
||||
// containing persistent client.
|
||||
s.stats.ShouldCount(host, q.Qtype, q.Qclass, ids) {
|
||||
if s.shouldCountStat(host, qt, cl, ids) {
|
||||
s.updateStats(dctx, elapsed, *dctx.result, ipStr)
|
||||
} else {
|
||||
log.Debug(
|
||||
"dnsforward: request %s %s %q from %s ignored; not counting in stats",
|
||||
dns.Class(cl),
|
||||
dns.Type(qt),
|
||||
host,
|
||||
ip,
|
||||
)
|
||||
}
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// shouldLog returns true if the query with the given data should be logged in
|
||||
// the query log. s.serverLock is expected to be locked.
|
||||
func (s *Server) shouldLog(host string, qt, cl uint16, ids []string) (ok bool) {
|
||||
if qt == dns.TypeANY && s.conf.RefuseAny {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(s.chzhen): Use dnsforward.dnsContext when it will start containing
|
||||
// persistent client.
|
||||
return s.queryLog != nil && s.queryLog.ShouldLog(host, qt, cl, ids)
|
||||
}
|
||||
|
||||
// shouldCountStat returns true if the query with the given data should be
|
||||
// counted in the statistics. s.serverLock is expected to be locked.
|
||||
func (s *Server) shouldCountStat(host string, qt, cl uint16, ids []string) (ok bool) {
|
||||
// TODO(s.chzhen): Use dnsforward.dnsContext when it will start containing
|
||||
// persistent client.
|
||||
return s.stats != nil && s.stats.ShouldCount(host, qt, cl, ids)
|
||||
}
|
||||
|
||||
// logQuery pushes the request details into the query log.
|
||||
func (s *Server) logQuery(
|
||||
dctx *dnsContext,
|
||||
|
||||
311
internal/dnsforward/upstreams.go
Normal file
311
internal/dnsforward/upstreams.go
Normal file
@@ -0,0 +1,311 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// loadUpstreams parses upstream DNS servers from the configured file or from
|
||||
// the configuration itself.
|
||||
func (s *Server) loadUpstreams() (upstreams []string, err error) {
|
||||
if s.conf.UpstreamDNSFileName == "" {
|
||||
return stringutil.FilterOut(s.conf.UpstreamDNS, IsCommentOrEmpty), nil
|
||||
}
|
||||
|
||||
var data []byte
|
||||
data, err = os.ReadFile(s.conf.UpstreamDNSFileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading upstream from file: %w", err)
|
||||
}
|
||||
|
||||
upstreams = stringutil.SplitTrimmed(string(data), "\n")
|
||||
|
||||
log.Debug("dnsforward: got %d upstreams in %q", len(upstreams), s.conf.UpstreamDNSFileName)
|
||||
|
||||
return stringutil.FilterOut(upstreams, IsCommentOrEmpty), nil
|
||||
}
|
||||
|
||||
// prepareUpstreamSettings sets upstream DNS server settings.
|
||||
func (s *Server) prepareUpstreamSettings() (err error) {
|
||||
// We're setting a customized set of RootCAs. The reason is that Go default
|
||||
// mechanism of loading TLS roots does not always work properly on some
|
||||
// routers so we're loading roots manually and pass it here.
|
||||
//
|
||||
// See [aghtls.SystemRootCAs].
|
||||
upstream.RootCAs = s.conf.TLSv12Roots
|
||||
upstream.CipherSuites = s.conf.TLSCiphers
|
||||
|
||||
// Load upstreams either from the file, or from the settings
|
||||
var upstreams []string
|
||||
upstreams, err = s.loadUpstreams()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading upstreams: %w", err)
|
||||
}
|
||||
|
||||
s.conf.UpstreamConfig, err = s.prepareUpstreamConfig(upstreams, defaultDNS, &upstream.Options{
|
||||
Bootstrap: s.conf.BootstrapDNS,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing upstream config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareUpstreamConfig sets upstream configuration based on upstreams and
|
||||
// configuration of s.
|
||||
func (s *Server) prepareUpstreamConfig(
|
||||
upstreams []string,
|
||||
defaultUpstreams []string,
|
||||
opts *upstream.Options,
|
||||
) (uc *proxy.UpstreamConfig, err error) {
|
||||
uc, err = proxy.ParseUpstreamsConfig(upstreams, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing upstream config: %w", err)
|
||||
}
|
||||
|
||||
if len(uc.Upstreams) == 0 && defaultUpstreams != nil {
|
||||
log.Info("dnsforward: warning: no default upstreams specified, using %v", defaultUpstreams)
|
||||
var defaultUpstreamConfig *proxy.UpstreamConfig
|
||||
defaultUpstreamConfig, err = proxy.ParseUpstreamsConfig(defaultUpstreams, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing default upstreams: %w", err)
|
||||
}
|
||||
|
||||
uc.Upstreams = defaultUpstreamConfig.Upstreams
|
||||
}
|
||||
|
||||
if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil {
|
||||
err = s.replaceUpstreamsWithHosts(uc, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving upstreams with hosts: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// replaceUpstreamsWithHosts replaces unique upstreams with their resolved
|
||||
// versions based on the system hosts file.
|
||||
//
|
||||
// TODO(e.burkov): This should be performed inside dnsproxy, which should
|
||||
// actually consider /etc/hosts. See TODO on [aghnet.HostsContainer].
|
||||
func (s *Server) replaceUpstreamsWithHosts(
|
||||
upsConf *proxy.UpstreamConfig,
|
||||
opts *upstream.Options,
|
||||
) (err error) {
|
||||
resolved := map[string]*upstream.Options{}
|
||||
|
||||
err = s.resolveUpstreamsWithHosts(resolved, upsConf.Upstreams, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving upstreams: %w", err)
|
||||
}
|
||||
|
||||
hosts := maps.Keys(upsConf.DomainReservedUpstreams)
|
||||
// TODO(e.burkov): Think of extracting sorted range into an util function.
|
||||
slices.Sort(hosts)
|
||||
for _, host := range hosts {
|
||||
err = s.resolveUpstreamsWithHosts(resolved, upsConf.DomainReservedUpstreams[host], opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving upstreams reserved for %s: %w", host, err)
|
||||
}
|
||||
}
|
||||
|
||||
hosts = maps.Keys(upsConf.SpecifiedDomainUpstreams)
|
||||
slices.Sort(hosts)
|
||||
for _, host := range hosts {
|
||||
err = s.resolveUpstreamsWithHosts(resolved, upsConf.SpecifiedDomainUpstreams[host], opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving upstreams specific for %s: %w", host, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveUpstreamsWithHosts resolves the IP addresses of each of the upstreams
|
||||
// and replaces those both in upstreams and resolved. Upstreams that failed to
|
||||
// resolve are placed to resolved as-is. This function only returns error of
|
||||
// upstreams closing.
|
||||
func (s *Server) resolveUpstreamsWithHosts(
|
||||
resolved map[string]*upstream.Options,
|
||||
upstreams []upstream.Upstream,
|
||||
opts *upstream.Options,
|
||||
) (err error) {
|
||||
for i := range upstreams {
|
||||
u := upstreams[i]
|
||||
addr := u.Address()
|
||||
host := extractUpstreamHost(addr)
|
||||
|
||||
withIPs, ok := resolved[host]
|
||||
if !ok {
|
||||
ips := s.resolveUpstreamHost(host)
|
||||
if len(ips) == 0 {
|
||||
resolved[host] = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
sortNetIPAddrs(ips, opts.PreferIPv6)
|
||||
|
||||
withIPs = opts.Clone()
|
||||
withIPs.ServerIPAddrs = ips
|
||||
resolved[host] = withIPs
|
||||
} else if withIPs == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = u.Close(); err != nil {
|
||||
return fmt.Errorf("closing upstream %s: %w", addr, err)
|
||||
}
|
||||
|
||||
upstreams[i], err = upstream.AddressToUpstream(addr, withIPs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("replacing upstream %s with resolved %s: %w", addr, host, err)
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: using %s for %s", withIPs.ServerIPAddrs, upstreams[i].Address())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractUpstreamHost returns the hostname of addr without port with an
|
||||
// assumption that any address passed here has already been successfully parsed
|
||||
// by [upstream.AddressToUpstream]. This function eesentially mirrors the logic
|
||||
// of [upstream.AddressToUpstream], see TODO on [replaceUpstreamsWithHosts].
|
||||
func extractUpstreamHost(addr string) (host string) {
|
||||
var err error
|
||||
if strings.Contains(addr, "://") {
|
||||
var u *url.URL
|
||||
u, err = url.Parse(addr)
|
||||
if err != nil {
|
||||
log.Debug("dnsforward: parsing upstream %s: %s", addr, err)
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
// Probably, plain UDP upstream defined by address or address:port.
|
||||
host, err = netutil.SplitHost(addr)
|
||||
if err != nil {
|
||||
return addr
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
// resolveUpstreamHost returns the version of ups with IP addresses from the
|
||||
// system hosts file placed into its options.
|
||||
func (s *Server) resolveUpstreamHost(host string) (addrs []net.IP) {
|
||||
req := &urlfilter.DNSRequest{
|
||||
Hostname: host,
|
||||
DNSType: dns.TypeA,
|
||||
}
|
||||
aRes, _ := s.dnsFilter.EtcHosts.MatchRequest(req)
|
||||
|
||||
req.DNSType = dns.TypeAAAA
|
||||
aaaaRes, _ := s.dnsFilter.EtcHosts.MatchRequest(req)
|
||||
|
||||
var ips []net.IP
|
||||
for _, rw := range append(aRes.DNSRewrites(), aaaaRes.DNSRewrites()...) {
|
||||
dr := rw.DNSRewrite
|
||||
if dr == nil || dr.Value == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip, ok := dr.Value.(net.IP); ok {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
|
||||
return ips
|
||||
}
|
||||
|
||||
// sortNetIPAddrs sorts addrs in accordance with the protocol preferences.
|
||||
// Invalid addresses are sorted near the end.
|
||||
//
|
||||
// TODO(e.burkov): This function taken from dnsproxy, which also already
|
||||
// contains a few similar functions. Think of moving to golibs.
|
||||
func sortNetIPAddrs(addrs []net.IP, preferIPv6 bool) {
|
||||
l := len(addrs)
|
||||
if l <= 1 {
|
||||
return
|
||||
}
|
||||
|
||||
slices.SortStableFunc(addrs, func(addrA, addrB net.IP) (sortsBefore bool) {
|
||||
switch len(addrA) {
|
||||
case net.IPv4len, net.IPv6len:
|
||||
switch len(addrB) {
|
||||
case net.IPv4len, net.IPv6len:
|
||||
// Go on.
|
||||
default:
|
||||
return true
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
if aIs4, bIs4 := addrA.To4() != nil, addrB.To4() != nil; aIs4 != bIs4 {
|
||||
if aIs4 {
|
||||
return !preferIPv6
|
||||
}
|
||||
|
||||
return preferIPv6
|
||||
}
|
||||
|
||||
return bytes.Compare(addrA, addrB) < 0
|
||||
})
|
||||
}
|
||||
|
||||
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
|
||||
// depending on configuration.
|
||||
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
|
||||
if !http3 {
|
||||
return upstream.DefaultHTTPVersions
|
||||
}
|
||||
|
||||
return []upstream.HTTPVersion{
|
||||
upstream.HTTPVersion3,
|
||||
upstream.HTTPVersion2,
|
||||
upstream.HTTPVersion11,
|
||||
}
|
||||
}
|
||||
|
||||
// setProxyUpstreamMode sets the upstream mode and related settings in conf
|
||||
// based on provided parameters.
|
||||
func setProxyUpstreamMode(
|
||||
conf *proxy.Config,
|
||||
allServers bool,
|
||||
fastestAddr bool,
|
||||
fastestTimeout time.Duration,
|
||||
) {
|
||||
if allServers {
|
||||
conf.UpstreamMode = proxy.UModeParallel
|
||||
} else if fastestAddr {
|
||||
conf.UpstreamMode = proxy.UModeFastestAddr
|
||||
conf.FastestPingTimeout = fastestTimeout
|
||||
} else {
|
||||
conf.UpstreamMode = proxy.UModeLoadBalance
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package filtering
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -55,11 +56,29 @@ type BlockedServices struct {
|
||||
IDs []string `yaml:"ids"`
|
||||
}
|
||||
|
||||
// BlockedSvcKnown returns true if a blocked service ID is known.
|
||||
func BlockedSvcKnown(s string) (ok bool) {
|
||||
_, ok = serviceRules[s]
|
||||
// Clone returns a deep copy of blocked services.
|
||||
func (s *BlockedServices) Clone() (c *BlockedServices) {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ok
|
||||
return &BlockedServices{
|
||||
Schedule: s.Schedule.Clone(),
|
||||
IDs: slices.Clone(s.IDs),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate returns an error if blocked services contain unknown service ID. s
|
||||
// must not be nil.
|
||||
func (s *BlockedServices) Validate() (err error) {
|
||||
for _, id := range s.IDs {
|
||||
_, ok := serviceRules[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown blocked-service %q", id)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyBlockedServices - set blocked services settings for this DNS request
|
||||
|
||||
@@ -519,7 +519,7 @@ func (d *DNSFilter) matchSysHosts(
|
||||
dnsres, _ := d.EtcHosts.MatchRequest(&urlfilter.DNSRequest{
|
||||
Hostname: host,
|
||||
SortedClientTags: setts.ClientTags,
|
||||
// TODO(e.burkov): Wait for urlfilter update to pass net.IP.
|
||||
// TODO(e.burkov): Wait for urlfilter update to pass netip.Addr.
|
||||
ClientIP: setts.ClientIP.String(),
|
||||
ClientName: setts.ClientName,
|
||||
DNSType: qtype,
|
||||
@@ -988,17 +988,11 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
|
||||
}
|
||||
|
||||
if d.BlockedServices != nil {
|
||||
bsvcs := []string{}
|
||||
for _, s := range d.BlockedServices.IDs {
|
||||
if !BlockedSvcKnown(s) {
|
||||
log.Debug("skipping unknown blocked-service %q", s)
|
||||
err = d.BlockedServices.Validate()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
bsvcs = append(bsvcs, s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("filtering: %w", err)
|
||||
}
|
||||
d.BlockedServices.IDs = bsvcs
|
||||
}
|
||||
|
||||
if blockFilters != nil {
|
||||
|
||||
@@ -169,7 +169,7 @@ func (d *DNSFilter) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ
|
||||
deleted = (*filters)[delIdx]
|
||||
p := deleted.Path(d.DataDir)
|
||||
err = os.Rename(p, p+".old")
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
log.Error("deleting filter %d: renaming file %q: %s", deleted.ID, p, err)
|
||||
|
||||
return
|
||||
|
||||
@@ -27,6 +27,25 @@ var blockedServices = []blockedService{{
|
||||
"||9cache.com^",
|
||||
"||9gag.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "activision_blizzard",
|
||||
Name: "Activision Blizzard",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"-237 0 1572 1572\"><path d=\"m549.1.2 548.4 1571.4H798l-74.2-200H374.5l-74.3 200H.7zM626 1085.1l-83-274.3-82.9 274.3z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||activision.com^",
|
||||
"||activisionblizzard.com^",
|
||||
"||demonware.net^",
|
||||
},
|
||||
}, {
|
||||
ID: "aliexpress",
|
||||
Name: "AliExpress",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M9 4C6.25 4 4 6.25 4 9v32c0 2.75 2.25 5 5 5h32c2.75 0 5-2.25 5-5V9c0-2.75-2.25-5-5-5H9zm0 2h32c1.668 0 3 1.332 3 3v3.38A3.973 3.973 0 0 0 41 11H9a3.973 3.973 0 0 0-3 1.38V9c0-1.668 1.332-3 3-3zm6 11a1 1 0 0 1 1 1c0 4.962 4.037 9 9 9s9-4.038 9-9a1 1 0 1 1 2 0c0 6.065-4.935 11-11 11s-11-4.935-11-11a1 1 0 0 1 1-1z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||ae-rus.net^",
|
||||
"||ae-rus.ru^",
|
||||
"||aliexpress.com^",
|
||||
"||aliexpress.ru^",
|
||||
},
|
||||
}, {
|
||||
ID: "amazon",
|
||||
Name: "Amazon",
|
||||
@@ -234,6 +253,16 @@ var blockedServices = []blockedService{{
|
||||
"||z.cn^",
|
||||
"||zappos^",
|
||||
},
|
||||
}, {
|
||||
ID: "battle_net",
|
||||
Name: "Battle.net",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M43.11 22.15s3.95.2 3.95-2.12c0-3.03-5.26-5.77-5.26-5.77s.83-1.74 1.34-2.72a37.3 37.3 0 0 0 2.09-5.65c.16-1.1-.09-1.44-.09-1.44-.35 2.34-4.17 9.09-4.47 9.32-3.72-1.75-8.83-2.23-8.83-2.23S26.84 1 22.13 1c-4.67 0-4.65 9.02-4.65 9.02s-1.32-2.56-2.97-2.56c-2.42 0-3.22 3.67-3.22 7.64a37.8 37.8 0 0 0-9.16 1.17c-.36.1-1.49.92-.97.82 1.04-.34 5.95-1.1 10.25-.72.24 3.77 2.44 8.68 2.44 8.68S9.13 31.9 9.13 36.78c0 1.29.56 3.64 3.95 3.64 2.84 0 6.03-1.7 6.63-2.06a6.33 6.33 0 0 0-.91 2.83c0 .54.31 2.06 2.5 2.06 2.82 0 5.96-2.16 5.96-2.16s2.96 4.93 5.5 7.2c.69.6 1.34.71 1.34.71s-2.52-2.43-5.84-8.68c3.08-1.9 6.3-6.4 6.3-6.4l3.3.01c4.6 0 11.11-.96 11.11-4.61 0-3.77-5.86-7.17-5.86-7.17Zm.52-2.26c0 1.33-1.27 1.3-1.27 1.3l-.97.08s-1.82-.97-2.93-1.41c0 0 1.72-2.65 2.12-3.4.3.18 3.05 1.9 3.05 3.43ZM24.43 6.3c2.15 0 5.23 5.1 5.23 5.1s-4.8-.44-8.76 1.89c.1-3.67 1.34-7 3.52-7Zm-8.56 4.13c.69 0 1.36.83 1.64 1.54 0 .47.24 3.2.24 3.2l-3.96-.16c0-3.57 1.4-4.58 2.08-4.58Zm-.4 24.8c-2.17 0-2.62-1.2-2.62-2.29 0-2.45 1.96-5.9 1.96-5.9s2.2 4.63 6.04 6.59a10.02 10.02 0 0 1-5.39 1.6Zm7.02 4.85c-1.52 0-1.7-.98-1.7-1.21 0-.7.55-1.54.55-1.54s2.55-1.73 2.71-1.91l1.89 3.52s-1.93 1.14-3.45 1.14Zm4.74-1.92c-.93-1.62-1.6-3.3-1.6-3.3s3.78.24 5.82-1.86a11.2 11.2 0 0 1-5.65 1.07c4.93-4.34 7.8-7.48 10.23-10.74a9.46 9.46 0 0 0-1.6-1.15c-1.46 1.76-7.16 7.86-12.45 10.88-6.69-3.64-8.09-14.38-8.23-16.6l3.65.34s-1.37 2.44-1.37 4.23c0 1.79.21 1.89.21 1.89s-.04-3.13 1.89-5.54c1.46 7.82 3 11.83 4.19 14.22.6-.25 1.74-.76 1.74-.76s-3.38-9.73-3.19-16.31a13.8 13.8 0 0 1 6.36-1.66c6.73 0 12.14 2.9 12.14 2.9l-2.12 2.95s-1.89-3.42-4.55-4.03c1.4 1.05 2.98 2.44 3.8 4.43a68.4 68.4 0 0 0-14.47-3.59c-.19.8-.17 1.94-.17 1.94s9.03 1.66 15.6 5.43c-.05 8.21-9 14.53-10.23 15.26Zm8.55-6.14s2.8-3.68 2.76-8.55c0 0 4.52 2.8 4.52 5.54 0 3.05-7.28 3-7.28 3Z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||battle.net^",
|
||||
"||battlenet.com.cn^",
|
||||
"||bnet.163.com^",
|
||||
"||bnet.cn^",
|
||||
},
|
||||
}, {
|
||||
ID: "bilibili",
|
||||
Name: "Bilibili",
|
||||
@@ -283,6 +312,21 @@ var blockedServices = []blockedService{{
|
||||
"||mincdn.com^",
|
||||
"||yo9.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "blizzard_entertainment",
|
||||
Name: "Blizzard Entertainment",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 -32 128 128\"><path fill-rule=\"evenodd\" d=\"M105 2h3v1h2l2 1 1 1h3l1 1h4l1 1 2 2v1l1 3v4l1 2v6l-1 2v2l-1 3v2l-1 2v14l-1 2v1l-1 3-1 1h-3l-1 1h-6a5 5 0 0 0 1-6l2-1h-1l-1-3v-3a350 350 0 0 1 0-8l-1-3v-1l-1-1V9h1V6l-1-1-4-3Zm9 13v10h1v25a8 8 0 0 0 2-4l1-1 1-3V30l1-1v-2l1-1v-5l-1-2-2-3-1-1h-3Z\" clip-rule=\"evenodd\"/><path fill-rule=\"evenodd\" d=\"M101 24v1l2 1h1v2h1l1 2v5l1 2s0-1 0 0l1 7 1 2v7l-1 5h-2l-2-2-4-1 1-3 1-2a22 22 0 0 0-1-10l-1-4h-1l1-4-1-1-2-3v2l-1 1v3l1 6v4l1 1-1 3v4l1 1-1 2v4l-1-1a13 13 0 0 0-4-5l-2-2 2-5V27l-1-1v-4l-1-1v-5h-1a33 33 0 0 1 0-4l4-4h-2l-4-4h-1V3h10l2 1 2 1h1c2 0 2 1 3 2l2 3 1 3v1l-1 2v1a11 11 0 0 1-1 4l-4 3ZM96 9v13l1 1a3 3 0 0 0 1-1c1 0 2-1 2-3v-1l1-1v-3l-2-3-2-2h-1ZM26 3l1 1h1l2 3v5l1 1v2l-1 1v9l1 1 1 1-1 7v9l-1 1 1 1-1 1v8h3l1-1h7v-1h16v6l1 2h-6l-1-1h-2l-1-1H31a4 4 0 0 0-3-1l-1 1h-1l-1 1h-5l1-1a10 10 0 0 0 3-2v-9l1-1-1-1V35l1-1V21l-1-1v-4l1-1v-3l1-2-1-3h-1l-2-2-1-1 1-1h4Z\" clip-rule=\"evenodd\"/><path fill-rule=\"evenodd\" d=\"M84 60v-3l-1-2v-4l-3-2v-1l1-2a11 11 0 0 0 2-6l-1-1-3-2h-2v3l1 1h1l-1 2h-4l-2 1-2 1-1-2v-1l1-1 1-1 1-2v-5l1-1v-6l1-1v-3l1-1v-3l1-2 1-1-1-1 1-1 1-3 1-1V7l1-1c1-1 0-4 2-3l1 3 1 1 1 2v1l1 5 1 3v2l1 1v2l1 1v8l1 3v9l-1 1-2 5v3l-1 2v4l-1 1h-1Zm-4-36-1 1v2l-1 2v4l4 1h2v-7l-1-3-2-1-1-1v2Z\" clip-rule=\"evenodd\"/><path fill-rule=\"evenodd\" d=\"M77 4v1l-2 3v2l-1 2v1l-1 1-1 4v7h-1v2a5 5 0 0 1-1 2v2l-2 2v7l-1 2v2l-2 4v3-1h3v-1l3-1 1-1 3-2h3l1 1-1 1a3 3 0 0 0 0 1l1 1v5l-1 1h-7v-1h-2l-2 1h-4l-2 1-1-2v-2l1-1v-1l1-1-1-1 1-1v-2l1-2-1-2v-8l1-2 2-5-1-1 1-2v-1l1-1v-4l1-1 2-4v-2l1-1h-3V8h-1l-1 1-2 3-1 4h-1l-1-1v-2l1-1V4h16ZM32 4h9l1 2-3 2 1 2-1 1v13l1 2-1 2v6l-1 1v2l1 1v5l-1 1 1 2 1 1 2 1v2h-7l-2 1-1-1 3-2v-8a4 4 0 0 1 0-2l1-1v-3l-1-14v-2l1-1h-1V7l-2-1h-1l-1-1 1-1Zm12 0h14v15c-2 1-2 4-3 6v2c-1 0-3 1-2 4h-1l-2 3-1 2v3l-1 2-2 5h2l1-1h2l1-1c1-1 1-3 3-3l1-2 2-2h1l1 3h-1v1l-1 1v7h-8l-1 1-2-1h-3l-1-1 1-1v-3l-1-2 1-1-1-1 1-3v-2l1-2 1-3a7 7 0 0 1 2-4l1-4 2-2 2-3v-3h1l2-3V8l-3-1h-2l-1 1a3 3 0 0 0-2 3l-1 1v4l-1 1-1 1v-1l-1-1V4ZM17 22l1 1h1v3s0-1 0 0l2 1v5l1 2-1 8v3a6 6 0 0 1 0 2l-1 2-1 2-1 3-3 2-2 2-3 1-1-1-1 1H1l-1-1 2-1 1-4V26l1-1-1-3V11l1-1-1-1H2V8L1 7 0 6V5l1-1h15l1 1c2 0 3 1 3 2l1 3v6l-4 6Zm-6-11v9h1l1-2 2-1v-6h-1l-1-1h-2v1Zm0 19-1 1 1 2-1 3v9a2 2 0 0 0 0 1v6l-1 1 3-1 1-2h1v-4l1-4v-5l-1-1 1-3-1-1v-2s0 1 0 0v-1l-1-1a20 20 0 0 1-2-2v4Z\" clip-rule=\"evenodd\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||battle.net^",
|
||||
"||battlenet.com.cn^",
|
||||
"||blizzard.cn^",
|
||||
"||blizzardgames.cn^",
|
||||
"||blz-contentstack.com^",
|
||||
"||blzstatic.cn^",
|
||||
"||bnet.163.com^",
|
||||
"||bnet.cn^",
|
||||
"||lizzard.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "cloudflare",
|
||||
Name: "CloudFlare",
|
||||
@@ -319,6 +363,14 @@ var blockedServices = []blockedService{{
|
||||
"||warp.plus^",
|
||||
"||workers.dev^",
|
||||
},
|
||||
}, {
|
||||
ID: "clubhouse",
|
||||
Name: "Clubhouse",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M29.8 4a1 1 0 0 0-.92.7 1 1 0 0 0 .36 1.1 31.2 31.2 0 0 1 6 6.02 1 1 0 1 0 1.6-1.2 33.2 33.2 0 0 0-6.4-6.4A1 1 0 0 0 29.8 4Zm-7.16 1.06c-.46 0-.87.3-.99.74a1 1 0 0 0 .5 1.15 31.13 31.13 0 0 1 11.13 10.6 1 1 0 1 0 1.7-1.07A33.12 33.12 0 0 0 23.11 5.2a.96.96 0 0 0-.48-.14ZM14.5 7.01a3.42 3.42 0 0 0-3.27 2.28l-.26-.27A3.49 3.49 0 0 0 8.5 8.01c-.9 0-1.8.34-2.48 1.01a3.51 3.51 0 0 0-.57 4.17c-.52.15-1.01.42-1.43.84a3.52 3.52 0 0 0 0 4.94l.27.27c-.46.16-.9.41-1.27.79a3.52 3.52 0 0 0 0 4.94l.88.88 16.47 16.47a9.01 9.01 0 0 0 12.72 0l4.23-4.22a9.94 9.94 0 0 0 2.3-3.59l2.63-7.08a8.03 8.03 0 0 1 1.84-2.87l1.74-1.73 1-1a4.02 4.02 0 0 0 0-5.66 4.02 4.02 0 0 0-5.66 0l-1 1-.7.71-4.2 4.2a2.98 2.98 0 0 1-4.24 0L17.9 8.96l-.94-.94a3.49 3.49 0 0 0-2.47-1.01Zm0 1.98c.38 0 .76.15 1.06.45l.94.94 13.1 13.1a5.02 5.02 0 0 0 7.08 0l4.2-4.18.7-.71 1-1c.8-.8 2.05-.8 2.83 0 .8.79.8 2.04 0 2.83l-2.73 2.73a10.03 10.03 0 0 0-2.3 3.58l-2.63 7.08a8.02 8.02 0 0 1-1.84 2.87l-4.23 4.23a6.99 6.99 0 0 1-9.9 0L4.44 23.56a1.5 1.5 0 0 1 0-2.12c.59-.59 1.45-.55 2.08.08l.1.09 8.2 8.37a1 1 0 0 0 .97.29 1 1 0 0 0 .46-1.68l-9.52-9.73-.01-.01-1.28-1.29a1.5 1.5 0 0 1 0-2.12c.6-.6 1.47-.58 2.08.03l9.18 9.17a1 1 0 0 0 1.69-.43 1 1 0 0 0-.28-.98L9 14.13l-.06-.07-1.5-1.5c-.6-.6-.6-1.53 0-2.12a1.5 1.5 0 0 1 2.12 0L20.8 21.67a1 1 0 0 0 1.68-.44 1 1 0 0 0-.27-.97l-8.7-8.7-.06-.06a1.4 1.4 0 0 1-.01-2.06c.3-.3.68-.45 1.06-.45ZM4.23 32a1 1 0 0 0-.82 1.51c3 5.18 7.36 9.46 12.59 12.37a1 1 0 0 0 1.51-.89 1 1 0 0 0-.54-.86A31.16 31.16 0 0 1 5.15 32.5a1.01 1.01 0 0 0-.92-.51Z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||clubhouse.com^",
|
||||
"||clubhouseapi.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "crunchyroll",
|
||||
Name: "Crunchyroll",
|
||||
@@ -726,6 +778,18 @@ var blockedServices = []blockedService{{
|
||||
"||xxbay.com^",
|
||||
"||yibei.org^",
|
||||
},
|
||||
}, {
|
||||
ID: "electronic_arts",
|
||||
Name: "Electronic Arts",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 1000 1000\"><path d=\"M500 1000C224.3 1000 0 775.7 0 500S224.3 0 500 0s500 224.3 500 500-224.3 500-500 500zm84.63-693.4H302.05l-42.87 68.9h282.25zm57.75.66L469.63 582.33H278.02l44.2-68.96h114.85l43.87-68.93h-265.5l-43.86 68.93h62.9L147.2 651.05h364.2L645.9 438.9l49.05 74.46h-44.23l-41.88 68.96H739.8l45.48 68.72h83.54z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||ea.com^",
|
||||
"||eamobile.com^",
|
||||
"||easports.com^",
|
||||
"||nearpolar.com^",
|
||||
"||swtor.com^",
|
||||
"||tnt-ea.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "epic_games",
|
||||
Name: "Epic Games",
|
||||
@@ -1390,11 +1454,39 @@ var blockedServices = []blockedService{{
|
||||
"||line-apps.com^",
|
||||
"||line-cdn.net^",
|
||||
"||line-scdn.net^",
|
||||
"||line.biz^",
|
||||
"||line.me^",
|
||||
"||line.naver.jp^",
|
||||
"||linecorp.com^",
|
||||
"||linefriends.com.tw^",
|
||||
"||linefriends.com^",
|
||||
"||linegame.jp^",
|
||||
"||linemobile.com^",
|
||||
"||linemyshop.com^",
|
||||
"||lineshoppingseller.com^",
|
||||
"||linetv.tw^",
|
||||
},
|
||||
}, {
|
||||
ID: "linkedin",
|
||||
Name: "LinkedIn",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M41,4H9C6.24,4,4,6.24,4,9v32c0,2.76,2.24,5,5,5h32c2.76,0,5-2.24,5-5V9C46,6.24,43.76,4,41,4z M17,20v19h-6V20H17z M11,14.47c0-1.4,1.2-2.47,3-2.47s2.93,1.07,3,2.47c0,1.4-1.12,2.53-3,2.53C12.2,17,11,15.87,11,14.47z M39,39h-6c0,0,0-9.26,0-10 c0-2-1-4-3.5-4.04h-0.08C27,24.96,26,27.02,26,29c0,0.91,0,10,0,10h-6V20h6v2.56c0,0,1.93-2.56,5.81-2.56 c3.97,0,7.19,2.73,7.19,8.26V39z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||bizographics.com^",
|
||||
"||cs1404.wpc.epsiloncdn.net^",
|
||||
"||cs767.wpc.epsiloncdn.net^",
|
||||
"||l-0005.dc-msedge.net^",
|
||||
"||l-0005.l-dc-msedge.net^",
|
||||
"||l-0005.l-msedge.net^",
|
||||
"||l-0015.l-msedge.net^",
|
||||
"||licdn.cn^",
|
||||
"||licdn.com^",
|
||||
"||linkedin.at^",
|
||||
"||linkedin.be^",
|
||||
"||linkedin.cn^",
|
||||
"||linkedin.com^",
|
||||
"||linkedin.nl^",
|
||||
"||linkedin.qtlcdn.com^",
|
||||
"||lnkd.in^",
|
||||
},
|
||||
}, {
|
||||
ID: "mail_ru",
|
||||
@@ -1438,7 +1530,6 @@ var blockedServices = []blockedService{{
|
||||
"||masto.pt^",
|
||||
"||mastodon.au^",
|
||||
"||mastodon.bida.im^",
|
||||
"||mastodon.com.tr^",
|
||||
"||mastodon.eus^",
|
||||
"||mastodon.green^",
|
||||
"||mastodon.ie^",
|
||||
@@ -1454,7 +1545,7 @@ var blockedServices = []blockedService{{
|
||||
"||mastodon.social^",
|
||||
"||mastodon.uno^",
|
||||
"||mastodon.world^",
|
||||
"||mastodon.xyz^",
|
||||
"||mastodon.zaclys.com^",
|
||||
"||mastodonapp.uk^",
|
||||
"||mastodonners.nl^",
|
||||
"||mastodont.cat^",
|
||||
@@ -1465,12 +1556,12 @@ var blockedServices = []blockedService{{
|
||||
"||metalhead.club^",
|
||||
"||mindly.social^",
|
||||
"||mstdn.ca^",
|
||||
"||mstdn.jp^",
|
||||
"||mstdn.party^",
|
||||
"||mstdn.plus^",
|
||||
"||mstdn.social^",
|
||||
"||muenchen.social^",
|
||||
"||newsie.social^",
|
||||
"||muenster.im^",
|
||||
"||nerdculture.de^",
|
||||
"||noc.social^",
|
||||
"||norden.social^",
|
||||
"||nrw.social^",
|
||||
@@ -1498,16 +1589,17 @@ var blockedServices = []blockedService{{
|
||||
"||techhub.social^",
|
||||
"||theblower.au^",
|
||||
"||tkz.one^",
|
||||
"||todon.eu^",
|
||||
"||toot.aquilenet.fr^",
|
||||
"||toot.community^",
|
||||
"||toot.funami.tech^",
|
||||
"||toot.io^",
|
||||
"||toot.wales^",
|
||||
"||troet.cafe^",
|
||||
"||twingyeo.kr^",
|
||||
"||union.place^",
|
||||
"||universeodon.com^",
|
||||
"||urbanists.social^",
|
||||
"||wien.rocks^",
|
||||
"||wxw.moe^",
|
||||
},
|
||||
}, {
|
||||
@@ -1550,6 +1642,44 @@ var blockedServices = []blockedService{{
|
||||
"||nflxso.net^",
|
||||
"||nflxvideo.net^",
|
||||
},
|
||||
}, {
|
||||
ID: "nintendo",
|
||||
Name: "Nintendo",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M6 7v36h12.6V21.75l13 20.78.27.47H44V7H31.4v1l.04 20.22L18.5 7.47 18.22 7Zm2 2h9.1l14.5 23.22 1.84 3v-3.5L33.4 9H42v32h-9L18.44 17.75l-1.85-2.94V41H8Z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||nintendo-europe.com^",
|
||||
"||nintendo.be^",
|
||||
"||nintendo.co.jp^",
|
||||
"||nintendo.co.uk^",
|
||||
"||nintendo.com.au^",
|
||||
"||nintendo.com^",
|
||||
"||nintendo.de^",
|
||||
"||nintendo.es^",
|
||||
"||nintendo.eu^",
|
||||
"||nintendo.fr^",
|
||||
"||nintendo.it^",
|
||||
"||nintendo.jp^",
|
||||
"||nintendo.net^",
|
||||
"||nintendo.nl^",
|
||||
"||nintendoswitch.cn^",
|
||||
"||nintendowifi.net^",
|
||||
},
|
||||
}, {
|
||||
ID: "nvidia",
|
||||
Name: "Nvidia",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 48 48\"><path d=\"M20 8a2 2 0 0 0-2 2v2.55l.84-.05c10.76-.37 17.78 8.82 17.78 8.82s-8.05 9.8-16.44 9.8c-.73 0-1.47-.07-2.18-.19v-2.2c.73.23 1.52.35 2.3.35 5.88 0 11.35-7.6 11.35-7.6s-5.07-6.91-12.81-6.66l-.82.03v-2.3c-9.49.77-17.68 8.8-17.68 8.8S4.97 34.76 18 35.98v-2.44c.59.07 1.22.12 1.81.12 7.82 0 13.47-3.99 18.94-8.7.91.73 4.62 2.49 5.4 3.26-5.2 4.36-17.33 7.86-24.2 7.86-.66 0-1.32-.03-1.95-.1V38c0 1.1.9 2 2 2h25a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H20zm-2 6.86v2.82a11.8 11.8 0 0 1 1.57-.07c4.95 0 7.9 3.85 7.9 3.85l-4.03 3.39c-1.8-3.02-2.43-4.35-5.44-4.7v8.57c-4.06-1.38-5.4-6.14-5.4-6.14s2.37-2.83 5.38-2.46H18v-2.44a15.66 15.66 0 0 0-9.22 4.46s2 7.52 9.22 8.8v2.6c-9.56-1.17-12.82-11.7-12.82-11.7s4.27-6.3 12.82-6.97z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||geforce.com^",
|
||||
"||geforcenow.com^",
|
||||
"||nvidia.cn^",
|
||||
"||nvidia.com.global.ogslb.com^",
|
||||
"||nvidia.com^",
|
||||
"||nvidia.eu^",
|
||||
"||nvidia.partners^",
|
||||
"||nvidiagrid.net^",
|
||||
"||nvidianews.com^",
|
||||
"||tegrazone.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "ok",
|
||||
Name: "OK.ru",
|
||||
@@ -1704,6 +1834,14 @@ var blockedServices = []blockedService{{
|
||||
"||robloxcdn.com^",
|
||||
"||robloxdev.cn^",
|
||||
},
|
||||
}, {
|
||||
ID: "rockstar_games",
|
||||
Name: "Rockstar games",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M12 3c-4.96 0-9 4.04-9 9v26c0 4.96 4.04 9 9 9h26c4.96 0 9-4.04 9-9V12c0-4.96-4.04-9-9-9H12zm0 2h26c3.88 0 7 3.12 7 7v26c0 3.88-3.12 7-7 7H12c-3.88 0-7-3.12-7-7V12c0-3.88 3.12-7 7-7zm3.72 5a1 1 0 0 0-.97.79l-3.87 18a1 1 0 0 0 .98 1.21h4.27a1 1 0 0 0 .97-.79L18.47 23h2.07c.94 0 1.12.15 1.36.73.24.57.3 1.76.1 3.4-.08.68-.05 1.22.02 1.6v.03a1 1 0 0 0 .3.97l3.37 3.12-2.6 5.74a1 1 0 0 0 1.43 1.26l5.58-3.39 4.29 3.33a1 1 0 0 0 1.6-.98l-1.09-5.56 4.7-3.47a1 1 0 0 0-.6-1.8h-4.86l-.82-5.14a1 1 0 0 0-.98-.84 1 1 0 0 0-.88.51l-2.77 5a14.3 14.3 0 0 1 .06-2.83c.15-1.48.01-2.64-.18-3.45-.06-.28-.08-.25-.15-.45.3-.17.4-.13.77-.5.8-.8 1.6-2.18 1.75-4.26.17-2.26-.55-3.98-1.92-4.9C27.65 10.17 25.91 10 24 10h-8.28zm.81 2H24c1.75 0 3.13.25 3.9.77.76.52 1.18 1.27 1.05 3.1-.13 1.67-.69 2.51-1.17 3a2 2 0 0 1-.82.56 1 1 0 0 0-.6 1.44s.12.21.27.82c.14.6.26 1.53.13 2.79a14.24 14.24 0 0 0-.01 3.52h-2.76c-.01-.19-.04-.32 0-.62.22-1.78.25-3.21-.24-4.42A3.38 3.38 0 0 0 20.54 21h-2.87a1 1 0 0 0-.98.78L15.32 28H13.1l3.44-16zm2.76 1.03a1 1 0 0 0-.98.8l-.98 4.94a1 1 0 0 0 .98 1.2h4.47c.79 0 1.65-.12 2.44-.58a3.6 3.6 0 0 0 1.68-2.41 3.3 3.3 0 0 0-.72-2.92 3.35 3.35 0 0 0-2.47-1.03h-4.42zm.82 2h3.6c.41 0 .79.16 1 .4.22.22.36.52.23 1.15-.13.62-.36.88-.72 1.08a3 3 0 0 1-1.44.3h-3.25l.58-2.93zm11.7 10.99.49 3.11a1 1 0 0 0 .98.84h2.69l-2.76 2.05a1 1 0 0 0-.4 1l.7 3.56-2.73-2.12a1 1 0 0 0-1.13-.07l-3.4 2.07 1.56-3.44a1 1 0 0 0-.23-1.15L25.55 30H29a1 1 0 0 0 .88-.51l1.92-3.47z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||rockstargames.com^",
|
||||
"||rsg.sc^",
|
||||
},
|
||||
}, {
|
||||
ID: "shopee",
|
||||
Name: "Shopee",
|
||||
@@ -1940,6 +2078,16 @@ var blockedServices = []blockedService{{
|
||||
"||twvid.com^",
|
||||
"||vine.co^",
|
||||
},
|
||||
}, {
|
||||
ID: "ubisoft",
|
||||
Name: "Ubisoft",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 32 32\"><path d=\"M15.22 3C7.14 3 3.66 10.18 3.66 10.18l1.03.74s-1.3 2.45-1.26 5.6A12.5 12.5 0 0 0 16.08 29a12.5 12.5 0 0 0 12.49-12.46c0-9-6.98-13.54-13.35-13.54zm.07 2.2c6.3 0 11.2 5.07 11.2 10.98 0 6.27-4.71 10.62-10.2 10.62-4.04 0-7.69-3.08-7.69-7.3a5.8 5.8 0 0 1 2.75-5.03l.21.23a6.37 6.37 0 0 0-1.53 3.91c0 3.32 2.6 5.62 5.88 5.62 4.18 0 6.97-3.56 6.97-7.7 0-4.81-4.25-8.9-9.36-8.9a11.1 11.1 0 0 0-6.61 2.3l-.21-.2a10.07 10.07 0 0 1 8.59-4.54zM13.4 9.8c3.26 0 6.44 2.15 7.24 5.22l-.3.1a8.35 8.35 0 0 0-6.52-3.44c-5.08 0-7.75 4.62-7.36 8.47l-.3.12s-.56-1.24-.56-2.71a7.8 7.8 0 0 1 7.8-7.76zm2.15 5.33a2.77 2.77 0 0 1 2.78 2.74c0 1.23-.79 1.96-.79 1.96l.94.65s-.93 1.46-2.82 1.46a3.4 3.4 0 0 1-.1-6.8z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||ubi.com^",
|
||||
"||ubisoft.com^",
|
||||
"||ubisoft.org^",
|
||||
"||ubisoftconnect.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "valorant",
|
||||
Name: "Valorant",
|
||||
|
||||
@@ -23,12 +23,14 @@ type Client struct {
|
||||
safeSearchConf filtering.SafeSearchConfig
|
||||
SafeSearch filtering.SafeSearch
|
||||
|
||||
// BlockedServices is the configuration of blocked services of a client.
|
||||
BlockedServices *filtering.BlockedServices
|
||||
|
||||
Name string
|
||||
|
||||
IDs []string
|
||||
Tags []string
|
||||
BlockedServices []string
|
||||
Upstreams []string
|
||||
IDs []string
|
||||
Tags []string
|
||||
Upstreams []string
|
||||
|
||||
UseOwnSettings bool
|
||||
FilteringEnabled bool
|
||||
@@ -44,9 +46,9 @@ type Client struct {
|
||||
func (c *Client) ShallowClone() (sh *Client) {
|
||||
clone := *c
|
||||
|
||||
clone.BlockedServices = c.BlockedServices.Clone()
|
||||
clone.IDs = stringutil.CloneSlice(c.IDs)
|
||||
clone.Tags = stringutil.CloneSlice(c.Tags)
|
||||
clone.BlockedServices = stringutil.CloneSlice(c.BlockedServices)
|
||||
clone.Upstreams = stringutil.CloneSlice(c.Upstreams)
|
||||
|
||||
return &clone
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||
@@ -24,6 +25,23 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// DHCP is an interface for accessing DHCP lease data the [clientsContainer]
|
||||
// needs.
|
||||
type DHCP interface {
|
||||
// Leases returns all the DHCP leases.
|
||||
Leases() (leases []*dhcpsvc.Lease)
|
||||
|
||||
// HostByIP returns the hostname of the DHCP client with the given IP
|
||||
// address. The address will be netip.Addr{} if there is no such client,
|
||||
// due to an assumption that a DHCP client must always have an IP address.
|
||||
HostByIP(ip netip.Addr) (host string)
|
||||
|
||||
// MACByIP returns the MAC address for the given IP address leased. It
|
||||
// returns nil if there is no such client, due to an assumption that a DHCP
|
||||
// client must always have a MAC address.
|
||||
MACByIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
}
|
||||
|
||||
// clientsContainer is the storage of all runtime and persistent clients.
|
||||
type clientsContainer struct {
|
||||
// TODO(a.garipov): Perhaps use a number of separate indices for different
|
||||
@@ -78,7 +96,7 @@ func (clients *clientsContainer) Init(
|
||||
etcHosts *aghnet.HostsContainer,
|
||||
arpdb aghnet.ARPDB,
|
||||
filteringConf *filtering.Config,
|
||||
) {
|
||||
) (err error) {
|
||||
if clients.list != nil {
|
||||
log.Fatal("clients.list != nil")
|
||||
}
|
||||
@@ -92,23 +110,29 @@ func (clients *clientsContainer) Init(
|
||||
clients.dhcpServer = dhcpServer
|
||||
clients.etcHosts = etcHosts
|
||||
clients.arpdb = arpdb
|
||||
clients.addFromConfig(objects, filteringConf)
|
||||
err = clients.addFromConfig(objects, filteringConf)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
clients.safeSearchCacheSize = filteringConf.SafeSearchCacheSize
|
||||
clients.safeSearchCacheTTL = time.Minute * time.Duration(filteringConf.CacheTime)
|
||||
|
||||
if clients.testing {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
clients.updateFromDHCP(true)
|
||||
if clients.dhcpServer != nil {
|
||||
clients.dhcpServer.SetOnLeaseChanged(clients.onDHCPLeaseChanged)
|
||||
clients.onDHCPLeaseChanged(dhcpd.LeaseChangedAdded)
|
||||
}
|
||||
|
||||
if clients.etcHosts != nil {
|
||||
go clients.handleHostsUpdates()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (clients *clientsContainer) handleHostsUpdates() {
|
||||
@@ -148,12 +172,14 @@ func (clients *clientsContainer) reloadARP() {
|
||||
type clientObject struct {
|
||||
SafeSearchConf filtering.SafeSearchConfig `yaml:"safe_search"`
|
||||
|
||||
// BlockedServices is the configuration of blocked services of a client.
|
||||
BlockedServices *filtering.BlockedServices `yaml:"blocked_services"`
|
||||
|
||||
Name string `yaml:"name"`
|
||||
|
||||
Tags []string `yaml:"tags"`
|
||||
IDs []string `yaml:"ids"`
|
||||
BlockedServices []string `yaml:"blocked_services"`
|
||||
Upstreams []string `yaml:"upstreams"`
|
||||
IDs []string `yaml:"ids"`
|
||||
Tags []string `yaml:"tags"`
|
||||
Upstreams []string `yaml:"upstreams"`
|
||||
|
||||
UseGlobalSettings bool `yaml:"use_global_settings"`
|
||||
FilteringEnabled bool `yaml:"filtering_enabled"`
|
||||
@@ -167,7 +193,10 @@ type clientObject struct {
|
||||
|
||||
// addFromConfig initializes the clients container with objects from the
|
||||
// configuration file.
|
||||
func (clients *clientsContainer) addFromConfig(objects []*clientObject, filteringConf *filtering.Config) {
|
||||
func (clients *clientsContainer) addFromConfig(
|
||||
objects []*clientObject,
|
||||
filteringConf *filtering.Config,
|
||||
) (err error) {
|
||||
for _, o := range objects {
|
||||
cli := &Client{
|
||||
Name: o.Name,
|
||||
@@ -188,7 +217,7 @@ func (clients *clientsContainer) addFromConfig(objects []*clientObject, filterin
|
||||
if o.SafeSearchConf.Enabled {
|
||||
o.SafeSearchConf.CustomResolver = safeSearchResolver{}
|
||||
|
||||
err := cli.setSafeSearch(
|
||||
err = cli.setSafeSearch(
|
||||
o.SafeSearchConf,
|
||||
filteringConf.SafeSearchCacheSize,
|
||||
time.Minute*time.Duration(filteringConf.CacheTime),
|
||||
@@ -200,14 +229,13 @@ func (clients *clientsContainer) addFromConfig(objects []*clientObject, filterin
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range o.BlockedServices {
|
||||
if filtering.BlockedSvcKnown(s) {
|
||||
cli.BlockedServices = append(cli.BlockedServices, s)
|
||||
} else {
|
||||
log.Info("clients: skipping unknown blocked service %q", s)
|
||||
}
|
||||
err = o.BlockedServices.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("clients: init client blocked services %q: %w", cli.Name, err)
|
||||
}
|
||||
|
||||
cli.BlockedServices = o.BlockedServices.Clone()
|
||||
|
||||
for _, t := range o.Tags {
|
||||
if clients.allTags.Has(t) {
|
||||
cli.Tags = append(cli.Tags, t)
|
||||
@@ -218,11 +246,13 @@ func (clients *clientsContainer) addFromConfig(objects []*clientObject, filterin
|
||||
|
||||
slices.Sort(cli.Tags)
|
||||
|
||||
_, err := clients.Add(cli)
|
||||
_, err = clients.Add(cli)
|
||||
if err != nil {
|
||||
log.Error("clients: adding clients %s: %s", cli.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// forConfig returns all currently known persistent clients as objects for the
|
||||
@@ -236,10 +266,11 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
|
||||
o := &clientObject{
|
||||
Name: cli.Name,
|
||||
|
||||
Tags: stringutil.CloneSlice(cli.Tags),
|
||||
IDs: stringutil.CloneSlice(cli.IDs),
|
||||
BlockedServices: stringutil.CloneSlice(cli.BlockedServices),
|
||||
Upstreams: stringutil.CloneSlice(cli.Upstreams),
|
||||
BlockedServices: cli.BlockedServices.Clone(),
|
||||
|
||||
IDs: stringutil.CloneSlice(cli.IDs),
|
||||
Tags: stringutil.CloneSlice(cli.Tags),
|
||||
Upstreams: stringutil.CloneSlice(cli.Upstreams),
|
||||
|
||||
UseGlobalSettings: !cli.UseOwnSettings,
|
||||
FilteringEnabled: cli.FilteringEnabled,
|
||||
@@ -277,15 +308,38 @@ func (clients *clientsContainer) periodicUpdate() {
|
||||
}
|
||||
}
|
||||
|
||||
// onDHCPLeaseChanged is a callback for the DHCP server. It updates the list of
|
||||
// runtime clients using the DHCP server's leases.
|
||||
//
|
||||
// TODO(e.burkov): Remove when switched to dhcpsvc.
|
||||
func (clients *clientsContainer) onDHCPLeaseChanged(flags int) {
|
||||
switch flags {
|
||||
case dhcpd.LeaseChangedAdded,
|
||||
dhcpd.LeaseChangedAddedStatic,
|
||||
dhcpd.LeaseChangedRemovedStatic:
|
||||
clients.updateFromDHCP(true)
|
||||
case dhcpd.LeaseChangedRemovedAll:
|
||||
clients.updateFromDHCP(false)
|
||||
if clients.dhcpServer == nil || !config.Clients.Sources.DHCP {
|
||||
return
|
||||
}
|
||||
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
clients.rmHostsBySrc(ClientSourceDHCP)
|
||||
|
||||
if flags == dhcpd.LeaseChangedRemovedAll {
|
||||
return
|
||||
}
|
||||
|
||||
leases := clients.dhcpServer.Leases(dhcpd.LeasesAll)
|
||||
n := 0
|
||||
for _, l := range leases {
|
||||
if l.Hostname == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ok := clients.addHostLocked(l.IP, l.Hostname, ClientSourceDHCP)
|
||||
if ok {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("clients: added %d client aliases from dhcp", n)
|
||||
}
|
||||
|
||||
// clientSource checks if client with this IP address already exists and returns
|
||||
@@ -301,11 +355,11 @@ func (clients *clientsContainer) clientSource(ip netip.Addr) (src clientSource)
|
||||
}
|
||||
|
||||
rc, ok := clients.ipToRC[ip]
|
||||
if !ok {
|
||||
return ClientSourceNone
|
||||
if ok {
|
||||
return rc.Source
|
||||
}
|
||||
|
||||
return rc.Source
|
||||
return ClientSourceNone
|
||||
}
|
||||
|
||||
// findMultiple is a wrapper around Find to make it a valid client finder for
|
||||
@@ -466,11 +520,11 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if clients.dhcpServer == nil {
|
||||
return nil, false
|
||||
if clients.dhcpServer != nil {
|
||||
return clients.findDHCP(ip)
|
||||
}
|
||||
|
||||
return clients.findDHCP(ip)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// findDHCP searches for a client by its MAC, if the DHCP server is active and
|
||||
@@ -697,28 +751,27 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
|
||||
_, ok := clients.findLocked(ip.String())
|
||||
if ok {
|
||||
log.Debug("clients: client for %s is already created, ignore whois info", ip)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Consider storing WHOIS information separately and
|
||||
// potentially get rid of [RuntimeClient].
|
||||
rc, ok := clients.ipToRC[ip]
|
||||
if ok {
|
||||
rc.WHOIS = wi
|
||||
if !ok {
|
||||
// Create a RuntimeClient implicitly so that we don't do this check
|
||||
// again.
|
||||
rc = &RuntimeClient{
|
||||
Source: ClientSourceWHOIS,
|
||||
}
|
||||
clients.ipToRC[ip] = rc
|
||||
|
||||
log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi)
|
||||
} else {
|
||||
log.Debug("clients: set whois info for runtime client %s: %+v", rc.Host, wi)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Create a RuntimeClient implicitly so that we don't do this check
|
||||
// again.
|
||||
rc = &RuntimeClient{
|
||||
Source: ClientSourceWHOIS,
|
||||
}
|
||||
|
||||
rc.WHOIS = wi
|
||||
|
||||
clients.ipToRC[ip] = rc
|
||||
|
||||
log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi)
|
||||
}
|
||||
|
||||
// AddHost adds a new IP-hostname pairing. The priorities of the sources are
|
||||
@@ -742,23 +795,19 @@ func (clients *clientsContainer) addHostLocked(
|
||||
src clientSource,
|
||||
) (ok bool) {
|
||||
rc, ok := clients.ipToRC[ip]
|
||||
if ok {
|
||||
if rc.Source > src {
|
||||
return false
|
||||
}
|
||||
|
||||
rc.Host = host
|
||||
rc.Source = src
|
||||
} else {
|
||||
if !ok {
|
||||
rc = &RuntimeClient{
|
||||
Host: host,
|
||||
Source: src,
|
||||
WHOIS: &whois.Info{},
|
||||
WHOIS: &whois.Info{},
|
||||
}
|
||||
|
||||
clients.ipToRC[ip] = rc
|
||||
} else if src < rc.Source {
|
||||
return false
|
||||
}
|
||||
|
||||
rc.Host = host
|
||||
rc.Source = src
|
||||
|
||||
log.Debug("clients: added %s -> %q [%d]", ip, host, len(clients.ipToRC))
|
||||
|
||||
return true
|
||||
@@ -827,38 +876,6 @@ func (clients *clientsContainer) addFromSystemARP() {
|
||||
log.Debug("clients: added %d client aliases from arp neighborhood", added)
|
||||
}
|
||||
|
||||
// updateFromDHCP adds the clients that have a non-empty hostname from the DHCP
|
||||
// server.
|
||||
func (clients *clientsContainer) updateFromDHCP(add bool) {
|
||||
if clients.dhcpServer == nil || !config.Clients.Sources.DHCP {
|
||||
return
|
||||
}
|
||||
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
clients.rmHostsBySrc(ClientSourceDHCP)
|
||||
|
||||
if !add {
|
||||
return
|
||||
}
|
||||
|
||||
leases := clients.dhcpServer.Leases(dhcpd.LeasesAll)
|
||||
n := 0
|
||||
for _, l := range leases {
|
||||
if l.Hostname == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ok := clients.addHostLocked(l.IP, l.Hostname, ClientSourceDHCP)
|
||||
if ok {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("clients: added %d client aliases from dhcp", n)
|
||||
}
|
||||
|
||||
// close gracefully closes all the client-specific upstream configurations of
|
||||
// the persistent clients.
|
||||
func (clients *clientsContainer) close() (err error) {
|
||||
|
||||
@@ -16,18 +16,19 @@ import (
|
||||
|
||||
// newClientsContainer is a helper that creates a new clients container for
|
||||
// tests.
|
||||
func newClientsContainer() (c *clientsContainer) {
|
||||
func newClientsContainer(t *testing.T) (c *clientsContainer) {
|
||||
c = &clientsContainer{
|
||||
testing: true,
|
||||
}
|
||||
|
||||
c.Init(nil, nil, nil, nil, &filtering.Config{})
|
||||
err := c.Init(nil, nil, nil, nil, &filtering.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func TestClients(t *testing.T) {
|
||||
clients := newClientsContainer()
|
||||
clients := newClientsContainer(t)
|
||||
|
||||
t.Run("add_success", func(t *testing.T) {
|
||||
var (
|
||||
@@ -198,7 +199,7 @@ func TestClients(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClientsWHOIS(t *testing.T) {
|
||||
clients := newClientsContainer()
|
||||
clients := newClientsContainer(t)
|
||||
whois := &whois.Info{
|
||||
Country: "AU",
|
||||
Orgname: "Example Org",
|
||||
@@ -244,7 +245,7 @@ func TestClientsWHOIS(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClientsAddExisting(t *testing.T) {
|
||||
clients := newClientsContainer()
|
||||
clients := newClientsContainer(t)
|
||||
|
||||
t.Run("simple", func(t *testing.T) {
|
||||
ip := netip.MustParseAddr("1.1.1.1")
|
||||
@@ -316,7 +317,7 @@ func TestClientsAddExisting(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClientsCustomUpstream(t *testing.T) {
|
||||
clients := newClientsContainer()
|
||||
clients := newClientsContainer(t)
|
||||
|
||||
// Add client with upstreams.
|
||||
ok, err := clients.Add(&Client{
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||
)
|
||||
|
||||
@@ -118,15 +119,24 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
|
||||
}
|
||||
}
|
||||
|
||||
weekly := schedule.EmptyWeekly()
|
||||
if prev != nil {
|
||||
weekly = prev.BlockedServices.Schedule.Clone()
|
||||
}
|
||||
|
||||
c = &Client{
|
||||
safeSearchConf: safeSearchConf,
|
||||
|
||||
Name: cj.Name,
|
||||
|
||||
IDs: cj.IDs,
|
||||
Tags: cj.Tags,
|
||||
BlockedServices: cj.BlockedServices,
|
||||
Upstreams: cj.Upstreams,
|
||||
BlockedServices: &filtering.BlockedServices{
|
||||
Schedule: weekly,
|
||||
IDs: cj.BlockedServices,
|
||||
},
|
||||
|
||||
IDs: cj.IDs,
|
||||
Tags: cj.Tags,
|
||||
Upstreams: cj.Upstreams,
|
||||
|
||||
UseOwnSettings: !cj.UseGlobalSettings,
|
||||
FilteringEnabled: cj.FilteringEnabled,
|
||||
@@ -180,7 +190,8 @@ func clientToJSON(c *Client) (cj *clientJSON) {
|
||||
SafeBrowsingEnabled: c.SafeBrowsingEnabled,
|
||||
|
||||
UseGlobalBlockedServices: !c.UseOwnBlockedServices,
|
||||
BlockedServices: c.BlockedServices,
|
||||
|
||||
BlockedServices: c.BlockedServices.IDs,
|
||||
|
||||
Upstreams: c.Upstreams,
|
||||
|
||||
|
||||
@@ -91,18 +91,17 @@ type clientSourcesConfig struct {
|
||||
HostsFile bool `yaml:"hosts"`
|
||||
}
|
||||
|
||||
// configuration is loaded from YAML
|
||||
// field ordering is important -- yaml fields will mirror ordering from here
|
||||
// configuration is loaded from YAML.
|
||||
//
|
||||
// Field ordering is important, YAML fields better not to be reordered, if it's
|
||||
// not absolutely necessary.
|
||||
type configuration struct {
|
||||
// Raw file data to avoid re-reading of configuration file
|
||||
// It's reset after config is parsed
|
||||
fileData []byte
|
||||
|
||||
// BindHost is the address for the web interface server to listen on.
|
||||
BindHost netip.Addr `yaml:"bind_host"`
|
||||
// BindPort is the port for the web interface server to listen on.
|
||||
BindPort int `yaml:"bind_port"`
|
||||
|
||||
// HTTPConfig is the block with http conf.
|
||||
HTTPConfig httpConfig `yaml:"http"`
|
||||
// Users are the clients capable for accessing the web interface.
|
||||
Users []webUser `yaml:"users"`
|
||||
// AuthAttempts is the maximum number of failed login attempts a user
|
||||
@@ -120,10 +119,6 @@ type configuration struct {
|
||||
// DebugPProf defines if the profiling HTTP handler will listen on :6060.
|
||||
DebugPProf bool `yaml:"debug_pprof"`
|
||||
|
||||
// TTL for a web session (in hours)
|
||||
// An active session is automatically refreshed once a day.
|
||||
WebSessionTTLHours uint32 `yaml:"web_session_ttl"`
|
||||
|
||||
DNS dnsConfig `yaml:"dns"`
|
||||
TLS tlsConfigSettings `yaml:"tls"`
|
||||
QueryLog queryLogConfig `yaml:"querylog"`
|
||||
@@ -156,7 +151,23 @@ type configuration struct {
|
||||
SchemaVersion int `yaml:"schema_version"` // keeping last so that users will be less tempted to change it -- used when upgrading between versions
|
||||
}
|
||||
|
||||
// field ordering is important -- yaml fields will mirror ordering from here
|
||||
// httpConfig is a block with HTTP configuration params.
|
||||
//
|
||||
// Field ordering is important, YAML fields better not to be reordered, if it's
|
||||
// not absolutely necessary.
|
||||
type httpConfig struct {
|
||||
// Address is the address to serve the web UI on.
|
||||
Address netip.AddrPort
|
||||
|
||||
// SessionTTL for a web session.
|
||||
// An active session is automatically refreshed once a day.
|
||||
SessionTTL timeutil.Duration `yaml:"session_ttl"`
|
||||
}
|
||||
|
||||
// dnsConfig is a block with DNS configuration params.
|
||||
//
|
||||
// Field ordering is important, YAML fields better not to be reordered, if it's
|
||||
// not absolutely necessary.
|
||||
type dnsConfig struct {
|
||||
BindHosts []netip.Addr `yaml:"bind_hosts"`
|
||||
Port int `yaml:"port"`
|
||||
@@ -261,11 +272,12 @@ type statsConfig struct {
|
||||
//
|
||||
// TODO(a.garipov, e.burkov): This global is awful and must be removed.
|
||||
var config = &configuration{
|
||||
BindPort: 3000,
|
||||
BindHost: netip.IPv4Unspecified(),
|
||||
AuthAttempts: 5,
|
||||
AuthBlockMin: 15,
|
||||
WebSessionTTLHours: 30 * 24,
|
||||
AuthAttempts: 5,
|
||||
AuthBlockMin: 15,
|
||||
HTTPConfig: httpConfig{
|
||||
Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000),
|
||||
SessionTTL: timeutil.Duration{Duration: 30 * timeutil.Day},
|
||||
},
|
||||
DNS: dnsConfig{
|
||||
BindHosts: []netip.Addr{netip.IPv4Unspecified()},
|
||||
Port: defaultPortDNS,
|
||||
@@ -427,8 +439,8 @@ func readLogSettings() (ls *logSettings) {
|
||||
// validateBindHosts returns error if any of binding hosts from configuration is
|
||||
// not a valid IP address.
|
||||
func validateBindHosts(conf *configuration) (err error) {
|
||||
if !conf.BindHost.IsValid() {
|
||||
return errors.Error("bind_host is not a valid ip address")
|
||||
if !conf.HTTPConfig.Address.IsValid() {
|
||||
return errors.Error("http.address is not a valid ip address")
|
||||
}
|
||||
|
||||
for i, addr := range conf.DNS.BindHosts {
|
||||
@@ -462,7 +474,7 @@ func parseConfig() (err error) {
|
||||
}
|
||||
|
||||
tcpPorts := aghalg.UniqChecker[tcpPort]{}
|
||||
addPorts(tcpPorts, tcpPort(config.BindPort))
|
||||
addPorts(tcpPorts, tcpPort(config.HTTPConfig.Address.Port()))
|
||||
|
||||
udpPorts := aghalg.UniqChecker[udpPort]{}
|
||||
addPorts(udpPorts, udpPort(config.DNS.Port))
|
||||
|
||||
@@ -103,7 +103,7 @@ type statusResponse struct {
|
||||
Language string `json:"language"`
|
||||
DNSAddrs []string `json:"dns_addresses"`
|
||||
DNSPort int `json:"dns_port"`
|
||||
HTTPPort int `json:"http_port"`
|
||||
HTTPPort uint16 `json:"http_port"`
|
||||
|
||||
// ProtectionDisabledDuration is the duration of the protection pause in
|
||||
// milliseconds.
|
||||
@@ -158,7 +158,7 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
Language: config.Language,
|
||||
DNSAddrs: dnsAddrs,
|
||||
DNSPort: config.DNS.Port,
|
||||
HTTPPort: config.BindPort,
|
||||
HTTPPort: config.HTTPConfig.Address.Port(),
|
||||
ProtectionDisabledDuration: protectionDisabledDuration,
|
||||
ProtectionEnabled: protectionEnabled,
|
||||
IsRunning: isRunning(),
|
||||
|
||||
@@ -96,8 +96,9 @@ type checkConfResp struct {
|
||||
func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "validating ports: %w") }()
|
||||
|
||||
portInt := req.Web.Port
|
||||
port := tcpPort(portInt)
|
||||
// TODO(a.garipov): Declare all port variables anywhere as uint16.
|
||||
reqPort := uint16(req.Web.Port)
|
||||
port := tcpPort(reqPort)
|
||||
addPorts(tcpPorts, port)
|
||||
if err = tcpPorts.Validate(); err != nil {
|
||||
// Reset the value for the port to 1 to make sure that validateDNS
|
||||
@@ -108,15 +109,15 @@ func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err
|
||||
return err
|
||||
}
|
||||
|
||||
switch portInt {
|
||||
case 0, config.BindPort:
|
||||
switch reqPort {
|
||||
case 0, config.HTTPConfig.Address.Port():
|
||||
return nil
|
||||
default:
|
||||
// Go on and check the port binding only if it's not zero or won't be
|
||||
// unbound after install.
|
||||
}
|
||||
|
||||
return aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(portInt)))
|
||||
return aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, reqPort))
|
||||
}
|
||||
|
||||
// validateDNS returns error if the DNS part of the initial configuration can't
|
||||
@@ -127,11 +128,11 @@ func (req *checkConfReq) validateDNS(
|
||||
) (canAutofix bool, err error) {
|
||||
defer func() { err = errors.Annotate(err, "validating ports: %w") }()
|
||||
|
||||
port := req.DNS.Port
|
||||
port := uint16(req.DNS.Port)
|
||||
switch port {
|
||||
case 0:
|
||||
return false, nil
|
||||
case config.BindPort:
|
||||
case config.HTTPConfig.Address.Port():
|
||||
// Go on and only check the UDP port since the TCP one is already bound
|
||||
// by AdGuard Home for web interface.
|
||||
default:
|
||||
@@ -318,8 +319,7 @@ type applyConfigReq struct {
|
||||
// copyInstallSettings copies the installation parameters between two
|
||||
// configuration structures.
|
||||
func copyInstallSettings(dst, src *configuration) {
|
||||
dst.BindHost = src.BindHost
|
||||
dst.BindPort = src.BindPort
|
||||
dst.HTTPConfig = src.HTTPConfig
|
||||
dst.DNS.BindHosts = src.DNS.BindHosts
|
||||
dst.DNS.Port = src.DNS.Port
|
||||
}
|
||||
@@ -413,8 +413,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
||||
copyInstallSettings(curConfig, config)
|
||||
|
||||
Context.firstRun = false
|
||||
config.BindHost = req.Web.IP
|
||||
config.BindPort = req.Web.Port
|
||||
config.HTTPConfig.Address = netip.AddrPortFrom(req.Web.IP, uint16(req.Web.Port))
|
||||
config.DNS.BindHosts = []netip.Addr{req.DNS.IP}
|
||||
config.DNS.Port = req.DNS.Port
|
||||
|
||||
@@ -487,7 +486,8 @@ func decodeApplyConfigReq(r io.Reader) (req *applyConfigReq, restartHTTP bool, e
|
||||
return nil, false, errors.Error("ports cannot be 0")
|
||||
}
|
||||
|
||||
restartHTTP = config.BindHost != req.Web.IP || config.BindPort != req.Web.Port
|
||||
addrPort := config.HTTPConfig.Address
|
||||
restartHTTP = addrPort.Addr() != req.Web.IP || int(addrPort.Port()) != req.Web.Port
|
||||
if restartHTTP {
|
||||
err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(req.Web.Port)))
|
||||
if err != nil {
|
||||
|
||||
@@ -157,7 +157,9 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
|
||||
Context.tls.WriteDiskConfig(tlsConf)
|
||||
|
||||
canUpdate := true
|
||||
if tlsConfUsesPrivilegedPorts(tlsConf) || config.BindPort < 1024 || config.DNS.Port < 1024 {
|
||||
if tlsConfUsesPrivilegedPorts(tlsConf) ||
|
||||
config.HTTPConfig.Address.Port() < 1024 ||
|
||||
config.DNS.Port < 1024 {
|
||||
canUpdate, err = aghnet.CanBindPrivilegedPorts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking ability to bind privileged ports: %w", err)
|
||||
|
||||
@@ -473,12 +473,12 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering
|
||||
|
||||
if c.UseOwnBlockedServices {
|
||||
// TODO(e.burkov): Get rid of this crutch.
|
||||
svcs := c.BlockedServices
|
||||
if svcs == nil {
|
||||
svcs = []string{}
|
||||
setts.ServicesRules = nil
|
||||
svcs := c.BlockedServices.IDs
|
||||
if !c.BlockedServices.Schedule.Contains(time.Now()) {
|
||||
Context.filters.ApplyBlockedServicesList(setts, svcs)
|
||||
log.Debug("%s: services for client %q set: %s", pref, c.Name, svcs)
|
||||
}
|
||||
Context.filters.ApplyBlockedServicesList(setts, svcs)
|
||||
log.Debug("%s: services for client %q set: %s", pref, c.Name, svcs)
|
||||
}
|
||||
|
||||
setts.ClientName = c.Name
|
||||
|
||||
176
internal/home/dns_internal_test.go
Normal file
176
internal/home/dns_internal_test.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestApplyAdditionalFiltering(t *testing.T) {
|
||||
var err error
|
||||
|
||||
Context.filters, err = filtering.New(&filtering.Config{
|
||||
BlockedServices: &filtering.BlockedServices{
|
||||
Schedule: schedule.EmptyWeekly(),
|
||||
},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
Context.clients.idIndex = map[string]*Client{
|
||||
"default": {
|
||||
UseOwnSettings: false,
|
||||
safeSearchConf: filtering.SafeSearchConfig{Enabled: false},
|
||||
FilteringEnabled: false,
|
||||
SafeBrowsingEnabled: false,
|
||||
ParentalEnabled: false,
|
||||
},
|
||||
"custom_filtering": {
|
||||
UseOwnSettings: true,
|
||||
safeSearchConf: filtering.SafeSearchConfig{Enabled: true},
|
||||
FilteringEnabled: true,
|
||||
SafeBrowsingEnabled: true,
|
||||
ParentalEnabled: true,
|
||||
},
|
||||
"partial_custom_filtering": {
|
||||
UseOwnSettings: true,
|
||||
safeSearchConf: filtering.SafeSearchConfig{Enabled: true},
|
||||
FilteringEnabled: true,
|
||||
SafeBrowsingEnabled: false,
|
||||
ParentalEnabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
id string
|
||||
FilteringEnabled assert.BoolAssertionFunc
|
||||
SafeSearchEnabled assert.BoolAssertionFunc
|
||||
SafeBrowsingEnabled assert.BoolAssertionFunc
|
||||
ParentalEnabled assert.BoolAssertionFunc
|
||||
}{{
|
||||
name: "global_settings",
|
||||
id: "default",
|
||||
FilteringEnabled: assert.False,
|
||||
SafeSearchEnabled: assert.False,
|
||||
SafeBrowsingEnabled: assert.False,
|
||||
ParentalEnabled: assert.False,
|
||||
}, {
|
||||
name: "custom_settings",
|
||||
id: "custom_filtering",
|
||||
FilteringEnabled: assert.True,
|
||||
SafeSearchEnabled: assert.True,
|
||||
SafeBrowsingEnabled: assert.True,
|
||||
ParentalEnabled: assert.True,
|
||||
}, {
|
||||
name: "partial",
|
||||
id: "partial_custom_filtering",
|
||||
FilteringEnabled: assert.True,
|
||||
SafeSearchEnabled: assert.True,
|
||||
SafeBrowsingEnabled: assert.False,
|
||||
ParentalEnabled: assert.False,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
setts := &filtering.Settings{}
|
||||
|
||||
applyAdditionalFiltering(net.IP{1, 2, 3, 4}, tc.id, setts)
|
||||
tc.FilteringEnabled(t, setts.FilteringEnabled)
|
||||
tc.SafeSearchEnabled(t, setts.SafeSearchEnabled)
|
||||
tc.SafeBrowsingEnabled(t, setts.SafeBrowsingEnabled)
|
||||
tc.ParentalEnabled(t, setts.ParentalEnabled)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAdditionalFiltering_blockedServices(t *testing.T) {
|
||||
filtering.InitModule()
|
||||
|
||||
var (
|
||||
globalBlockedServices = []string{"ok"}
|
||||
clientBlockedServices = []string{"ok", "mail_ru", "vk"}
|
||||
invalidBlockedServices = []string{"invalid"}
|
||||
|
||||
err error
|
||||
)
|
||||
|
||||
Context.filters, err = filtering.New(&filtering.Config{
|
||||
BlockedServices: &filtering.BlockedServices{
|
||||
Schedule: schedule.EmptyWeekly(),
|
||||
IDs: globalBlockedServices,
|
||||
},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
Context.clients.idIndex = map[string]*Client{
|
||||
"default": {
|
||||
UseOwnBlockedServices: false,
|
||||
},
|
||||
"no_services": {
|
||||
BlockedServices: &filtering.BlockedServices{
|
||||
Schedule: schedule.EmptyWeekly(),
|
||||
},
|
||||
UseOwnBlockedServices: true,
|
||||
},
|
||||
"services": {
|
||||
BlockedServices: &filtering.BlockedServices{
|
||||
Schedule: schedule.EmptyWeekly(),
|
||||
IDs: clientBlockedServices,
|
||||
},
|
||||
UseOwnBlockedServices: true,
|
||||
},
|
||||
"invalid_services": {
|
||||
BlockedServices: &filtering.BlockedServices{
|
||||
Schedule: schedule.EmptyWeekly(),
|
||||
IDs: invalidBlockedServices,
|
||||
},
|
||||
UseOwnBlockedServices: true,
|
||||
},
|
||||
"allow_all": {
|
||||
BlockedServices: &filtering.BlockedServices{
|
||||
Schedule: schedule.FullWeekly(),
|
||||
IDs: clientBlockedServices,
|
||||
},
|
||||
UseOwnBlockedServices: true,
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
id string
|
||||
wantLen int
|
||||
}{{
|
||||
name: "global_settings",
|
||||
id: "default",
|
||||
wantLen: len(globalBlockedServices),
|
||||
}, {
|
||||
name: "custom_settings",
|
||||
id: "no_services",
|
||||
wantLen: 0,
|
||||
}, {
|
||||
name: "custom_settings_block",
|
||||
id: "services",
|
||||
wantLen: len(clientBlockedServices),
|
||||
}, {
|
||||
name: "custom_settings_invalid",
|
||||
id: "invalid_services",
|
||||
wantLen: 0,
|
||||
}, {
|
||||
name: "custom_settings_inactive_schedule",
|
||||
id: "allow_all",
|
||||
wantLen: 0,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
setts := &filtering.Settings{}
|
||||
|
||||
applyAdditionalFiltering(net.IP{1, 2, 3, 4}, tc.id, setts)
|
||||
require.Len(t, setts.ServicesRules, tc.wantLen)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -355,21 +355,43 @@ func initContextClients() (err error) {
|
||||
arpdb = aghnet.NewARPDB()
|
||||
}
|
||||
|
||||
Context.clients.Init(
|
||||
err = Context.clients.Init(
|
||||
config.Clients.Persistent,
|
||||
Context.dhcpServer,
|
||||
Context.etcHosts,
|
||||
arpdb,
|
||||
config.DNS.DnsfilterConf,
|
||||
)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupBindOpts overrides bind host/port from the opts.
|
||||
func setupBindOpts(opts options) (err error) {
|
||||
bindAddr := opts.bindAddr
|
||||
if bindAddr != (netip.AddrPort{}) {
|
||||
config.HTTPConfig.Address = bindAddr
|
||||
|
||||
if config.HTTPConfig.Address.Port() != 0 {
|
||||
err = checkPorts()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.bindPort != 0 {
|
||||
config.BindPort = opts.bindPort
|
||||
config.HTTPConfig.Address = netip.AddrPortFrom(
|
||||
config.HTTPConfig.Address.Addr(),
|
||||
uint16(opts.bindPort),
|
||||
)
|
||||
|
||||
err = checkPorts()
|
||||
if err != nil {
|
||||
@@ -379,7 +401,10 @@ func setupBindOpts(opts options) (err error) {
|
||||
}
|
||||
|
||||
if opts.bindHost.IsValid() {
|
||||
config.BindHost = opts.bindHost
|
||||
config.HTTPConfig.Address = netip.AddrPortFrom(
|
||||
opts.bindHost,
|
||||
config.HTTPConfig.Address.Port(),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -463,7 +488,7 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) {
|
||||
// checkPorts is a helper for ports validation in config.
|
||||
func checkPorts() (err error) {
|
||||
tcpPorts := aghalg.UniqChecker[tcpPort]{}
|
||||
addPorts(tcpPorts, tcpPort(config.BindPort))
|
||||
addPorts(tcpPorts, tcpPort(config.HTTPConfig.Address.Port()))
|
||||
|
||||
udpPorts := aghalg.UniqChecker[udpPort]{}
|
||||
addPorts(udpPorts, udpPort(config.DNS.Port))
|
||||
@@ -503,8 +528,8 @@ func initWeb(opts options, clientBuildFS fs.FS) (web *webAPI, err error) {
|
||||
|
||||
webConf := webConfig{
|
||||
firstRun: Context.firstRun,
|
||||
BindHost: config.BindHost,
|
||||
BindPort: config.BindPort,
|
||||
BindHost: config.HTTPConfig.Address.Addr(),
|
||||
BindPort: int(config.HTTPConfig.Address.Port()),
|
||||
|
||||
ReadTimeout: readTimeout,
|
||||
ReadHeaderTimeout: readHdrTimeout,
|
||||
@@ -640,8 +665,8 @@ func initUsers() (auth *Auth, err error) {
|
||||
log.Info("authratelimiter is disabled")
|
||||
}
|
||||
|
||||
sessionTTL := config.WebSessionTTLHours * 60 * 60
|
||||
auth = InitAuth(sessFilename, config.Users, sessionTTL, rateLimiter)
|
||||
sessionTTL := config.HTTPConfig.SessionTTL.Seconds()
|
||||
auth = InitAuth(sessFilename, config.Users, uint32(sessionTTL), rateLimiter)
|
||||
if auth == nil {
|
||||
return nil, errors.Error("initializing auth module failed")
|
||||
}
|
||||
@@ -919,7 +944,7 @@ func printHTTPAddresses(proto string) {
|
||||
Context.tls.WriteDiskConfig(&tlsConf)
|
||||
}
|
||||
|
||||
port := config.BindPort
|
||||
port := int(config.HTTPConfig.Address.Port())
|
||||
if proto == aghhttp.SchemeHTTPS {
|
||||
port = tlsConf.PortHTTPS
|
||||
}
|
||||
@@ -931,9 +956,9 @@ func printHTTPAddresses(proto string) {
|
||||
return
|
||||
}
|
||||
|
||||
bindhost := config.BindHost
|
||||
if !bindhost.IsUnspecified() {
|
||||
printWebAddrs(proto, bindhost.String(), port)
|
||||
bindHost := config.HTTPConfig.Address.Addr()
|
||||
if !bindHost.IsUnspecified() {
|
||||
printWebAddrs(proto, bindHost.String(), port)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -944,14 +969,14 @@ func printHTTPAddresses(proto string) {
|
||||
// That's weird, but we'll ignore it.
|
||||
//
|
||||
// TODO(e.burkov): Find out when it happens.
|
||||
printWebAddrs(proto, bindhost.String(), port)
|
||||
printWebAddrs(proto, bindHost.String(), port)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
for _, addr := range iface.Addresses {
|
||||
printWebAddrs(proto, addr.String(), config.BindPort)
|
||||
printWebAddrs(proto, addr.String(), port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,18 @@ type options struct {
|
||||
serviceControlAction string
|
||||
|
||||
// bindHost is the address on which to serve the HTTP UI.
|
||||
//
|
||||
// Deprecated: Use bindAddr.
|
||||
bindHost netip.Addr
|
||||
|
||||
// bindPort is the port on which to serve the HTTP UI.
|
||||
//
|
||||
// Deprecated: Use bindAddr.
|
||||
bindPort int
|
||||
|
||||
// bindAddr is the address to serve the web UI on.
|
||||
bindAddr netip.AddrPort
|
||||
|
||||
// checkConfig is true if the current invocation is only required to check
|
||||
// the configuration file and exit.
|
||||
checkConfig bool
|
||||
@@ -147,9 +154,10 @@ var cmdLineOpts = []cmdLineOpt{{
|
||||
|
||||
return o.bindHost.String(), true
|
||||
},
|
||||
description: "Host address to bind HTTP server on.",
|
||||
longName: "host",
|
||||
shortName: "h",
|
||||
description: "Deprecated. Host address to bind HTTP server on. Use --web-addr. " +
|
||||
"The short -h will work as --help in the future.",
|
||||
longName: "host",
|
||||
shortName: "h",
|
||||
}, {
|
||||
updateWithValue: func(o options, v string) (options, error) {
|
||||
var err error
|
||||
@@ -174,9 +182,23 @@ var cmdLineOpts = []cmdLineOpt{{
|
||||
|
||||
return strconv.Itoa(o.bindPort), true
|
||||
},
|
||||
description: "Port to serve HTTP pages on.",
|
||||
description: "Deprecated. Port to serve HTTP pages on. Use --web-addr.",
|
||||
longName: "port",
|
||||
shortName: "p",
|
||||
}, {
|
||||
updateWithValue: func(o options, v string) (oo options, err error) {
|
||||
o.bindAddr, err = netip.ParseAddrPort(v)
|
||||
|
||||
return o, err
|
||||
},
|
||||
updateNoValue: nil,
|
||||
effect: nil,
|
||||
serialize: func(o options) (val string, ok bool) {
|
||||
return o.bindAddr.String(), o.bindAddr.IsValid()
|
||||
},
|
||||
description: "Address to serve the web UI on, in the host:port format.",
|
||||
longName: "web-addr",
|
||||
shortName: "",
|
||||
}, {
|
||||
updateWithValue: func(o options, v string) (options, error) {
|
||||
o.serviceControlAction = v
|
||||
|
||||
@@ -82,6 +82,23 @@ func TestParseBindPort(t *testing.T) {
|
||||
testParseErr(t, "port too high", "-p", "18446744073709551617") // 2^64 + 1
|
||||
}
|
||||
|
||||
func TestParseBindAddr(t *testing.T) {
|
||||
wantAddrPort := netip.MustParseAddrPort("1.2.3.4:8089")
|
||||
|
||||
assert.Zero(t, testParseOK(t).bindAddr, "empty is not web-addr")
|
||||
|
||||
assert.Equal(t, wantAddrPort, testParseOK(t, "--web-addr", "1.2.3.4:8089").bindAddr)
|
||||
assert.Equal(t, netip.MustParseAddrPort("1.2.3.4:0"), testParseOK(t, "--web-addr", "1.2.3.4:0").bindAddr)
|
||||
testParseParamMissing(t, "-web-addr")
|
||||
|
||||
testParseErr(t, "not an int", "--web-addr", "1.2.3.4:x")
|
||||
testParseErr(t, "hex not supported", "--web-addr", "1.2.3.4:0x100")
|
||||
testParseErr(t, "port negative", "--web-addr", "1.2.3.4:-1")
|
||||
testParseErr(t, "port too high", "--web-addr", "1.2.3.4:65536")
|
||||
testParseErr(t, "port too high", "--web-addr", "1.2.3.4:4294967297") // 2^32 + 1
|
||||
testParseErr(t, "port too high", "--web-addr", "1.2.3.4:18446744073709551617") // 2^64 + 1
|
||||
}
|
||||
|
||||
func TestParseLogfile(t *testing.T) {
|
||||
assert.Equal(t, "", testParseOK(t).logFile, "empty is no log file")
|
||||
assert.Equal(t, "path", testParseOK(t, "-l", "path").logFile, "-l is log file")
|
||||
@@ -162,6 +179,10 @@ func TestOptsToArgs(t *testing.T) {
|
||||
name: "bind_port",
|
||||
args: []string{"-p", "666"},
|
||||
opts: options{bindPort: 666},
|
||||
}, {
|
||||
name: "web-addr",
|
||||
args: []string{"--web-addr", "1.2.3.4:8080"},
|
||||
opts: options{bindAddr: netip.MustParseAddrPort("1.2.3.4:8080")},
|
||||
}, {
|
||||
name: "log_file",
|
||||
args: []string{"-l", "path"},
|
||||
|
||||
@@ -228,12 +228,7 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
w.Reset()
|
||||
|
||||
cc := &clientsContainer{
|
||||
list: map[string]*Client{},
|
||||
idIndex: map[string]*Client{},
|
||||
ipToRC: map[netip.Addr]*RuntimeClient{},
|
||||
allTags: stringutil.NewSet(),
|
||||
}
|
||||
cc := newClientsContainer(t)
|
||||
ch := make(chan netip.Addr)
|
||||
rdns := &RDNS{
|
||||
exchanger: &rDNSExchanger{
|
||||
|
||||
@@ -320,7 +320,7 @@ func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if setts.Enabled {
|
||||
err = validatePorts(
|
||||
tcpPort(config.BindPort),
|
||||
tcpPort(config.HTTPConfig.Address.Port()),
|
||||
tcpPort(setts.PortHTTPS),
|
||||
tcpPort(setts.PortDNSOverTLS),
|
||||
tcpPort(setts.PortDNSCrypt),
|
||||
@@ -407,7 +407,7 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
if req.Enabled {
|
||||
err = validatePorts(
|
||||
tcpPort(config.BindPort),
|
||||
tcpPort(config.HTTPConfig.Address.Port()),
|
||||
tcpPort(req.PortHTTPS),
|
||||
tcpPort(req.PortDNSOverTLS),
|
||||
tcpPort(req.PortDNSCrypt),
|
||||
|
||||
@@ -3,6 +3,7 @@ package home
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
@@ -22,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
// currentSchemaVersion is the current schema version.
|
||||
const currentSchemaVersion = 21
|
||||
const currentSchemaVersion = 23
|
||||
|
||||
// These aliases are provided for convenience.
|
||||
type (
|
||||
@@ -95,6 +96,8 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
|
||||
upgradeSchema18to19,
|
||||
upgradeSchema19to20,
|
||||
upgradeSchema20to21,
|
||||
upgradeSchema21to22,
|
||||
upgradeSchema22to23,
|
||||
}
|
||||
|
||||
n := 0
|
||||
@@ -1179,6 +1182,149 @@ func upgradeSchema20to21(diskConf yobj) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeSchema21to22 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
// 'persistent':
|
||||
// - 'name': 'client_name'
|
||||
// 'blocked_services':
|
||||
// - 'svc_name'
|
||||
//
|
||||
// # AFTER:
|
||||
// 'persistent':
|
||||
// - 'name': 'client_name'
|
||||
// 'blocked_services':
|
||||
// 'ids':
|
||||
// - 'svc_name'
|
||||
// 'schedule':
|
||||
// 'time_zone': 'Local'
|
||||
func upgradeSchema21to22(diskConf yobj) (err error) {
|
||||
log.Println("Upgrade yaml: 21 to 22")
|
||||
diskConf["schema_version"] = 22
|
||||
|
||||
const field = "blocked_services"
|
||||
|
||||
clientsVal, ok := diskConf["clients"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
clients, ok := clientsVal.(yobj)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of clients: %T", clientsVal)
|
||||
}
|
||||
|
||||
persistentVal, ok := clients["persistent"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
persistent, ok := persistentVal.([]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of persistent clients: %T", persistentVal)
|
||||
}
|
||||
|
||||
for i, val := range persistent {
|
||||
var c yobj
|
||||
c, ok = val.(yobj)
|
||||
if !ok {
|
||||
return fmt.Errorf("persistent client at index %d: unexpected type %T", i, val)
|
||||
}
|
||||
|
||||
var blockedVal any
|
||||
blockedVal, ok = c[field]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var services yarr
|
||||
services, ok = blockedVal.(yarr)
|
||||
if !ok {
|
||||
return fmt.Errorf(
|
||||
"persistent client at index %d: unexpected type of blocked services: %T",
|
||||
i,
|
||||
blockedVal,
|
||||
)
|
||||
}
|
||||
|
||||
c[field] = yobj{
|
||||
"ids": services,
|
||||
"schedule": yobj{
|
||||
"time_zone": "Local",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeSchema22to23 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
// 'bind_host': '1.2.3.4'
|
||||
// 'bind_port': 8080
|
||||
// 'web_session_ttl': 720
|
||||
//
|
||||
// # AFTER:
|
||||
// 'http':
|
||||
// 'address': '1.2.3.4:8080'
|
||||
// 'session_ttl': '720h'
|
||||
func upgradeSchema22to23(diskConf yobj) (err error) {
|
||||
log.Printf("Upgrade yaml: 22 to 23")
|
||||
diskConf["schema_version"] = 23
|
||||
|
||||
bindHostVal, ok := diskConf["bind_host"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
bindHost, ok := bindHostVal.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of bind_host: %T", bindHostVal)
|
||||
}
|
||||
|
||||
bindHostAddr, err := netip.ParseAddr(bindHost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid bind_host value: %s", bindHost)
|
||||
}
|
||||
|
||||
bindPortVal, ok := diskConf["bind_port"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
bindPort, ok := bindPortVal.(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of bind_port: %T", bindPortVal)
|
||||
}
|
||||
|
||||
sessionTTLVal, ok := diskConf["web_session_ttl"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
sessionTTL, ok := sessionTTLVal.(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of web_session_ttl: %T", sessionTTLVal)
|
||||
}
|
||||
|
||||
addr := netip.AddrPortFrom(bindHostAddr, uint16(bindPort))
|
||||
if !addr.IsValid() {
|
||||
return fmt.Errorf("invalid address: %s", addr)
|
||||
}
|
||||
|
||||
diskConf["http"] = yobj{
|
||||
"address": addr.String(),
|
||||
"session_ttl": timeutil.Duration{Duration: time.Duration(sessionTTL) * time.Hour}.String(),
|
||||
}
|
||||
|
||||
delete(diskConf, "bind_host")
|
||||
delete(diskConf, "bind_port")
|
||||
delete(diskConf, "web_session_ttl")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Replace with log.Output when we port it to our logging
|
||||
// package.
|
||||
func funcName() string {
|
||||
|
||||
@@ -1183,3 +1183,126 @@ func TestUpgradeSchema20to21(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgradeSchema21to22(t *testing.T) {
|
||||
const newSchemaVer = 22
|
||||
|
||||
testCases := []struct {
|
||||
in yobj
|
||||
want yobj
|
||||
name string
|
||||
}{{
|
||||
in: yobj{
|
||||
"clients": yobj{},
|
||||
},
|
||||
want: yobj{
|
||||
"clients": yobj{},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
name: "nothing",
|
||||
}, {
|
||||
in: yobj{
|
||||
"clients": yobj{
|
||||
"persistent": []any{yobj{"name": "localhost", "blocked_services": yarr{}}},
|
||||
},
|
||||
},
|
||||
want: yobj{
|
||||
"clients": yobj{
|
||||
"persistent": []any{yobj{
|
||||
"name": "localhost",
|
||||
"blocked_services": yobj{
|
||||
"ids": yarr{},
|
||||
"schedule": yobj{
|
||||
"time_zone": "Local",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
name: "no_services",
|
||||
}, {
|
||||
in: yobj{
|
||||
"clients": yobj{
|
||||
"persistent": []any{yobj{"name": "localhost", "blocked_services": yarr{"ok"}}},
|
||||
},
|
||||
},
|
||||
want: yobj{
|
||||
"clients": yobj{
|
||||
"persistent": []any{yobj{
|
||||
"name": "localhost",
|
||||
"blocked_services": yobj{
|
||||
"ids": yarr{"ok"},
|
||||
"schedule": yobj{
|
||||
"time_zone": "Local",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
name: "services",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := upgradeSchema21to22(tc.in)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.want, tc.in)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgradeSchema22to23(t *testing.T) {
|
||||
const newSchemaVer = 23
|
||||
|
||||
testCases := []struct {
|
||||
in yobj
|
||||
want yobj
|
||||
name string
|
||||
}{{
|
||||
name: "empty",
|
||||
in: yobj{},
|
||||
want: yobj{
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
}, {
|
||||
name: "ok",
|
||||
in: yobj{
|
||||
"bind_host": "1.2.3.4",
|
||||
"bind_port": 8081,
|
||||
"web_session_ttl": 720,
|
||||
},
|
||||
want: yobj{
|
||||
"http": yobj{
|
||||
"address": "1.2.3.4:8081",
|
||||
"session_ttl": "720h",
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
}, {
|
||||
name: "v6_address",
|
||||
in: yobj{
|
||||
"bind_host": "2001:db8::1",
|
||||
"bind_port": 8081,
|
||||
"web_session_ttl": 720,
|
||||
},
|
||||
want: yobj{
|
||||
"http": yobj{
|
||||
"address": "[2001:db8::1]:8081",
|
||||
"session_ttl": "720h",
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := upgradeSchema22to23(tc.in)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.want, tc.in)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,9 @@ func webCheckPortAvailable(port int) (ok bool) {
|
||||
return true
|
||||
}
|
||||
|
||||
return aghnet.CheckPort("tcp", netip.AddrPortFrom(config.BindHost, uint16(port))) == nil
|
||||
addrPort := netip.AddrPortFrom(config.HTTPConfig.Address.Addr(), uint16(port))
|
||||
|
||||
return aghnet.CheckPort("tcp", addrPort) == nil
|
||||
}
|
||||
|
||||
// tlsConfigChanged updates the TLS configuration and restarts the HTTPS server
|
||||
|
||||
27
internal/next/AdGuardHome.example.yaml
Normal file
27
internal/next/AdGuardHome.example.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# This is a file showing example configuration for AdGuard Home.
|
||||
#
|
||||
# TODO(a.garipov): Move to the top level once the rewrite is over.
|
||||
|
||||
dns:
|
||||
addresses:
|
||||
- '0.0.0.0:53'
|
||||
bootstrap_dns:
|
||||
- '9.9.9.10'
|
||||
- '149.112.112.10'
|
||||
- '2620:fe::10'
|
||||
- '2620:fe::fe:10'
|
||||
upstream_dns:
|
||||
- '8.8.8.8'
|
||||
dns64_prefixes:
|
||||
- '1234::/64'
|
||||
upstream_timeout: 1s
|
||||
bootstrap_prefer_ipv6: true
|
||||
use_dns64: true
|
||||
http:
|
||||
addresses:
|
||||
- '0.0.0.0:3000'
|
||||
secure_addresses: []
|
||||
timeout: 5s
|
||||
force_https: true
|
||||
log:
|
||||
verbose: true
|
||||
@@ -24,6 +24,7 @@ enough.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `--check-config` breaking the configuration file ([#4067]).
|
||||
- Inconsistent application of `--work-dir/-w` ([#2598], [#2902]).
|
||||
- The order of `-v/--verbose` and `--version` being significant ([#2893]).
|
||||
|
||||
@@ -36,4 +37,5 @@ enough.
|
||||
[#2598]: https://github.com/AdguardTeam/AdGuardHome/issues/2598
|
||||
[#2893]: https://github.com/AdguardTeam/AdGuardHome/issues/2893
|
||||
[#2902]: https://github.com/AdguardTeam/AdGuardHome/issues/2902
|
||||
[#4067]: https://github.com/AdguardTeam/AdGuardHome/issues/4067
|
||||
[#5676]: https://github.com/AdguardTeam/AdGuardHome/issues/5676
|
||||
|
||||
@@ -16,11 +16,9 @@ import (
|
||||
)
|
||||
|
||||
// Main is the entry point of AdGuard Home.
|
||||
func Main(frontend fs.FS) {
|
||||
func Main(embeddedFrontend fs.FS) {
|
||||
start := time.Now()
|
||||
|
||||
// Initial Configuration
|
||||
|
||||
cmdName := os.Args[0]
|
||||
opts, err := parseOptions(cmdName, os.Args[1:])
|
||||
exitCode, needExit := processOptions(opts, cmdName, err)
|
||||
@@ -39,9 +37,17 @@ func Main(frontend fs.FS) {
|
||||
check(err)
|
||||
}
|
||||
|
||||
// Web Service
|
||||
frontend, err := frontendFromOpts(opts, embeddedFrontend)
|
||||
check(err)
|
||||
|
||||
confMgr, err := configmgr.New(opts.confFile, frontend, start)
|
||||
confMgrConf := &configmgr.Config{
|
||||
Frontend: frontend,
|
||||
WebAddr: opts.webAddr,
|
||||
Start: start,
|
||||
FileName: opts.confFile,
|
||||
}
|
||||
|
||||
confMgr, err := newConfigMgr(confMgrConf)
|
||||
check(err)
|
||||
|
||||
web := confMgr.Web()
|
||||
@@ -53,9 +59,8 @@ func Main(frontend fs.FS) {
|
||||
check(err)
|
||||
|
||||
sigHdlr := newSignalHandler(
|
||||
opts.confFile,
|
||||
frontend,
|
||||
start,
|
||||
confMgrConf,
|
||||
opts.pidFile,
|
||||
web,
|
||||
dns,
|
||||
)
|
||||
@@ -73,6 +78,15 @@ func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), defaultTimeout)
|
||||
}
|
||||
|
||||
// newConfigMgr returns a new configuration manager using defaultTimeout as the
|
||||
// context timeout.
|
||||
func newConfigMgr(c *configmgr.Config) (m *configmgr.Manager, err error) {
|
||||
ctx, cancel := ctxWithDefaultTimeout()
|
||||
defer cancel()
|
||||
|
||||
return configmgr.New(ctx, c)
|
||||
}
|
||||
|
||||
// check is a simple error-checking helper. It must only be used within Main.
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
@@ -25,8 +29,6 @@ type options struct {
|
||||
logFile string
|
||||
|
||||
// pidFile is the path to the file where to store the PID.
|
||||
//
|
||||
// TODO(a.garipov): Use.
|
||||
pidFile string
|
||||
|
||||
// serviceAction is the service control action to perform:
|
||||
@@ -49,15 +51,12 @@ type options struct {
|
||||
// other configuration is read, so all relative paths are relative to it.
|
||||
workDir string
|
||||
|
||||
// webAddrs contains the addresses on which to serve the web UI.
|
||||
//
|
||||
// TODO(a.garipov): Use.
|
||||
webAddrs []netip.AddrPort
|
||||
// webAddr contains the address on which to serve the web UI.
|
||||
webAddr netip.AddrPort
|
||||
|
||||
// checkConfig, if true, instructs AdGuard Home to check the configuration
|
||||
// file and exit with a corresponding exit code.
|
||||
//
|
||||
// TODO(a.garipov): Use.
|
||||
// file, optionally print an error message to stdout, and exit with a
|
||||
// corresponding exit code.
|
||||
checkConfig bool
|
||||
|
||||
// disableUpdate, if true, prevents AdGuard Home from automatically checking
|
||||
@@ -103,7 +102,7 @@ const (
|
||||
pidFileIdx
|
||||
serviceActionIdx
|
||||
workDirIdx
|
||||
webAddrsIdx
|
||||
webAddrIdx
|
||||
checkConfigIdx
|
||||
disableUpdateIdx
|
||||
glinetModeIdx
|
||||
@@ -129,8 +128,8 @@ type commandLineOption struct {
|
||||
// AdGuard Home.
|
||||
var commandLineOptions = []*commandLineOption{
|
||||
confFileIdx: {
|
||||
// TODO(a.garipov): Remove the ".1" when the new code is ready.
|
||||
defaultValue: "AdGuardHome.1.yaml",
|
||||
// TODO(a.garipov): Remove the directory when the new code is ready.
|
||||
defaultValue: "internal/next/AdGuardHome.yaml",
|
||||
description: "Path to the config file.",
|
||||
long: "config",
|
||||
short: "c",
|
||||
@@ -172,18 +171,17 @@ var commandLineOptions = []*commandLineOption{
|
||||
valueType: "path",
|
||||
},
|
||||
|
||||
webAddrsIdx: {
|
||||
defaultValue: []netip.AddrPort(nil),
|
||||
description: `Address(es) to serve the web UI on, in the host:port format. ` +
|
||||
`Can be used multiple times.`,
|
||||
long: "web-addr",
|
||||
short: "",
|
||||
valueType: "host:port",
|
||||
webAddrIdx: {
|
||||
defaultValue: netip.AddrPort{},
|
||||
description: `Address to serve the web UI on, in the host:port format.`,
|
||||
long: "web-addr",
|
||||
short: "",
|
||||
valueType: "host:port",
|
||||
},
|
||||
|
||||
checkConfigIdx: {
|
||||
defaultValue: false,
|
||||
description: "Check configuration and quit.",
|
||||
description: "Check configuration, print errors to stdout, and quit.",
|
||||
long: "check-config",
|
||||
short: "",
|
||||
valueType: "",
|
||||
@@ -258,7 +256,7 @@ func parseOptions(cmdName string, args []string) (opts *options, err error) {
|
||||
pidFileIdx: &opts.pidFile,
|
||||
serviceActionIdx: &opts.serviceAction,
|
||||
workDirIdx: &opts.workDir,
|
||||
webAddrsIdx: &opts.webAddrs,
|
||||
webAddrIdx: &opts.webAddr,
|
||||
checkConfigIdx: &opts.checkConfig,
|
||||
disableUpdateIdx: &opts.disableUpdate,
|
||||
glinetModeIdx: &opts.glinetMode,
|
||||
@@ -291,23 +289,16 @@ func addOption(flags *flag.FlagSet, fieldPtr any, o *commandLineOption) {
|
||||
if o.short != "" {
|
||||
flags.StringVar(fieldPtr, o.short, o.defaultValue.(string), o.description)
|
||||
}
|
||||
case *[]netip.AddrPort:
|
||||
flags.Func(o.long, o.description, func(s string) (err error) {
|
||||
addr, err := netip.ParseAddrPort(s)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
*fieldPtr = append(*fieldPtr, addr)
|
||||
|
||||
return nil
|
||||
})
|
||||
case *bool:
|
||||
flags.BoolVar(fieldPtr, o.long, o.defaultValue.(bool), o.description)
|
||||
if o.short != "" {
|
||||
flags.BoolVar(fieldPtr, o.short, o.defaultValue.(bool), o.description)
|
||||
}
|
||||
case encoding.TextUnmarshaler:
|
||||
flags.TextVar(fieldPtr, o.long, o.defaultValue.(encoding.TextMarshaler), o.description)
|
||||
if o.short != "" {
|
||||
flags.TextVar(fieldPtr, o.short, o.defaultValue.(encoding.TextMarshaler), o.description)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected field pointer type %T", fieldPtr))
|
||||
}
|
||||
@@ -380,13 +371,13 @@ func processOptions(
|
||||
) (exitCode int, needExit bool) {
|
||||
if parseErr != nil {
|
||||
// Assume that usage has already been printed.
|
||||
return 2, true
|
||||
return statusArgumentError, true
|
||||
}
|
||||
|
||||
if opts.help {
|
||||
usage(cmdName, os.Stdout)
|
||||
|
||||
return 0, true
|
||||
return statusSuccess, true
|
||||
}
|
||||
|
||||
if opts.version {
|
||||
@@ -396,8 +387,32 @@ func processOptions(
|
||||
fmt.Printf("AdGuard Home %s\n", version.Version())
|
||||
}
|
||||
|
||||
return 0, true
|
||||
return statusSuccess, true
|
||||
}
|
||||
|
||||
if opts.checkConfig {
|
||||
err := configmgr.Validate(opts.confFile)
|
||||
if err != nil {
|
||||
_, _ = io.WriteString(os.Stdout, err.Error()+"\n")
|
||||
|
||||
return statusError, true
|
||||
}
|
||||
|
||||
return statusSuccess, true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// frontendFromOpts returns the frontend to use based on the options.
|
||||
func frontendFromOpts(opts *options, embeddedFrontend fs.FS) (frontend fs.FS, err error) {
|
||||
const frontendSubdir = "build/static"
|
||||
|
||||
if opts.localFrontend {
|
||||
log.Info("warning: using local frontend files")
|
||||
|
||||
return os.DirFS(frontendSubdir), nil
|
||||
}
|
||||
|
||||
return fs.Sub(embeddedFrontend, frontendSubdir)
|
||||
}
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"time"
|
||||
"strconv"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/google/renameio/maybe"
|
||||
)
|
||||
|
||||
// signalHandler processes incoming signals and shuts services down.
|
||||
type signalHandler struct {
|
||||
// confMgrConf contains the configuration parameters for the configuration
|
||||
// manager.
|
||||
confMgrConf *configmgr.Config
|
||||
|
||||
// signal is the channel to which OS signals are sent.
|
||||
signal chan os.Signal
|
||||
|
||||
// confFile is the path to the configuration file.
|
||||
confFile string
|
||||
|
||||
// frontend is the filesystem with the frontend and other statically
|
||||
// compiled files.
|
||||
frontend fs.FS
|
||||
|
||||
// start is the time at which AdGuard Home has been started.
|
||||
start time.Time
|
||||
// pidFile is the path to the file where to store the PID, if any.
|
||||
pidFile string
|
||||
|
||||
// services are the services that are shut down before application exiting.
|
||||
services []agh.Service
|
||||
@@ -34,6 +31,8 @@ type signalHandler struct {
|
||||
func (h *signalHandler) handle() {
|
||||
defer log.OnPanic("signalHandler.handle")
|
||||
|
||||
h.writePID()
|
||||
|
||||
for sig := range h.signal {
|
||||
log.Info("sighdlr: received signal %q", sig)
|
||||
|
||||
@@ -41,6 +40,8 @@ func (h *signalHandler) handle() {
|
||||
h.reconfigure()
|
||||
} else if aghos.IsShutdownSignal(sig) {
|
||||
status := h.shutdown()
|
||||
h.removePID()
|
||||
|
||||
log.Info("sighdlr: exiting with status %d", status)
|
||||
|
||||
os.Exit(status)
|
||||
@@ -63,7 +64,7 @@ func (h *signalHandler) reconfigure() {
|
||||
// reconfigured without the full shutdown, and the error handling is
|
||||
// currently not the best.
|
||||
|
||||
confMgr, err := configmgr.New(h.confFile, h.frontend, h.start)
|
||||
confMgr, err := newConfigMgr(h.confMgrConf)
|
||||
check(err)
|
||||
|
||||
web := confMgr.Web()
|
||||
@@ -84,8 +85,9 @@ func (h *signalHandler) reconfigure() {
|
||||
|
||||
// Exit status constants.
|
||||
const (
|
||||
statusSuccess = 0
|
||||
statusError = 1
|
||||
statusSuccess = 0
|
||||
statusError = 1
|
||||
statusArgumentError = 2
|
||||
)
|
||||
|
||||
// shutdown gracefully shuts down all services.
|
||||
@@ -109,17 +111,15 @@ func (h *signalHandler) shutdown() (status int) {
|
||||
|
||||
// newSignalHandler returns a new signalHandler that shuts down svcs.
|
||||
func newSignalHandler(
|
||||
confFile string,
|
||||
frontend fs.FS,
|
||||
start time.Time,
|
||||
confMgrConf *configmgr.Config,
|
||||
pidFile string,
|
||||
svcs ...agh.Service,
|
||||
) (h *signalHandler) {
|
||||
h = &signalHandler{
|
||||
signal: make(chan os.Signal, 1),
|
||||
confFile: confFile,
|
||||
frontend: frontend,
|
||||
start: start,
|
||||
services: svcs,
|
||||
confMgrConf: confMgrConf,
|
||||
signal: make(chan os.Signal, 1),
|
||||
pidFile: pidFile,
|
||||
services: svcs,
|
||||
}
|
||||
|
||||
aghos.NotifyShutdownSignal(h.signal)
|
||||
@@ -127,3 +127,41 @@ func newSignalHandler(
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// writePID writes the PID to the file, if needed. Any errors are reported to
|
||||
// log.
|
||||
func (h *signalHandler) writePID() {
|
||||
if h.pidFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Use 8, since most PIDs will fit.
|
||||
data := make([]byte, 0, 8)
|
||||
data = strconv.AppendInt(data, int64(os.Getpid()), 10)
|
||||
data = append(data, '\n')
|
||||
|
||||
err := maybe.WriteFile(h.pidFile, data, 0o644)
|
||||
if err != nil {
|
||||
log.Error("sighdlr: writing pidfile: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("sighdlr: wrote pid to %q", h.pidFile)
|
||||
}
|
||||
|
||||
// removePID removes the PID file, if any.
|
||||
func (h *signalHandler) removePID() {
|
||||
if h.pidFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
err := os.Remove(h.pidFile)
|
||||
if err != nil {
|
||||
log.Error("sighdlr: removing pidfile: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("sighdlr: removed pid at %q", h.pidFile)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user