Merge branch 'master' into websvc-confin-manager

This commit is contained in:
Ainar Garipov 2022-09-30 15:27:54 +03:00
commit b448a3b5dc
162 changed files with 2095 additions and 1755 deletions

4
.github/stale.yml vendored
View file

@ -13,6 +13,8 @@
- 'needs investigation' - 'needs investigation'
- 'recurrent' - 'recurrent'
- 'research' - 'research'
# Set to true to ignore issues in a milestone.
'exemptMilestones': true
# Label to use when marking an issue as stale. # Label to use when marking an issue as stale.
'staleLabel': 'wontfix' 'staleLabel': 'wontfix'
# Comment to post when marking an issue as stale. Set to `false` to disable. # Comment to post when marking an issue as stale. Set to `false` to disable.
@ -22,3 +24,5 @@
for your contributions. for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable. # Comment to post when closing a stale issue. Set to `false` to disable.
'closeComment': false 'closeComment': false
# Limit the number of actions per hour.
'limitPerRun': 1

View file

@ -1,7 +1,7 @@
'name': 'build' 'name': 'build'
'env': 'env':
'GO_VERSION': '1.18' 'GO_VERSION': '1.18.6'
'NODE_VERSION': '14' 'NODE_VERSION': '14'
'on': 'on':
@ -31,7 +31,7 @@
'with': 'with':
'fetch-depth': 0 'fetch-depth': 0
- 'name': 'Set up Go' - 'name': 'Set up Go'
'uses': 'actions/setup-go@v2' 'uses': 'actions/setup-go@v3'
'with': 'with':
'go-version': '${{ env.GO_VERSION }}' 'go-version': '${{ env.GO_VERSION }}'
- 'name': 'Set up Node' - 'name': 'Set up Node'
@ -72,7 +72,7 @@
'with': 'with':
'fetch-depth': 0 'fetch-depth': 0
- 'name': 'Set up Go' - 'name': 'Set up Go'
'uses': 'actions/setup-go@v2' 'uses': 'actions/setup-go@v3'
'with': 'with':
'go-version': '${{ env.GO_VERSION }}' 'go-version': '${{ env.GO_VERSION }}'
- 'name': 'Set up Node' - 'name': 'Set up Node'
@ -112,7 +112,9 @@
# Use always() to signal to the runner that this job must run even if the # Use always() to signal to the runner that this job must run even if the
# previous ones failed. # previous ones failed.
'if': 'if':
${{ always() && ${{
always() &&
github.repository_owner == 'AdguardTeam' &&
( (
github.event_name == 'push' || github.event_name == 'push' ||
github.event.pull_request.head.repo.full_name == github.repository github.event.pull_request.head.repo.full_name == github.repository

View file

@ -1,7 +1,7 @@
'name': 'lint' 'name': 'lint'
'env': 'env':
'GO_VERSION': '1.18' 'GO_VERSION': '1.18.6'
'on': 'on':
'push': 'push':
@ -17,7 +17,7 @@
'steps': 'steps':
- 'uses': 'actions/checkout@v2' - 'uses': 'actions/checkout@v2'
- 'name': 'Set up Go' - 'name': 'Set up Go'
'uses': 'actions/setup-go@v2' 'uses': 'actions/setup-go@v3'
'with': 'with':
'go-version': '${{ env.GO_VERSION }}' 'go-version': '${{ env.GO_VERSION }}'
- 'name': 'run-lint' - 'name': 'run-lint'
@ -43,7 +43,9 @@
# Use always() to signal to the runner that this job must run even if the # Use always() to signal to the runner that this job must run even if the
# previous ones failed. # previous ones failed.
'if': 'if':
${{ always() && ${{
always() &&
github.repository_owner == 'AdguardTeam' &&
( (
github.event_name == 'push' || github.event_name == 'push' ||
github.event.pull_request.head.repo.full_name == github.repository github.event.pull_request.head.repo.full_name == github.repository

View file

@ -12,26 +12,119 @@ and this project adheres to
## [Unreleased] ## [Unreleased]
<!-- <!--
## [v0.108.0] - 2022-12-01 (APPROX.) ## [v0.108.0] - TBA (APPROX.)
--> -->
### Security ### Security
- Weaker cipher suites that use the CBC (cipher block chaining) mode of - As an additional CSRF protection measure, AdGuard Home now ensures that
operation have been disabled ([#2993]). requests that change its state but have no body (such as `POST
/control/stats_reset` requests) do not have a `Content-Type` header set on
them ([#4970]).
[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993 ### Fixed
- `only application/json is allowed` errors in various APIs ([#4970]).
[#4970]: https://github.com/AdguardTeam/AdGuardHome/issues/4970
<!-- <!--
## [v0.107.13] - 2022-10-05 (APPROX.) ## [v0.107.15] - 2022-10-26 (APPROX.)
See also the [v0.107.15 GitHub milestone][ms-v0.107.15].
[ms-v0.107.15]: https://github.com/AdguardTeam/AdGuardHome/milestone/51?closed=1
-->
## [v0.107.14] - 2022-09-29
See also the [v0.107.14 GitHub milestone][ms-v0.107.14].
### Security
A Cross-Site Request Forgery (CSRF) vulnerability has been discovered. The CVE
number is to be assigned. We thank Daniel Elkabes from Mend.io for reporting
this vulnerability to us.
#### `SameSite` Policy
The `SameSite` policy on the AdGuard Home session cookies is now set to `Lax`.
Which means that the only cross-site HTTP request for which the browser is
allowed to send the session cookie is navigating to the AdGuard Home domain.
**Users are strongly advised to log out, clear browser cache, and log in again
after updating.**
#### Removal Of Plain-Text APIs (BREAKING API CHANGE)
We have implemented several measures to prevent such vulnerabilities in the
future, but some of these measures break backwards compatibility for the sake of
better protection.
The following APIs, which previously accepted or returned `text/plain` data,
now accept or return data as JSON. All new formats for the request and response
bodies are documented in `openapi/openapi.yaml` and `openapi/CHANGELOG.md`.
- `GET /control/i18n/current_language`;
- `POST /control/dhcp/find_active_dhcp`;
- `POST /control/filtering/set_rules`;
- `POST /control/i18n/change_language`.
#### Stricter Content-Type Checks (BREAKING API CHANGE)
All JSON APIs that expect a body now check if the request actually has
`Content-Type` set to `application/json`.
#### Other Security Changes
- Weaker cipher suites that use the CBC (cipher block chaining) mode of
operation have been disabled ([#2993]).
### Added
- Support for plain (unencrypted) HTTP/2 ([#4930]). This is useful for AdGuard
Home installations behind a reverse proxy.
### Fixed
- Incorrect path template in DDR responses ([#4927]).
[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
[#4927]: https://github.com/AdguardTeam/AdGuardHome/issues/4927
[#4930]: https://github.com/AdguardTeam/AdGuardHome/issues/4930
[ms-v0.107.14]: https://github.com/AdguardTeam/AdGuardHome/milestone/50?closed=1
## [v0.107.13] - 2022-09-14
See also the [v0.107.13 GitHub milestone][ms-v0.107.13]. See also the [v0.107.13 GitHub milestone][ms-v0.107.13].
### Added
- The new optional `dns.ipset_file` property, which can be set in the
configuration file. It allows loading the `ipset` list from a file, just like
`dns.upstream_dns_file` does for upstream servers ([#4686]).
### Changed
- The minimum DHCP message size is reassigned back to BOOTP's constraint of 300
bytes ([#4904]).
### Fixed
- Panic when adding a static lease within the disabled DHCP server ([#4722]).
[#4686]: https://github.com/AdguardTeam/AdGuardHome/issues/4686
[#4722]: https://github.com/AdguardTeam/AdGuardHome/issues/4722
[#4904]: https://github.com/AdguardTeam/AdGuardHome/issues/4904
[ms-v0.107.13]: https://github.com/AdguardTeam/AdGuardHome/milestone/49?closed=1 [ms-v0.107.13]: https://github.com/AdguardTeam/AdGuardHome/milestone/49?closed=1
-->
@ -1203,11 +1296,13 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!-- <!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.13...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.15...HEAD
[v0.107.13]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.11...v0.107.13 [v0.107.15]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...v0.107.15
--> -->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.12...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...HEAD
[v0.107.14]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.13...v0.107.14
[v0.107.13]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.12...v0.107.13
[v0.107.12]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.11...v0.107.12 [v0.107.12]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.11...v0.107.12
[v0.107.11]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...v0.107.11 [v0.107.11]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.10...v0.107.11
[v0.107.10]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...v0.107.10 [v0.107.10]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...v0.107.10

18
SECURITY.md Normal file
View file

@ -0,0 +1,18 @@
# Security Policy
## Reporting a Vulnerability
Please send your vulnerability reports to <security@adguard.com>. To make sure
that your report reaches us, please:
1. Include the words “AdGuard Home” and “vulnerability” to the subject line as
well as a short description of the vulnerability. For example:
> AdGuard Home API vulnerability: possible XSS attack
2. Make sure that the message body contains a clear description of the
vulnerability.
If you have not received a reply to your email within 7 days, please make sure
to follow up with us again at <security@adguard.com>. Once again, make sure
that the word “vulnerability” is in the subject line.

View file

@ -70,7 +70,7 @@
"dhcp_warning": "Калі вы ўсё адно хочаце ўключыць DHCP-сервер, пераканайцеся, што ў сеціве больш няма актыўных DHCP-сервераў. Інакш гэта можа зламаць доступ у сеціва для падлучаных прылад!", "dhcp_warning": "Калі вы ўсё адно хочаце ўключыць DHCP-сервер, пераканайцеся, што ў сеціве больш няма актыўных DHCP-сервераў. Інакш гэта можа зламаць доступ у сеціва для падлучаных прылад!",
"dhcp_error": "AdGuard Home не можа вызначыць, ці ёсць у сетцы іншы актыўны DHCP-сервер", "dhcp_error": "AdGuard Home не можа вызначыць, ці ёсць у сетцы іншы актыўны DHCP-сервер",
"dhcp_static_ip_error": "Для таго, каб выкарыстоўваць DHCP-сервер, павінен быць усталяваны статычны IP-адрас. Мы не змаглі вызначыць, ці выкарыстоўвае гэты інтэрфейс сеціва статычны IP-адрас. Калі ласка, усталюйце яго ручна.", "dhcp_static_ip_error": "Для таго, каб выкарыстоўваць DHCP-сервер, павінен быць усталяваны статычны IP-адрас. Мы не змаглі вызначыць, ці выкарыстоўвае гэты інтэрфейс сеціва статычны IP-адрас. Калі ласка, усталюйце яго ручна.",
"dhcp_dynamic_ip_found": "Ваша сістэма выкарыстоўвае дынамічны IP-адрас для інтэрфейсу <0>{{interfaceName}}</0>. Каб выкарыстоўваць DHCP-сервер трэба ўсталяваць статычны IP-адрас. Ваш бягучы IP-адрас <0>{{ipAddress}}</0>. Мы аўтаматычна ўсталюем яго як статычны, калі вы націснеце кнопку Ўключыць DHCP.", "dhcp_dynamic_ip_found": "Ваша сістэма выкарыстоўвае дынамічны IP-адрас для інтэрфейсу <0>{{interfaceName}}</0>. Каб выкарыстоўваць DHCP-сервер трэба ўсталяваць статычны IP-адрас. Ваш бягучы IP-адрас <0>{{ipAddress}}</0>. Мы аўтаматычна ўсталюем яго як статычны, калі вы націснеце кнопку «Ўключыць DHCP».",
"dhcp_lease_added": "Статычная арэнда «{{key}}» паспяхова дададзена", "dhcp_lease_added": "Статычная арэнда «{{key}}» паспяхова дададзена",
"dhcp_lease_deleted": "Статычная арэнда «{{key}}» паспяхова выдалена", "dhcp_lease_deleted": "Статычная арэнда «{{key}}» паспяхова выдалена",
"dhcp_new_static_lease": "Новая статычная арэнда", "dhcp_new_static_lease": "Новая статычная арэнда",
@ -447,7 +447,7 @@
"access_disallowed_title": "Забароненыя кліенты", "access_disallowed_title": "Забароненыя кліенты",
"access_disallowed_desc": "Спіс CIDR, IP-адрасоў або <a>ClientID</a>. Калі ў гэтым спісе ёсць запісы, AdGuard Home выдаліць запыты ад гэтых кліентаў. Гэта поле ігнаруецца, калі ёсць запісы ў Дазволеныя кліенты.", "access_disallowed_desc": "Спіс CIDR, IP-адрасоў або <a>ClientID</a>. Калі ў гэтым спісе ёсць запісы, AdGuard Home выдаліць запыты ад гэтых кліентаў. Гэта поле ігнаруецца, калі ёсць запісы ў Дазволеныя кліенты.",
"access_blocked_title": "Заблакаваныя дамены", "access_blocked_title": "Заблакаваныя дамены",
"access_blocked_desc": "Не блытайце гэта з фільтрамі. AdGuard Home будзе ігнараваць DNS-запыты з гэтымі даменамі.", "access_blocked_desc": "Не блытаць з фільтрамі. AdGuard Home выдаляе запыты DNS, якія адпавядаюць гэтым даменам, і гэтыя запыты нават не з'яўляюцца ў журнале запытаў. Вы можаце ўказаць дакладныя даменныя імёны, падстаноўныя знакі або правілы фільтрацыі URL-адрасоў, напрыклад, «example.org», «*.example.org» ці «||example.org^» адпаведна.",
"access_settings_saved": "Налады доступу паспяхова захаваны", "access_settings_saved": "Налады доступу паспяхова захаваны",
"updates_checked": "Даступная новая версія AdGuard Home", "updates_checked": "Даступная новая версія AdGuard Home",
"updates_version_equal": "Версія AdGuard Home актуальная", "updates_version_equal": "Версія AdGuard Home актуальная",
@ -635,5 +635,6 @@
"parental_control": "Бацькоўскі кантроль", "parental_control": "Бацькоўскі кантроль",
"safe_browsing": "Бяспечны інтэрнэт", "safe_browsing": "Бяспечны інтэрнэт",
"served_from_cache": "{{value}} <i>(атрымана з кэша)</i>", "served_from_cache": "{{value}} <i>(атрымана з кэша)</i>",
"form_error_password_length": "Пароль павінен быць не менш за {{value}} сімвалаў" "form_error_password_length": "Пароль павінен быць не менш за {{value}} сімвалаў",
"anonymizer_notification": "<0>Заўвага:</0> Ананімізацыя IP уключана. Вы можаце адключыць яго ў <1>Агульных наладах</1> ."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Rodičovská ochrana", "parental_control": "Rodičovská ochrana",
"safe_browsing": "Bezpečné prohlížení", "safe_browsing": "Bezpečné prohlížení",
"served_from_cache": "{{value}} <i>(převzato z mezipaměti)</i>", "served_from_cache": "{{value}} <i>(převzato z mezipaměti)</i>",
"form_error_password_length": "Heslo musí být alespoň {{value}} znaků dlouhé" "form_error_password_length": "Heslo musí být alespoň {{value}} znaků dlouhé",
"anonymizer_notification": "<0>Poznámka:</0> Anonymizace IP je zapnuta. Můžete ji vypnout v <1>Obecných nastaveních</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Forældrekontrol", "parental_control": "Forældrekontrol",
"safe_browsing": "Sikker Browsing", "safe_browsing": "Sikker Browsing",
"served_from_cache": "{{value}} <i>(leveret fra cache)</i>", "served_from_cache": "{{value}} <i>(leveret fra cache)</i>",
"form_error_password_length": "Adgangskoden skal udgøre mindst {{value}} tegn." "form_error_password_length": "Adgangskoden skal udgøre mindst {{value}} tegn.",
"anonymizer_notification": "<0>Bemærk:</0> IP-anonymisering er aktiveret. Det kan deaktiveres via <1>Generelle indstillinger</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Kindersicherung", "parental_control": "Kindersicherung",
"safe_browsing": "Internetsicherheit", "safe_browsing": "Internetsicherheit",
"served_from_cache": "{{value}} <i>(aus dem Cache abgerufen)</i>", "served_from_cache": "{{value}} <i>(aus dem Cache abgerufen)</i>",
"form_error_password_length": "Das Passwort muss mindestens {{value}} Zeichen enthalten" "form_error_password_length": "Das Passwort muss mindestens {{value}} Zeichen enthalten",
"anonymizer_notification": "<0>Hinweis:</0> Die IP-Anonymisierung ist aktiviert. Sie können sie in den <1>Allgemeinen Einstellungen</1> deaktivieren."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Parental Control", "parental_control": "Parental Control",
"safe_browsing": "Safe Browsing", "safe_browsing": "Safe Browsing",
"served_from_cache": "{{value}} <i>(served from cache)</i>", "served_from_cache": "{{value}} <i>(served from cache)</i>",
"form_error_password_length": "Password must be at least {{value}} characters long" "form_error_password_length": "Password must be at least {{value}} characters long",
"anonymizer_notification": "<0>Note:</0> IP anonymization is enabled. You can disable it in <1>General settings</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Control parental", "parental_control": "Control parental",
"safe_browsing": "Navegación segura", "safe_browsing": "Navegación segura",
"served_from_cache": "{{value}} <i>(servido desde la caché)</i>", "served_from_cache": "{{value}} <i>(servido desde la caché)</i>",
"form_error_password_length": "La contraseña debe tener al menos {{value}} caracteres" "form_error_password_length": "La contraseña debe tener al menos {{value}} caracteres",
"anonymizer_notification": "<0>Nota:</0> La anonimización de IP está habilitada. Puedes deshabilitarla en <1>Configuración general</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Lapsilukko", "parental_control": "Lapsilukko",
"safe_browsing": "Turvallinen selaus", "safe_browsing": "Turvallinen selaus",
"served_from_cache": "{{value}} <i>(jaettu välimuistista)</i>", "served_from_cache": "{{value}} <i>(jaettu välimuistista)</i>",
"form_error_password_length": "Salasanan on oltava ainakin {{value}} merkkiä" "form_error_password_length": "Salasanan on oltava ainakin {{value}} merkkiä",
"anonymizer_notification": "<0>Huomioi:</0> IP-osoitteen anonymisointi on käytössä. Voit poistaa sen käytöstä <1>Yleisistä asetuksista</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Contrôle parental", "parental_control": "Contrôle parental",
"safe_browsing": "Navigation sécurisée", "safe_browsing": "Navigation sécurisée",
"served_from_cache": "{{value}} <i>(depuis le cache)</i>", "served_from_cache": "{{value}} <i>(depuis le cache)</i>",
"form_error_password_length": "Le mot de passe doit comporter au moins {{value}} caractères" "form_error_password_length": "Le mot de passe doit comporter au moins {{value}} caractères",
"anonymizer_notification": "<0>Note :</0> L'anonymisation IP est activée. Vous pouvez la désactiver dans les <1>paramètres généraux</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Roditeljska zaštita", "parental_control": "Roditeljska zaštita",
"safe_browsing": "Sigurno surfanje", "safe_browsing": "Sigurno surfanje",
"served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>", "served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>",
"form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova" "form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova",
"anonymizer_notification": "<0>Napomena:</0>IP anonimizacija je omogućena. Možete ju onemogućiti u <1>općim postavkama</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Szülői felügyelet", "parental_control": "Szülői felügyelet",
"safe_browsing": "Biztonságos böngészés", "safe_browsing": "Biztonságos böngészés",
"served_from_cache": "{{value}} <i>(gyorsítótárból kiszolgálva)</i>", "served_from_cache": "{{value}} <i>(gyorsítótárból kiszolgálva)</i>",
"form_error_password_length": "A jelszó legalább {{value}} karakter hosszú kell, hogy legyen" "form_error_password_length": "A jelszó legalább {{value}} karakter hosszú kell, hogy legyen",
"anonymizer_notification": "<0>Megjegyzés:</0> Az IP anonimizálás engedélyezve van. Az <1>Általános beállításoknál letilthatja</1> ."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Kontrol Orang Tua", "parental_control": "Kontrol Orang Tua",
"safe_browsing": "Penjelajahan Aman", "safe_browsing": "Penjelajahan Aman",
"served_from_cache": "{{value}} <i>(disajikan dari cache)</i>", "served_from_cache": "{{value}} <i>(disajikan dari cache)</i>",
"form_error_password_length": "Kata sandi harus minimal {{value}} karakter" "form_error_password_length": "Kata sandi harus minimal {{value}} karakter",
"anonymizer_notification": "<0>Catatan:</0> Anonimisasi IP diaktifkan. Anda dapat menonaktifkannya di <1>Pengaturan umum</1> ."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Controllo Parentale", "parental_control": "Controllo Parentale",
"safe_browsing": "Navigazione Sicura", "safe_browsing": "Navigazione Sicura",
"served_from_cache": "{{value}} <i>(fornito dalla cache)</i>", "served_from_cache": "{{value}} <i>(fornito dalla cache)</i>",
"form_error_password_length": "La password deve contenere almeno {{value}} caratteri" "form_error_password_length": "La password deve contenere almeno {{value}} caratteri",
"anonymizer_notification": "<0>Attenzione:</0> L'anonimizzazione dell'IP è abilitata. Puoi disabilitarla in <1>Impostazioni generali</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "ペアレンタルコントロール", "parental_control": "ペアレンタルコントロール",
"safe_browsing": "セーフブラウジング", "safe_browsing": "セーフブラウジング",
"served_from_cache": "{{value}} <i>(キャッシュから応答)</i>", "served_from_cache": "{{value}} <i>(キャッシュから応答)</i>",
"form_error_password_length": "パスワードは{{value}}文字以上にしてください" "form_error_password_length": "パスワードは{{value}}文字以上にしてください",
"anonymizer_notification": "【<0>注意</0>】IPの匿名化が有効になっています。 <1>一般設定</1>で無効にできます。"
} }

View file

@ -635,5 +635,6 @@
"parental_control": "자녀 보호", "parental_control": "자녀 보호",
"safe_browsing": "세이프 브라우징", "safe_browsing": "세이프 브라우징",
"served_from_cache": "{{value}} <i>(캐시에서 제공)</i>", "served_from_cache": "{{value}} <i>(캐시에서 제공)</i>",
"form_error_password_length": "비밀번호는 {{value}}자 이상이어야 합니다" "form_error_password_length": "비밀번호는 {{value}}자 이상이어야 합니다",
"anonymizer_notification": "<0>참고:</0> IP 익명화가 활성화되었습니다. <1>일반 설정</1>에서 비활성화할 수 있습니다."
} }

View file

@ -557,7 +557,7 @@
"fastest_addr_desc": "Alle DNS-servers bevragen en het snelste IP adres terugkoppelen. Dit zal de DNS verzoeken vertragen omdat AdGuard Home moet wachten op de antwoorden van alles DNS-servers, maar verbetert wel de connectiviteit.", "fastest_addr_desc": "Alle DNS-servers bevragen en het snelste IP adres terugkoppelen. Dit zal de DNS verzoeken vertragen omdat AdGuard Home moet wachten op de antwoorden van alles DNS-servers, maar verbetert wel de connectiviteit.",
"autofix_warning_text": "Als je op \"Repareren\" klikt, configureert AdGuard Home jouw systeem om de AdGuard Home DNS-server te gebruiken.", "autofix_warning_text": "Als je op \"Repareren\" klikt, configureert AdGuard Home jouw systeem om de AdGuard Home DNS-server te gebruiken.",
"autofix_warning_list": "De volgende taken worden uitgevoerd: <0> Deactiveren van Systeem DNSStubListener</0> <0> DNS-serveradres instellen op 127.0.0.1 </0> <0> Symbolisch koppelingsdoel van /etc/resolv.conf vervangen door /run/systemd/resolve/resolv.conf </0> <0> Stop DNSStubListener (herlaad systemd-resolved service) </0>", "autofix_warning_list": "De volgende taken worden uitgevoerd: <0> Deactiveren van Systeem DNSStubListener</0> <0> DNS-serveradres instellen op 127.0.0.1 </0> <0> Symbolisch koppelingsdoel van /etc/resolv.conf vervangen door /run/systemd/resolve/resolv.conf </0> <0> Stop DNSStubListener (herlaad systemd-resolved service) </0>",
"autofix_warning_result": "Als gevolg hiervan worden alle DNS-verzoeken van je systeem standaard door AdGuard Home verwerkt.", "autofix_warning_result": "Als gevolg hiervan worden alle DNS-aanvragen van je systeem standaard door AdGuard Home verwerkt.",
"tags_title": "Labels", "tags_title": "Labels",
"tags_desc": "Je kunt labels selecteren die overeenkomen met de client. Labels kunnen worden opgenomen in de filterregels om ze \n nauwkeuriger toe te passen. <0>Meer informatie</0>.", "tags_desc": "Je kunt labels selecteren die overeenkomen met de client. Labels kunnen worden opgenomen in de filterregels om ze \n nauwkeuriger toe te passen. <0>Meer informatie</0>.",
"form_select_tags": "Client tags selecteren", "form_select_tags": "Client tags selecteren",
@ -628,12 +628,13 @@
"original_response": "Oorspronkelijke reactie", "original_response": "Oorspronkelijke reactie",
"click_to_view_queries": "Klik om queries te bekijken", "click_to_view_queries": "Klik om queries te bekijken",
"port_53_faq_link": "Poort 53 wordt vaak gebruikt door services als DNSStubListener- of de systeem DNS-resolver. Lees a.u.b. <0>deze instructie</0> hoe dit is op te lossen.", "port_53_faq_link": "Poort 53 wordt vaak gebruikt door services als DNSStubListener- of de systeem DNS-resolver. Lees a.u.b. <0>deze instructie</0> hoe dit is op te lossen.",
"adg_will_drop_dns_queries": "AdGuard Home zal alle DNS-verzoeken van deze cliënt laten vervallen.", "adg_will_drop_dns_queries": "AdGuard Home zal alle DNS-aanvragen van deze cliënt laten vervallen.",
"filter_allowlist": "WAARSCHUWING: Deze actie zal ook de regel \"{{disallowed_rule}}\" uitsluiten van de lijst met toegestane clients.", "filter_allowlist": "WAARSCHUWING: Deze actie zal ook de regel \"{{disallowed_rule}}\" uitsluiten van de lijst met toegestane clients.",
"last_rule_in_allowlist": "Kan deze client niet weigeren omdat het uitsluiten van de regel \"{{disallowed_rule}}\" de lijst \"Toegestane clients\" zal UITSCHAKELEN.", "last_rule_in_allowlist": "Kan deze client niet weigeren omdat het uitsluiten van de regel \"{{disallowed_rule}}\" de lijst \"Toegestane clients\" zal UITSCHAKELEN.",
"use_saved_key": "De eerder opgeslagen sleutel gebruiken", "use_saved_key": "De eerder opgeslagen sleutel gebruiken",
"parental_control": "Ouderlijk toezicht", "parental_control": "Ouderlijk toezicht",
"safe_browsing": "Veilig browsen", "safe_browsing": "Veilig browsen",
"served_from_cache": "{{value}} <i>(geleverd vanuit cache)</i>", "served_from_cache": "{{value}} <i>(geleverd vanuit cache)</i>",
"form_error_password_length": "Wachtwoord moet minimaal {{value}} tekens lang zijn" "form_error_password_length": "Wachtwoord moet minimaal {{value}} tekens lang zijn",
"anonymizer_notification": "<0>Opmerking:</0> IP-anonimisering is ingeschakeld. Je kunt het uitschakelen in <1>Algemene instellingen</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Kontrola rodzicielska", "parental_control": "Kontrola rodzicielska",
"safe_browsing": "Bezpieczne przeglądanie", "safe_browsing": "Bezpieczne przeglądanie",
"served_from_cache": "{{value}} <i>(podawane z pamięci podręcznej)</i>", "served_from_cache": "{{value}} <i>(podawane z pamięci podręcznej)</i>",
"form_error_password_length": "Hasło musi mieć co najmniej {{value}} znaków" "form_error_password_length": "Hasło musi mieć co najmniej {{value}} znaków",
"anonymizer_notification": "<0>Uwaga:</0> Anonimizacja IP jest włączona. Możesz ją wyłączyć w <1>Ustawieniach ogólnych</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Controle parental", "parental_control": "Controle parental",
"safe_browsing": "Navegação segura", "safe_browsing": "Navegação segura",
"served_from_cache": "{{value}} <i>(servido do cache)</i>", "served_from_cache": "{{value}} <i>(servido do cache)</i>",
"form_error_password_length": "A senha deve ter pelo menos {{value}} caracteres" "form_error_password_length": "A senha deve ter pelo menos {{value}} caracteres",
"anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-lo em <1>Configurações gerais</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Controlo parental", "parental_control": "Controlo parental",
"safe_browsing": "Navegação segura", "safe_browsing": "Navegação segura",
"served_from_cache": "{{value}} <i>(servido do cache)</i>", "served_from_cache": "{{value}} <i>(servido do cache)</i>",
"form_error_password_length": "A palavra-passe deve ter pelo menos {{value}} caracteres" "form_error_password_length": "A palavra-passe deve ter pelo menos {{value}} caracteres",
"anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-la em <1>Definições gerais</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Control Parental", "parental_control": "Control Parental",
"safe_browsing": "Navigare în siguranță", "safe_browsing": "Navigare în siguranță",
"served_from_cache": "{{value}} <i>(furnizat din cache)</i>", "served_from_cache": "{{value}} <i>(furnizat din cache)</i>",
"form_error_password_length": "Parola trebuie să aibă cel puțin {{value}} caractere" "form_error_password_length": "Parola trebuie să aibă cel puțin {{value}} caractere",
"anonymizer_notification": "<0>Nota:</0> Anonimizarea IP este activată. Puteți să o dezactivați în <1>Setări generale</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Родительский контроль", "parental_control": "Родительский контроль",
"safe_browsing": "Безопасный интернет", "safe_browsing": "Безопасный интернет",
"served_from_cache": "{{value}} <i>(получено из кеша)</i>", "served_from_cache": "{{value}} <i>(получено из кеша)</i>",
"form_error_password_length": "Пароль должен быть длиной не меньше {{value}} символов" "form_error_password_length": "Пароль должен быть длиной не меньше {{value}} символов",
"anonymizer_notification": "<0>Внимание:</0> включена анонимизация IP-адресов. Вы можете отключить её в разделе <1>Основные настройки</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Rodičovská kontrola", "parental_control": "Rodičovská kontrola",
"safe_browsing": "Bezpečné prehliadanie", "safe_browsing": "Bezpečné prehliadanie",
"served_from_cache": "{{value}} <i>(prevzatá z cache pamäte)</i>", "served_from_cache": "{{value}} <i>(prevzatá z cache pamäte)</i>",
"form_error_password_length": "Heslo musí mať dĺžku aspoň {{value}} znakov" "form_error_password_length": "Heslo musí mať dĺžku aspoň {{value}} znakov",
"anonymizer_notification": "<0>Poznámka:</0> Anonymizácia IP je zapnutá. Môžete ju vypnúť vo <1>Všeobecných nastaveniach</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Starševski nadzor", "parental_control": "Starševski nadzor",
"safe_browsing": "Varno brskanje", "safe_browsing": "Varno brskanje",
"served_from_cache": "{{value}} <i>(postreženo iz predpomnilnika)</i>", "served_from_cache": "{{value}} <i>(postreženo iz predpomnilnika)</i>",
"form_error_password_length": "Geslo mora vsebovati najmanj {{value}} znakov" "form_error_password_length": "Geslo mora vsebovati najmanj {{value}} znakov",
"anonymizer_notification": "<0>Opomba:</0> Anonimizacija IP je omogočena. Onemogočite ga lahko v <1>Splošnih nastavitvah</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Roditeljska kontrola", "parental_control": "Roditeljska kontrola",
"safe_browsing": "Sigurno pregledanje", "safe_browsing": "Sigurno pregledanje",
"served_from_cache": "{{value}} <i>(posluženo iz predmemorije)</i>", "served_from_cache": "{{value}} <i>(posluženo iz predmemorije)</i>",
"form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova" "form_error_password_length": "Lozinka mora imati najmanje {{value}} znakova",
"anonymizer_notification": "<0>Nota:</0> IP prepoznavanje je omogućeno. Možete ga onemogućiti u opštim <1>postavkama</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Föräldrakontroll", "parental_control": "Föräldrakontroll",
"safe_browsing": "Säker surfning", "safe_browsing": "Säker surfning",
"served_from_cache": "{{value}} <i>(levereras från cache)</i>", "served_from_cache": "{{value}} <i>(levereras från cache)</i>",
"form_error_password_length": "Lösenordet måste vara minst {{value}} tecken långt" "form_error_password_length": "Lösenordet måste vara minst {{value}} tecken långt",
"anonymizer_notification": "<0>Observera:</0> IP-anonymisering är aktiverad. Du kan inaktivera den i <1>Allmänna inställningar</1>."
} }

View file

@ -368,7 +368,7 @@
"encryption_server_enter": "Alan adınızı girin", "encryption_server_enter": "Alan adınızı girin",
"encryption_server_desc": "Ayarlanırsa, AdGuard Home ClientID'leri algılar, DDR sorgularına yanıt verir ve ek bağlantı doğrulamaları gerçekleştirir. Ayarlanmazsa, bu özellikler devre dışı bırakılır. Sertifikadaki DNS Adlarından biriyle eşleşmelidir.", "encryption_server_desc": "Ayarlanırsa, AdGuard Home ClientID'leri algılar, DDR sorgularına yanıt verir ve ek bağlantı doğrulamaları gerçekleştirir. Ayarlanmazsa, bu özellikler devre dışı bırakılır. Sertifikadaki DNS Adlarından biriyle eşleşmelidir.",
"encryption_redirect": "Otomatik olarak HTTPS'e yönlendir", "encryption_redirect": "Otomatik olarak HTTPS'e yönlendir",
"encryption_redirect_desc": "Etkinleştirirseniz, AdGuard Home sizi HTTP adresi yerine HTTPS adresine yönlendirir.", "encryption_redirect_desc": "İşaretlenirse, AdGuard Home sizi otomatik olarak HTTP adresinden HTTPS adreslerine yönlendirecektir.",
"encryption_https": "HTTPS bağlantı noktası", "encryption_https": "HTTPS bağlantı noktası",
"encryption_https_desc": "HTTPS bağlantı noktası yapılandırılırsa, AdGuard Home yönetici arayüzüne HTTPS aracılığıyla erişilebilir olacak ve ayrıca '/dns-query' üzerinden DNS-over-HTTPS bağlantısı sağlayacaktır.", "encryption_https_desc": "HTTPS bağlantı noktası yapılandırılırsa, AdGuard Home yönetici arayüzüne HTTPS aracılığıyla erişilebilir olacak ve ayrıca '/dns-query' üzerinden DNS-over-HTTPS bağlantısı sağlayacaktır.",
"encryption_dot": "DNS-over-TLS bağlantı noktası", "encryption_dot": "DNS-over-TLS bağlantı noktası",
@ -408,7 +408,7 @@
"fix": "Düzelt", "fix": "Düzelt",
"dns_providers": "Aralarından seçim yapabileceğiniz, bilinen <0>DNS sağlayıcıların listesi</0>.", "dns_providers": "Aralarından seçim yapabileceğiniz, bilinen <0>DNS sağlayıcıların listesi</0>.",
"update_now": "Şimdi güncelle", "update_now": "Şimdi güncelle",
"update_failed": "Otomatik güncelleme başarısız oldu. Elle güncellemek için lütfen <a>bu adımları uygulayın</a>.", "update_failed": "Otomatik güncelleme başarısız oldu. Elle güncellemek için lütfen <a>bu adımları izleyin</a>.",
"manual_update": "Elle güncellemek için lütfen <a>bu adımları uygulayın</a>.", "manual_update": "Elle güncellemek için lütfen <a>bu adımları uygulayın</a>.",
"processing_update": "Lütfen bekleyin, AdGuard Home güncelleniyor", "processing_update": "Lütfen bekleyin, AdGuard Home güncelleniyor",
"clients_title": "Kalıcı istemciler", "clients_title": "Kalıcı istemciler",
@ -635,5 +635,6 @@
"parental_control": "Ebeveyn Denetimi", "parental_control": "Ebeveyn Denetimi",
"safe_browsing": "Güvenli Gezinti", "safe_browsing": "Güvenli Gezinti",
"served_from_cache": "{{value}} <i>(önbellekten kullanıldı)</i>", "served_from_cache": "{{value}} <i>(önbellekten kullanıldı)</i>",
"form_error_password_length": "Parola en az {{value}} karakter uzunluğunda olmalıdır" "form_error_password_length": "Parola en az {{value}} karakter uzunluğunda olmalıdır",
"anonymizer_notification": "<0>Not:</0> IP anonimleştirme etkinleştirildi. Bunu <1>Genel ayarlardan</1> devre dışı bırakabilirsiniz."
} }

View file

@ -447,7 +447,7 @@
"access_disallowed_title": "Заборонені клієнти", "access_disallowed_title": "Заборонені клієнти",
"access_disallowed_desc": "Перелік CIDR, IP-адрес та <a>ClientIDs</a>. Якщо налаштовано, AdGuard Home буде скасовувати запити від цих клієнтів. Проте якщо налаштовано список Дозволених клієнтів, то це поле проігнорується.", "access_disallowed_desc": "Перелік CIDR, IP-адрес та <a>ClientIDs</a>. Якщо налаштовано, AdGuard Home буде скасовувати запити від цих клієнтів. Проте якщо налаштовано список Дозволених клієнтів, то це поле проігнорується.",
"access_blocked_title": "Заборонені домени", "access_blocked_title": "Заборонені домени",
"access_blocked_desc": "Не плутайте з фільтрами. AdGuard Home буде ігнорувати DNS-запити з цими доменами, такі запити навіть не будуть записані до журналу. Ви можете вказати точні доменні імена, замінні знаки та правила фільтрування URL-адрес, наприклад, 'example.org', '*.example.org' або '||example.org^' відповідно.", "access_blocked_desc": "Не плутайте з фільтрами. AdGuard Home буде ігнорувати DNS-запити з цими доменами, такі запити навіть не будуть записані до журналу. Ви можете вказати точні доменні імена, замінні знаки та правила фільтрування URL-адрес, наприклад, «example.org», «*.example.org» або «||example.org^» відповідно.",
"access_settings_saved": "Налаштування доступу успішно збережено", "access_settings_saved": "Налаштування доступу успішно збережено",
"updates_checked": "Доступна нова версія AdGuard Home", "updates_checked": "Доступна нова версія AdGuard Home",
"updates_version_equal": "AdGuard Home останньої версії", "updates_version_equal": "AdGuard Home останньої версії",
@ -635,5 +635,6 @@
"parental_control": "Батьківський контроль", "parental_control": "Батьківський контроль",
"safe_browsing": "Безпечний перегляд", "safe_browsing": "Безпечний перегляд",
"served_from_cache": "{{value}} <i>(отримано з кешу)</i>", "served_from_cache": "{{value}} <i>(отримано з кешу)</i>",
"form_error_password_length": "Пароль мусить мати принаймні {{value}} символів" "form_error_password_length": "Пароль мусить мати принаймні {{value}} символів",
"anonymizer_notification": "<0>Примітка:</0> IP-анонімізацію ввімкнено. Ви можете вимкнути його в <1>Загальні налаштування</1> ."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "Quản lý của phụ huynh", "parental_control": "Quản lý của phụ huynh",
"safe_browsing": "Duyệt web an toàn", "safe_browsing": "Duyệt web an toàn",
"served_from_cache": "{{value}} <i>(được phục vụ từ bộ nhớ cache)</i>", "served_from_cache": "{{value}} <i>(được phục vụ từ bộ nhớ cache)</i>",
"form_error_password_length": "Mật khẩu phải có ít nhất {{value}} ký tự" "form_error_password_length": "Mật khẩu phải có ít nhất {{value}} ký tự",
"anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>."
} }

View file

@ -635,5 +635,6 @@
"parental_control": "家长控制", "parental_control": "家长控制",
"safe_browsing": "安全浏览", "safe_browsing": "安全浏览",
"served_from_cache": "{{value}}<i>(由缓存提供)</i>", "served_from_cache": "{{value}}<i>(由缓存提供)</i>",
"form_error_password_length": "密码必须至少有 {{value}} 个字符" "form_error_password_length": "密码必须至少有 {{value}} 个字符",
"anonymizer_notification": "<0>注意:</0> IP 匿名化已启用。您可以在<1>常规设置</1>中禁用它。"
} }

View file

@ -635,5 +635,6 @@
"parental_control": "家長控制", "parental_control": "家長控制",
"safe_browsing": "安全瀏覽", "safe_browsing": "安全瀏覽",
"served_from_cache": "{{value}} <i>(由快取提供)</i>", "served_from_cache": "{{value}} <i>(由快取提供)</i>",
"form_error_password_length": "密碼必須為至少長 {{value}} 個字元" "form_error_password_length": "密碼必須為至少長 {{value}} 個字元",
"anonymizer_notification": "<0>注意:</0>IP 匿名化被啟用。您可在<1>一般設定</1>中禁用它。"
} }

View file

@ -31,7 +31,9 @@ export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
export const setRules = (rules) => async (dispatch) => { export const setRules = (rules) => async (dispatch) => {
dispatch(setRulesRequest()); dispatch(setRulesRequest());
try { try {
const normalizedRules = normalizeRulesTextarea(rules); const normalizedRules = {
rules: normalizeRulesTextarea(rules)?.split('\n'),
};
await apiClient.setRules(normalizedRules); await apiClient.setRules(normalizedRules);
dispatch(addSuccessToast('updated_custom_filtering_toast')); dispatch(addSuccessToast('updated_custom_filtering_toast'));
dispatch(setRulesSuccess()); dispatch(setRulesSuccess());

View file

@ -355,7 +355,7 @@ export const changeLanguageSuccess = createAction('CHANGE_LANGUAGE_SUCCESS');
export const changeLanguage = (lang) => async (dispatch) => { export const changeLanguage = (lang) => async (dispatch) => {
dispatch(changeLanguageRequest()); dispatch(changeLanguageRequest());
try { try {
await apiClient.changeLanguage(lang); await apiClient.changeLanguage({ language: lang });
dispatch(changeLanguageSuccess()); dispatch(changeLanguageSuccess());
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
@ -370,8 +370,8 @@ export const getLanguageSuccess = createAction('GET_LANGUAGE_SUCCESS');
export const getLanguage = () => async (dispatch) => { export const getLanguage = () => async (dispatch) => {
dispatch(getLanguageRequest()); dispatch(getLanguageRequest());
try { try {
const language = await apiClient.getCurrentLanguage(); const langSettings = await apiClient.getCurrentLanguage();
dispatch(getLanguageSuccess(language)); dispatch(getLanguageSuccess(langSettings.language));
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(getLanguageFailure()); dispatch(getLanguageFailure());
@ -421,7 +421,10 @@ export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
export const findActiveDhcp = (name) => async (dispatch, getState) => { export const findActiveDhcp = (name) => async (dispatch, getState) => {
dispatch(findActiveDhcpRequest()); dispatch(findActiveDhcpRequest());
try { try {
const activeDhcp = await apiClient.findActiveDhcp(name); const req = {
interface: name,
};
const activeDhcp = await apiClient.findActiveDhcp(req);
dispatch(findActiveDhcpSuccess(activeDhcp)); dispatch(findActiveDhcpSuccess(activeDhcp));
const { check, interface_name, interfaces } = getState().dhcp; const { check, interface_name, interfaces } = getState().dhcp;
const selectedInterface = getState().form[FORM_NAME.DHCP_INTERFACES].values.interface_name; const selectedInterface = getState().form[FORM_NAME.DHCP_INTERFACES].values.interface_name;

View file

@ -10,11 +10,17 @@ class Api {
async makeRequest(path, method = 'POST', config) { async makeRequest(path, method = 'POST', config) {
const url = `${this.baseUrl}/${path}`; const url = `${this.baseUrl}/${path}`;
const axiosConfig = config || {};
if (method !== 'GET' && axiosConfig.data) {
axiosConfig.headers = axiosConfig.headers || {};
axiosConfig.headers['Content-Type'] = axiosConfig.headers['Content-Type'] || 'application/json';
}
try { try {
const response = await axios({ const response = await axios({
url, url,
method, method,
...config, ...axiosConfig,
}); });
return response.data; return response.data;
} catch (error) { } catch (error) {
@ -55,7 +61,6 @@ class Api {
const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS; const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS;
const config = { const config = {
data: servers, data: servers,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, config); return this.makeRequest(path, method, config);
} }
@ -64,7 +69,6 @@ class Api {
const { path, method } = this.GLOBAL_VERSION; const { path, method } = this.GLOBAL_VERSION;
const config = { const config = {
data, data,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, config); return this.makeRequest(path, method, config);
} }
@ -100,7 +104,6 @@ class Api {
const { path, method } = this.FILTERING_REFRESH; const { path, method } = this.FILTERING_REFRESH;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
@ -110,7 +113,6 @@ class Api {
const { path, method } = this.FILTERING_ADD_FILTER; const { path, method } = this.FILTERING_ADD_FILTER;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
@ -120,7 +122,6 @@ class Api {
const { path, method } = this.FILTERING_REMOVE_FILTER; const { path, method } = this.FILTERING_REMOVE_FILTER;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
@ -130,7 +131,6 @@ class Api {
const { path, method } = this.FILTERING_SET_RULES; const { path, method } = this.FILTERING_SET_RULES;
const parameters = { const parameters = {
data: rules, data: rules,
headers: { 'Content-Type': 'text/plain' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -139,7 +139,6 @@ class Api {
const { path, method } = this.FILTERING_CONFIG; const { path, method } = this.FILTERING_CONFIG;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -148,7 +147,6 @@ class Api {
const { path, method } = this.FILTERING_SET_URL; const { path, method } = this.FILTERING_SET_URL;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -173,12 +171,7 @@ class Api {
enableParentalControl() { enableParentalControl() {
const { path, method } = this.PARENTAL_ENABLE; const { path, method } = this.PARENTAL_ENABLE;
const parameter = 'sensitivity=TEEN'; // this parameter TEEN is hardcoded return this.makeRequest(path, method);
const config = {
data: parameter,
headers: { 'Content-Type': 'text/plain' },
};
return this.makeRequest(path, method, config);
} }
disableParentalControl() { disableParentalControl() {
@ -240,11 +233,10 @@ class Api {
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
changeLanguage(lang) { changeLanguage(config) {
const { path, method } = this.CHANGE_LANGUAGE; const { path, method } = this.CHANGE_LANGUAGE;
const parameters = { const parameters = {
data: lang, data: config,
headers: { 'Content-Type': 'text/plain' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -280,16 +272,14 @@ class Api {
const { path, method } = this.DHCP_SET_CONFIG; const { path, method } = this.DHCP_SET_CONFIG;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
findActiveDhcp(name) { findActiveDhcp(req) {
const { path, method } = this.DHCP_FIND_ACTIVE; const { path, method } = this.DHCP_FIND_ACTIVE;
const parameters = { const parameters = {
data: name, data: req,
headers: { 'Content-Type': 'text/plain' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -298,7 +288,6 @@ class Api {
const { path, method } = this.DHCP_ADD_STATIC_LEASE; const { path, method } = this.DHCP_ADD_STATIC_LEASE;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -307,7 +296,6 @@ class Api {
const { path, method } = this.DHCP_REMOVE_STATIC_LEASE; const { path, method } = this.DHCP_REMOVE_STATIC_LEASE;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -338,7 +326,6 @@ class Api {
const { path, method } = this.INSTALL_CONFIGURE; const { path, method } = this.INSTALL_CONFIGURE;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -347,7 +334,6 @@ class Api {
const { path, method } = this.INSTALL_CHECK_CONFIG; const { path, method } = this.INSTALL_CHECK_CONFIG;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -368,7 +354,6 @@ class Api {
const { path, method } = this.TLS_CONFIG; const { path, method } = this.TLS_CONFIG;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -377,7 +362,6 @@ class Api {
const { path, method } = this.TLS_VALIDATE; const { path, method } = this.TLS_VALIDATE;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -402,7 +386,6 @@ class Api {
const { path, method } = this.ADD_CLIENT; const { path, method } = this.ADD_CLIENT;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -411,7 +394,6 @@ class Api {
const { path, method } = this.DELETE_CLIENT; const { path, method } = this.DELETE_CLIENT;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -420,7 +402,6 @@ class Api {
const { path, method } = this.UPDATE_CLIENT; const { path, method } = this.UPDATE_CLIENT;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -445,7 +426,6 @@ class Api {
const { path, method } = this.ACCESS_SET; const { path, method } = this.ACCESS_SET;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -466,7 +446,6 @@ class Api {
const { path, method } = this.REWRITE_ADD; const { path, method } = this.REWRITE_ADD;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -475,7 +454,6 @@ class Api {
const { path, method } = this.REWRITE_DELETE; const { path, method } = this.REWRITE_DELETE;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -501,7 +479,6 @@ class Api {
const { path, method } = this.BLOCKED_SERVICES_SET; const { path, method } = this.BLOCKED_SERVICES_SET;
const parameters = { const parameters = {
data: config, data: config,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
@ -529,7 +506,6 @@ class Api {
const { path, method } = this.STATS_CONFIG; const { path, method } = this.STATS_CONFIG;
const config = { const config = {
data, data,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, config); return this.makeRequest(path, method, config);
} }
@ -565,7 +541,6 @@ class Api {
const { path, method } = this.QUERY_LOG_CONFIG; const { path, method } = this.QUERY_LOG_CONFIG;
const config = { const config = {
data, data,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, config); return this.makeRequest(path, method, config);
} }
@ -582,7 +557,6 @@ class Api {
const { path, method } = this.LOGIN; const { path, method } = this.LOGIN;
const config = { const config = {
data, data,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, config); return this.makeRequest(path, method, config);
} }
@ -609,7 +583,6 @@ class Api {
const { path, method } = this.SET_DNS_CONFIG; const { path, method } = this.SET_DNS_CONFIG;
const config = { const config = {
data, data,
headers: { 'Content-Type': 'application/json' },
}; };
return this.makeRequest(path, method, config); return this.makeRequest(path, method, config);
} }

View file

@ -41,13 +41,13 @@ class Table extends Component {
{ {
Header: <Trans>name_table_header</Trans>, Header: <Trans>name_table_header</Trans>,
accessor: 'name', accessor: 'name',
minWidth: 200, minWidth: 180,
Cell: CellWrap, Cell: CellWrap,
}, },
{ {
Header: <Trans>list_url_table_header</Trans>, Header: <Trans>list_url_table_header</Trans>,
accessor: 'url', accessor: 'url',
minWidth: 200, minWidth: 180,
Cell: ({ value }) => ( Cell: ({ value }) => (
<div className="logs__row"> <div className="logs__row">
{isValidAbsolutePath(value) ? value {isValidAbsolutePath(value) ? value
@ -73,7 +73,7 @@ class Table extends Component {
Header: <Trans>last_time_updated_table_header</Trans>, Header: <Trans>last_time_updated_table_header</Trans>,
accessor: 'lastUpdated', accessor: 'lastUpdated',
className: 'text-center', className: 'text-center',
minWidth: 150, minWidth: 180,
Cell: this.getDateCell, Cell: this.getDateCell,
}, },
{ {

View file

@ -0,0 +1,16 @@
import React from 'react';
import { Trans } from 'react-i18next';
import { HashLink as Link } from 'react-router-hash-link';
const AnonymizerNotification = () => (
<div className="alert alert-primary mt-6">
<Trans components={[
<strong key="0">text</strong>,
<Link to="/settings#logs-config" key="1">link</Link>,
]}>
anonymizer_notification
</Trans>
</div>
);
export default AnonymizerNotification;

View file

@ -62,7 +62,7 @@ const ClientCell = ({
'white-space--nowrap': isDetailed, 'white-space--nowrap': isDetailed,
}); });
const hintClass = classNames('icons mr-4 icon--24 icon--lightgray', { const hintClass = classNames('icons mr-4 icon--24 logs__question icon--lightgray', {
'my-3': isDetailed, 'my-3': isDetailed,
}); });

View file

@ -34,7 +34,7 @@ const DomainCell = ({
'my-3': isDetailed, 'my-3': isDetailed,
}); });
const privacyIconClass = classNames('icons mx-2 icon--24 d-none d-sm-block', { const privacyIconClass = classNames('icons mx-2 icon--24 d-none d-sm-block logs__question', {
'icon--green': hasTracker, 'icon--green': hasTracker,
'icon--disabled': !hasTracker, 'icon--disabled': !hasTracker,
'my-3': isDetailed, 'my-3': isDetailed,

View file

@ -49,6 +49,12 @@
padding-top: 1rem; padding-top: 1rem;
} }
@media (max-width: 1024px) {
.grid .key-colon, .grid .title--border {
font-weight: 600;
}
}
@media (max-width: 767.98px) { @media (max-width: 767.98px) {
.grid { .grid {
grid-template-columns: 35% 55%; grid-template-columns: 35% 55%;
@ -70,10 +76,6 @@
grid-column: 2 / span 1; grid-column: 2 / span 1;
margin: 0 !important; margin: 0 !important;
} }
.grid .key-colon, .grid .title--border {
font-weight: 600;
}
} }
.grid .key-colon:nth-child(odd)::after { .grid .key-colon:nth-child(odd)::after {

View file

@ -97,7 +97,7 @@ const ResponseCell = ({
return ( return (
<div className="logs__cell logs__cell--response" role="gridcell"> <div className="logs__cell logs__cell--response" role="gridcell">
<IconTooltip <IconTooltip
className={classNames('icons mr-4 icon--24 icon--lightgray', { 'my-3': isDetailed })} className={classNames('icons mr-4 icon--24 icon--lightgray logs__question', { 'my-3': isDetailed })}
columnClass='grid grid--limited' columnClass='grid grid--limited'
tooltipClass='px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details' tooltipClass='px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details'
contentItemClass='text-truncate key-colon o-hidden' contentItemClass='text-truncate key-colon o-hidden'

View file

@ -485,3 +485,13 @@
.bg--green { .bg--green {
color: var(--green79); color: var(--green79);
} }
@media (max-width: 1024px) {
.logs__question {
display: none;
}
}
.logs__modal {
max-width: 720px;
}

View file

@ -25,6 +25,7 @@ import {
import InfiniteTable from './InfiniteTable'; import InfiniteTable from './InfiniteTable';
import './Logs.css'; import './Logs.css';
import { BUTTON_PREFIX } from './Cells/helpers'; import { BUTTON_PREFIX } from './Cells/helpers';
import AnonymizerNotification from './AnonymizerNotification';
const processContent = (data) => Object.entries(data) const processContent = (data) => Object.entries(data)
.map(([key, value]) => { .map(([key, value]) => {
@ -73,6 +74,7 @@ const Logs = () => {
processingGetConfig, processingGetConfig,
processingAdditionalLogs, processingAdditionalLogs,
processingGetLogs, processingGetLogs,
anonymize_client_ip: anonymizeClientIp,
} = useSelector((state) => state.queryLogs, shallowEqual); } = useSelector((state) => state.queryLogs, shallowEqual);
const filter = useSelector((state) => state.queryLogs.filter, shallowEqual); const filter = useSelector((state) => state.queryLogs.filter, shallowEqual);
const logs = useSelector((state) => state.queryLogs.logs, shallowEqual); const logs = useSelector((state) => state.queryLogs.logs, shallowEqual);
@ -104,6 +106,8 @@ const Logs = () => {
setIsSmallScreen(e.matches); setIsSmallScreen(e.matches);
if (e.matches) { if (e.matches) {
dispatch(toggleDetailedLogs(false)); dispatch(toggleDetailedLogs(false));
} else {
dispatch(toggleDetailedLogs(true));
} }
}; };
@ -180,35 +184,49 @@ const Logs = () => {
setButtonType={setButtonType} setButtonType={setButtonType}
setModalOpened={setModalOpened} setModalOpened={setModalOpened}
/> />
<Modal portalClassName='grid' isOpen={isSmallScreen && isModalOpened} <Modal
portalClassName='grid'
isOpen={isSmallScreen && isModalOpened}
onRequestClose={closeModal} onRequestClose={closeModal}
style={{ style={{
content: { content: {
width: '100%', width: '100%',
height: 'fit-content', height: 'fit-content',
left: 0, left: '50%',
top: 47, top: 47,
padding: '1rem 1.5rem 1rem', padding: '1rem 1.5rem 1rem',
maxWidth: '720px',
transform: 'translateX(-50%)',
}, },
overlay: { overlay: {
backgroundColor: 'rgba(0,0,0,0.5)', backgroundColor: 'rgba(0,0,0,0.5)',
}, },
}} }}
> >
<div className="logs__modal-wrap">
<svg <svg
className="icon icon--24 icon-cross d-block d-md-none cursor--pointer" className="icon icon--24 icon-cross d-block cursor--pointer"
onClick={closeModal}> onClick={closeModal}
>
<use xlinkHref="#cross" /> <use xlinkHref="#cross" />
</svg> </svg>
{processContent(detailedDataCurrent, buttonType)} {processContent(detailedDataCurrent, buttonType)}
</div>
</Modal> </Modal>
</>; </>;
return <> return (
{enabled && processingGetConfig && <Loading />} <>
{enabled && !processingGetConfig && renderPage()} {enabled && (
<>
{processingGetConfig && <Loading />}
{anonymizeClientIp && <AnonymizerNotification />}
{!processingGetConfig && renderPage()}
</>
)}
{!enabled && !processingGetConfig && <Disabled />} {!enabled && !processingGetConfig && <Disabled />}
</>; </>
);
}; };
export default Logs; export default Logs;

View file

@ -2820,6 +2820,11 @@ fieldset:disabled a.btn {
} }
.btn-outline-primary:focus, .btn-outline-primary:focus,
.btn-outline-primary.focus {
box-shadow: none;
}
.btn-outline-primary:focus-visible,
.btn-outline-primary.focus { .btn-outline-primary.focus {
box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.5); box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.5);
} }
@ -2858,6 +2863,11 @@ fieldset:disabled a.btn {
} }
.btn-outline-secondary:focus, .btn-outline-secondary:focus,
.btn-outline-secondary.focus {
box-shadow: none;
}
.btn-outline-secondary:focus-visible,
.btn-outline-secondary.focus { .btn-outline-secondary.focus {
box-shadow: 0 0 0 2px rgba(134, 142, 150, 0.5); box-shadow: 0 0 0 2px rgba(134, 142, 150, 0.5);
} }

View file

@ -67,6 +67,7 @@
height: 24px; height: 24px;
margin-bottom: 6px; margin-bottom: 6px;
fill: #4a4a4a; fill: #4a4a4a;
touch-action: initial;
} }
.tab__text { .tab__text {

View file

@ -526,8 +526,8 @@ export const DEFAULT_DATE_FORMAT_OPTIONS = {
month: 'numeric', month: 'numeric',
day: 'numeric', day: 'numeric',
hour: 'numeric', hour: 'numeric',
hourCycle: 'h23',
minute: 'numeric', minute: 'numeric',
hour12: false,
}; };
export const DETAILED_DATE_FORMAT_OPTIONS = { export const DETAILED_DATE_FORMAT_OPTIONS = {

View file

@ -2,6 +2,7 @@
package aghhttp package aghhttp
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -10,6 +11,12 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
) )
// HTTP scheme constants.
const (
SchemeHTTP = "http"
SchemeHTTPS = "https"
)
// RegisterFunc is the function that sets the handler to handle the URL for the // RegisterFunc is the function that sets the handler to handle the URL for the
// method. // method.
// //
@ -26,7 +33,7 @@ func OK(w http.ResponseWriter) {
// Error writes formatted message to w and also logs it. // Error writes formatted message to w and also logs it.
func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...any) { func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...any) {
text := fmt.Sprintf(format, args...) text := fmt.Sprintf(format, args...)
log.Error("%s %s: %s", r.Method, r.URL, text) log.Error("%s %s %s: %s", r.Method, r.Host, r.URL, text)
http.Error(w, text, code) http.Error(w, text, code)
} }
@ -35,3 +42,34 @@ func Error(r *http.Request, w http.ResponseWriter, code int, format string, args
func UserAgent() (ua string) { func UserAgent() (ua string) {
return fmt.Sprintf("AdGuardHome/%s", version.Version()) return fmt.Sprintf("AdGuardHome/%s", version.Version())
} }
// textPlainDeprMsg is the message returned to API users when they try to use
// an API that used to accept "text/plain" but doesn't anymore.
const textPlainDeprMsg = `using this api with the text/plain content-type is deprecated; ` +
`use application/json`
// WriteTextPlainDeprecated responds to the request with a message about
// deprecation and removal of a plain-text API if the request is made with the
// "text/plain" content-type.
func WriteTextPlainDeprecated(w http.ResponseWriter, r *http.Request) (isPlainText bool) {
if r.Header.Get(HdrNameContentType) != HdrValTextPlain {
return false
}
Error(r, w, http.StatusUnsupportedMediaType, textPlainDeprMsg)
return true
}
// WriteJSONResponse sets the content-type header in w.Header() to
// "application/json", encodes resp to w, calls Error on any returned error, and
// returns it as well.
func WriteJSONResponse(w http.ResponseWriter, r *http.Request, resp any) (err error) {
w.Header().Set(HdrNameContentType, HdrValApplicationJSON)
err = json.NewEncoder(w).Encode(resp)
if err != nil {
Error(r, w, http.StatusInternalServerError, "encoding resp: %s", err)
}
return err
}

View file

@ -8,8 +8,8 @@ package aghhttp
const ( const (
HdrNameAcceptEncoding = "Accept-Encoding" HdrNameAcceptEncoding = "Accept-Encoding"
HdrNameAccessControlAllowOrigin = "Access-Control-Allow-Origin" HdrNameAccessControlAllowOrigin = "Access-Control-Allow-Origin"
HdrNameContentType = "Content-Type"
HdrNameContentEncoding = "Content-Encoding" HdrNameContentEncoding = "Content-Encoding"
HdrNameContentType = "Content-Type"
HdrNameServer = "Server" HdrNameServer = "Server"
HdrNameTrailer = "Trailer" HdrNameTrailer = "Trailer"
HdrNameUserAgent = "User-Agent" HdrNameUserAgent = "User-Agent"
@ -18,4 +18,5 @@ const (
// HTTP header value constants. // HTTP header value constants.
const ( const (
HdrValApplicationJSON = "application/json" HdrValApplicationJSON = "application/json"
HdrValTextPlain = "text/plain"
) )

View file

@ -1,5 +1,4 @@
//go:build darwin || freebsd //go:build darwin || freebsd
// +build darwin freebsd
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build darwin || freebsd //go:build darwin || freebsd
// +build darwin freebsd
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build openbsd //go:build openbsd
// +build openbsd
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build openbsd //go:build openbsd
// +build openbsd
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris //go:build darwin || freebsd || linux || openbsd
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build !(windows || linux) //go:build !(windows || linux)
// +build !windows,!linux
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build aix || darwin || dragonfly || freebsd || netbsd || openbsd || solaris //go:build darwin || freebsd || openbsd
// +build aix darwin dragonfly freebsd netbsd openbsd solaris
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package aghnet package aghnet
@ -19,15 +18,9 @@ import (
// How to test on a real Linux machine: // How to test on a real Linux machine:
// //
// 1. Run: // 1. Run "sudo ipset create example_set hash:ip family ipv4".
// //
// sudo ipset create example_set hash:ip family ipv4 // 2. Run "sudo ipset list example_set". The Members field should be empty.
//
// 2. Run:
//
// sudo ipset list example_set
//
// The Members field should be empty.
// //
// 3. Add the line "example.com/example_set" to your AdGuardHome.yaml. // 3. Add the line "example.com/example_set" to your AdGuardHome.yaml.
// //
@ -35,11 +28,8 @@ import (
// //
// 5. Make requests to example.com and its subdomains. // 5. Make requests to example.com and its subdomains.
// //
// 6. Run: // 6. Run "sudo ipset list example_set". The Members field should contain the
// // resolved IP addresses.
// sudo ipset list example_set
//
// The Members field should contain the resolved IP addresses.
// newIpsetMgr returns a new Linux ipset manager. // newIpsetMgr returns a new Linux ipset manager.
func newIpsetMgr(ipsetConf []string) (set IpsetManager, err error) { func newIpsetMgr(ipsetConf []string) (set IpsetManager, err error) {

View file

@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build !linux //go:build !linux
// +build !linux
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build darwin || freebsd || openbsd //go:build darwin || freebsd || openbsd
// +build darwin freebsd openbsd
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build darwin //go:build darwin
// +build darwin
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build freebsd //go:build freebsd
// +build freebsd
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build freebsd //go:build freebsd
// +build freebsd
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build openbsd //go:build openbsd
// +build openbsd
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build openbsd //go:build openbsd
// +build openbsd
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build openbsd || freebsd || linux || darwin //go:build darwin || freebsd || linux || openbsd
// +build openbsd freebsd linux darwin
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build !windows //go:build !windows
// +build !windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build !windows //go:build !windows
// +build !windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghnet package aghnet

View file

@ -1,5 +1,4 @@
//go:build mips || mips64 //go:build mips || mips64
// +build mips mips64
// This file is an adapted version of github.com/josharian/native. // This file is an adapted version of github.com/josharian/native.

View file

@ -1,5 +1,4 @@
//go:build amd64 || 386 || arm || arm64 || mipsle || mips64le || ppc64le //go:build amd64 || 386 || arm || arm64 || mipsle || mips64le || ppc64le
// +build amd64 386 arm arm64 mipsle mips64le ppc64le
// This file is an adapted version of github.com/josharian/native. // This file is an adapted version of github.com/josharian/native.

View file

@ -1,5 +1,4 @@
//go:build darwin || netbsd || openbsd //go:build darwin || openbsd
// +build darwin netbsd openbsd
package aghos package aghos

View file

@ -1,5 +1,4 @@
//go:build freebsd //go:build freebsd
// +build freebsd
package aghos package aghos

View file

@ -1,5 +1,4 @@
//go:build linux //go:build linux
// +build linux
package aghos package aghos

View file

@ -1,5 +1,4 @@
//go:build darwin || freebsd || linux || openbsd //go:build darwin || freebsd || linux || openbsd
// +build darwin freebsd linux openbsd
package aghos package aghos

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghos package aghos

View file

@ -1,5 +1,4 @@
//go:build !(windows || plan9) //go:build !windows
// +build !windows,!plan9
package aghos package aghos

View file

@ -1,5 +1,4 @@
//go:build windows || plan9 //go:build windows
// +build windows plan9
package aghos package aghos

View file

@ -1,5 +1,4 @@
//go:build darwin || freebsd || linux || netbsd || openbsd //go:build darwin || freebsd || linux || openbsd
// +build darwin freebsd linux netbsd openbsd
package aghos package aghos

View file

@ -1,5 +1,4 @@
//go:build windows //go:build windows
// +build windows
package aghos package aghos

View file

@ -1,5 +1,4 @@
//go:build freebsd || openbsd //go:build freebsd || openbsd
// +build freebsd openbsd
package dhcpd package dhcpd
@ -10,11 +9,10 @@ import (
// broadcast sends resp to the broadcast address specific for network interface. // broadcast sends resp to the broadcast address specific for network interface.
func (c *dhcpConn) broadcast(respData []byte, peer *net.UDPAddr) (n int, err error) { func (c *dhcpConn) broadcast(respData []byte, peer *net.UDPAddr) (n int, err error) {
// Despite the fact that server4.NewIPv4UDPConn explicitly sets socket // Despite the fact that server4.NewIPv4UDPConn explicitly sets socket
// options to allow broadcasting, it also binds the connection to a // options to allow broadcasting, it also binds the connection to a specific
// specific interface. On FreeBSD and OpenBSD net.UDPConn.WriteTo // interface. On FreeBSD and OpenBSD net.UDPConn.WriteTo causes errors
// causes errors while writing to the addresses that belong to another // while writing to the addresses that belong to another interface. So, use
// interface. So, use the broadcast address specific for the interface // the broadcast address specific for the interface bound.
// bound.
peer.IP = c.bcastIP peer.IP = c.bcastIP
return c.udpConn.WriteTo(respData, peer) return c.udpConn.WriteTo(respData, peer)

View file

@ -1,5 +1,4 @@
//go:build freebsd || openbsd //go:build freebsd || openbsd
// +build freebsd openbsd
package dhcpd package dhcpd

View file

@ -1,5 +1,4 @@
//go:build aix || darwin || dragonfly || linux || netbsd || solaris //go:build darwin || linux
// +build aix darwin dragonfly linux netbsd solaris
package dhcpd package dhcpd

View file

@ -1,5 +1,4 @@
//go:build aix || darwin || dragonfly || linux || netbsd || solaris //go:build darwin || linux
// +build aix darwin dragonfly linux netbsd solaris
package dhcpd package dhcpd

View file

@ -1,10 +1,40 @@
package dhcpd package dhcpd
import ( import (
"fmt"
"net" "net"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
) )
// ServerConfig is the configuration for the DHCP server. The order of YAML
// fields is important, since the YAML configuration file follows it.
type ServerConfig struct {
// Called when the configuration is changed by HTTP request
ConfigModified func() `yaml:"-"`
// Register an HTTP handler
HTTPRegister aghhttp.RegisterFunc `yaml:"-"`
Enabled bool `yaml:"enabled"`
InterfaceName string `yaml:"interface_name"`
// LocalDomainName is the domain name used for DHCP hosts. For example,
// a DHCP client with the hostname "myhost" can be addressed as "myhost.lan"
// when LocalDomainName is "lan".
LocalDomainName string `yaml:"local_domain_name"`
Conf4 V4ServerConf `yaml:"dhcpv4"`
Conf6 V6ServerConf `yaml:"dhcpv6"`
WorkDir string `yaml:"-"`
DBFilePath string `yaml:"-"`
}
// DHCPServer - DHCP server interface // DHCPServer - DHCP server interface
type DHCPServer interface { type DHCPServer interface {
// ResetLeases resets leases. // ResetLeases resets leases.
@ -80,6 +110,86 @@ type V4ServerConf struct {
notify func(uint32) notify func(uint32)
} }
// errNilConfig is an error returned by validation method if the config is nil.
const errNilConfig errors.Error = "nil config"
// ensureV4 returns a 4-byte version of ip. An error is returned if the passed
// ip is not an IPv4.
func ensureV4(ip net.IP) (ip4 net.IP, err error) {
if ip == nil {
return nil, fmt.Errorf("%v is not an IP address", ip)
}
ip4 = ip.To4()
if ip4 == nil {
return nil, fmt.Errorf("%v is not an IPv4 address", ip)
}
return ip4, nil
}
// Validate returns an error if c is not a valid configuration.
//
// TODO(e.burkov): Don't set the config fields when the server itself will stop
// containing the config.
func (c *V4ServerConf) Validate() (err error) {
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
if c == nil {
return errNilConfig
}
var gatewayIP net.IP
gatewayIP, err = ensureV4(c.GatewayIP)
if err != nil {
// Don't wrap an errors since it's inforative enough as is and there is
// an annotation deferred already.
return err
}
if c.SubnetMask == nil {
return fmt.Errorf("invalid subnet mask: %v", c.SubnetMask)
}
subnetMask := net.IPMask(netutil.CloneIP(c.SubnetMask.To4()))
c.subnet = &net.IPNet{
IP: gatewayIP,
Mask: subnetMask,
}
c.broadcastIP = aghnet.BroadcastFromIPNet(c.subnet)
c.ipRange, err = newIPRange(c.RangeStart, c.RangeEnd)
if err != nil {
// Don't wrap an errors since it's inforative enough as is and there is
// an annotation deferred already.
return err
}
if c.ipRange.contains(gatewayIP) {
return fmt.Errorf("gateway ip %v in the ip range: %v-%v",
gatewayIP,
c.RangeStart,
c.RangeEnd,
)
}
if !c.subnet.Contains(c.RangeStart) {
return fmt.Errorf("range start %v is outside network %v",
c.RangeStart,
c.subnet,
)
}
if !c.subnet.Contains(c.RangeEnd) {
return fmt.Errorf("range end %v is outside network %v",
c.RangeEnd,
c.subnet,
)
}
return nil
}
// V6ServerConf - server configuration // V6ServerConf - server configuration
type V6ServerConf struct { type V6ServerConf struct {
Enabled bool `yaml:"-" json:"-"` Enabled bool `yaml:"-" json:"-"`

View file

@ -1,5 +1,4 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris //go:build darwin || freebsd || linux || openbsd
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package dhcpd package dhcpd

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