Compare commits
1 Commits
master
...
AGDNS-2743
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53cb84efc0 |
44
CHANGELOG.md
44
CHANGELOG.md
@@ -9,45 +9,26 @@ The format is based on [*Keep a Changelog*](https://keepachangelog.com/en/1.0.0/
|
|||||||
<!--
|
<!--
|
||||||
## [v0.108.0] – TBA
|
## [v0.108.0] – TBA
|
||||||
|
|
||||||
## [v0.107.62] - 2025-04-30 (APPROX.)
|
## [v0.107.61] - 2025-04-22 (APPROX.)
|
||||||
|
|
||||||
See also the [v0.107.62 GitHub milestone][ms-v0.107.62].
|
See also the [v0.107.61 GitHub milestone][ms-v0.107.61].
|
||||||
|
|
||||||
[ms-v0.107.62]: https://github.com/AdguardTeam/AdGuardHome/milestone/97?closed=1
|
[ms-v0.107.61]: https://github.com/AdguardTeam/AdGuardHome/milestone/96?closed=1
|
||||||
|
|
||||||
NOTE: Add new changes BELOW THIS COMMENT.
|
NOTE: Add new changes BELOW THIS COMMENT.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Command line option `--update` when the `dns.serve_plain_dns` configuration property was disabled ([7801]).
|
|
||||||
|
|
||||||
- DNS cache not working for custom upstream configurations.
|
|
||||||
|
|
||||||
- Validation process for the DNS-over-TLS, DNS-over-QUIC, and HTTPS ports on the *Encryption Settings* page.
|
|
||||||
|
|
||||||
[#7801]: https://github.com/AdguardTeam/AdGuardHome/issues/7801
|
|
||||||
|
|
||||||
<!--
|
|
||||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## [v0.107.61] - 2025-04-22
|
|
||||||
|
|
||||||
See also the [v0.107.61 GitHub milestone][ms-v0.107.61].
|
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Any simultaneous requests that are considered duplicates will now only result in a single request to upstreams, reducing the chance of a cache poisoning attack succeeding. This is controlled by the new configuration object `pending_requests`, which has a single `enabled` property, set to `true` by default.
|
- Any simultaneous requests that are considered duplicates will now only result in a single request to upstreams, reducing the chance of a cache poisoning attack succeeding. This is controlled by the new configuration object `pending_requests`, which has a single `enabled` property, set to `true` by default.
|
||||||
|
|
||||||
**NOTE:** We thank [Xiang Li][mr-xiang-li] for reporting this security issue. It's strongly recommended to leave it enabled, otherwise AdGuard Home will be vulnerable to untrusted clients.
|
**NOTE:** We thank [Xiang Li][mr-xiang-li] for reporting this security issue. It's strongly recommended to leave it enabled, otherwise AdGuard Home will be vulnerable to untrusted clients.
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Searching for persistent clients using an exact match for CIDR in the `POST /clients/search HTTP API`.
|
|
||||||
|
|
||||||
[mr-xiang-li]: https://lixiang521.com/
|
[mr-xiang-li]: https://lixiang521.com/
|
||||||
[ms-v0.107.61]: https://github.com/AdguardTeam/AdGuardHome/milestone/96?closed=1
|
|
||||||
|
<!--
|
||||||
|
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||||
|
-->
|
||||||
|
|
||||||
## [v0.107.60] - 2025-04-14
|
## [v0.107.60] - 2025-04-14
|
||||||
|
|
||||||
@@ -90,6 +71,10 @@ See also the [v0.107.60 GitHub milestone][ms-v0.107.60].
|
|||||||
|
|
||||||
See also the [v0.107.59 GitHub milestone][ms-v0.107.59].
|
See also the [v0.107.59 GitHub milestone][ms-v0.107.59].
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Validation process for the DNS-over-TLS, DNS-over-QUIC, and HTTPS ports on the *Encryption Settings* page.
|
||||||
|
|
||||||
- Rules with the `client` modifier not working ([#7708]).
|
- Rules with the `client` modifier not working ([#7708]).
|
||||||
|
|
||||||
- The search form not working in the query log ([#7704]).
|
- The search form not working in the query log ([#7704]).
|
||||||
@@ -3130,12 +3115,11 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
|||||||
[ms-v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/milestone/28?closed=1
|
[ms-v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/milestone/28?closed=1
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.62...HEAD
|
|
||||||
[v0.107.62]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.61...v0.107.62
|
|
||||||
-->
|
|
||||||
|
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.61...HEAD
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.61...HEAD
|
||||||
[v0.107.61]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.60...v0.107.61
|
[v0.107.61]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.60...v0.107.61
|
||||||
|
-->
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.60...HEAD
|
||||||
[v0.107.60]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.59...v0.107.60
|
[v0.107.60]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.59...v0.107.60
|
||||||
[v0.107.59]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.58...v0.107.59
|
[v0.107.59]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.58...v0.107.59
|
||||||
[v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...v0.107.58
|
[v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...v0.107.58
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Налады кліентаў",
|
"client_settings": "Налады кліентаў",
|
||||||
"example_upstream_reserved": "upstream <0>для канкрэтных даменаў</0>;",
|
"example_upstream_reserved": "upstream <0>для канкрэтных даменаў</0>;",
|
||||||
"example_multiple_upstreams_reserved": "некалькі сервер DNSаў <0>для канкрэтных даменаў</0>;",
|
"example_multiple_upstreams_reserved": "некалькі DNS-сервераў <0>для канкрэтных даменаў</0>;",
|
||||||
"example_upstream_comment": "каментар.",
|
"example_upstream_comment": "каментар.",
|
||||||
"upstream_parallel": "Ужыць адначасныя запыты да ўсіх сервераў для паскарэння апрацоўкі запыту",
|
"upstream_parallel": "Ужыць адначасныя запыты да ўсіх сервераў для паскарэння апрацоўкі запыту",
|
||||||
"parallel_requests": "Паралельныя запыты",
|
"parallel_requests": "Паралельныя запыты",
|
||||||
"load_balancing": "Размеркаванне нагрузкі",
|
"load_balancing": "Размеркаванне нагрузкі",
|
||||||
"load_balancing_desc": "Запытвайце па адным серверы за раз. AdGuard Home будзе выкарыстоўваць выпадковы алгарытм для выбару сервера, так што самы хуткі сервер будзе выкарыстоўвацца часцей.",
|
"load_balancing_desc": "Запытвайце па адным серверы за раз. AdGuard Home будзе выкарыстоўваць выпадковы алгарытм для выбару сервера, так што самы хуткі сервер будзе выкарыстоўвацца часцей.",
|
||||||
"bootstrap_dns": "Bootstrap сервер DNSы",
|
"bootstrap_dns": "Bootstrap DNS-серверы",
|
||||||
"bootstrap_dns_desc": "IP-адрасы сервер DNSаў, якія выкарыстоўваюцца для вырашэння IP-адрасоў распознавальнікаў DoH/DoT, якія вы ўказваеце ў якасці перадачы. Каментары не дапускаюцца.",
|
"bootstrap_dns_desc": "IP-адрасы DNS-сервераў, якія выкарыстоўваюцца для вырашэння IP-адрасоў распознавальнікаў DoH/DoT, якія вы ўказваеце ў якасці перадачы. Каментары не дапускаюцца.",
|
||||||
"fallback_dns_title": "Рэзервовыя сервер DNSы",
|
"fallback_dns_title": "Рэзервовыя DNS-серверы",
|
||||||
"fallback_dns_desc": "Спіс рэзервовых сервер DNSаў, якія выкарыстоўваюцца, калі вышэйшыя сервер DNSы не адказваюць. Сінтаксіс такі ж, як і ў галоўным полі ўверх.",
|
"fallback_dns_desc": "Спіс рэзервовых DNS-сервераў, якія выкарыстоўваюцца, калі вышэйшыя DNS-серверы не адказваюць. Сінтаксіс такі ж, як і ў галоўным полі ўверх.",
|
||||||
"fallback_dns_placeholder": "Увядзіце па адным рэзервовым серверы DNS у радку",
|
"fallback_dns_placeholder": "Увядзіце па адным рэзервовым серверы DNS у радку",
|
||||||
"local_ptr_title": "Прыватныя сервер DNSы",
|
"local_ptr_title": "Прыватныя DNS-серверы",
|
||||||
"local_ptr_desc": "DNS-серверы, якія AdGuard Home выкарыстоўвае для лакальных PTR-запытаў. Гэтыя серверы выкарыстоўваюцца, каб атрымаць даменавыя імёны кліентаў з прыватнымі IP-адрасамі, напрыклад «192.168.12.34», з дапамогай rDNS. Калі спіс пусты, AdGuard Home выкарыстоўвае прадвызначаныя DNS-серверы вашай АС.",
|
"local_ptr_desc": "DNS-серверы, якія AdGuard Home выкарыстоўвае для лакальных PTR-запытаў. Гэтыя серверы выкарыстоўваюцца, каб атрымаць даменавыя імёны кліентаў з прыватнымі IP-адрасамі, напрыклад «192.168.12.34», з дапамогай rDNS. Калі спіс пусты, AdGuard Home выкарыстоўвае прадвызначаныя DNS-серверы вашай АС.",
|
||||||
"local_ptr_default_resolver": "Па змаўчанні AdGuard Home выкарыстоўвае наступныя зваротныя DNS-рэзолверы: {{ip}}.",
|
"local_ptr_default_resolver": "Па змаўчанні AdGuard Home выкарыстоўвае наступныя зваротныя DNS-рэзолверы: {{ip}}.",
|
||||||
"local_ptr_no_default_resolver": "AdGuard Home не змог вызначыць прыдатныя прыватныя адваротныя DNS-рэзолверы для гэтай сістэмы.",
|
"local_ptr_no_default_resolver": "AdGuard Home не змог вызначыць прыдатныя прыватныя адваротныя DNS-рэзолверы для гэтай сістэмы.",
|
||||||
"local_ptr_placeholder": "Увядзіце па адным адрасе на радок",
|
"local_ptr_placeholder": "Увядзіце па адным адрасе на радок",
|
||||||
"resolve_clients_title": "Уключыць запытванне даменавых імёнаў для кліентаў",
|
"resolve_clients_title": "Уключыць запытванне даменавых імёнаў для кліентаў",
|
||||||
"resolve_clients_desc": "AdGuard Home будзе спрабаваць аўтаматычна вызначыць даменавыя імёны кліентаў праз PTR-запыты да адпаведных сервераў (прыватны сервер DNS для лакальных кліентаў, upstream-серверы для кліентаў з публічным IP-адрасам).",
|
"resolve_clients_desc": "AdGuard Home будзе спрабаваць аўтаматычна вызначыць даменавыя імёны кліентаў праз PTR-запыты да адпаведных сервераў (прыватны DNS-сервер для лакальных кліентаў, upstream-серверы для кліентаў з публічным IP-адрасам).",
|
||||||
"use_private_ptr_resolvers_title": "Ужываць прыватныя адваротныя DNS-рэзолверы",
|
"use_private_ptr_resolvers_title": "Ужываць прыватныя адваротныя DNS-рэзолверы",
|
||||||
"use_private_ptr_resolvers_desc": "Пасылаць адваротныя DNS-запыты для лакальна абслугоўных адрасоў на паказаныя серверы. Калі адключана, AdGuard Home будзе адказваць NXDOMAIN на ўсе падобныя PTR-запыты, апроч запытаў пра кліентаў, ужо вядомых па DHCP, /etc/hosts і гэтак далей.",
|
"use_private_ptr_resolvers_desc": "Пасылаць адваротныя DNS-запыты для лакальна абслугоўных адрасоў на паказаныя серверы. Калі адключана, AdGuard Home будзе адказваць NXDOMAIN на ўсе падобныя PTR-запыты, апроч запытаў пра кліентаў, ужо вядомых па DHCP, /etc/hosts і гэтак далей.",
|
||||||
"check_dhcp_servers": "Праверыць DHCP-серверы",
|
"check_dhcp_servers": "Праверыць DHCP-серверы",
|
||||||
@@ -101,13 +101,13 @@
|
|||||||
"compact": "Компактный",
|
"compact": "Компактный",
|
||||||
"nothing_found": "Нічога не знойдзена",
|
"nothing_found": "Нічога не знойдзена",
|
||||||
"faq": "FAQ",
|
"faq": "FAQ",
|
||||||
"version": "Версія",
|
"version": "версія",
|
||||||
"address": "Адрас",
|
"address": "Адрас",
|
||||||
"protocol": "Пратакол",
|
"protocol": "Пратакол",
|
||||||
"on": "УКЛ",
|
"on": "УКЛ",
|
||||||
"off": "Выкл",
|
"off": "Выкл",
|
||||||
"copyright": "Усе правы захаваныя",
|
"copyright": "Усе правы захаваныя",
|
||||||
"homepage": "Хатняя старонка",
|
"homepage": "Галоўная",
|
||||||
"report_an_issue": "Паведаміць пра праблему",
|
"report_an_issue": "Паведаміць пра праблему",
|
||||||
"privacy_policy": "Палітыка прыватнасці",
|
"privacy_policy": "Палітыка прыватнасці",
|
||||||
"enable_protection": "Уключыць абарону",
|
"enable_protection": "Уключыць абарону",
|
||||||
@@ -165,8 +165,8 @@
|
|||||||
"custom_filtering_rules": "Карыстальніцкія правілы фільтрацыі",
|
"custom_filtering_rules": "Карыстальніцкія правілы фільтрацыі",
|
||||||
"encryption_settings": "Налады шыфравання",
|
"encryption_settings": "Налады шыфравання",
|
||||||
"dhcp_settings": "Налады DHCP",
|
"dhcp_settings": "Налады DHCP",
|
||||||
"upstream_dns": "Upstream сервер DNSы",
|
"upstream_dns": "Upstream DNS-серверы",
|
||||||
"upstream_dns_help": "Увядзіце адрасы сервераў па адным у радку. <a>Даведацца больш </a> пра наладжванне сервер DNSаў.",
|
"upstream_dns_help": "Увядзіце адрасы сервераў па адным у радку. <a>Даведацца больш </a> пра наладжванне DNS-сервераў.",
|
||||||
"upstream_dns_configured_in_file": "Наладжаны ў {{path}}",
|
"upstream_dns_configured_in_file": "Наладжаны ў {{path}}",
|
||||||
"test_upstream_btn": "Тэст upstream сервераў",
|
"test_upstream_btn": "Тэст upstream сервераў",
|
||||||
"upstreams": "Upstreams",
|
"upstreams": "Upstreams",
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
"enabled_save_search_toast": "Уключаны бяспечны пошук",
|
"enabled_save_search_toast": "Уключаны бяспечны пошук",
|
||||||
"updated_save_search_toast": "Налады бяспечнага пошуку абноўлены",
|
"updated_save_search_toast": "Налады бяспечнага пошуку абноўлены",
|
||||||
"enabled_table_header": "УКЛ.",
|
"enabled_table_header": "УКЛ.",
|
||||||
"name_table_header": "Назва",
|
"name_table_header": "Імя",
|
||||||
"list_url_table_header": "URL-адрас спіса",
|
"list_url_table_header": "URL-адрас спіса",
|
||||||
"rules_count_table_header": "Колькасць правілаў:",
|
"rules_count_table_header": "Колькасць правілаў:",
|
||||||
"last_time_updated_table_header": "Апошняе абнаўленне",
|
"last_time_updated_table_header": "Апошняе абнаўленне",
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
"no_whitelist_added": "Белыя спісы не дададзены",
|
"no_whitelist_added": "Белыя спісы не дададзены",
|
||||||
"add_blocklist": "Дадаць чорны спіс",
|
"add_blocklist": "Дадаць чорны спіс",
|
||||||
"add_allowlist": "Дадаць белы спіс",
|
"add_allowlist": "Дадаць белы спіс",
|
||||||
"cancel_btn": "Скасаваць",
|
"cancel_btn": "Адмена",
|
||||||
"enter_name_hint": "Увядзіце імя",
|
"enter_name_hint": "Увядзіце імя",
|
||||||
"enter_url_or_path_hint": "Увядзіце URL-адрас ці абсалютны шлях да спіса",
|
"enter_url_or_path_hint": "Увядзіце URL-адрас ці абсалютны шлях да спіса",
|
||||||
"check_updates_btn": "Праверыць абнаўленні",
|
"check_updates_btn": "Праверыць абнаўленні",
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
"example_meaning_host_block": "адказаць 127.0.0.1 для example.org (але не для яго паддаменаў);",
|
"example_meaning_host_block": "адказаць 127.0.0.1 для example.org (але не для яго паддаменаў);",
|
||||||
"example_comment": "! Так можна дадаваць апісанне.",
|
"example_comment": "! Так можна дадаваць апісанне.",
|
||||||
"example_comment_meaning": "каментар;",
|
"example_comment_meaning": "каментар;",
|
||||||
"example_comment_hash": "# Таксама каментарый.",
|
"example_comment_hash": "# І вось так таксама.",
|
||||||
"example_regex_meaning": "блакаваць доступ да даменаў, якія адпавядаюць зададзенаму рэгулярнаму выразу.",
|
"example_regex_meaning": "блакаваць доступ да даменаў, якія адпавядаюць зададзенаму рэгулярнаму выразу.",
|
||||||
"example_upstream_regular": "звычайны DNS (наўзверх UDP);",
|
"example_upstream_regular": "звычайны DNS (наўзверх UDP);",
|
||||||
"example_upstream_regular_port": "звычайны DNS (праз UDP, імя хаста);",
|
"example_upstream_regular_port": "звычайны DNS (праз UDP, імя хаста);",
|
||||||
@@ -233,13 +233,13 @@
|
|||||||
"example_upstream_tcp_port": "звычайны DNS (праз TCP, імя хаста);",
|
"example_upstream_tcp_port": "звычайны DNS (праз TCP, імя хаста);",
|
||||||
"example_upstream_tcp_hostname": "звычайны DNS (праз TCP, імя хаста);",
|
"example_upstream_tcp_hostname": "звычайны DNS (праз TCP, імя хаста);",
|
||||||
"all_lists_up_to_date_toast": "Усе спісы ўжо абноўлены",
|
"all_lists_up_to_date_toast": "Усе спісы ўжо абноўлены",
|
||||||
"updated_upstream_dns_toast": "Upstream сервер DNSы абноўлены",
|
"updated_upstream_dns_toast": "Upstream DNS-серверы абноўлены",
|
||||||
"dns_test_ok_toast": "Паказаныя серверы DNS працуюць карэктна",
|
"dns_test_ok_toast": "Паказаныя серверы DNS працуюць карэктна",
|
||||||
"dns_test_not_ok_toast": "Сервер «{{key}}»: немагчыма выкарыстоўваць, праверце слушнасць напісання",
|
"dns_test_not_ok_toast": "Сервер «{{key}}»: немагчыма выкарыстоўваць, праверце слушнасць напісання",
|
||||||
"dns_test_parsing_error_toast": "Раздзел {{section}}: радок {{line}}: немагчыма выкарыстоўваць, праверце слушнасць напісання",
|
"dns_test_parsing_error_toast": "Раздзел {{section}}: радок {{line}}: немагчыма выкарыстоўваць, праверце слушнасць напісання",
|
||||||
"dns_test_warning_toast": "Upstream «{{key}}» не адказвае на тэставыя запыты і можа не працаваць належным чынам",
|
"dns_test_warning_toast": "Upstream «{{key}}» не адказвае на тэставыя запыты і можа не працаваць належным чынам",
|
||||||
"unblock": "Адблакаваць",
|
"unblock": "Адблакаваць",
|
||||||
"block": "Заблакіраваць",
|
"block": "Заблакаваць",
|
||||||
"disallow_this_client": "Забараніць доступ гэтаму кліенту",
|
"disallow_this_client": "Забараніць доступ гэтаму кліенту",
|
||||||
"allow_this_client": "Дазволіць доступ гэтаму кліенту",
|
"allow_this_client": "Дазволіць доступ гэтаму кліенту",
|
||||||
"block_for_this_client_only": "Заблакаваць толькі для гэтага кліента",
|
"block_for_this_client_only": "Заблакаваць толькі для гэтага кліента",
|
||||||
@@ -259,7 +259,7 @@
|
|||||||
"no_logs_found": "Логі не знойдзены",
|
"no_logs_found": "Логі не знойдзены",
|
||||||
"refresh_btn": "Абнавіць",
|
"refresh_btn": "Абнавіць",
|
||||||
"previous_btn": "Назад",
|
"previous_btn": "Назад",
|
||||||
"next_btn": "Далей",
|
"next_btn": "Наперад",
|
||||||
"loading_table_status": "Загрузка...",
|
"loading_table_status": "Загрузка...",
|
||||||
"page_table_footer_text": "Старонка",
|
"page_table_footer_text": "Старонка",
|
||||||
"rows_table_footer_text": "радкоў",
|
"rows_table_footer_text": "радкоў",
|
||||||
@@ -280,7 +280,7 @@
|
|||||||
"query_log_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання запытаў? Пры памяншэнні інтэрвалу, некаторыя даныя могуць быць страчаны",
|
"query_log_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання запытаў? Пры памяншэнні інтэрвалу, некаторыя даныя могуць быць страчаны",
|
||||||
"anonymize_client_ip": "Ананімізацыя IP-адрасы кліента",
|
"anonymize_client_ip": "Ананімізацыя IP-адрасы кліента",
|
||||||
"anonymize_client_ip_desc": "Не захоўвайце поўныя IP-адрасы гэтых удзельнікаў у часопісах або статыстыцы",
|
"anonymize_client_ip_desc": "Не захоўвайце поўныя IP-адрасы гэтых удзельнікаў у часопісах або статыстыцы",
|
||||||
"dns_config": "Налады сервер DNSа",
|
"dns_config": "Налады DNS-сервера",
|
||||||
"dns_cache_config": "Налада кэша DNS",
|
"dns_cache_config": "Налада кэша DNS",
|
||||||
"dns_cache_config_desc": "Тут можна наладзіць кэш DNS",
|
"dns_cache_config_desc": "Тут можна наладзіць кэш DNS",
|
||||||
"blocking_mode": "Рэжым блакавання",
|
"blocking_mode": "Рэжым блакавання",
|
||||||
@@ -342,14 +342,14 @@
|
|||||||
"unknown_filter": "Невядомы фільтр {{filterId}}",
|
"unknown_filter": "Невядомы фільтр {{filterId}}",
|
||||||
"known_tracker": "Вядомы трэкер",
|
"known_tracker": "Вядомы трэкер",
|
||||||
"install_welcome_title": "Сардэчна запрашаем у AdGuard Home!",
|
"install_welcome_title": "Сардэчна запрашаем у AdGuard Home!",
|
||||||
"install_welcome_desc": "AdGuard Home – гэта сервер DNS, што блакуе рэкламу і трэкінг. Яго мэта – даць вам магчымасць кантраляваць усю ваша сеціва і ўсе падлучаныя прылады. Ён не патрабуе ўсталёўкі кліенцкіх праграм.",
|
"install_welcome_desc": "AdGuard Home – гэта DNS-сервер, што блакуе рэкламу і трэкінг. Яго мэта – даць вам магчымасць кантраляваць усю ваша сеціва і ўсе падлучаныя прылады. Ён не патрабуе ўсталёўкі кліенцкіх праграм.",
|
||||||
"install_settings_title": "Ўэб-інтэрфейс адміністравання",
|
"install_settings_title": "Ўэб-інтэрфейс адміністравання",
|
||||||
"install_settings_listen": "Інтэрфейс сеціва",
|
"install_settings_listen": "Інтэрфейс сеціва",
|
||||||
"install_settings_port": "Порт",
|
"install_settings_port": "Порт",
|
||||||
"install_settings_interface_link": "Ваш ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны па наступных адрасах:",
|
"install_settings_interface_link": "Ваш ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны па наступных адрасах:",
|
||||||
"form_error_port": "Увядзіце карэктны нумар порта",
|
"form_error_port": "Увядзіце карэктны нумар порта",
|
||||||
"install_settings_dns": "DNS-сервер",
|
"install_settings_dns": "DNS-сервер",
|
||||||
"install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне сервер DNSа на адным з наступных адрасоў:",
|
"install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне DNS-сервера на адным з наступных адрасоў:",
|
||||||
"install_settings_all_interfaces": "Усе інтэрфейсы",
|
"install_settings_all_interfaces": "Усе інтэрфейсы",
|
||||||
"install_auth_title": "Аўтарызацыя",
|
"install_auth_title": "Аўтарызацыя",
|
||||||
"install_auth_desc": "Настойліва рэкамендуецца наладзіць аўтэнтыфікацыю паролем для ўэб-інтэрфейсу AdGuard Home. Нават калі ён даступны толькі ў вашай лакальнай сетцы, важна абараніць яго ад неабмежаванага доступу.",
|
"install_auth_desc": "Настойліва рэкамендуецца наладзіць аўтэнтыфікацыю паролем для ўэб-інтэрфейсу AdGuard Home. Нават калі ён даступны толькі ў вашай лакальнай сетцы, важна абараніць яго ад неабмежаванага доступу.",
|
||||||
@@ -365,17 +365,17 @@
|
|||||||
"install_submit_desc": "Працэдура налады завершана і вы гатовы пачаць выкарыстанне AdGuard Home.",
|
"install_submit_desc": "Працэдура налады завершана і вы гатовы пачаць выкарыстанне AdGuard Home.",
|
||||||
"install_devices_router": "Роўтар",
|
"install_devices_router": "Роўтар",
|
||||||
"install_devices_router_desc": "Такая наладка аўтаматычна пакрые ўсе прылады, што выкарыстоўваюць ваш хатні роўтар, і вам не трэба будзе наладжваць кожнае з іх у асобнасці.",
|
"install_devices_router_desc": "Такая наладка аўтаматычна пакрые ўсе прылады, што выкарыстоўваюць ваш хатні роўтар, і вам не трэба будзе наладжваць кожнае з іх у асобнасці.",
|
||||||
"install_devices_address": "сервер DNS AdGuard Home даступны па наступных адрасах",
|
"install_devices_address": "DNS-сервер AdGuard Home даступны па наступных адрасах",
|
||||||
"install_devices_router_list_1": "Адкрыйце налады вашага роўтара. Звычайна вы можаце адкрыць іх у вашым браўзары, напрыклад, http://192.168.0.1/ ці http://192.168.1.1/. Вас могуць папрасіць увесці пароль. Калі вы не помніце яго, пароль часта можна скінуць, націснуўшы на кнопку на самым роўтары. Некаторыя роўтары патрабуюць адмысловага дадатку, які ў гэтым выпадку павінен быць ужо ўсталявана на ваш кампутар ці тэлефон.",
|
"install_devices_router_list_1": "Адкрыйце налады вашага роўтара. Звычайна вы можаце адкрыць іх у вашым браўзары, напрыклад, http://192.168.0.1/ ці http://192.168.1.1/. Вас могуць папрасіць увесці пароль. Калі вы не помніце яго, пароль часта можна скінуць, націснуўшы на кнопку на самым роўтары. Некаторыя роўтары патрабуюць адмысловага дадатку, які ў гэтым выпадку павінен быць ужо ўсталявана на ваш кампутар ці тэлефон.",
|
||||||
"install_devices_router_list_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары «DNS» поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.",
|
"install_devices_router_list_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары «DNS» поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.",
|
||||||
"install_devices_router_list_3": "Увядзіце туды адрас вашага AdGuard Home.",
|
"install_devices_router_list_3": "Увядзіце туды адрас вашага AdGuard Home.",
|
||||||
"install_devices_router_list_4": "Вы не можаце ўсталяваць уласны сервер DNS на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе сервер DNSаў для вашай пэўнай мадэлі маршрутызатара.",
|
"install_devices_router_list_4": "Вы не можаце ўсталяваць уласны DNS-сервер на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе DNS-сервераў для вашай пэўнай мадэлі маршрутызатара.",
|
||||||
"install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню «Пуск» ці праз пошук Windows.",
|
"install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню «Пуск» ці праз пошук Windows.",
|
||||||
"install_devices_windows_list_2": "Перайдзіце ў «Сеціва і інтэрнэт», а потым у «Цэнтр кіравання сеціва і агульным доступам».",
|
"install_devices_windows_list_2": "Перайдзіце ў «Сеціва і інтэрнэт», а потым у «Цэнтр кіравання сеціва і агульным доступам».",
|
||||||
"install_devices_windows_list_3": "У левым боку экрана клікніце «Змена параметраў адаптара».",
|
"install_devices_windows_list_3": "У левым боку экрана клікніце «Змена параметраў адаптара».",
|
||||||
"install_devices_windows_list_4": "Пстрыкніце правай кнопкай мышы ваша актыўнае злучэнне і абярыце Уласцівасці.",
|
"install_devices_windows_list_4": "Пстрыкніце правай кнопкай мышы ваша актыўнае злучэнне і абярыце Уласцівасці.",
|
||||||
"install_devices_windows_list_5": "Знайдзіце ў спісе пункт «IP версіі 4 (TCP/IPv4)», вылучыце яго і потым ізноў націсніце «Уласцівасці».",
|
"install_devices_windows_list_5": "Знайдзіце ў спісе пункт «IP версіі 4 (TCP/IPv4)», вылучыце яго і потым ізноў націсніце «Уласцівасці».",
|
||||||
"install_devices_windows_list_6": "Абярыце «Выкарыстаць наступныя адрасы сервер DNSаў» і ўвядзіце адрас AdGuard Home.",
|
"install_devices_windows_list_6": "Абярыце «Выкарыстаць наступныя адрасы DNS-сервераў» і ўвядзіце адрас AdGuard Home.",
|
||||||
"install_devices_macos_list_1": "Клікніце па абразку Apple і перайдзіце ў Сістэмныя налады.",
|
"install_devices_macos_list_1": "Клікніце па абразку Apple і перайдзіце ў Сістэмныя налады.",
|
||||||
"install_devices_macos_list_2": "Клікніце па іконцы Сеціва.",
|
"install_devices_macos_list_2": "Клікніце па іконцы Сеціва.",
|
||||||
"install_devices_macos_list_3": "Абярыце першае падлучэнне ў спісе і націсніце кнопку «Дадаткова».",
|
"install_devices_macos_list_3": "Абярыце першае падлучэнне ў спісе і націсніце кнопку «Дадаткова».",
|
||||||
@@ -415,7 +415,7 @@
|
|||||||
"encryption_key": "Прыватны ключ",
|
"encryption_key": "Прыватны ключ",
|
||||||
"encryption_key_input": "Скапіюйце сюды прыватны ключ у PEM-кадоўцы.",
|
"encryption_key_input": "Скапіюйце сюды прыватны ключ у PEM-кадоўцы.",
|
||||||
"encryption_enable": "Уключыць шыфраванне (HTTPS, DNS-over-HTTPS і DNS-over-TLS)",
|
"encryption_enable": "Уключыць шыфраванне (HTTPS, DNS-over-HTTPS і DNS-over-TLS)",
|
||||||
"encryption_enable_desc": "Калі шыфраванне ўлучана, ўэб-інтэрфейс AdGuard Home будзе працаваць па HTTPS, а сервер DNS будзе таксама працаваць па DNS-over-HTTPS і DNS-over-TLS.",
|
"encryption_enable_desc": "Калі шыфраванне ўлучана, ўэб-інтэрфейс AdGuard Home будзе працаваць па HTTPS, а DNS-сервер будзе таксама працаваць па DNS-over-HTTPS і DNS-over-TLS.",
|
||||||
"encryption_chain_valid": "Ланцужок сертыфікатаў валідны",
|
"encryption_chain_valid": "Ланцужок сертыфікатаў валідны",
|
||||||
"encryption_chain_invalid": "Ланцужок сертыфікатаў не валідны",
|
"encryption_chain_invalid": "Ланцужок сертыфікатаў не валідны",
|
||||||
"encryption_key_valid": "Валідны {{type}} прыватны ключ",
|
"encryption_key_valid": "Валідны {{type}} прыватны ключ",
|
||||||
@@ -435,8 +435,8 @@
|
|||||||
"update_announcement": "AdGuard Home {{version}} ужо даступная! <0>Націсніце сюды</0>, каб даведацца больш.",
|
"update_announcement": "AdGuard Home {{version}} ужо даступная! <0>Націсніце сюды</0>, каб даведацца больш.",
|
||||||
"setup_guide": "Інструкцыя па наладзе",
|
"setup_guide": "Інструкцыя па наладзе",
|
||||||
"dns_addresses": "Адрасы DNS",
|
"dns_addresses": "Адрасы DNS",
|
||||||
"dns_start": "сервер DNS запускаецца",
|
"dns_start": "DNS-сервер запускаецца",
|
||||||
"dns_status_error": "Памылка праверкі стану сервер DNSа",
|
"dns_status_error": "Памылка праверкі стану DNS-сервера",
|
||||||
"down": "Уніз",
|
"down": "Уніз",
|
||||||
"fix": "Выправіць",
|
"fix": "Выправіць",
|
||||||
"dns_providers": "<0>Спіс вядомых DNS-правайдараў</0> на выбар.",
|
"dns_providers": "<0>Спіс вядомых DNS-правайдараў</0> на выбар.",
|
||||||
@@ -449,7 +449,7 @@
|
|||||||
"settings_global": "Глабальныя",
|
"settings_global": "Глабальныя",
|
||||||
"settings_custom": "Свае",
|
"settings_custom": "Свае",
|
||||||
"table_client": "Кліент",
|
"table_client": "Кліент",
|
||||||
"table_name": "Назва",
|
"table_name": "Імя",
|
||||||
"save_btn": "Захаваць",
|
"save_btn": "Захаваць",
|
||||||
"client_add": "Дадаць кліента",
|
"client_add": "Дадаць кліента",
|
||||||
"client_new": "Новы кліент",
|
"client_new": "Новы кліент",
|
||||||
@@ -475,7 +475,7 @@
|
|||||||
"auto_clients_title": "Кліенты (runtime)",
|
"auto_clients_title": "Кліенты (runtime)",
|
||||||
"auto_clients_desc": "Інфармацыя аб IP-адрасах прылад, якія выкарыстоўваюць або могуць выкарыстоўваць AdGuard Home. Гэтая інфармацыя збіраецца з некалькіх крыніц, уключаючы файлы хостаў, зваротны DNS і г.д.",
|
"auto_clients_desc": "Інфармацыя аб IP-адрасах прылад, якія выкарыстоўваюць або могуць выкарыстоўваць AdGuard Home. Гэтая інфармацыя збіраецца з некалькіх крыніц, уключаючы файлы хостаў, зваротны DNS і г.д.",
|
||||||
"access_title": "Налады доступу",
|
"access_title": "Налады доступу",
|
||||||
"access_desc": "Тут вы можаце наладзіць правілы доступу да сервер DNSу AdGuard Home",
|
"access_desc": "Тут вы можаце наладзіць правілы доступу да DNS-серверу AdGuard Home",
|
||||||
"access_allowed_title": "Дазволеныя кліенты",
|
"access_allowed_title": "Дазволеныя кліенты",
|
||||||
"access_allowed_desc": "Спіс CIDR, IP-адрасоў або <a>ClientID</a>. Калі ў гэтым спісе ёсць запісы, AdGuard Home будзе прымаць запыты толькі ад гэтых кліентаў.",
|
"access_allowed_desc": "Спіс CIDR, IP-адрасоў або <a>ClientID</a>. Калі ў гэтым спісе ёсць запісы, AdGuard Home будзе прымаць запыты толькі ад гэтых кліентаў.",
|
||||||
"access_disallowed_title": "Забароненыя кліенты",
|
"access_disallowed_title": "Забароненыя кліенты",
|
||||||
@@ -596,7 +596,7 @@
|
|||||||
"disable_ipv6_desc": "Ігнараваць усе запыты DNS для адрасоў IPv6 (тып AAAA) і выдаленне дадзеных IPv6 з адказаў тыпу HTTPS.",
|
"disable_ipv6_desc": "Ігнараваць усе запыты DNS для адрасоў IPv6 (тып AAAA) і выдаленне дадзеных IPv6 з адказаў тыпу HTTPS.",
|
||||||
"fastest_addr": "Найхуткі IP-адрас",
|
"fastest_addr": "Найхуткі IP-адрас",
|
||||||
"fastest_addr_desc": "Апытайце ўсе DNS-серверы і вярніце самы хуткі IP-адрас сярод усіх адказаў. Гэта замарудзіць выкананне DNS-запытаў, бо нам давядзецца чакаць адказаў ад усіх DNS-сервераў, але палепшыць агульную ўзаемасувязь.",
|
"fastest_addr_desc": "Апытайце ўсе DNS-серверы і вярніце самы хуткі IP-адрас сярод усіх адказаў. Гэта замарудзіць выкананне DNS-запытаў, бо нам давядзецца чакаць адказаў ад усіх DNS-сервераў, але палепшыць агульную ўзаемасувязь.",
|
||||||
"autofix_warning_text": "Пры націску «Выправіць» AdGuard Home наладзіць вашу сістэму на выкарыстанне сервер DNSа AdGuard Home.",
|
"autofix_warning_text": "Пры націску «Выправіць» AdGuard Home наладзіць вашу сістэму на выкарыстанне DNS-сервера AdGuard Home.",
|
||||||
"autofix_warning_list": "Будуць выконвацца наступныя заданні: <0>Дэактываваць сістэмны DNSStubListener</0> <0>Усталяваць адрас сервера DNS на 127.0.0.1</0> <0>Стварыць сімвалічную спасылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Спыніць DNSStubListener (перазагрузіць сістэмную службу)</0>.",
|
"autofix_warning_list": "Будуць выконвацца наступныя заданні: <0>Дэактываваць сістэмны DNSStubListener</0> <0>Усталяваць адрас сервера DNS на 127.0.0.1</0> <0>Стварыць сімвалічную спасылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Спыніць DNSStubListener (перазагрузіць сістэмную службу)</0>.",
|
||||||
"autofix_warning_result": "У выніку ўсе DNS-запыты ад вашай сістэмы будуць па змаўчанні апрацоўвацца AdGuard Home.\n",
|
"autofix_warning_result": "У выніку ўсе DNS-запыты ад вашай сістэмы будуць па змаўчанні апрацоўвацца AdGuard Home.\n",
|
||||||
"tags_title": "Тэгі",
|
"tags_title": "Тэгі",
|
||||||
@@ -634,12 +634,12 @@
|
|||||||
"validated_with_dnssec": "Проверено с помощью DNSSEC",
|
"validated_with_dnssec": "Проверено с помощью DNSSEC",
|
||||||
"all_queries": "Усе запыты",
|
"all_queries": "Усе запыты",
|
||||||
"show_blocked_responses": "Заблакавана",
|
"show_blocked_responses": "Заблакавана",
|
||||||
"show_whitelisted_responses": "У белым спісе",
|
"show_whitelisted_responses": "Белы спіс",
|
||||||
"show_processed_responses": "Апрацавана",
|
"show_processed_responses": "Апрацавана",
|
||||||
"blocked_safebrowsing": "Заблакіравана згодна з базай даных Safe Browsing",
|
"blocked_safebrowsing": "Заблакіравана згодна з базай даных Safe Browsing",
|
||||||
"blocked_adult_websites": "Заблакавана Бацькоўскім кантролем",
|
"blocked_adult_websites": "Заблакавана Бацькоўскім кантролем",
|
||||||
"blocked_threats": "Заблакавана пагроз",
|
"blocked_threats": "Заблакавана пагроз",
|
||||||
"allowed": "У белым спісе",
|
"allowed": "Дазволены",
|
||||||
"filtered": "Адфільтраваныя",
|
"filtered": "Адфільтраваныя",
|
||||||
"rewritten": "Перапісаныя",
|
"rewritten": "Перапісаныя",
|
||||||
"safe_search": "Бяспечны пошук",
|
"safe_search": "Бяспечны пошук",
|
||||||
@@ -738,7 +738,7 @@
|
|||||||
"thursday_short": "Чц.",
|
"thursday_short": "Чц.",
|
||||||
"friday_short": "Пт.",
|
"friday_short": "Пт.",
|
||||||
"saturday_short": "Сб.",
|
"saturday_short": "Сб.",
|
||||||
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream сервер DNSаў",
|
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream DNS-сервераў",
|
||||||
"enable_upstream_dns_cache": "Ўключыць кэшаванне для карыстацкай канфігурацыі upstream-сервераў гэтага кліента",
|
"enable_upstream_dns_cache": "Ўключыць кэшаванне для карыстацкай канфігурацыі upstream-сервераў гэтага кліента",
|
||||||
"dns_cache_size": "Памер кэша DNS, у байтах"
|
"dns_cache_size": "Памер кэша DNS, у байтах"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Zakázaný",
|
"blocklist": "Zakázaný",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Velikost mezipaměti",
|
"cache_size": "Velikost mezipaměti",
|
||||||
"cache_size_desc": "Velikost mezipaměti DNS (v bajtech). Chcete-li ukládání do mezipaměti zakázat, nastavte 0.",
|
"cache_size_desc": "Velikost mezipaměti DNS (v bajtech). Chcete-li ukládání do mezipaměti zakázat, ponechte prázdné.",
|
||||||
"cache_ttl_min_override": "Přepsat minimální hodnotu TTL",
|
"cache_ttl_min_override": "Přepsat minimální hodnotu TTL",
|
||||||
"cache_ttl_max_override": "Přepsat maximální hodnotu TTL",
|
"cache_ttl_max_override": "Přepsat maximální hodnotu TTL",
|
||||||
"enter_cache_size": "Zadejte velikost mezipaměti (v bajtech)",
|
"enter_cache_size": "Zadejte velikost mezipaměti (v bajtech)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Sortliste",
|
"blocklist": "Sortliste",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Cache-størrelse",
|
"cache_size": "Cache-størrelse",
|
||||||
"cache_size_desc": "DNS cache-størrelse (i bytes). Sæt til 0 for at deaktivere cache.",
|
"cache_size_desc": "DNS cache-størrelse (i bytes). Lad stå tomt for at deaktivere cache.",
|
||||||
"cache_ttl_min_override": "Tilsidesæt minimum TTL",
|
"cache_ttl_min_override": "Tilsidesæt minimum TTL",
|
||||||
"cache_ttl_max_override": "Tilsidesæt maksimal TTL",
|
"cache_ttl_max_override": "Tilsidesæt maksimal TTL",
|
||||||
"enter_cache_size": "Angiv cache-størrelse (bytes)",
|
"enter_cache_size": "Angiv cache-størrelse (bytes)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Sperrliste",
|
"blocklist": "Sperrliste",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Größe des Cache",
|
"cache_size": "Größe des Cache",
|
||||||
"cache_size_desc": "Größe des DNS-Cache (in Bytes). Um das Caching zu deaktivieren, setzen Sie den Wert auf 0.",
|
"cache_size_desc": "Größe des DNS-Zwischenspeichers (in Bytes)",
|
||||||
"cache_ttl_min_override": "TTL-Minimalwert überschreiben",
|
"cache_ttl_min_override": "TTL-Minimalwert überschreiben",
|
||||||
"cache_ttl_max_override": "TTL-Höchstwert überschreiben",
|
"cache_ttl_max_override": "TTL-Höchstwert überschreiben",
|
||||||
"enter_cache_size": "Größe des Cache (Bytes) eingeben",
|
"enter_cache_size": "Größe des Cache (Bytes) eingeben",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Blocklist",
|
"blocklist": "Blocklist",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Cache size",
|
"cache_size": "Cache size",
|
||||||
"cache_size_desc": "DNS cache size (in bytes). To disable caching, set to 0.",
|
"cache_size_desc": "DNS cache size (in bytes). To disable caching, leave empty.",
|
||||||
"cache_ttl_min_override": "Override minimum TTL",
|
"cache_ttl_min_override": "Override minimum TTL",
|
||||||
"cache_ttl_max_override": "Override maximum TTL",
|
"cache_ttl_max_override": "Override maximum TTL",
|
||||||
"enter_cache_size": "Enter cache size (bytes)",
|
"enter_cache_size": "Enter cache size (bytes)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Lista de bloqueo",
|
"blocklist": "Lista de bloqueo",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Tamaño de la caché",
|
"cache_size": "Tamaño de la caché",
|
||||||
"cache_size_desc": "Tamaño de la caché DNS (en bytes). Para desactivar el almacenamiento en caché, configúralo en 0.",
|
"cache_size_desc": "Tamaño de la caché DNS (en bytes). Para deshabilitar el almacenamiento en caché, déjalo vacío.",
|
||||||
"cache_ttl_min_override": "Anular TTL mínimo",
|
"cache_ttl_min_override": "Anular TTL mínimo",
|
||||||
"cache_ttl_max_override": "Anular TTL máximo",
|
"cache_ttl_max_override": "Anular TTL máximo",
|
||||||
"enter_cache_size": "Ingresa el tamaño de la caché (bytes)",
|
"enter_cache_size": "Ingresa el tamaño de la caché (bytes)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Liste de blocage",
|
"blocklist": "Liste de blocage",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Taille du cache",
|
"cache_size": "Taille du cache",
|
||||||
"cache_size_desc": "Taille du cache DNS (en octets). Pour désactiver la mise en cache, mettez la valeur sur 0.",
|
"cache_size_desc": "Taille du cache DNS (en octets). Pour désactiver la mise en cache, laissez vide.",
|
||||||
"cache_ttl_min_override": "Remplacer le TTL minimum",
|
"cache_ttl_min_override": "Remplacer le TTL minimum",
|
||||||
"cache_ttl_max_override": "Remplacer le TTL maximum",
|
"cache_ttl_max_override": "Remplacer le TTL maximum",
|
||||||
"enter_cache_size": "Entrer la taille du cache (octets)",
|
"enter_cache_size": "Entrer la taille du cache (octets)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Lista nera",
|
"blocklist": "Lista nera",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Dimensioni cache",
|
"cache_size": "Dimensioni cache",
|
||||||
"cache_size_desc": "Dimensione della cache DNS (in byte). Per disabilitare la cache, impostare su 0.",
|
"cache_size_desc": "Dimensione della cache DNS (in byte). Per disabilitare la memorizzazione nella cache, lascia vuoto.",
|
||||||
"cache_ttl_min_override": "Sovrascrivi TTL minimo",
|
"cache_ttl_min_override": "Sovrascrivi TTL minimo",
|
||||||
"cache_ttl_max_override": "Sovrascrivi TTL massimo",
|
"cache_ttl_max_override": "Sovrascrivi TTL massimo",
|
||||||
"enter_cache_size": "Immetti dimensioni cache (in byte)",
|
"enter_cache_size": "Immetti dimensioni cache (in byte)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "ブロックリスト",
|
"blocklist": "ブロックリスト",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "キャッシュサイズ",
|
"cache_size": "キャッシュサイズ",
|
||||||
"cache_size_desc": "DNSキャッシュサイズ(バイト単位)※キャッシュを無効化するには、「0」(ゼロ)にしてください。",
|
"cache_size_desc": "DNSキャッシュサイズ(バイト単位)。※キャッシュを無効化するには、この欄を空してください。",
|
||||||
"cache_ttl_min_override": "最小TTLの上書き(秒単位)",
|
"cache_ttl_min_override": "最小TTLの上書き(秒単位)",
|
||||||
"cache_ttl_max_override": "最大TTLの上書き(秒単位)",
|
"cache_ttl_max_override": "最大TTLの上書き(秒単位)",
|
||||||
"enter_cache_size": "キャッシュサイズ(バイト単位)を入力してください",
|
"enter_cache_size": "キャッシュサイズ(バイト単位)を入力してください",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "차단 목록",
|
"blocklist": "차단 목록",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "캐시 크기",
|
"cache_size": "캐시 크기",
|
||||||
"cache_size_desc": "DNS 캐시 크기(바이트). 캐싱을 사용하지 않으려면 0으로 설정합니다.",
|
"cache_size_desc": "DNS 캐시 크기(바이트). 캐싱을 비활성화하려면 비워 둡니다.",
|
||||||
"cache_ttl_min_override": "최소 TTL (초) 무시",
|
"cache_ttl_min_override": "최소 TTL (초) 무시",
|
||||||
"cache_ttl_max_override": "최대 TTL (초) 무시",
|
"cache_ttl_max_override": "최대 TTL (초) 무시",
|
||||||
"enter_cache_size": "캐시 크기를 입력하세요",
|
"enter_cache_size": "캐시 크기를 입력하세요",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Blokkeerlijst",
|
"blocklist": "Blokkeerlijst",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Cache grootte",
|
"cache_size": "Cache grootte",
|
||||||
"cache_size_desc": "DNS-cachegrootte (in bytes). Om caching uit te schakelen, stel deze in op 0.",
|
"cache_size_desc": "DNS-cachegrootte (in bytes). Leeg laten om caching uit te schakelen.",
|
||||||
"cache_ttl_min_override": "Minimale TTL overschrijven",
|
"cache_ttl_min_override": "Minimale TTL overschrijven",
|
||||||
"cache_ttl_max_override": "Maximale TTL overschrijven",
|
"cache_ttl_max_override": "Maximale TTL overschrijven",
|
||||||
"enter_cache_size": "Cache grootte invoeren (bytes)",
|
"enter_cache_size": "Cache grootte invoeren (bytes)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Lista de bloqueio",
|
"blocklist": "Lista de bloqueio",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Tamanho do cache",
|
"cache_size": "Tamanho do cache",
|
||||||
"cache_size_desc": "Tamanho do cache do DNS (em bytes). Para desativar o cache, defina como 0.",
|
"cache_size_desc": "Tamanho do cache do DNS (em bytes). Para desativar o cache, deixe em branco.",
|
||||||
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
|
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
|
||||||
"cache_ttl_max_override": "Sobrepor o TTL máximo",
|
"cache_ttl_max_override": "Sobrepor o TTL máximo",
|
||||||
"enter_cache_size": "Digite o tamanho do cache (bytes)",
|
"enter_cache_size": "Digite o tamanho do cache (bytes)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Lista de bloqueio",
|
"blocklist": "Lista de bloqueio",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Tamanho do cache",
|
"cache_size": "Tamanho do cache",
|
||||||
"cache_size_desc": "Tamanho do cache DNS (em bytes). Para desativar o cache, defina como 0.",
|
"cache_size_desc": "Tamanho do cache DNS (em bytes). Para desativar o cache, deixar o campo vazio.",
|
||||||
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
|
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
|
||||||
"cache_ttl_max_override": "Sobrepor o TTL máximo",
|
"cache_ttl_max_override": "Sobrepor o TTL máximo",
|
||||||
"enter_cache_size": "Digite o tamanho do cache (bytes)",
|
"enter_cache_size": "Digite o tamanho do cache (bytes)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Чёрный список",
|
"blocklist": "Чёрный список",
|
||||||
"milliseconds_abbreviation": "мс",
|
"milliseconds_abbreviation": "мс",
|
||||||
"cache_size": "Размер кеша",
|
"cache_size": "Размер кеша",
|
||||||
"cache_size_desc": "Размер кеша DNS (в байтах). Чтобы отключить кеширование, установите значение 0.",
|
"cache_size_desc": "Размера кеша DNS (в байтах). Чтобы отключить кэширование, оставьте поле пустым.",
|
||||||
"cache_ttl_min_override": "Переопределить минимальный TTL",
|
"cache_ttl_min_override": "Переопределить минимальный TTL",
|
||||||
"cache_ttl_max_override": "Переопределить максимальный TTL",
|
"cache_ttl_max_override": "Переопределить максимальный TTL",
|
||||||
"enter_cache_size": "Введите размер кеша (в байтах)",
|
"enter_cache_size": "Введите размер кеша (в байтах)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Zoznam blokovaní",
|
"blocklist": "Zoznam blokovaní",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Veľkosť cache",
|
"cache_size": "Veľkosť cache",
|
||||||
"cache_size_desc": "Veľkosť vyrovnávacej pamäte DNS (v bajtoch). Ak chcete vypnúť ukladanie do vyrovnávacej pamäte, nastavte hodnotu 0.",
|
"cache_size_desc": "Veľkosť vyrovnávacej pamäte DNS (v bajtoch). Ak chcete zakázať ukladanie do vyrovnávacej pamäte, ponechajte pole prázdne.",
|
||||||
"cache_ttl_min_override": "Prepísať minimálne TTL",
|
"cache_ttl_min_override": "Prepísať minimálne TTL",
|
||||||
"cache_ttl_max_override": "Prepísať maximálne TTL",
|
"cache_ttl_max_override": "Prepísať maximálne TTL",
|
||||||
"enter_cache_size": "Zadať veľkosť cache (v bajtoch)",
|
"enter_cache_size": "Zadať veľkosť cache (v bajtoch)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "Engel listesi",
|
"blocklist": "Engel listesi",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "Önbellek boyutu",
|
"cache_size": "Önbellek boyutu",
|
||||||
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden). Önbelleği devre dışı bırakmak için 0 olarak ayarlayın.",
|
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden). Önbelleğe almayı devre dışı bırakmak için boş bırakın.",
|
||||||
"cache_ttl_min_override": "Minimum kullanım süresini geçersiz kıl",
|
"cache_ttl_min_override": "Minimum kullanım süresini geçersiz kıl",
|
||||||
"cache_ttl_max_override": "Maksimum kullanım süresini geçersiz kıl",
|
"cache_ttl_max_override": "Maksimum kullanım süresini geçersiz kıl",
|
||||||
"enter_cache_size": "Önbellek boyutunu girin (bayt)",
|
"enter_cache_size": "Önbellek boyutunu girin (bayt)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "黑名单",
|
"blocklist": "黑名单",
|
||||||
"milliseconds_abbreviation": "毫秒",
|
"milliseconds_abbreviation": "毫秒",
|
||||||
"cache_size": "缓存大小",
|
"cache_size": "缓存大小",
|
||||||
"cache_size_desc": "DNS 缓存大小(单位:字节)。若要禁用缓存,请设置为 0。",
|
"cache_size_desc": "DNS 缓存大小(单位:字节)。若要关闭缓存,请留空。",
|
||||||
"cache_ttl_min_override": "覆盖最小 TTL 值",
|
"cache_ttl_min_override": "覆盖最小 TTL 值",
|
||||||
"cache_ttl_max_override": "覆盖最大 TTL 值",
|
"cache_ttl_max_override": "覆盖最大 TTL 值",
|
||||||
"enter_cache_size": "输入缓存大小(字节)",
|
"enter_cache_size": "输入缓存大小(字节)",
|
||||||
|
|||||||
@@ -656,7 +656,7 @@
|
|||||||
"blocklist": "封鎖清單",
|
"blocklist": "封鎖清單",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "快取大小",
|
"cache_size": "快取大小",
|
||||||
"cache_size_desc": "DNS 快取大小(位元組)。若要停用快取,請設為 0。",
|
"cache_size_desc": "DNS 快取大小 (位元組)。若要停用快取,請留空。",
|
||||||
"cache_ttl_min_override": "覆寫最小的存活時間(TTL)",
|
"cache_ttl_min_override": "覆寫最小的存活時間(TTL)",
|
||||||
"cache_ttl_max_override": "覆寫最大的存活時間(TTL)",
|
"cache_ttl_max_override": "覆寫最大的存活時間(TTL)",
|
||||||
"enter_cache_size": "輸入快取大小(位元組)",
|
"enter_cache_size": "輸入快取大小(位元組)",
|
||||||
|
|||||||
12
go.mod
12
go.mod
@@ -3,8 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
|
|||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.75.5
|
github.com/AdguardTeam/dnsproxy v0.75.3
|
||||||
github.com/AdguardTeam/golibs v0.32.9
|
github.com/AdguardTeam/golibs v0.32.8
|
||||||
github.com/AdguardTeam/urlfilter v0.20.0
|
github.com/AdguardTeam/urlfilter v0.20.0
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.4.0
|
github.com/ameshkov/dnscrypt/v2 v2.4.0
|
||||||
@@ -36,7 +36,7 @@ require (
|
|||||||
golang.org/x/crypto v0.37.0
|
golang.org/x/crypto v0.37.0
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
golang.org/x/net v0.39.0
|
golang.org/x/net v0.39.0
|
||||||
golang.org/x/sys v0.33.0
|
golang.org/x/sys v0.32.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
howett.net/plist v1.0.1
|
howett.net/plist v1.0.1
|
||||||
@@ -61,7 +61,7 @@ require (
|
|||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/golangci/misspell v0.6.0 // indirect
|
github.com/golangci/misspell v0.6.0 // indirect
|
||||||
github.com/google/generative-ai-go v0.19.0 // indirect
|
github.com/google/generative-ai-go v0.19.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||||
github.com/google/s2a-go v0.1.9 // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||||
@@ -89,11 +89,11 @@ require (
|
|||||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
go.uber.org/mock v0.5.2 // indirect
|
go.uber.org/mock v0.5.1 // indirect
|
||||||
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||||
golang.org/x/mod v0.24.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/oauth2 v0.29.0 // indirect
|
golang.org/x/oauth2 v0.29.0 // indirect
|
||||||
golang.org/x/sync v0.14.0 // indirect
|
golang.org/x/sync v0.13.0 // indirect
|
||||||
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 // indirect
|
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 // indirect
|
||||||
golang.org/x/term v0.31.0 // indirect
|
golang.org/x/term v0.31.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
|
|||||||
24
go.sum
24
go.sum
@@ -10,10 +10,10 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
|
|||||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||||
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||||
github.com/AdguardTeam/dnsproxy v0.75.5 h1:/P7+Ku4bjl+sVC/FW3PbT7pabgCjKTcrAOHqsZe2e60=
|
github.com/AdguardTeam/dnsproxy v0.75.3 h1:pxlMNO+cP1A3px40PY/old6SAE82pkdLPUA2P3KY8u0=
|
||||||
github.com/AdguardTeam/dnsproxy v0.75.5/go.mod h1:fdwtHhrDkTueDagDCasYKZbXdppkkBXW7RGPBNH+pis=
|
github.com/AdguardTeam/dnsproxy v0.75.3/go.mod h1:50OyTHao+uQzUJiXay08hgfvWQ3o2Q2WV99W8u8ypDE=
|
||||||
github.com/AdguardTeam/golibs v0.32.9 h1:/6luT0aMOn05/s9eh1yA4lbcHgl0d1iEEvEBbIMMUk0=
|
github.com/AdguardTeam/golibs v0.32.8 h1:O3mc3kYcPkW3kbmd+gqzFNgUka13a+iBgFLThwOYSQE=
|
||||||
github.com/AdguardTeam/golibs v0.32.9/go.mod h1:McV1QFFlKLElKa306V4OL/T2kr7564PhsayfvTWYBVs=
|
github.com/AdguardTeam/golibs v0.32.8/go.mod h1:McV1QFFlKLElKa306V4OL/T2kr7564PhsayfvTWYBVs=
|
||||||
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
|
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
|
||||||
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
|
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
@@ -72,8 +72,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||||
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
|
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
|
||||||
@@ -199,8 +199,8 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt
|
|||||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
|
||||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
@@ -227,8 +227,8 @@ golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
|||||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -241,8 +241,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 h1:RXY2+rSHXvxO2Y+gKrPjYVaEoGOqh3VEXFhnWAt1Irg=
|
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 h1:RXY2+rSHXvxO2Y+gKrPjYVaEoGOqh3VEXFhnWAt1Irg=
|
||||||
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3/go.mod h1:RoaXAWDwS90j6FxVKwJdBV+0HCU+llrKUGgJaxiKl6M=
|
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3/go.mod h1:RoaXAWDwS90j6FxVKwJdBV+0HCU+llrKUGgJaxiKl6M=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
|||||||
@@ -355,8 +355,12 @@ func (ds *DefaultSessionStorage) store(s *Session) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindByToken implements the [SessionStorage] interface for *DefaultSessionStorage.
|
// FindByToken implements the [SessionStorage] interface for
|
||||||
func (ds *DefaultSessionStorage) FindByToken(ctx context.Context, t SessionToken) (s *Session, err error) {
|
// *DefaultSessionStorage.
|
||||||
|
func (ds *DefaultSessionStorage) FindByToken(
|
||||||
|
ctx context.Context,
|
||||||
|
t SessionToken,
|
||||||
|
) (s *Session, err error) {
|
||||||
ds.mu.Lock()
|
ds.mu.Lock()
|
||||||
defer ds.mu.Unlock()
|
defer ds.mu.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -11,34 +11,8 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientID is a unique identifier for a persistent client used in
|
|
||||||
// DNS-over-HTTPS, DNS-over-TLS, and DNS-over-QUIC queries.
|
|
||||||
//
|
|
||||||
// TODO(s.chzhen): Use everywhere.
|
|
||||||
type ClientID string
|
|
||||||
|
|
||||||
// ValidateClientID returns an error if id is not a valid ClientID.
|
|
||||||
//
|
|
||||||
// TODO(s.chzhen): Consider implementing [validate.Interface] for ClientID.
|
|
||||||
func ValidateClientID(id string) (err error) {
|
|
||||||
err = netutil.ValidateHostnameLabel(id)
|
|
||||||
if err != nil {
|
|
||||||
// Replace the domain name label wrapper with our own.
|
|
||||||
return fmt.Errorf("invalid clientid %q: %w", id, errors.Unwrap(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValidClientID returns false if id is not a valid ClientID.
|
|
||||||
func isValidClientID(id string) (ok bool) {
|
|
||||||
return netutil.IsValidHostnameLabel(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source represents the source from which the information about the client has
|
// Source represents the source from which the information about the client has
|
||||||
// been obtained.
|
// been obtained.
|
||||||
type Source uint8
|
type Source uint8
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ type index struct {
|
|||||||
nameToUID map[string]UID
|
nameToUID map[string]UID
|
||||||
|
|
||||||
// clientIDToUID maps ClientID to UID.
|
// clientIDToUID maps ClientID to UID.
|
||||||
clientIDToUID map[ClientID]UID
|
clientIDToUID map[string]UID
|
||||||
|
|
||||||
// ipToUID maps IP address to UID.
|
// ipToUID maps IP address to UID.
|
||||||
ipToUID map[netip.Addr]UID
|
ipToUID map[netip.Addr]UID
|
||||||
@@ -54,7 +54,7 @@ type index struct {
|
|||||||
func newIndex() (ci *index) {
|
func newIndex() (ci *index) {
|
||||||
return &index{
|
return &index{
|
||||||
nameToUID: map[string]UID{},
|
nameToUID: map[string]UID{},
|
||||||
clientIDToUID: map[ClientID]UID{},
|
clientIDToUID: map[string]UID{},
|
||||||
ipToUID: map[netip.Addr]UID{},
|
ipToUID: map[netip.Addr]UID{},
|
||||||
subnetToUID: aghalg.NewSortedMap[netip.Prefix, UID](subnetCompare),
|
subnetToUID: aghalg.NewSortedMap[netip.Prefix, UID](subnetCompare),
|
||||||
macToUID: map[macKey]UID{},
|
macToUID: map[macKey]UID{},
|
||||||
@@ -207,7 +207,7 @@ func (ci *index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr)
|
|||||||
// find finds persistent client by string representation of the ClientID, IP
|
// find finds persistent client by string representation of the ClientID, IP
|
||||||
// address, or MAC.
|
// address, or MAC.
|
||||||
func (ci *index) find(id string) (c *Persistent, ok bool) {
|
func (ci *index) find(id string) (c *Persistent, ok bool) {
|
||||||
c, ok = ci.findByClientID(ClientID(id))
|
c, ok = ci.findByClientID(id)
|
||||||
if ok {
|
if ok {
|
||||||
return c, true
|
return c, true
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,7 @@ func (ci *index) find(id string) (c *Persistent, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// findByClientID finds persistent client by ClientID.
|
// findByClientID finds persistent client by ClientID.
|
||||||
func (ci *index) findByClientID(clientID ClientID) (c *Persistent, ok bool) {
|
func (ci *index) findByClientID(clientID string) (c *Persistent, ok bool) {
|
||||||
uid, ok := ci.clientIDToUID[clientID]
|
uid, ok := ci.clientIDToUID[clientID]
|
||||||
if ok {
|
if ok {
|
||||||
return ci.uidToClient[uid], true
|
return ci.uidToClient[uid], true
|
||||||
@@ -275,26 +275,6 @@ func (ci *index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// findByCIDR searches for a persistent client with the provided subnet as an
|
|
||||||
// identifier. Note that this function looks for an exact match of subnets,
|
|
||||||
// rather than checking if one subnet contains another.
|
|
||||||
func (ci *index) findByCIDR(subnet netip.Prefix) (c *Persistent, ok bool) {
|
|
||||||
var uid UID
|
|
||||||
for pref, id := range ci.subnetToUID.Range {
|
|
||||||
if subnet == pref {
|
|
||||||
uid, ok = id, true
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return ci.uidToClient[uid], true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// findByMAC finds persistent client by MAC.
|
// findByMAC finds persistent client by MAC.
|
||||||
func (ci *index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
|
func (ci *index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
|
||||||
k := macToKey(mac)
|
k := macToKey(mac)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -59,12 +58,12 @@ func TestClientIndex_Find(t *testing.T) {
|
|||||||
|
|
||||||
clientWithMAC = &Persistent{
|
clientWithMAC = &Persistent{
|
||||||
Name: "client_with_mac",
|
Name: "client_with_mac",
|
||||||
MACs: []net.HardwareAddr{errors.Must(net.ParseMAC(cliMAC))},
|
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
|
||||||
}
|
}
|
||||||
|
|
||||||
clientWithID = &Persistent{
|
clientWithID = &Persistent{
|
||||||
Name: "client_with_id",
|
Name: "client_with_id",
|
||||||
ClientIDs: []ClientID{cliID},
|
ClientIDs: []string{cliID},
|
||||||
}
|
}
|
||||||
|
|
||||||
clientLinkLocal = &Persistent{
|
clientLinkLocal = &Persistent{
|
||||||
@@ -142,10 +141,10 @@ func TestClientIndex_Clashes(t *testing.T) {
|
|||||||
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
|
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
|
||||||
}, {
|
}, {
|
||||||
Name: "client_with_mac",
|
Name: "client_with_mac",
|
||||||
MACs: []net.HardwareAddr{errors.Must(net.ParseMAC(cliMAC))},
|
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
|
||||||
}, {
|
}, {
|
||||||
Name: "client_with_id",
|
Name: "client_with_id",
|
||||||
ClientIDs: []ClientID{cliID},
|
ClientIDs: []string{cliID},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
ci := newIDIndex(clients)
|
ci := newIDIndex(clients)
|
||||||
@@ -182,6 +181,17 @@ func TestClientIndex_Clashes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mustParseMAC is wrapper around [net.ParseMAC] that panics if there is an
|
||||||
|
// error.
|
||||||
|
func mustParseMAC(s string) (mac net.HardwareAddr) {
|
||||||
|
mac, err := net.ParseMAC(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mac
|
||||||
|
}
|
||||||
|
|
||||||
func TestMACToKey(t *testing.T) {
|
func TestMACToKey(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
want any
|
want any
|
||||||
@@ -190,44 +200,44 @@ func TestMACToKey(t *testing.T) {
|
|||||||
}{{
|
}{{
|
||||||
name: "column6",
|
name: "column6",
|
||||||
in: "00:00:5e:00:53:01",
|
in: "00:00:5e:00:53:01",
|
||||||
want: [6]byte(errors.Must(net.ParseMAC("00:00:5e:00:53:01"))),
|
want: [6]byte(mustParseMAC("00:00:5e:00:53:01")),
|
||||||
}, {
|
}, {
|
||||||
name: "column8",
|
name: "column8",
|
||||||
in: "02:00:5e:10:00:00:00:01",
|
in: "02:00:5e:10:00:00:00:01",
|
||||||
want: [8]byte(errors.Must(net.ParseMAC("02:00:5e:10:00:00:00:01"))),
|
want: [8]byte(mustParseMAC("02:00:5e:10:00:00:00:01")),
|
||||||
}, {
|
}, {
|
||||||
name: "column20",
|
name: "column20",
|
||||||
in: "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01",
|
in: "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01",
|
||||||
want: [20]byte(errors.Must(net.ParseMAC("00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01"))),
|
want: [20]byte(mustParseMAC("00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01")),
|
||||||
}, {
|
}, {
|
||||||
name: "hyphen6",
|
name: "hyphen6",
|
||||||
in: "00-00-5e-00-53-01",
|
in: "00-00-5e-00-53-01",
|
||||||
want: [6]byte(errors.Must(net.ParseMAC("00-00-5e-00-53-01"))),
|
want: [6]byte(mustParseMAC("00-00-5e-00-53-01")),
|
||||||
}, {
|
}, {
|
||||||
name: "hyphen8",
|
name: "hyphen8",
|
||||||
in: "02-00-5e-10-00-00-00-01",
|
in: "02-00-5e-10-00-00-00-01",
|
||||||
want: [8]byte(errors.Must(net.ParseMAC("02-00-5e-10-00-00-00-01"))),
|
want: [8]byte(mustParseMAC("02-00-5e-10-00-00-00-01")),
|
||||||
}, {
|
}, {
|
||||||
name: "hyphen20",
|
name: "hyphen20",
|
||||||
in: "00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01",
|
in: "00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01",
|
||||||
want: [20]byte(errors.Must(net.ParseMAC("00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01"))),
|
want: [20]byte(mustParseMAC("00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01")),
|
||||||
}, {
|
}, {
|
||||||
name: "dot6",
|
name: "dot6",
|
||||||
in: "0000.5e00.5301",
|
in: "0000.5e00.5301",
|
||||||
want: [6]byte(errors.Must(net.ParseMAC("0000.5e00.5301"))),
|
want: [6]byte(mustParseMAC("0000.5e00.5301")),
|
||||||
}, {
|
}, {
|
||||||
name: "dot8",
|
name: "dot8",
|
||||||
in: "0200.5e10.0000.0001",
|
in: "0200.5e10.0000.0001",
|
||||||
want: [8]byte(errors.Must(net.ParseMAC("0200.5e10.0000.0001"))),
|
want: [8]byte(mustParseMAC("0200.5e10.0000.0001")),
|
||||||
}, {
|
}, {
|
||||||
name: "dot20",
|
name: "dot20",
|
||||||
in: "0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001",
|
in: "0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001",
|
||||||
want: [20]byte(errors.Must(net.ParseMAC("0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001"))),
|
want: [20]byte(mustParseMAC("0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001")),
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
mac := errors.Must(net.ParseMAC(tc.in))
|
mac := mustParseMAC(tc.in)
|
||||||
|
|
||||||
key := macToKey(mac)
|
key := macToKey(mac)
|
||||||
assert.Equal(t, tc.want, key)
|
assert.Equal(t, tc.want, key)
|
||||||
@@ -292,19 +302,19 @@ func TestIndex_FindByIPWithoutZone(t *testing.T) {
|
|||||||
func TestClientIndex_RangeByName(t *testing.T) {
|
func TestClientIndex_RangeByName(t *testing.T) {
|
||||||
sortedClients := []*Persistent{{
|
sortedClients := []*Persistent{{
|
||||||
Name: "clientA",
|
Name: "clientA",
|
||||||
ClientIDs: []ClientID{"A"},
|
ClientIDs: []string{"A"},
|
||||||
}, {
|
}, {
|
||||||
Name: "clientB",
|
Name: "clientB",
|
||||||
ClientIDs: []ClientID{"B"},
|
ClientIDs: []string{"B"},
|
||||||
}, {
|
}, {
|
||||||
Name: "clientC",
|
Name: "clientC",
|
||||||
ClientIDs: []ClientID{"C"},
|
ClientIDs: []string{"C"},
|
||||||
}, {
|
}, {
|
||||||
Name: "clientD",
|
Name: "clientD",
|
||||||
ClientIDs: []ClientID{"D"},
|
ClientIDs: []string{"D"},
|
||||||
}, {
|
}, {
|
||||||
Name: "clientE",
|
Name: "clientE",
|
||||||
ClientIDs: []ClientID{"E"},
|
ClientIDs: []string{"E"},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -339,115 +349,3 @@ func TestClientIndex_RangeByName(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIndex_FindByName(t *testing.T) {
|
|
||||||
const (
|
|
||||||
clientExistingName = "client_existing"
|
|
||||||
clientAnotherExistingName = "client_another_existing"
|
|
||||||
nonExistingClientName = "client_non_existing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
clientExisting = &Persistent{
|
|
||||||
Name: clientExistingName,
|
|
||||||
IPs: []netip.Addr{netip.MustParseAddr("192.0.2.1")},
|
|
||||||
}
|
|
||||||
|
|
||||||
clientAnotherExisting = &Persistent{
|
|
||||||
Name: clientAnotherExistingName,
|
|
||||||
IPs: []netip.Addr{netip.MustParseAddr("192.0.2.2")},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
clients := []*Persistent{
|
|
||||||
clientExisting,
|
|
||||||
clientAnotherExisting,
|
|
||||||
}
|
|
||||||
ci := newIDIndex(clients)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
want *Persistent
|
|
||||||
found assert.BoolAssertionFunc
|
|
||||||
name string
|
|
||||||
clientName string
|
|
||||||
}{{
|
|
||||||
want: clientExisting,
|
|
||||||
found: assert.True,
|
|
||||||
name: "existing",
|
|
||||||
clientName: clientExistingName,
|
|
||||||
}, {
|
|
||||||
want: clientAnotherExisting,
|
|
||||||
found: assert.True,
|
|
||||||
name: "another_existing",
|
|
||||||
clientName: clientAnotherExistingName,
|
|
||||||
}, {
|
|
||||||
want: nil,
|
|
||||||
found: assert.False,
|
|
||||||
name: "non_existing",
|
|
||||||
clientName: nonExistingClientName,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
c, ok := ci.findByName(tc.clientName)
|
|
||||||
assert.Equal(t, tc.want, c)
|
|
||||||
tc.found(t, ok)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIndex_FindByMAC(t *testing.T) {
|
|
||||||
var (
|
|
||||||
cliMAC = errors.Must(net.ParseMAC("11:11:11:11:11:11"))
|
|
||||||
cliAnotherMAC = errors.Must(net.ParseMAC("22:22:22:22:22:22"))
|
|
||||||
nonExistingClientMAC = errors.Must(net.ParseMAC("33:33:33:33:33:33"))
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
clientExisting = &Persistent{
|
|
||||||
Name: "client",
|
|
||||||
MACs: []net.HardwareAddr{cliMAC},
|
|
||||||
}
|
|
||||||
|
|
||||||
clientAnotherExisting = &Persistent{
|
|
||||||
Name: "another_client",
|
|
||||||
MACs: []net.HardwareAddr{cliAnotherMAC},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
clients := []*Persistent{
|
|
||||||
clientExisting,
|
|
||||||
clientAnotherExisting,
|
|
||||||
}
|
|
||||||
ci := newIDIndex(clients)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
want *Persistent
|
|
||||||
found assert.BoolAssertionFunc
|
|
||||||
name string
|
|
||||||
clientMAC net.HardwareAddr
|
|
||||||
}{{
|
|
||||||
want: clientExisting,
|
|
||||||
found: assert.True,
|
|
||||||
name: "existing",
|
|
||||||
clientMAC: cliMAC,
|
|
||||||
}, {
|
|
||||||
want: clientAnotherExisting,
|
|
||||||
found: assert.True,
|
|
||||||
name: "another_existing",
|
|
||||||
clientMAC: cliAnotherMAC,
|
|
||||||
}, {
|
|
||||||
want: nil,
|
|
||||||
found: assert.False,
|
|
||||||
name: "non_existing",
|
|
||||||
clientMAC: nonExistingClientMAC,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
c, ok := ci.findByMAC(tc.clientMAC)
|
|
||||||
assert.Equal(t, tc.want, c)
|
|
||||||
tc.found(t, ok)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,9 +71,7 @@ type Persistent struct {
|
|||||||
// Tags is a list of client tags that categorize the client.
|
// Tags is a list of client tags that categorize the client.
|
||||||
Tags []string
|
Tags []string
|
||||||
|
|
||||||
// Upstreams is a list of custom upstream DNS servers for the client. If
|
// Upstreams is a list of custom upstream DNS servers for the client.
|
||||||
// it's empty, the custom upstream cache is disabled, regardless of the
|
|
||||||
// value of UpstreamsCacheEnabled.
|
|
||||||
Upstreams []string
|
Upstreams []string
|
||||||
|
|
||||||
// IPs is a list of IP addresses that identify the client. The client must
|
// IPs is a list of IP addresses that identify the client. The client must
|
||||||
@@ -91,16 +90,15 @@ type Persistent struct {
|
|||||||
|
|
||||||
// ClientIDs identifying the client. The client must have at least one ID
|
// ClientIDs identifying the client. The client must have at least one ID
|
||||||
// (IP, subnet, MAC, or ClientID).
|
// (IP, subnet, MAC, or ClientID).
|
||||||
ClientIDs []ClientID
|
ClientIDs []string
|
||||||
|
|
||||||
// UID is the unique identifier of the persistent client.
|
// UID is the unique identifier of the persistent client.
|
||||||
UID UID
|
UID UID
|
||||||
|
|
||||||
// UpstreamsCacheSize defines the size of the custom upstream cache.
|
// UpstreamsCacheSize is the cache size for custom upstreams.
|
||||||
UpstreamsCacheSize uint32
|
UpstreamsCacheSize uint32
|
||||||
|
|
||||||
// UpstreamsCacheEnabled specifies whether the custom upstream cache is
|
// UpstreamsCacheEnabled specifies whether custom upstreams are used.
|
||||||
// used. If true, the list of Upstreams should not be empty.
|
|
||||||
UpstreamsCacheEnabled bool
|
UpstreamsCacheEnabled bool
|
||||||
|
|
||||||
// UseOwnSettings specifies whether custom filtering settings are used.
|
// UseOwnSettings specifies whether custom filtering settings are used.
|
||||||
@@ -136,7 +134,7 @@ func (c *Persistent) validate(ctx context.Context, l *slog.Logger, allTags []str
|
|||||||
switch {
|
switch {
|
||||||
case c.Name == "":
|
case c.Name == "":
|
||||||
return errors.Error("empty name")
|
return errors.Error("empty name")
|
||||||
case c.idendifiersLen() == 0:
|
case c.IDsLen() == 0:
|
||||||
return errors.Error("id required")
|
return errors.Error("id required")
|
||||||
case c.UID == UID{}:
|
case c.UID == UID{}:
|
||||||
return errors.Error("uid required")
|
return errors.Error("uid required")
|
||||||
@@ -239,15 +237,28 @@ func (c *Persistent) setID(id string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ClientIDs = append(c.ClientIDs, ClientID(strings.ToLower(id)))
|
c.ClientIDs = append(c.ClientIDs, strings.ToLower(id))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identifiers returns a list of client identifiers containing at least one
|
// ValidateClientID returns an error if id is not a valid ClientID.
|
||||||
// element.
|
//
|
||||||
func (c *Persistent) Identifiers() (ids []string) {
|
// TODO(s.chzhen): It's an exact copy of the [dnsforward.ValidateClientID] to
|
||||||
ids = make([]string, 0, c.idendifiersLen())
|
// avoid the import cycle. Remove it.
|
||||||
|
func ValidateClientID(id string) (err error) {
|
||||||
|
err = netutil.ValidateHostnameLabel(id)
|
||||||
|
if err != nil {
|
||||||
|
// Replace the domain name label wrapper with our own.
|
||||||
|
return fmt.Errorf("invalid clientid %q: %w", id, errors.Unwrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDs returns a list of ClientIDs containing at least one element.
|
||||||
|
func (c *Persistent) IDs() (ids []string) {
|
||||||
|
ids = make([]string, 0, c.IDsLen())
|
||||||
|
|
||||||
for _, ip := range c.IPs {
|
for _, ip := range c.IPs {
|
||||||
ids = append(ids, ip.String())
|
ids = append(ids, ip.String())
|
||||||
@@ -261,15 +272,11 @@ func (c *Persistent) Identifiers() (ids []string) {
|
|||||||
ids = append(ids, mac.String())
|
ids = append(ids, mac.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cid := range c.ClientIDs {
|
return append(ids, c.ClientIDs...)
|
||||||
ids = append(ids, string(cid))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ids
|
// IDsLen returns a length of ClientIDs.
|
||||||
}
|
func (c *Persistent) IDsLen() (n int) {
|
||||||
|
|
||||||
// identifiersLen returns the number of client identifiers.
|
|
||||||
func (c *Persistent) idendifiersLen() (n int) {
|
|
||||||
return len(c.IPs) + len(c.Subnets) + len(c.MACs) + len(c.ClientIDs)
|
return len(c.IPs) + len(c.Subnets) + len(c.MACs) + len(c.ClientIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/hostsfile"
|
"github.com/AdguardTeam/golibs/hostsfile"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
|
||||||
"github.com/AdguardTeam/golibs/timeutil"
|
"github.com/AdguardTeam/golibs/timeutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -434,138 +433,48 @@ func (s *Storage) Add(ctx context.Context, p *Persistent) (err error) {
|
|||||||
ctx,
|
ctx,
|
||||||
"client added",
|
"client added",
|
||||||
"name", p.Name,
|
"name", p.Name,
|
||||||
"ids", p.Identifiers(),
|
"ids", p.IDs(),
|
||||||
"clients_count", s.index.size(),
|
"clients_count", s.index.size(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindParams represents the parameters for searching a client. At least one
|
// FindByName finds persistent client by name. And returns its shallow copy.
|
||||||
// field must be non-empty.
|
func (s *Storage) FindByName(name string) (p *Persistent, ok bool) {
|
||||||
type FindParams struct {
|
|
||||||
// ClientID is a unique identifier for the client used in DoH, DoT, and DoQ
|
|
||||||
// DNS queries.
|
|
||||||
ClientID ClientID
|
|
||||||
|
|
||||||
// RemoteIP is the IP address used as a client search parameter.
|
|
||||||
RemoteIP netip.Addr
|
|
||||||
|
|
||||||
// Subnet is the CIDR used as a client search parameter.
|
|
||||||
Subnet netip.Prefix
|
|
||||||
|
|
||||||
// MAC is the physical hardware address used as a client search parameter.
|
|
||||||
MAC net.HardwareAddr
|
|
||||||
|
|
||||||
// UID is the unique ID of persistent client used as a search parameter.
|
|
||||||
//
|
|
||||||
// TODO(s.chzhen): Use this.
|
|
||||||
UID UID
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrBadIdentifier is returned by [FindParams.Set] when it cannot parse the
|
|
||||||
// provided client identifier.
|
|
||||||
const ErrBadIdentifier errors.Error = "bad client identifier"
|
|
||||||
|
|
||||||
// Set clears the stored search parameters and parses the string representation
|
|
||||||
// of the search parameter into typed parameter, storing it. In some cases, it
|
|
||||||
// may result in storing both an IP address and a MAC address because they might
|
|
||||||
// have identical string representations. It returns [ErrBadIdentifier] if id
|
|
||||||
// cannot be parsed.
|
|
||||||
//
|
|
||||||
// TODO(s.chzhen): Add support for UID.
|
|
||||||
func (p *FindParams) Set(id string) (err error) {
|
|
||||||
*p = FindParams{}
|
|
||||||
|
|
||||||
isFound := false
|
|
||||||
|
|
||||||
if netutil.IsValidIPString(id) {
|
|
||||||
// It is safe to use [netip.MustParseAddr] because it has already been
|
|
||||||
// validated that id contains the string representation of the IP
|
|
||||||
// address.
|
|
||||||
p.RemoteIP = netip.MustParseAddr(id)
|
|
||||||
|
|
||||||
// Even if id can be parsed as an IP address, it may be a MAC address.
|
|
||||||
// So do not return prematurely, continue parsing.
|
|
||||||
isFound = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if netutil.IsValidMACString(id) {
|
|
||||||
p.MAC, err = net.ParseMAC(id)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("parsing mac from %q: %w", id, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
isFound = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if netutil.IsValidIPPrefixString(id) {
|
|
||||||
// It is safe to use [netip.MustParsePrefix] because it has already been
|
|
||||||
// validated that id contains the string representation of IP prefix.
|
|
||||||
p.Subnet = netip.MustParsePrefix(id)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isValidClientID(id) {
|
|
||||||
return ErrBadIdentifier
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ClientID = ClientID(id)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find represents the parameters for searching a client. params must not be
|
|
||||||
// nil and must have at least one non-empty field.
|
|
||||||
func (s *Storage) Find(params *FindParams) (p *Persistent, ok bool) {
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
isClientID := params.ClientID != ""
|
p, ok = s.index.findByName(name)
|
||||||
isRemoteIP := params.RemoteIP != (netip.Addr{})
|
if ok {
|
||||||
isSubnet := params.Subnet != (netip.Prefix{})
|
return p.ShallowClone(), ok
|
||||||
isMAC := params.MAC != nil
|
}
|
||||||
|
|
||||||
for {
|
|
||||||
switch {
|
|
||||||
case isClientID:
|
|
||||||
isClientID = false
|
|
||||||
p, ok = s.index.findByClientID(params.ClientID)
|
|
||||||
case isRemoteIP:
|
|
||||||
isRemoteIP = false
|
|
||||||
p, ok = s.findByIP(params.RemoteIP)
|
|
||||||
case isSubnet:
|
|
||||||
isSubnet = false
|
|
||||||
p, ok = s.index.findByCIDR(params.Subnet)
|
|
||||||
case isMAC:
|
|
||||||
isMAC = false
|
|
||||||
p, ok = s.index.findByMAC(params.MAC)
|
|
||||||
default:
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find finds persistent client by string representation of the ClientID, IP
|
||||||
|
// address, or MAC. And returns its shallow copy.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Accept ClientIDData structure instead, which will contain
|
||||||
|
// the parsed IP address, if any.
|
||||||
|
func (s *Storage) Find(id string) (p *Persistent, ok bool) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
p, ok = s.index.find(id)
|
||||||
if ok {
|
if ok {
|
||||||
return p.ShallowClone(), true
|
return p.ShallowClone(), ok
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findByIP finds persistent client by IP address. s.mu is expected to be
|
ip, err := netip.ParseAddr(id)
|
||||||
// locked.
|
if err != nil {
|
||||||
func (s *Storage) findByIP(addr netip.Addr) (p *Persistent, ok bool) {
|
return nil, false
|
||||||
p, ok = s.index.findByIP(addr)
|
|
||||||
if ok {
|
|
||||||
return p, true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foundMAC := s.dhcp.MACByIP(addr)
|
foundMAC := s.dhcp.MACByIP(ip)
|
||||||
if foundMAC != nil {
|
if foundMAC != nil {
|
||||||
return s.index.findByMAC(foundMAC)
|
return s.FindByMAC(foundMAC)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -578,8 +487,6 @@ func (s *Storage) findByIP(addr netip.Addr) (p *Persistent, ok bool) {
|
|||||||
//
|
//
|
||||||
// Note that multiple clients can have the same IP address with different zones.
|
// Note that multiple clients can have the same IP address with different zones.
|
||||||
// Therefore, the result of this method is indeterminate.
|
// Therefore, the result of this method is indeterminate.
|
||||||
//
|
|
||||||
// TODO(s.chzhen): Consider accepting [FindParams].
|
|
||||||
func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
|
func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
@@ -591,7 +498,7 @@ func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
|
|||||||
|
|
||||||
foundMAC := s.dhcp.MACByIP(ip)
|
foundMAC := s.dhcp.MACByIP(ip)
|
||||||
if foundMAC != nil {
|
if foundMAC != nil {
|
||||||
return s.index.findByMAC(foundMAC)
|
return s.FindByMAC(foundMAC)
|
||||||
}
|
}
|
||||||
|
|
||||||
p = s.index.findByIPWithoutZone(ip)
|
p = s.index.findByIPWithoutZone(ip)
|
||||||
@@ -602,6 +509,17 @@ func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindByMAC finds persistent client by MAC and returns its shallow copy. s.mu
|
||||||
|
// is expected to be locked.
|
||||||
|
func (s *Storage) FindByMAC(mac net.HardwareAddr) (p *Persistent, ok bool) {
|
||||||
|
p, ok = s.index.findByMAC(mac)
|
||||||
|
if ok {
|
||||||
|
return p.ShallowClone(), ok
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveByName removes persistent client information. ok is false if no such
|
// RemoveByName removes persistent client information. ok is false if no such
|
||||||
// client exists by that name.
|
// client exists by that name.
|
||||||
func (s *Storage) RemoveByName(ctx context.Context, name string) (ok bool) {
|
func (s *Storage) RemoveByName(ctx context.Context, name string) (ok bool) {
|
||||||
@@ -730,9 +648,9 @@ func (s *Storage) CustomUpstreamConfig(
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
c, ok := s.index.findByClientID(ClientID(id))
|
c, ok := s.index.findByClientID(id)
|
||||||
if !ok {
|
if !ok {
|
||||||
c, ok = s.findByIP(addr)
|
c, ok = s.index.findByIP(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -764,7 +682,7 @@ func (s *Storage) ClearUpstreamCache() {
|
|||||||
// ClientID or client IP address, and applies it to the filtering settings.
|
// ClientID or client IP address, and applies it to the filtering settings.
|
||||||
// setts must not be nil.
|
// setts must not be nil.
|
||||||
func (s *Storage) ApplyClientFiltering(id string, addr netip.Addr, setts *filtering.Settings) {
|
func (s *Storage) ApplyClientFiltering(id string, addr netip.Addr, setts *filtering.Settings) {
|
||||||
c, ok := s.index.findByClientID(ClientID(id))
|
c, ok := s.index.findByClientID(id)
|
||||||
if !ok {
|
if !ok {
|
||||||
c, ok = s.index.findByIP(addr)
|
c, ok = s.index.findByIP(addr)
|
||||||
}
|
}
|
||||||
@@ -772,7 +690,7 @@ func (s *Storage) ApplyClientFiltering(id string, addr netip.Addr, setts *filter
|
|||||||
if !ok {
|
if !ok {
|
||||||
foundMAC := s.dhcp.MACByIP(addr)
|
foundMAC := s.dhcp.MACByIP(addr)
|
||||||
if foundMAC != nil {
|
if foundMAC != nil {
|
||||||
c, ok = s.index.findByMAC(foundMAC)
|
c, ok = s.FindByMAC(foundMAC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
|
||||||
"github.com/AdguardTeam/golibs/hostsfile"
|
"github.com/AdguardTeam/golibs/hostsfile"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
@@ -351,15 +350,15 @@ func TestClientsDHCP(t *testing.T) {
|
|||||||
cliName1 = "one.dhcp"
|
cliName1 = "one.dhcp"
|
||||||
|
|
||||||
cliIP2 = netip.MustParseAddr("2.2.2.2")
|
cliIP2 = netip.MustParseAddr("2.2.2.2")
|
||||||
cliMAC2 = errors.Must(net.ParseMAC("22:22:22:22:22:22"))
|
cliMAC2 = mustParseMAC("22:22:22:22:22:22")
|
||||||
cliName2 = "two.dhcp"
|
cliName2 = "two.dhcp"
|
||||||
|
|
||||||
cliIP3 = netip.MustParseAddr("3.3.3.3")
|
cliIP3 = netip.MustParseAddr("3.3.3.3")
|
||||||
cliMAC3 = errors.Must(net.ParseMAC("33:33:33:33:33:33"))
|
cliMAC3 = mustParseMAC("33:33:33:33:33:33")
|
||||||
cliName3 = "three.dhcp"
|
cliName3 = "three.dhcp"
|
||||||
|
|
||||||
prsCliIP = netip.MustParseAddr("4.3.2.1")
|
prsCliIP = netip.MustParseAddr("4.3.2.1")
|
||||||
prsCliMAC = errors.Must(net.ParseMAC("AA:AA:AA:AA:AA:AA"))
|
prsCliMAC = mustParseMAC("AA:AA:AA:AA:AA:AA")
|
||||||
prsCliName = "persistent.dhcp"
|
prsCliName = "persistent.dhcp"
|
||||||
|
|
||||||
otherARPCliName = "other.arp"
|
otherARPCliName = "other.arp"
|
||||||
@@ -520,11 +519,7 @@ func TestClientsDHCP(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
params := &client.FindParams{}
|
prsCli, ok := storage.Find(prsCliIP.String())
|
||||||
err = params.Set(prsCliIP.String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
prsCli, ok := storage.Find(params)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
assert.Equal(t, prsCliName, prsCli.Name)
|
assert.Equal(t, prsCliName, prsCli.Name)
|
||||||
@@ -668,6 +663,17 @@ func newStorage(tb testing.TB, m []*client.Persistent) (s *client.Storage) {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mustParseMAC is wrapper around [net.ParseMAC] that panics if there is an
|
||||||
|
// error.
|
||||||
|
func mustParseMAC(s string) (mac net.HardwareAddr) {
|
||||||
|
mac, err := net.ParseMAC(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mac
|
||||||
|
}
|
||||||
|
|
||||||
func TestStorage_Add(t *testing.T) {
|
func TestStorage_Add(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
existingName = "existing_name"
|
existingName = "existing_name"
|
||||||
@@ -687,7 +693,7 @@ func TestStorage_Add(t *testing.T) {
|
|||||||
Name: existingName,
|
Name: existingName,
|
||||||
IPs: []netip.Addr{existingIP},
|
IPs: []netip.Addr{existingIP},
|
||||||
Subnets: []netip.Prefix{existingSubnet},
|
Subnets: []netip.Prefix{existingSubnet},
|
||||||
ClientIDs: []client.ClientID{existingClientID},
|
ClientIDs: []string{existingClientID},
|
||||||
UID: existingClientUID,
|
UID: existingClientUID,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,7 +761,7 @@ func TestStorage_Add(t *testing.T) {
|
|||||||
name: "duplicate_client_id",
|
name: "duplicate_client_id",
|
||||||
cli: &client.Persistent{
|
cli: &client.Persistent{
|
||||||
Name: "duplicate_client_id",
|
Name: "duplicate_client_id",
|
||||||
ClientIDs: []client.ClientID{existingClientID},
|
ClientIDs: []string{existingClientID},
|
||||||
UID: client.MustNewUID(),
|
UID: client.MustNewUID(),
|
||||||
},
|
},
|
||||||
wantErrMsg: `adding client: another client "existing_name" ` +
|
wantErrMsg: `adding client: another client "existing_name" ` +
|
||||||
@@ -892,12 +898,12 @@ func TestStorage_Find(t *testing.T) {
|
|||||||
|
|
||||||
clientWithMAC = &client.Persistent{
|
clientWithMAC = &client.Persistent{
|
||||||
Name: "client_with_mac",
|
Name: "client_with_mac",
|
||||||
MACs: []net.HardwareAddr{errors.Must(net.ParseMAC(cliMAC))},
|
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
|
||||||
}
|
}
|
||||||
|
|
||||||
clientWithID = &client.Persistent{
|
clientWithID = &client.Persistent{
|
||||||
Name: "client_with_id",
|
Name: "client_with_id",
|
||||||
ClientIDs: []client.ClientID{cliID},
|
ClientIDs: []string{cliID},
|
||||||
}
|
}
|
||||||
|
|
||||||
clientLinkLocal = &client.Persistent{
|
clientLinkLocal = &client.Persistent{
|
||||||
@@ -944,11 +950,7 @@ func TestStorage_Find(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
for _, id := range tc.ids {
|
for _, id := range tc.ids {
|
||||||
params := &client.FindParams{}
|
c, ok := s.Find(id)
|
||||||
err := params.Set(id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
c, ok := s.Find(params)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
assert.Equal(t, tc.want, c)
|
assert.Equal(t, tc.want, c)
|
||||||
@@ -957,11 +959,7 @@ func TestStorage_Find(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("not_found", func(t *testing.T) {
|
t.Run("not_found", func(t *testing.T) {
|
||||||
params := &client.FindParams{}
|
_, ok := s.Find(cliIPNone)
|
||||||
err := params.Set(cliIPNone)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, ok := s.Find(params)
|
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1027,6 +1025,127 @@ func TestStorage_FindLoose(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStorage_FindByName(t *testing.T) {
|
||||||
|
const (
|
||||||
|
cliIP1 = "1.1.1.1"
|
||||||
|
cliIP2 = "2.2.2.2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
clientExistingName = "client_existing"
|
||||||
|
clientAnotherExistingName = "client_another_existing"
|
||||||
|
nonExistingClientName = "client_non_existing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clientExisting = &client.Persistent{
|
||||||
|
Name: clientExistingName,
|
||||||
|
IPs: []netip.Addr{netip.MustParseAddr(cliIP1)},
|
||||||
|
}
|
||||||
|
|
||||||
|
clientAnotherExisting = &client.Persistent{
|
||||||
|
Name: clientAnotherExistingName,
|
||||||
|
IPs: []netip.Addr{netip.MustParseAddr(cliIP2)},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
clients := []*client.Persistent{
|
||||||
|
clientExisting,
|
||||||
|
clientAnotherExisting,
|
||||||
|
}
|
||||||
|
s := newStorage(t, clients)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
want *client.Persistent
|
||||||
|
name string
|
||||||
|
clientName string
|
||||||
|
}{{
|
||||||
|
name: "existing",
|
||||||
|
clientName: clientExistingName,
|
||||||
|
want: clientExisting,
|
||||||
|
}, {
|
||||||
|
name: "another_existing",
|
||||||
|
clientName: clientAnotherExistingName,
|
||||||
|
want: clientAnotherExisting,
|
||||||
|
}, {
|
||||||
|
name: "non_existing",
|
||||||
|
clientName: nonExistingClientName,
|
||||||
|
want: nil,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
c, ok := s.FindByName(tc.clientName)
|
||||||
|
if tc.want == nil {
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, tc.want, c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStorage_FindByMAC(t *testing.T) {
|
||||||
|
var (
|
||||||
|
cliMAC = mustParseMAC("11:11:11:11:11:11")
|
||||||
|
cliAnotherMAC = mustParseMAC("22:22:22:22:22:22")
|
||||||
|
nonExistingClientMAC = mustParseMAC("33:33:33:33:33:33")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clientExisting = &client.Persistent{
|
||||||
|
Name: "client",
|
||||||
|
MACs: []net.HardwareAddr{cliMAC},
|
||||||
|
}
|
||||||
|
|
||||||
|
clientAnotherExisting = &client.Persistent{
|
||||||
|
Name: "another_client",
|
||||||
|
MACs: []net.HardwareAddr{cliAnotherMAC},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
clients := []*client.Persistent{
|
||||||
|
clientExisting,
|
||||||
|
clientAnotherExisting,
|
||||||
|
}
|
||||||
|
s := newStorage(t, clients)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
want *client.Persistent
|
||||||
|
name string
|
||||||
|
clientMAC net.HardwareAddr
|
||||||
|
}{{
|
||||||
|
name: "existing",
|
||||||
|
clientMAC: cliMAC,
|
||||||
|
want: clientExisting,
|
||||||
|
}, {
|
||||||
|
name: "another_existing",
|
||||||
|
clientMAC: cliAnotherMAC,
|
||||||
|
want: clientAnotherExisting,
|
||||||
|
}, {
|
||||||
|
name: "non_existing",
|
||||||
|
clientMAC: nonExistingClientMAC,
|
||||||
|
want: nil,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
c, ok := s.FindByMAC(tc.clientMAC)
|
||||||
|
if tc.want == nil {
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, tc.want, c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStorage_Update(t *testing.T) {
|
func TestStorage_Update(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
clientName = "client_name"
|
clientName = "client_name"
|
||||||
@@ -1043,7 +1162,7 @@ func TestStorage_Update(t *testing.T) {
|
|||||||
Name: obstructingName,
|
Name: obstructingName,
|
||||||
IPs: []netip.Addr{obstructingIP},
|
IPs: []netip.Addr{obstructingIP},
|
||||||
Subnets: []netip.Prefix{obstructingSubnet},
|
Subnets: []netip.Prefix{obstructingSubnet},
|
||||||
ClientIDs: []client.ClientID{obstructingClientID},
|
ClientIDs: []string{obstructingClientID},
|
||||||
}
|
}
|
||||||
|
|
||||||
clientToUpdate := &client.Persistent{
|
clientToUpdate := &client.Persistent{
|
||||||
@@ -1092,7 +1211,7 @@ func TestStorage_Update(t *testing.T) {
|
|||||||
name: "duplicate_client_id",
|
name: "duplicate_client_id",
|
||||||
cli: &client.Persistent{
|
cli: &client.Persistent{
|
||||||
Name: "duplicate_client_id",
|
Name: "duplicate_client_id",
|
||||||
ClientIDs: []client.ClientID{obstructingClientID},
|
ClientIDs: []string{obstructingClientID},
|
||||||
UID: client.MustNewUID(),
|
UID: client.MustNewUID(),
|
||||||
},
|
},
|
||||||
wantErrMsg: `updating client: another client "obstructing_name" ` +
|
wantErrMsg: `updating client: another client "obstructing_name" ` +
|
||||||
@@ -1119,19 +1238,19 @@ func TestStorage_Update(t *testing.T) {
|
|||||||
func TestStorage_RangeByName(t *testing.T) {
|
func TestStorage_RangeByName(t *testing.T) {
|
||||||
sortedClients := []*client.Persistent{{
|
sortedClients := []*client.Persistent{{
|
||||||
Name: "clientA",
|
Name: "clientA",
|
||||||
ClientIDs: []client.ClientID{"A"},
|
ClientIDs: []string{"A"},
|
||||||
}, {
|
}, {
|
||||||
Name: "clientB",
|
Name: "clientB",
|
||||||
ClientIDs: []client.ClientID{"B"},
|
ClientIDs: []string{"B"},
|
||||||
}, {
|
}, {
|
||||||
Name: "clientC",
|
Name: "clientC",
|
||||||
ClientIDs: []client.ClientID{"C"},
|
ClientIDs: []string{"C"},
|
||||||
}, {
|
}, {
|
||||||
Name: "clientD",
|
Name: "clientD",
|
||||||
ClientIDs: []client.ClientID{"D"},
|
ClientIDs: []string{"D"},
|
||||||
}, {
|
}, {
|
||||||
Name: "clientE",
|
Name: "clientE",
|
||||||
ClientIDs: []client.ClientID{"E"},
|
ClientIDs: []string{"E"},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -1169,20 +1288,29 @@ func TestStorage_RangeByName(t *testing.T) {
|
|||||||
|
|
||||||
func TestStorage_CustomUpstreamConfig(t *testing.T) {
|
func TestStorage_CustomUpstreamConfig(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
|
existingName = "existing_name"
|
||||||
existingClientID = "existing_client_id"
|
existingClientID = "existing_client_id"
|
||||||
|
|
||||||
nonExistingClientID = "non_existing_client_id"
|
nonExistingClientID = "non_existing_client_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
existingClientUID = client.MustNewUID()
|
||||||
existingIP = netip.MustParseAddr("192.0.2.1")
|
existingIP = netip.MustParseAddr("192.0.2.1")
|
||||||
nonExistingIP = netip.MustParseAddr("192.0.2.255")
|
|
||||||
|
|
||||||
dhcpCliIP = netip.MustParseAddr("192.0.2.2")
|
nonExistingIP = netip.MustParseAddr("192.0.2.255")
|
||||||
dhcpCliMAC = errors.Must(net.ParseMAC("02:00:00:00:00:00"))
|
|
||||||
|
|
||||||
testUpstreamTimeout = time.Second
|
testUpstreamTimeout = time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
existingClient := &client.Persistent{
|
||||||
|
Name: existingName,
|
||||||
|
IPs: []netip.Addr{existingIP},
|
||||||
|
ClientIDs: []string{existingClientID},
|
||||||
|
UID: existingClientUID,
|
||||||
|
Upstreams: []string{"192.0.2.0"},
|
||||||
|
}
|
||||||
|
|
||||||
date := time.Now()
|
date := time.Now()
|
||||||
clock := &faketime.Clock{
|
clock := &faketime.Clock{
|
||||||
OnNow: func() (now time.Time) {
|
OnNow: func() (now time.Time) {
|
||||||
@@ -1192,30 +1320,7 @@ func TestStorage_CustomUpstreamConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ipToMAC := map[netip.Addr]net.HardwareAddr{
|
s := newTestStorage(t, clock)
|
||||||
dhcpCliIP: dhcpCliMAC,
|
|
||||||
}
|
|
||||||
|
|
||||||
dhcp := &testDHCP{
|
|
||||||
OnLeases: func() (ls []*dhcpsvc.Lease) {
|
|
||||||
panic("not implemented")
|
|
||||||
},
|
|
||||||
OnHostBy: func(ip netip.Addr) (host string) {
|
|
||||||
panic("not implemented")
|
|
||||||
},
|
|
||||||
OnMACBy: func(ip netip.Addr) (mac net.HardwareAddr) {
|
|
||||||
return ipToMAC[ip]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
|
||||||
s, err := client.NewStorage(ctx, &client.StorageConfig{
|
|
||||||
Logger: slogutil.NewDiscardLogger(),
|
|
||||||
Clock: clock,
|
|
||||||
DHCP: dhcp,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
s.UpdateCommonUpstreamConfig(&client.CommonUpstreamConfig{
|
s.UpdateCommonUpstreamConfig(&client.CommonUpstreamConfig{
|
||||||
UpstreamTimeout: testUpstreamTimeout,
|
UpstreamTimeout: testUpstreamTimeout,
|
||||||
})
|
})
|
||||||
@@ -1224,21 +1329,8 @@ func TestStorage_CustomUpstreamConfig(t *testing.T) {
|
|||||||
return s.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
|
return s.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
|
||||||
})
|
})
|
||||||
|
|
||||||
err = s.Add(ctx, &client.Persistent{
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
Name: "client_first",
|
err := s.Add(ctx, existingClient)
|
||||||
IPs: []netip.Addr{existingIP},
|
|
||||||
ClientIDs: []client.ClientID{existingClientID},
|
|
||||||
UID: client.MustNewUID(),
|
|
||||||
Upstreams: []string{"192.0.2.0"},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Add(ctx, &client.Persistent{
|
|
||||||
Name: "client_second",
|
|
||||||
MACs: []net.HardwareAddr{dhcpCliMAC},
|
|
||||||
UID: client.MustNewUID(),
|
|
||||||
Upstreams: []string{"192.0.2.0"},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -1256,11 +1348,6 @@ func TestStorage_CustomUpstreamConfig(t *testing.T) {
|
|||||||
cliID: "",
|
cliID: "",
|
||||||
cliAddr: existingIP,
|
cliAddr: existingIP,
|
||||||
wantNilConf: assert.NotNil,
|
wantNilConf: assert.NotNil,
|
||||||
}, {
|
|
||||||
name: "client_dhcp",
|
|
||||||
cliID: "",
|
|
||||||
cliAddr: dhcpCliIP,
|
|
||||||
wantNilConf: assert.NotNil,
|
|
||||||
}, {
|
}, {
|
||||||
name: "non_existing_client_id",
|
name: "non_existing_client_id",
|
||||||
cliID: nonExistingClientID,
|
cliID: nonExistingClientID,
|
||||||
@@ -1293,193 +1380,4 @@ func TestStorage_CustomUpstreamConfig(t *testing.T) {
|
|||||||
|
|
||||||
assert.NotEqual(t, conf, updConf)
|
assert.NotEqual(t, conf, updConf)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("same_custom_config", func(t *testing.T) {
|
|
||||||
firstConf := s.CustomUpstreamConfig(existingClientID, existingIP)
|
|
||||||
require.NotNil(t, firstConf)
|
|
||||||
|
|
||||||
secondConf := s.CustomUpstreamConfig(existingClientID, existingIP)
|
|
||||||
require.NotNil(t, secondConf)
|
|
||||||
|
|
||||||
assert.Same(t, firstConf, secondConf)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFindParams_Set(b *testing.B) {
|
|
||||||
const (
|
|
||||||
testIPStr = "192.0.2.1"
|
|
||||||
testCIDRStr = "192.0.2.0/24"
|
|
||||||
testMACStr = "02:00:00:00:00:00"
|
|
||||||
testClientID = "clientid"
|
|
||||||
)
|
|
||||||
|
|
||||||
benchCases := []struct {
|
|
||||||
wantErr error
|
|
||||||
params *client.FindParams
|
|
||||||
name string
|
|
||||||
id string
|
|
||||||
}{{
|
|
||||||
wantErr: nil,
|
|
||||||
params: &client.FindParams{
|
|
||||||
ClientID: testClientID,
|
|
||||||
},
|
|
||||||
name: "client_id",
|
|
||||||
id: testClientID,
|
|
||||||
}, {
|
|
||||||
wantErr: nil,
|
|
||||||
params: &client.FindParams{
|
|
||||||
RemoteIP: netip.MustParseAddr(testIPStr),
|
|
||||||
},
|
|
||||||
name: "ip_address",
|
|
||||||
id: testIPStr,
|
|
||||||
}, {
|
|
||||||
wantErr: nil,
|
|
||||||
params: &client.FindParams{
|
|
||||||
Subnet: netip.MustParsePrefix(testCIDRStr),
|
|
||||||
},
|
|
||||||
name: "subnet",
|
|
||||||
id: testCIDRStr,
|
|
||||||
}, {
|
|
||||||
wantErr: nil,
|
|
||||||
params: &client.FindParams{
|
|
||||||
MAC: errors.Must(net.ParseMAC(testMACStr)),
|
|
||||||
},
|
|
||||||
name: "mac_address",
|
|
||||||
id: testMACStr,
|
|
||||||
}, {
|
|
||||||
wantErr: client.ErrBadIdentifier,
|
|
||||||
params: &client.FindParams{},
|
|
||||||
name: "bad_id",
|
|
||||||
id: "!@#$%^&*()_+",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, bc := range benchCases {
|
|
||||||
b.Run(bc.name, func(b *testing.B) {
|
|
||||||
params := &client.FindParams{}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
for b.Loop() {
|
|
||||||
err = params.Set(bc.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.ErrorIs(b, err, bc.wantErr)
|
|
||||||
assert.Equal(b, bc.params, params)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Most recent results:
|
|
||||||
//
|
|
||||||
// goos: linux
|
|
||||||
// goarch: amd64
|
|
||||||
// pkg: github.com/AdguardTeam/AdGuardHome/internal/client
|
|
||||||
// cpu: Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz
|
|
||||||
// BenchmarkFindParams_Set/client_id-8 49463488 24.27 ns/op 0 B/op 0 allocs/op
|
|
||||||
// BenchmarkFindParams_Set/ip_address-8 18740977 62.22 ns/op 0 B/op 0 allocs/op
|
|
||||||
// BenchmarkFindParams_Set/subnet-8 10848192 110.0 ns/op 0 B/op 0 allocs/op
|
|
||||||
// BenchmarkFindParams_Set/mac_address-8 8148494 133.2 ns/op 8 B/op 1 allocs/op
|
|
||||||
// BenchmarkFindParams_Set/bad_id-8 73894278 16.29 ns/op 0 B/op 0 allocs/op
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkStorage_Find(b *testing.B) {
|
|
||||||
const (
|
|
||||||
cliID = "cid"
|
|
||||||
cliMAC = "02:00:00:00:00:00"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
cliNameWithID = "client_with_id"
|
|
||||||
cliNameWithIP = "client_with_ip"
|
|
||||||
cliNameWithCIDR = "client_with_cidr"
|
|
||||||
cliNameWithMAC = "client_with_mac"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
cliIP = netip.MustParseAddr("192.0.2.1")
|
|
||||||
cliCIDR = netip.MustParsePrefix("192.0.2.0/24")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
clientWithID = &client.Persistent{
|
|
||||||
Name: cliNameWithID,
|
|
||||||
ClientIDs: []client.ClientID{cliID},
|
|
||||||
}
|
|
||||||
clientWithIP = &client.Persistent{
|
|
||||||
Name: cliNameWithIP,
|
|
||||||
IPs: []netip.Addr{cliIP},
|
|
||||||
}
|
|
||||||
clientWithCIDR = &client.Persistent{
|
|
||||||
Name: cliNameWithCIDR,
|
|
||||||
Subnets: []netip.Prefix{cliCIDR},
|
|
||||||
}
|
|
||||||
clientWithMAC = &client.Persistent{
|
|
||||||
Name: cliNameWithMAC,
|
|
||||||
MACs: []net.HardwareAddr{errors.Must(net.ParseMAC(cliMAC))},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
clients := []*client.Persistent{
|
|
||||||
clientWithID,
|
|
||||||
clientWithIP,
|
|
||||||
clientWithCIDR,
|
|
||||||
clientWithMAC,
|
|
||||||
}
|
|
||||||
s := newStorage(b, clients)
|
|
||||||
|
|
||||||
benchCases := []struct {
|
|
||||||
params *client.FindParams
|
|
||||||
name string
|
|
||||||
wantName string
|
|
||||||
}{{
|
|
||||||
params: &client.FindParams{
|
|
||||||
ClientID: cliID,
|
|
||||||
},
|
|
||||||
name: "client_id",
|
|
||||||
wantName: cliNameWithID,
|
|
||||||
}, {
|
|
||||||
params: &client.FindParams{
|
|
||||||
RemoteIP: cliIP,
|
|
||||||
},
|
|
||||||
name: "ip_address",
|
|
||||||
wantName: cliNameWithIP,
|
|
||||||
}, {
|
|
||||||
params: &client.FindParams{
|
|
||||||
Subnet: cliCIDR,
|
|
||||||
},
|
|
||||||
name: "subnet",
|
|
||||||
wantName: cliNameWithCIDR,
|
|
||||||
}, {
|
|
||||||
params: &client.FindParams{
|
|
||||||
MAC: errors.Must(net.ParseMAC(cliMAC)),
|
|
||||||
},
|
|
||||||
name: "mac_address",
|
|
||||||
wantName: cliNameWithMAC,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, bc := range benchCases {
|
|
||||||
b.Run(bc.name, func(b *testing.B) {
|
|
||||||
var p *client.Persistent
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
for b.Loop() {
|
|
||||||
p, ok = s.Find(bc.params)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(b, ok)
|
|
||||||
assert.NotNil(b, p)
|
|
||||||
assert.Equal(b, bc.wantName, p.Name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Most recent results:
|
|
||||||
//
|
|
||||||
// goos: linux
|
|
||||||
// goarch: amd64
|
|
||||||
// pkg: github.com/AdguardTeam/AdGuardHome/internal/client
|
|
||||||
// cpu: Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz
|
|
||||||
// BenchmarkStorage_Find/client_id-8 7070107 154.4 ns/op 240 B/op 2 allocs/op
|
|
||||||
// BenchmarkStorage_Find/ip_address-8 6831823 168.6 ns/op 248 B/op 2 allocs/op
|
|
||||||
// BenchmarkStorage_Find/subnet-8 7209050 167.5 ns/op 256 B/op 2 allocs/op
|
|
||||||
// BenchmarkStorage_Find/mac_address-8 5776131 199.7 ns/op 256 B/op 3 allocs/op
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ func (m *upstreamManager) customUpstreamConfig(uid UID) (proxyConf *proxy.Custom
|
|||||||
|
|
||||||
proxyConf = newCustomUpstreamConfig(cliConf, m.commonConf)
|
proxyConf = newCustomUpstreamConfig(cliConf, m.commonConf)
|
||||||
cliConf.proxyConf = proxyConf
|
cliConf.proxyConf = proxyConf
|
||||||
cliConf.commonConfUpdate = m.confUpdate
|
|
||||||
cliConf.isChanged = false
|
cliConf.isChanged = false
|
||||||
|
|
||||||
return proxyConf
|
return proxyConf
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package dhcpsvc_test
|
package dhcpsvc_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testLocalTLD is a common local TLD for tests.
|
// testLocalTLD is a common local TLD for tests.
|
||||||
@@ -54,3 +56,11 @@ var testInterfaceConf = map[string]*dhcpsvc.InterfaceConfig{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mustParseMAC parses a hardware address from s and requires no errors.
|
||||||
|
func mustParseMAC(t require.TestingT, s string) (mac net.HardwareAddr) {
|
||||||
|
mac, err := net.ParseMAC(s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return mac
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package dhcpsvc_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -12,7 +11,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -178,9 +176,9 @@ func TestDHCPServer_AddLease(t *testing.T) {
|
|||||||
newIP = netip.MustParseAddr("192.168.0.3")
|
newIP = netip.MustParseAddr("192.168.0.3")
|
||||||
newIPv6 = netip.MustParseAddr("2001:db8::2")
|
newIPv6 = netip.MustParseAddr("2001:db8::2")
|
||||||
|
|
||||||
existMAC = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
|
existMAC = mustParseMAC(t, "01:02:03:04:05:06")
|
||||||
newMAC = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
|
newMAC = mustParseMAC(t, "06:05:04:03:02:01")
|
||||||
ipv6MAC = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
|
ipv6MAC = mustParseMAC(t, "02:03:04:05:06:07")
|
||||||
)
|
)
|
||||||
|
|
||||||
require.NoError(t, srv.AddLease(ctx, &dhcpsvc.Lease{
|
require.NoError(t, srv.AddLease(ctx, &dhcpsvc.Lease{
|
||||||
@@ -293,9 +291,9 @@ func TestDHCPServer_index(t *testing.T) {
|
|||||||
ip3 = netip.MustParseAddr("172.16.0.3")
|
ip3 = netip.MustParseAddr("172.16.0.3")
|
||||||
ip4 = netip.MustParseAddr("172.16.0.4")
|
ip4 = netip.MustParseAddr("172.16.0.4")
|
||||||
|
|
||||||
mac1 = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
|
mac1 = mustParseMAC(t, "01:02:03:04:05:06")
|
||||||
mac2 = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
|
mac2 = mustParseMAC(t, "06:05:04:03:02:01")
|
||||||
mac3 = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
|
mac3 = mustParseMAC(t, "02:03:04:05:06:07")
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run("ip_idx", func(t *testing.T) {
|
t.Run("ip_idx", func(t *testing.T) {
|
||||||
@@ -351,9 +349,9 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
|
|||||||
ip3 = netip.MustParseAddr("192.168.0.4")
|
ip3 = netip.MustParseAddr("192.168.0.4")
|
||||||
ip4 = netip.MustParseAddr("2001:db8::3")
|
ip4 = netip.MustParseAddr("2001:db8::3")
|
||||||
|
|
||||||
mac1 = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
|
mac1 = mustParseMAC(t, "01:02:03:04:05:06")
|
||||||
mac2 = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
|
mac2 = mustParseMAC(t, "06:05:04:03:02:01")
|
||||||
mac3 = errors.Must(net.ParseMAC("06:05:04:03:02:02"))
|
mac3 = mustParseMAC(t, "06:05:04:03:02:02")
|
||||||
)
|
)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -454,9 +452,9 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
|
|||||||
newIP = netip.MustParseAddr("192.168.0.3")
|
newIP = netip.MustParseAddr("192.168.0.3")
|
||||||
newIPv6 = netip.MustParseAddr("2001:db8::2")
|
newIPv6 = netip.MustParseAddr("2001:db8::2")
|
||||||
|
|
||||||
existMAC = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
|
existMAC = mustParseMAC(t, "01:02:03:04:05:06")
|
||||||
newMAC = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
|
newMAC = mustParseMAC(t, "02:03:04:05:06:07")
|
||||||
ipv6MAC = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
|
ipv6MAC = mustParseMAC(t, "06:05:04:03:02:01")
|
||||||
)
|
)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -561,13 +559,13 @@ func TestServer_Leases(t *testing.T) {
|
|||||||
Expiry: expiry,
|
Expiry: expiry,
|
||||||
IP: netip.MustParseAddr("192.168.0.3"),
|
IP: netip.MustParseAddr("192.168.0.3"),
|
||||||
Hostname: "example.host",
|
Hostname: "example.host",
|
||||||
HWAddr: errors.Must(net.ParseMAC("AA:AA:AA:AA:AA:AA")),
|
HWAddr: mustParseMAC(t, "AA:AA:AA:AA:AA:AA"),
|
||||||
IsStatic: false,
|
IsStatic: false,
|
||||||
}, {
|
}, {
|
||||||
Expiry: time.Time{},
|
Expiry: time.Time{},
|
||||||
IP: netip.MustParseAddr("192.168.0.4"),
|
IP: netip.MustParseAddr("192.168.0.4"),
|
||||||
Hostname: "example.static.host",
|
Hostname: "example.static.host",
|
||||||
HWAddr: errors.Must(net.ParseMAC("BB:BB:BB:BB:BB:BB")),
|
HWAddr: mustParseMAC(t, "BB:BB:BB:BB:BB:BB"),
|
||||||
IsStatic: true,
|
IsStatic: true,
|
||||||
}}
|
}}
|
||||||
assert.ElementsMatch(t, wantLeases, srv.Leases())
|
assert.ElementsMatch(t, wantLeases, srv.Leases())
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
|
||||||
"github.com/AdguardTeam/golibs/container"
|
"github.com/AdguardTeam/golibs/container"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
@@ -52,7 +51,7 @@ func processAccessClients(
|
|||||||
} else if ipnet, err = netip.ParsePrefix(s); err == nil {
|
} else if ipnet, err = netip.ParsePrefix(s); err == nil {
|
||||||
*nets = append(*nets, ipnet)
|
*nets = append(*nets, ipnet)
|
||||||
} else {
|
} else {
|
||||||
err = client.ValidateClientID(s)
|
err = ValidateClientID(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("value %q at index %d: bad ip, cidr, or clientid", s, i)
|
return fmt.Errorf("value %q at index %d: bad ip, cidr, or clientid", s, i)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,26 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ValidateClientID returns an error if id is not a valid ClientID.
|
||||||
|
//
|
||||||
|
// Keep in sync with [client.ValidateClientID].
|
||||||
|
func ValidateClientID(id string) (err error) {
|
||||||
|
err = netutil.ValidateHostnameLabel(id)
|
||||||
|
if err != nil {
|
||||||
|
// Replace the domain name label wrapper with our own.
|
||||||
|
return fmt.Errorf("invalid clientid %q: %w", id, errors.Unwrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// clientIDFromClientServerName extracts and validates a ClientID. hostSrvName
|
// clientIDFromClientServerName extracts and validates a ClientID. hostSrvName
|
||||||
// is the server name of the host. cliSrvName is the server name as sent by the
|
// is the server name of the host. cliSrvName is the server name as sent by the
|
||||||
// client. When strict is true, and client and host server name don't match,
|
// client. When strict is true, and client and host server name don't match,
|
||||||
@@ -40,7 +53,7 @@ func clientIDFromClientServerName(
|
|||||||
}
|
}
|
||||||
|
|
||||||
clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1]
|
clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1]
|
||||||
err = client.ValidateClientID(clientID)
|
err = ValidateClientID(clientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error, because it's informative enough as is.
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
return "", err
|
return "", err
|
||||||
@@ -80,7 +93,7 @@ func clientIDFromDNSContextHTTPS(pctx *proxy.DNSContext) (clientID string, err e
|
|||||||
return "", fmt.Errorf("clientid check: invalid path %q: extra parts", origPath)
|
return "", fmt.Errorf("clientid check: invalid path %q: extra parts", origPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.ValidateClientID(clientID)
|
err = ValidateClientID(clientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("clientid check: %w", err)
|
return "", fmt.Errorf("clientid check: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,317 +1,131 @@
|
|||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghuser"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"go.etcd.io/bbolt"
|
"github.com/AdguardTeam/golibs/timeutil"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// sessionTokenSize is the length of session token in bytes.
|
// webUser represents a user of the Web UI.
|
||||||
const sessionTokenSize = 16
|
type webUser struct {
|
||||||
|
// Name represents the login name of the web user.
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
|
||||||
type session struct {
|
// PasswordHash is the hashed representation of the web user password.
|
||||||
userName string
|
PasswordHash string `yaml:"password"`
|
||||||
// expire is the expiration time, in seconds.
|
|
||||||
expire uint32
|
// UserID is the unique identifier of the web user.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): !! Use this.
|
||||||
|
UserID aghuser.UserID `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) serialize() []byte {
|
// toUser returns the new properly initialized *aghuser.User using stored
|
||||||
const (
|
// properties. It panics if there is an error generating the user ID.
|
||||||
expireLen = 4
|
func (wu *webUser) toUser() (u *aghuser.User) {
|
||||||
nameLen = 2
|
uid := wu.UserID
|
||||||
)
|
if uid == (aghuser.UserID{}) {
|
||||||
data := make([]byte, expireLen+nameLen+len(s.userName))
|
uid = aghuser.MustNewUserID()
|
||||||
binary.BigEndian.PutUint32(data[0:4], s.expire)
|
|
||||||
binary.BigEndian.PutUint16(data[4:6], uint16(len(s.userName)))
|
|
||||||
copy(data[6:], []byte(s.userName))
|
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) deserialize(data []byte) bool {
|
return &aghuser.User{
|
||||||
if len(data) < 4+2 {
|
Password: aghuser.NewDefaultPassword(wu.PasswordHash),
|
||||||
return false
|
Login: aghuser.Login(wu.Name),
|
||||||
|
ID: uid,
|
||||||
}
|
}
|
||||||
s.expire = binary.BigEndian.Uint32(data[0:4])
|
|
||||||
nameLen := binary.BigEndian.Uint16(data[4:6])
|
|
||||||
data = data[6:]
|
|
||||||
|
|
||||||
if len(data) < int(nameLen) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
s.userName = string(data)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth is the global authentication object.
|
// Auth is the global authentication object.
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
trustedProxies netutil.SubnetSet
|
logger *slog.Logger
|
||||||
db *bbolt.DB
|
|
||||||
rateLimiter *authRateLimiter
|
rateLimiter *authRateLimiter
|
||||||
sessions map[string]*session
|
sessions aghuser.SessionStorage
|
||||||
users []webUser
|
trustedProxies netutil.SubnetSet
|
||||||
lock sync.Mutex
|
users aghuser.DB
|
||||||
sessionTTL uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// webUser represents a user of the Web UI.
|
// InitAuth initializes the global authentication object. baseLogger,
|
||||||
//
|
// rateLimiter, trustedProxies must not be nil. dbFilename and sessionTTL
|
||||||
// TODO(s.chzhen): Improve naming.
|
// should not be empty.
|
||||||
type webUser struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
PasswordHash string `yaml:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitAuth initializes the global authentication object.
|
|
||||||
func InitAuth(
|
func InitAuth(
|
||||||
|
ctx context.Context,
|
||||||
|
baseLogger *slog.Logger,
|
||||||
dbFilename string,
|
dbFilename string,
|
||||||
users []webUser,
|
users []webUser,
|
||||||
sessionTTL uint32,
|
sessionTTL time.Duration,
|
||||||
rateLimiter *authRateLimiter,
|
rateLimiter *authRateLimiter,
|
||||||
trustedProxies netutil.SubnetSet,
|
trustedProxies netutil.SubnetSet,
|
||||||
) (a *Auth) {
|
) (a *Auth, err error) {
|
||||||
log.Info("Initializing auth module: %s", dbFilename)
|
userDB := aghuser.NewDefaultDB()
|
||||||
|
for i, u := range users {
|
||||||
a = &Auth{
|
err = userDB.Create(ctx, u.toUser())
|
||||||
sessionTTL: sessionTTL,
|
|
||||||
rateLimiter: rateLimiter,
|
|
||||||
sessions: make(map[string]*session),
|
|
||||||
users: users,
|
|
||||||
trustedProxies: trustedProxies,
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
a.db, err = bbolt.Open(dbFilename, aghos.DefaultPermFile, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("auth: open DB: %s: %s", dbFilename, err)
|
return nil, fmt.Errorf("users: at index %d: %w", i, err)
|
||||||
if err.Error() == "invalid argument" {
|
}
|
||||||
log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#limitations")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
s, err := aghuser.NewDefaultSessionStorage(ctx, &aghuser.DefaultSessionStorageConfig{
|
||||||
|
Logger: baseLogger.With(slogutil.KeyPrefix, "session_storage"),
|
||||||
|
Clock: timeutil.SystemClock{},
|
||||||
|
UserDB: aghuser.NewDefaultDB(),
|
||||||
|
DBPath: dbFilename,
|
||||||
|
SessionTTL: sessionTTL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating session storage: %w", err)
|
||||||
}
|
}
|
||||||
a.loadSessions()
|
|
||||||
log.Info("auth: initialized. users:%d sessions:%d", len(a.users), len(a.sessions))
|
|
||||||
|
|
||||||
return a
|
return &Auth{
|
||||||
|
logger: baseLogger.With(slogutil.KeyPrefix, "auth"),
|
||||||
|
rateLimiter: rateLimiter,
|
||||||
|
trustedProxies: trustedProxies,
|
||||||
|
sessions: s,
|
||||||
|
users: userDB,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the authentication database.
|
// Close closes the authentication database.
|
||||||
func (a *Auth) Close() {
|
func (a *Auth) Close(ctx context.Context) {
|
||||||
_ = a.db.Close()
|
err := a.sessions.Close()
|
||||||
}
|
|
||||||
|
|
||||||
func bucketName() []byte {
|
|
||||||
return []byte("sessions-2")
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadSessions loads sessions from the database file and removes expired
|
|
||||||
// sessions.
|
|
||||||
func (a *Auth) loadSessions() {
|
|
||||||
tx, err := a.db.Begin(true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("auth: bbolt.Begin: %s", err)
|
a.logger.ErrorContext(ctx, "closing session storage", slogutil.KeyError, err)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
}()
|
|
||||||
|
|
||||||
bkt := tx.Bucket(bucketName())
|
|
||||||
if bkt == nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removed := 0
|
// isValidSession returns true if the session is valid.
|
||||||
|
func (a *Auth) isValidSession(ctx context.Context, cookieSess string) (ok bool) {
|
||||||
if tx.Bucket([]byte("sessions")) != nil {
|
sess, err := hex.DecodeString(cookieSess)
|
||||||
_ = tx.DeleteBucket([]byte("sessions"))
|
|
||||||
removed = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
now := uint32(time.Now().UTC().Unix())
|
|
||||||
forEach := func(k, v []byte) error {
|
|
||||||
s := session{}
|
|
||||||
if !s.deserialize(v) || s.expire <= now {
|
|
||||||
err = bkt.Delete(k)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("auth: bbolt.Delete: %s", err)
|
a.logger.ErrorContext(ctx, "checking session: decoding cookie", slogutil.KeyError, err)
|
||||||
} else {
|
|
||||||
removed++
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
a.sessions[hex.EncodeToString(k)] = &s
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_ = bkt.ForEach(forEach)
|
|
||||||
if removed != 0 {
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("bolt.Commit(): %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("auth: loaded %d sessions from DB (removed %d expired)", len(a.sessions), removed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addSession adds a new session to the list of sessions and saves it in the
|
|
||||||
// database file.
|
|
||||||
func (a *Auth) addSession(data []byte, s *session) {
|
|
||||||
name := hex.EncodeToString(data)
|
|
||||||
a.lock.Lock()
|
|
||||||
a.sessions[name] = s
|
|
||||||
a.lock.Unlock()
|
|
||||||
if a.storeSession(data, s) {
|
|
||||||
log.Debug("auth: created session %s: expire=%d", name, s.expire)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// storeSession saves a session in the database file.
|
|
||||||
func (a *Auth) storeSession(data []byte, s *session) bool {
|
|
||||||
tx, err := a.db.Begin(true)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("auth: bbolt.Begin: %s", err)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
}()
|
|
||||||
|
|
||||||
bkt, err := tx.CreateBucketIfNotExists(bucketName())
|
|
||||||
if err != nil {
|
|
||||||
log.Error("auth: bbolt.CreateBucketIfNotExists: %s", err)
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bkt.Put(data, s.serialize())
|
var t aghuser.SessionToken
|
||||||
|
copy(t[:], sess)
|
||||||
|
|
||||||
|
s, err := a.sessions.FindByToken(ctx, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("auth: bbolt.Put: %s", err)
|
a.logger.ErrorContext(ctx, "checking session", slogutil.KeyError, err)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit()
|
return s != nil
|
||||||
if err != nil {
|
|
||||||
log.Error("auth: bbolt.Commit: %s", err)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
// addUser adds a new user with the given password. u must not be nil.
|
||||||
}
|
func (a *Auth) addUser(ctx context.Context, u *webUser, password string) (err error) {
|
||||||
|
|
||||||
// removeSessionFromFile removes a stored session from the DB file on disk.
|
|
||||||
func (a *Auth) removeSessionFromFile(sess []byte) {
|
|
||||||
tx, err := a.db.Begin(true)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("auth: bbolt.Begin: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
}()
|
|
||||||
|
|
||||||
bkt := tx.Bucket(bucketName())
|
|
||||||
if bkt == nil {
|
|
||||||
log.Error("auth: bbolt.Bucket")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bkt.Delete(sess)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("auth: bbolt.Put: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("auth: bbolt.Commit: %s", err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("auth: removed session from DB")
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkSessionResult is the result of checking a session.
|
|
||||||
type checkSessionResult int
|
|
||||||
|
|
||||||
// checkSessionResult constants.
|
|
||||||
const (
|
|
||||||
checkSessionOK checkSessionResult = 0
|
|
||||||
checkSessionNotFound checkSessionResult = -1
|
|
||||||
checkSessionExpired checkSessionResult = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// checkSession checks if the session is valid.
|
|
||||||
func (a *Auth) checkSession(sess string) (res checkSessionResult) {
|
|
||||||
now := uint32(time.Now().UTC().Unix())
|
|
||||||
update := false
|
|
||||||
|
|
||||||
a.lock.Lock()
|
|
||||||
defer a.lock.Unlock()
|
|
||||||
|
|
||||||
s, ok := a.sessions[sess]
|
|
||||||
if !ok {
|
|
||||||
return checkSessionNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.expire <= now {
|
|
||||||
delete(a.sessions, sess)
|
|
||||||
key, _ := hex.DecodeString(sess)
|
|
||||||
a.removeSessionFromFile(key)
|
|
||||||
|
|
||||||
return checkSessionExpired
|
|
||||||
}
|
|
||||||
|
|
||||||
newExpire := now + a.sessionTTL
|
|
||||||
if s.expire/(24*60*60) != newExpire/(24*60*60) {
|
|
||||||
// update expiration time once a day
|
|
||||||
update = true
|
|
||||||
s.expire = newExpire
|
|
||||||
}
|
|
||||||
|
|
||||||
if update {
|
|
||||||
key, _ := hex.DecodeString(sess)
|
|
||||||
if a.storeSession(key, s) {
|
|
||||||
log.Debug("auth: updated session %s: expire=%d", sess, s.expire)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return checkSessionOK
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeSession removes the session from the active sessions and the disk.
|
|
||||||
func (a *Auth) removeSession(sess string) {
|
|
||||||
key, _ := hex.DecodeString(sess)
|
|
||||||
a.lock.Lock()
|
|
||||||
delete(a.sessions, sess)
|
|
||||||
a.lock.Unlock()
|
|
||||||
a.removeSessionFromFile(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addUser adds a new user with the given password.
|
|
||||||
func (a *Auth) addUser(u *webUser, password string) (err error) {
|
|
||||||
if len(password) == 0 {
|
if len(password) == 0 {
|
||||||
return errors.Error("empty password")
|
return errors.Error("empty password")
|
||||||
}
|
}
|
||||||
@@ -323,97 +137,129 @@ func (a *Auth) addUser(u *webUser, password string) (err error) {
|
|||||||
|
|
||||||
u.PasswordHash = string(hash)
|
u.PasswordHash = string(hash)
|
||||||
|
|
||||||
a.lock.Lock()
|
err = a.users.Create(ctx, u.toUser())
|
||||||
defer a.lock.Unlock()
|
if err != nil {
|
||||||
|
// Should not happen.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
a.users = append(a.users, *u)
|
a.logger.DebugContext(ctx, "added user", "login", u.Name)
|
||||||
|
|
||||||
log.Debug("auth: added user with login %q", u.Name)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findUser returns a user if there is one.
|
// findUser returns a user if one exists with the provided login and the
|
||||||
func (a *Auth) findUser(login, password string) (u webUser, ok bool) {
|
// password matches.
|
||||||
a.lock.Lock()
|
func (a *Auth) findUser(ctx context.Context, login, password string) (user *aghuser.User) {
|
||||||
defer a.lock.Unlock()
|
user, err := a.users.ByLogin(ctx, aghuser.Login(login))
|
||||||
|
if err != nil {
|
||||||
for _, u = range a.users {
|
return nil
|
||||||
if u.Name == login &&
|
|
||||||
bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password)) == nil {
|
|
||||||
return u, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return webUser{}, false
|
ok := user.Password.Authenticate(ctx, password)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCurrentUser returns the current user. It returns an empty User if the
|
return user
|
||||||
// user is not found.
|
}
|
||||||
func (a *Auth) getCurrentUser(r *http.Request) (u webUser) {
|
|
||||||
|
// getCurrentUser searches for a user using a cookie or credentials from basic
|
||||||
|
// authentication.
|
||||||
|
func (a *Auth) getCurrentUser(r *http.Request) (user *aghuser.User) {
|
||||||
|
ctx := r.Context()
|
||||||
cookie, err := r.Cookie(sessionCookieName)
|
cookie, err := r.Cookie(sessionCookieName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// There's no Cookie, check Basic authentication.
|
// There's no Cookie, check Basic authentication.
|
||||||
user, pass, ok := r.BasicAuth()
|
user, pass, ok := r.BasicAuth()
|
||||||
if ok {
|
if ok {
|
||||||
u, _ = globalContext.auth.findUser(user, pass)
|
return a.findUser(ctx, user, pass)
|
||||||
|
|
||||||
return u
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return webUser{}
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
a.lock.Lock()
|
sess, err := hex.DecodeString(cookie.Value)
|
||||||
defer a.lock.Unlock()
|
if err != nil {
|
||||||
|
a.logger.ErrorContext(
|
||||||
|
ctx,
|
||||||
|
"searching for user: decoding cookie value",
|
||||||
|
slogutil.KeyError, err,
|
||||||
|
)
|
||||||
|
|
||||||
s, ok := a.sessions[cookie.Value]
|
return nil
|
||||||
if !ok {
|
|
||||||
return webUser{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u = range a.users {
|
var t aghuser.SessionToken
|
||||||
if u.Name == s.userName {
|
copy(t[:], sess)
|
||||||
return u
|
|
||||||
|
s, err := a.sessions.FindByToken(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.ErrorContext(ctx, "searching for user", slogutil.KeyError, err)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &aghuser.User{
|
||||||
|
Login: s.UserLogin,
|
||||||
|
ID: s.UserID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return webUser{}
|
// removeSession deletes the session from the active sessions and the disk. It
|
||||||
|
// also logs any occurring errors.
|
||||||
|
func (a *Auth) removeSession(ctx context.Context, cookieSess string) {
|
||||||
|
sess, err := hex.DecodeString(cookieSess)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.ErrorContext(ctx, "removing session: decoding cookie", slogutil.KeyError, err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var t aghuser.SessionToken
|
||||||
|
copy(t[:], sess)
|
||||||
|
|
||||||
|
err = a.sessions.DeleteByToken(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.ErrorContext(ctx, "removing session by token", slogutil.KeyError, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// usersList returns a copy of a users list.
|
// usersList returns a copy of a users list.
|
||||||
func (a *Auth) usersList() (users []webUser) {
|
func (a *Auth) usersList(ctx context.Context) (webUsers []webUser) {
|
||||||
a.lock.Lock()
|
users, err := a.users.All(ctx)
|
||||||
defer a.lock.Unlock()
|
if err != nil {
|
||||||
|
// Should not happen.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
users = make([]webUser, len(a.users))
|
webUsers = make([]webUser, 0, len(users))
|
||||||
copy(users, a.users)
|
for _, u := range users {
|
||||||
|
webUsers = append(webUsers, webUser{
|
||||||
|
Name: string(u.Login),
|
||||||
|
PasswordHash: string(u.Password.Hash()),
|
||||||
|
UserID: u.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return users
|
return webUsers
|
||||||
}
|
}
|
||||||
|
|
||||||
// authRequired returns true if a authentication is required.
|
// authRequired returns true if a authentication is required.
|
||||||
func (a *Auth) authRequired() bool {
|
func (a *Auth) authRequired(ctx context.Context) (ok bool) {
|
||||||
if GLMode {
|
if GLMode {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
a.lock.Lock()
|
users, err := a.users.All(ctx)
|
||||||
defer a.lock.Unlock()
|
if err != nil {
|
||||||
|
// Should not happen.
|
||||||
return len(a.users) != 0
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSessionToken returns cryptographically secure randomly generated slice of
|
return len(users) != 0
|
||||||
// bytes of sessionTokenSize length.
|
|
||||||
//
|
|
||||||
// TODO(e.burkov): Think about using byte array instead of byte slice.
|
|
||||||
func newSessionToken() (data []byte) {
|
|
||||||
randData := make([]byte, sessionTokenSize)
|
|
||||||
|
|
||||||
// Since Go 1.24, crypto/rand.Read doesn't return an error and crashes
|
|
||||||
// unrecoverably instead.
|
|
||||||
_, _ = rand.Read(randData)
|
|
||||||
|
|
||||||
return randData
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
package home
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAuth(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
fn := filepath.Join(dir, "sessions.db")
|
|
||||||
|
|
||||||
users := []webUser{{
|
|
||||||
Name: "name",
|
|
||||||
PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2",
|
|
||||||
}}
|
|
||||||
a := InitAuth(fn, nil, 60, nil, nil)
|
|
||||||
s := session{}
|
|
||||||
|
|
||||||
user := webUser{Name: "name"}
|
|
||||||
err := a.addUser(&user, "password")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, checkSessionNotFound, a.checkSession("notfound"))
|
|
||||||
a.removeSession("notfound")
|
|
||||||
|
|
||||||
sess := newSessionToken()
|
|
||||||
sessStr := hex.EncodeToString(sess)
|
|
||||||
|
|
||||||
now := time.Now().UTC().Unix()
|
|
||||||
// check expiration
|
|
||||||
s.expire = uint32(now)
|
|
||||||
a.addSession(sess, &s)
|
|
||||||
assert.Equal(t, checkSessionExpired, a.checkSession(sessStr))
|
|
||||||
|
|
||||||
// add session with TTL = 2 sec
|
|
||||||
s = session{}
|
|
||||||
s.expire = uint32(time.Now().UTC().Unix() + 2)
|
|
||||||
a.addSession(sess, &s)
|
|
||||||
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
|
|
||||||
|
|
||||||
a.Close()
|
|
||||||
|
|
||||||
// load saved session
|
|
||||||
a = InitAuth(fn, users, 60, nil, nil)
|
|
||||||
|
|
||||||
// the session is still alive
|
|
||||||
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
|
|
||||||
// reset our expiration time because checkSession() has just updated it
|
|
||||||
s.expire = uint32(time.Now().UTC().Unix() + 2)
|
|
||||||
a.storeSession(sess, &s)
|
|
||||||
a.Close()
|
|
||||||
|
|
||||||
u, ok := a.findUser("name", "password")
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.NotEmpty(t, u.Name)
|
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// load and remove expired sessions
|
|
||||||
a = InitAuth(fn, users, 60, nil, nil)
|
|
||||||
assert.Equal(t, checkSessionNotFound, a.checkSession(sessStr))
|
|
||||||
|
|
||||||
a.Close()
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -32,10 +33,14 @@ type loginJSON struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newCookie creates a new authentication cookie.
|
// newCookie creates a new authentication cookie.
|
||||||
func (a *Auth) newCookie(req loginJSON, addr string) (c *http.Cookie, err error) {
|
func (a *Auth) newCookie(
|
||||||
|
ctx context.Context,
|
||||||
|
req loginJSON,
|
||||||
|
addr string,
|
||||||
|
) (c *http.Cookie, err error) {
|
||||||
rateLimiter := a.rateLimiter
|
rateLimiter := a.rateLimiter
|
||||||
u, ok := a.findUser(req.Name, req.Password)
|
u := a.findUser(ctx, req.Name, req.Password)
|
||||||
if !ok {
|
if u == nil {
|
||||||
if rateLimiter != nil {
|
if rateLimiter != nil {
|
||||||
rateLimiter.inc(addr)
|
rateLimiter.inc(addr)
|
||||||
}
|
}
|
||||||
@@ -47,19 +52,16 @@ func (a *Auth) newCookie(req loginJSON, addr string) (c *http.Cookie, err error)
|
|||||||
rateLimiter.remove(addr)
|
rateLimiter.remove(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := newSessionToken()
|
s, err := a.sessions.New(ctx, u)
|
||||||
now := time.Now().UTC()
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating session: %w", err)
|
||||||
a.addSession(sess, &session{
|
}
|
||||||
userName: u.Name,
|
|
||||||
expire: uint32(now.Unix()) + a.sessionTTL,
|
|
||||||
})
|
|
||||||
|
|
||||||
return &http.Cookie{
|
return &http.Cookie{
|
||||||
Name: sessionCookieName,
|
Name: sessionCookieName,
|
||||||
Value: hex.EncodeToString(sess),
|
Value: hex.EncodeToString(s.Token[:]),
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Expires: now.Add(cookieTTL),
|
Expires: time.Now().Add(cookieTTL),
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -172,7 +174,7 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Error("auth: getting real ip from request with remote ip %s: %s", remoteIP, err)
|
log.Error("auth: getting real ip from request with remote ip %s: %s", remoteIP, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie, err := globalContext.auth.newCookie(req, remoteIP)
|
cookie, err := globalContext.auth.newCookie(r.Context(), req, remoteIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logIP := remoteIP
|
logIP := remoteIP
|
||||||
if globalContext.auth.trustedProxies.Contains(ip.Unmap()) {
|
if globalContext.auth.trustedProxies.Contains(ip.Unmap()) {
|
||||||
@@ -209,7 +211,7 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
globalContext.auth.removeSession(c.Value)
|
globalContext.auth.removeSession(r.Context(), c.Value)
|
||||||
|
|
||||||
c = &http.Cookie{
|
c = &http.Cookie{
|
||||||
Name: sessionCookieName,
|
Name: sessionCookieName,
|
||||||
@@ -242,28 +244,7 @@ func optionalAuthThird(w http.ResponseWriter, r *http.Request) (mustAuth bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect to login page if not authenticated
|
if u := globalContext.auth.getCurrentUser(r); u != nil {
|
||||||
isAuthenticated := false
|
|
||||||
cookie, err := r.Cookie(sessionCookieName)
|
|
||||||
if err != nil {
|
|
||||||
// The only error that is returned from r.Cookie is [http.ErrNoCookie].
|
|
||||||
// Check Basic authentication.
|
|
||||||
user, pass, hasBasic := r.BasicAuth()
|
|
||||||
if hasBasic {
|
|
||||||
_, isAuthenticated = globalContext.auth.findUser(user, pass)
|
|
||||||
if !isAuthenticated {
|
|
||||||
log.Info("%s: invalid basic authorization value", pref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res := globalContext.auth.checkSession(cookie.Value)
|
|
||||||
isAuthenticated = res == checkSessionOK
|
|
||||||
if !isAuthenticated {
|
|
||||||
log.Debug("%s: invalid cookie value: %q", pref, cookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isAuthenticated {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,14 +270,14 @@ func optionalAuth(
|
|||||||
h func(http.ResponseWriter, *http.Request),
|
h func(http.ResponseWriter, *http.Request),
|
||||||
) (wrapped func(http.ResponseWriter, *http.Request)) {
|
) (wrapped func(http.ResponseWriter, *http.Request)) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
p := r.URL.Path
|
p := r.URL.Path
|
||||||
authRequired := globalContext.auth != nil && globalContext.auth.authRequired()
|
authRequired := globalContext.auth != nil && globalContext.auth.authRequired(ctx)
|
||||||
if p == "/login.html" {
|
if p == "/login.html" {
|
||||||
cookie, err := r.Cookie(sessionCookieName)
|
cookie, err := r.Cookie(sessionCookieName)
|
||||||
if authRequired && err == nil {
|
if authRequired && err == nil {
|
||||||
// Redirect to the dashboard if already authenticated.
|
// Redirect to the dashboard if already authenticated.
|
||||||
res := globalContext.auth.checkSession(cookie.Value)
|
if globalContext.auth.isValidSession(ctx, cookie.Value) {
|
||||||
if res == checkSessionOK {
|
|
||||||
http.Redirect(w, r, "", http.StatusFound)
|
http.Redirect(w, r, "", http.StatusFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/httphdr"
|
"github.com/AdguardTeam/golibs/httphdr"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -33,13 +35,20 @@ func (w *testResponseWriter) WriteHeader(statusCode int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthHTTP(t *testing.T) {
|
func TestAuthHTTP(t *testing.T) {
|
||||||
|
var (
|
||||||
|
ctx = testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
logger = slogutil.NewDiscardLogger()
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
fn := filepath.Join(dir, "sessions.db")
|
fn := filepath.Join(dir, "sessions.db")
|
||||||
|
|
||||||
users := []webUser{
|
users := []webUser{
|
||||||
{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||||
}
|
}
|
||||||
globalContext.auth = InitAuth(fn, users, 60, nil, nil)
|
globalContext.auth, err = InitAuth(ctx, logger, fn, users, time.Minute, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
handlerCalled := false
|
handlerCalled := false
|
||||||
handler := func(_ http.ResponseWriter, _ *http.Request) {
|
handler := func(_ http.ResponseWriter, _ *http.Request) {
|
||||||
@@ -68,7 +77,11 @@ func TestAuthHTTP(t *testing.T) {
|
|||||||
assert.True(t, handlerCalled)
|
assert.True(t, handlerCalled)
|
||||||
|
|
||||||
// perform login
|
// perform login
|
||||||
cookie, err := globalContext.auth.newCookie(loginJSON{Name: "name", Password: "password"}, "")
|
cookie, err := globalContext.auth.newCookie(
|
||||||
|
ctx,
|
||||||
|
loginJSON{Name: "name", Password: "password"},
|
||||||
|
"",
|
||||||
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, cookie)
|
require.NotNil(t, cookie)
|
||||||
|
|
||||||
@@ -114,7 +127,7 @@ func TestAuthHTTP(t *testing.T) {
|
|||||||
assert.True(t, handlerCalled)
|
assert.True(t, handlerCalled)
|
||||||
r.Header.Del(httphdr.Cookie)
|
r.Header.Del(httphdr.Cookie)
|
||||||
|
|
||||||
globalContext.auth.Close()
|
globalContext.auth.Close(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRealIP(t *testing.T) {
|
func TestRealIP(t *testing.T) {
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ type clientsContainer struct {
|
|||||||
// filter. It must not be nil.
|
// filter. It must not be nil.
|
||||||
baseLogger *slog.Logger
|
baseLogger *slog.Logger
|
||||||
|
|
||||||
// logger is used for logging the operation of the client container. It
|
|
||||||
// must not be nil.
|
|
||||||
logger *slog.Logger
|
|
||||||
|
|
||||||
// storage stores information about persistent clients.
|
// storage stores information about persistent clients.
|
||||||
storage *client.Storage
|
storage *client.Storage
|
||||||
|
|
||||||
@@ -62,7 +58,6 @@ type clientsContainer struct {
|
|||||||
// BlockedClientChecker checks if a client is blocked by the current access
|
// BlockedClientChecker checks if a client is blocked by the current access
|
||||||
// settings.
|
// settings.
|
||||||
type BlockedClientChecker interface {
|
type BlockedClientChecker interface {
|
||||||
// TODO(s.chzhen): Accept [client.FindParams].
|
|
||||||
IsBlockedClient(ip netip.Addr, clientID string) (blocked bool, rule string)
|
IsBlockedClient(ip netip.Addr, clientID string) (blocked bool, rule string)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +80,6 @@ func (clients *clientsContainer) Init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
clients.baseLogger = baseLogger
|
clients.baseLogger = baseLogger
|
||||||
clients.logger = baseLogger.With(slogutil.KeyPrefix, "client_container")
|
|
||||||
clients.safeSearchCacheSize = filteringConf.SafeSearchCacheSize
|
clients.safeSearchCacheSize = filteringConf.SafeSearchCacheSize
|
||||||
clients.safeSearchCacheTTL = time.Minute * time.Duration(filteringConf.CacheTime)
|
clients.safeSearchCacheTTL = time.Minute * time.Duration(filteringConf.CacheTime)
|
||||||
|
|
||||||
@@ -275,7 +269,7 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
|
|||||||
|
|
||||||
BlockedServices: cli.BlockedServices.Clone(),
|
BlockedServices: cli.BlockedServices.Clone(),
|
||||||
|
|
||||||
IDs: cli.Identifiers(),
|
IDs: cli.IDs(),
|
||||||
Tags: slices.Clone(cli.Tags),
|
Tags: slices.Clone(cli.Tags),
|
||||||
Upstreams: slices.Clone(cli.Upstreams),
|
Upstreams: slices.Clone(cli.Upstreams),
|
||||||
|
|
||||||
@@ -362,27 +356,15 @@ func (clients *clientsContainer) clientOrArtificial(
|
|||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// shouldCountClient is a wrapper around [client.Storage.Find] to make it a
|
// shouldCountClient is a wrapper around [clientsContainer.find] to make it a
|
||||||
// valid client information finder for the statistics. If no information about
|
// valid client information finder for the statistics. If no information about
|
||||||
// the client is found, it returns true. Values of ids must be either a valid
|
// the client is found, it returns true.
|
||||||
// ClientID or a valid IP address.
|
|
||||||
//
|
|
||||||
// TODO(s.chzhen): Accept [client.FindParams].
|
|
||||||
func (clients *clientsContainer) shouldCountClient(ids []string) (y bool) {
|
func (clients *clientsContainer) shouldCountClient(ids []string) (y bool) {
|
||||||
clients.lock.Lock()
|
clients.lock.Lock()
|
||||||
defer clients.lock.Unlock()
|
defer clients.lock.Unlock()
|
||||||
|
|
||||||
params := &client.FindParams{}
|
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
err := params.Set(id)
|
client, ok := clients.storage.Find(id)
|
||||||
if err != nil {
|
|
||||||
// Should not happen.
|
|
||||||
clients.logger.Warn("parsing find params", slogutil.KeyError, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
client, ok := clients.storage.Find(params)
|
|
||||||
if ok {
|
if ok {
|
||||||
return !client.IgnoreStatistics
|
return !client.IgnoreStatistics
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ func clientToJSON(c *client.Persistent) (cj *clientJSON) {
|
|||||||
|
|
||||||
return &clientJSON{
|
return &clientJSON{
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
IDs: c.Identifiers(),
|
IDs: c.IDs(),
|
||||||
Tags: c.Tags,
|
Tags: c.Tags,
|
||||||
UseGlobalSettings: !c.UseOwnSettings,
|
UseGlobalSettings: !c.UseOwnSettings,
|
||||||
FilteringEnabled: c.FilteringEnabled,
|
FilteringEnabled: c.FilteringEnabled,
|
||||||
@@ -428,53 +428,32 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
|
|||||||
// Deprecated: Remove it when migration to the new API is over.
|
// Deprecated: Remove it when migration to the new API is over.
|
||||||
func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) {
|
func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) {
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
data := make([]map[string]*clientJSON, 0, len(q))
|
data := []map[string]*clientJSON{}
|
||||||
params := &client.FindParams{}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for i := range len(q) {
|
for i := range len(q) {
|
||||||
idStr := q.Get(fmt.Sprintf("ip%d", i))
|
idStr := q.Get(fmt.Sprintf("ip%d", i))
|
||||||
if idStr == "" {
|
if idStr == "" {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
err = params.Set(idStr)
|
|
||||||
if err != nil {
|
|
||||||
clients.logger.DebugContext(
|
|
||||||
r.Context(),
|
|
||||||
"finding client",
|
|
||||||
"id", idStr,
|
|
||||||
slogutil.KeyError, err,
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
data = append(data, map[string]*clientJSON{
|
data = append(data, map[string]*clientJSON{
|
||||||
idStr: clients.findClient(idStr, params),
|
idStr: clients.findClient(idStr),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
aghhttp.WriteJSONResponseOK(w, r, data)
|
aghhttp.WriteJSONResponseOK(w, r, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findClient returns available information about a client by params from the
|
// findClient returns available information about a client by idStr from the
|
||||||
// client's storage or access settings. idStr is the string representation of
|
// client's storage or access settings. cj is guaranteed to be non-nil.
|
||||||
// typed params. params must not be nil. cj is guaranteed to be non-nil.
|
func (clients *clientsContainer) findClient(idStr string) (cj *clientJSON) {
|
||||||
func (clients *clientsContainer) findClient(
|
ip, _ := netip.ParseAddr(idStr)
|
||||||
idStr string,
|
c, ok := clients.storage.Find(idStr)
|
||||||
params *client.FindParams,
|
|
||||||
) (cj *clientJSON) {
|
|
||||||
c, ok := clients.storage.Find(params)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return clients.findRuntime(idStr, params)
|
return clients.findRuntime(ip, idStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
cj = clientToJSON(c)
|
cj = clientToJSON(c)
|
||||||
disallowed, rule := clients.clientChecker.IsBlockedClient(
|
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, idStr)
|
||||||
params.RemoteIP,
|
|
||||||
string(params.ClientID),
|
|
||||||
)
|
|
||||||
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
||||||
|
|
||||||
return cj
|
return cj
|
||||||
@@ -493,8 +472,7 @@ type searchClientJSON struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleSearchClient is the handler for the POST /control/clients/search HTTP
|
// handleSearchClient is the handler for the POST /control/clients/search HTTP API.
|
||||||
// API.
|
|
||||||
func (clients *clientsContainer) handleSearchClient(w http.ResponseWriter, r *http.Request) {
|
func (clients *clientsContainer) handleSearchClient(w http.ResponseWriter, r *http.Request) {
|
||||||
q := searchQueryJSON{}
|
q := searchQueryJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&q)
|
err := json.NewDecoder(r.Body).Decode(&q)
|
||||||
@@ -504,25 +482,11 @@ func (clients *clientsContainer) handleSearchClient(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]map[string]*clientJSON, 0, len(q.Clients))
|
data := []map[string]*clientJSON{}
|
||||||
params := &client.FindParams{}
|
|
||||||
|
|
||||||
for _, c := range q.Clients {
|
for _, c := range q.Clients {
|
||||||
idStr := c.ID
|
idStr := c.ID
|
||||||
err = params.Set(idStr)
|
|
||||||
if err != nil {
|
|
||||||
clients.logger.DebugContext(
|
|
||||||
r.Context(),
|
|
||||||
"searching client",
|
|
||||||
"id", idStr,
|
|
||||||
slogutil.KeyError, err,
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
data = append(data, map[string]*clientJSON{
|
data = append(data, map[string]*clientJSON{
|
||||||
idStr: clients.findClient(idStr, params),
|
idStr: clients.findClient(idStr),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,37 +494,38 @@ func (clients *clientsContainer) handleSearchClient(w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
// findRuntime looks up the IP in runtime and temporary storages, like
|
// findRuntime looks up the IP in runtime and temporary storages, like
|
||||||
// /etc/hosts tables, DHCP leases, or blocklists. params must not be nil. cj
|
// /etc/hosts tables, DHCP leases, or blocklists. cj is guaranteed to be
|
||||||
// is guaranteed to be non-nil.
|
// non-nil.
|
||||||
func (clients *clientsContainer) findRuntime(
|
func (clients *clientsContainer) findRuntime(ip netip.Addr, idStr string) (cj *clientJSON) {
|
||||||
idStr string,
|
|
||||||
params *client.FindParams,
|
|
||||||
) (cj *clientJSON) {
|
|
||||||
var host string
|
|
||||||
whois := &whois.Info{}
|
|
||||||
|
|
||||||
ip := params.RemoteIP
|
|
||||||
rc := clients.storage.ClientRuntime(ip)
|
rc := clients.storage.ClientRuntime(ip)
|
||||||
if rc != nil {
|
if rc == nil {
|
||||||
_, host = rc.Info()
|
// It is still possible that the IP used to be in the runtime clients
|
||||||
whois = whoisOrEmpty(rc)
|
// list, but then the server was reloaded. So, check the DNS server's
|
||||||
}
|
// blocked IP list.
|
||||||
|
|
||||||
// Check the DNS server's blocked IP list regardless of whether a runtime
|
|
||||||
// client was found or not. This is because it's still possible that the
|
|
||||||
// runtime client associated with the IP address was stored previously, but
|
|
||||||
// then the server was reloaded.
|
|
||||||
//
|
//
|
||||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2428.
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2428.
|
||||||
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, string(params.ClientID))
|
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, idStr)
|
||||||
|
cj = &clientJSON{
|
||||||
return &clientJSON{
|
|
||||||
Name: host,
|
|
||||||
IDs: []string{idStr},
|
IDs: []string{idStr},
|
||||||
WHOIS: whois,
|
|
||||||
Disallowed: &disallowed,
|
Disallowed: &disallowed,
|
||||||
DisallowedRule: &rule,
|
DisallowedRule: &rule,
|
||||||
|
WHOIS: &whois.Info{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cj
|
||||||
|
}
|
||||||
|
|
||||||
|
_, host := rc.Info()
|
||||||
|
cj = &clientJSON{
|
||||||
|
Name: host,
|
||||||
|
IDs: []string{idStr},
|
||||||
|
WHOIS: whoisOrEmpty(rc),
|
||||||
|
}
|
||||||
|
|
||||||
|
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, idStr)
|
||||||
|
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
||||||
|
|
||||||
|
return cj
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterClientsHandlers registers HTTP handlers
|
// RegisterClientsHandlers registers HTTP handlers
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func TestClientsContainer_HandleAddClient(t *testing.T) {
|
|||||||
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||||
|
|
||||||
clientEmptyID := newPersistentClient("empty_client_id")
|
clientEmptyID := newPersistentClient("empty_client_id")
|
||||||
clientEmptyID.ClientIDs = []client.ClientID{""}
|
clientEmptyID.ClientIDs = []string{""}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -278,7 +278,7 @@ func TestClientsContainer_HandleUpdateClient(t *testing.T) {
|
|||||||
clientModified := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
clientModified := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||||
|
|
||||||
clientEmptyID := newPersistentClient("empty_client_id")
|
clientEmptyID := newPersistentClient("empty_client_id")
|
||||||
clientEmptyID.ClientIDs = []client.ClientID{""}
|
clientEmptyID.ClientIDs = []string{""}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package home
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -748,7 +749,8 @@ func (c *configuration) write(tlsMgr *tlsManager) (err error) {
|
|||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
|
|
||||||
if globalContext.auth != nil {
|
if globalContext.auth != nil {
|
||||||
config.Users = globalContext.auth.usersList()
|
// TODO(s.chzhen): Pass context.
|
||||||
|
config.Users = globalContext.auth.usersList(context.TODO())
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsMgr != nil {
|
if tlsMgr != nil {
|
||||||
|
|||||||
@@ -392,6 +392,8 @@ const PasswordMinRunes = 8
|
|||||||
|
|
||||||
// Apply new configuration, start DNS server, restart Web server
|
// Apply new configuration, start DNS server, restart Web server
|
||||||
func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
req, restartHTTP, err := decodeApplyConfigReq(r.Body)
|
req, restartHTTP, err := decodeApplyConfigReq(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||||
@@ -439,7 +441,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
|||||||
u := &webUser{
|
u := &webUser{
|
||||||
Name: req.Username,
|
Name: req.Username,
|
||||||
}
|
}
|
||||||
err = globalContext.auth.addUser(u, req.Password)
|
err = globalContext.auth.addUser(ctx, u, req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
globalContext.firstRun = true
|
globalContext.firstRun = true
|
||||||
copyInstallSettings(config, curConfig)
|
copyInstallSettings(config, curConfig)
|
||||||
@@ -452,7 +454,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
|||||||
// moment we'll allow setting up TLS in the initial configuration or the
|
// moment we'll allow setting up TLS in the initial configuration or the
|
||||||
// configuration itself will use HTTPS protocol, because the underlying
|
// configuration itself will use HTTPS protocol, because the underlying
|
||||||
// functions potentially restart the HTTPS server.
|
// functions potentially restart the HTTPS server.
|
||||||
err = startMods(r.Context(), web.baseLogger, web.tlsManager)
|
err = startMods(ctx, web.baseLogger, web.tlsManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
globalContext.firstRun = true
|
globalContext.firstRun = true
|
||||||
copyInstallSettings(config, curConfig)
|
copyInstallSettings(config, curConfig)
|
||||||
@@ -488,11 +490,11 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
|||||||
// and with its own context, because it waits until all requests are handled
|
// and with its own context, because it waits until all requests are handled
|
||||||
// and will be blocked by it's own caller.
|
// and will be blocked by it's own caller.
|
||||||
go func(timeout time.Duration) {
|
go func(timeout time.Duration) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
defer slogutil.RecoverAndLog(ctx, web.logger)
|
defer slogutil.RecoverAndLog(shutdownCtx, web.logger)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
shutdownSrv(ctx, web.logger, web.httpServer)
|
shutdownSrv(shutdownCtx, web.logger, web.httpServer)
|
||||||
}(shutdownTimeout)
|
}(shutdownTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func (web *webAPI) requestVersionInfo(
|
|||||||
) (err error) {
|
) (err error) {
|
||||||
updater := web.conf.updater
|
updater := web.conf.updater
|
||||||
for range 3 {
|
for range 3 {
|
||||||
resp.VersionInfo, err = updater.VersionInfo(ctx, recheck)
|
resp.VersionInfo, err = updater.VersionInfo(recheck)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = updater.Update(r.Context(), false)
|
err = updater.Update(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
|
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
|
||||||
|
|
||||||
|
|||||||
@@ -119,15 +119,16 @@ func initDNS(
|
|||||||
globalContext.dhcpServer,
|
globalContext.dhcpServer,
|
||||||
anonymizer,
|
anonymizer,
|
||||||
httpRegister,
|
httpRegister,
|
||||||
|
tlsMgr.config(),
|
||||||
tlsMgr,
|
tlsMgr,
|
||||||
baseLogger,
|
baseLogger,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initDNSServer initializes the [context.dnsServer]. To only use the internal
|
// initDNSServer initializes the [context.dnsServer]. To only use the internal
|
||||||
// proxy, none of the arguments are required, but tlsMgr and l still must not be
|
// proxy, none of the arguments are required, but tlsConf, tlsMgr and l still
|
||||||
// nil, in other cases all the arguments also must not be nil. It also must not
|
// must not be nil, in other cases all the arguments also must not be nil. It
|
||||||
// be called unless [config] and [globalContext] are initialized.
|
// also must not be called unless [config] and [globalContext] are initialized.
|
||||||
//
|
//
|
||||||
// TODO(e.burkov): Use [dnsforward.DNSCreateParams] as a parameter.
|
// TODO(e.burkov): Use [dnsforward.DNSCreateParams] as a parameter.
|
||||||
func initDNSServer(
|
func initDNSServer(
|
||||||
@@ -137,6 +138,7 @@ func initDNSServer(
|
|||||||
dhcpSrv dnsforward.DHCP,
|
dhcpSrv dnsforward.DHCP,
|
||||||
anonymizer *aghnet.IPMut,
|
anonymizer *aghnet.IPMut,
|
||||||
httpReg aghhttp.RegisterFunc,
|
httpReg aghhttp.RegisterFunc,
|
||||||
|
tlsConf *tlsConfigSettings,
|
||||||
tlsMgr *tlsManager,
|
tlsMgr *tlsManager,
|
||||||
l *slog.Logger,
|
l *slog.Logger,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
@@ -165,7 +167,7 @@ func initDNSServer(
|
|||||||
dnsConf, err := newServerConfig(
|
dnsConf, err := newServerConfig(
|
||||||
&config.DNS,
|
&config.DNS,
|
||||||
config.Clients.Sources,
|
config.Clients.Sources,
|
||||||
tlsMgr.config(),
|
tlsConf,
|
||||||
tlsMgr,
|
tlsMgr,
|
||||||
httpReg,
|
httpReg,
|
||||||
globalContext.clients.storage,
|
globalContext.clients.storage,
|
||||||
@@ -345,6 +347,13 @@ func newDNSTLSConfig(
|
|||||||
return nil, fmt.Errorf(format, err)
|
return nil, fmt.Errorf(format, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unencrypted DoH is managed by AdGuard Home itself, not by dnsproxy.
|
||||||
|
// Therefore, avoid setting the certificate property to prevent dnsproxy
|
||||||
|
// from starting encrypted listeners. See [dnsforward.Server.prepareTLS].
|
||||||
|
if conf.AllowUnencryptedDoH {
|
||||||
|
return dnsConf, nil
|
||||||
|
}
|
||||||
|
|
||||||
dnsConf.Cert = &cert
|
dnsConf.Cert = &cert
|
||||||
|
|
||||||
return dnsConf, nil
|
return dnsConf, nil
|
||||||
|
|||||||
@@ -487,14 +487,9 @@ func checkPorts() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isUpdateEnabled returns true if the update is enabled for current
|
// isUpdateEnabled returns true if the update is enabled for current
|
||||||
// configuration. It also logs the decision. isCustomURL should be true if the
|
// configuration. It also logs the decision. customURL should be true if the
|
||||||
// updater is using a custom URL.
|
// updater is using a custom URL.
|
||||||
func isUpdateEnabled(
|
func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customURL bool) (ok bool) {
|
||||||
ctx context.Context,
|
|
||||||
l *slog.Logger,
|
|
||||||
opts *options,
|
|
||||||
isCustomURL bool,
|
|
||||||
) (ok bool) {
|
|
||||||
if opts.disableUpdate {
|
if opts.disableUpdate {
|
||||||
l.DebugContext(ctx, "updates are disabled by command-line option")
|
l.DebugContext(ctx, "updates are disabled by command-line option")
|
||||||
|
|
||||||
@@ -505,13 +500,13 @@ func isUpdateEnabled(
|
|||||||
case
|
case
|
||||||
version.ChannelDevelopment,
|
version.ChannelDevelopment,
|
||||||
version.ChannelCandidate:
|
version.ChannelCandidate:
|
||||||
if isCustomURL {
|
if customURL {
|
||||||
l.DebugContext(ctx, "updates are enabled because custom url is used")
|
l.DebugContext(ctx, "updates are enabled because custom url is used")
|
||||||
} else {
|
} else {
|
||||||
l.DebugContext(ctx, "updates are disabled for development and candidate builds")
|
l.DebugContext(ctx, "updates are disabled for development and candidate builds")
|
||||||
}
|
}
|
||||||
|
|
||||||
return isCustomURL
|
return customURL
|
||||||
default:
|
default:
|
||||||
l.DebugContext(ctx, "updates are enabled")
|
l.DebugContext(ctx, "updates are enabled")
|
||||||
|
|
||||||
@@ -528,7 +523,7 @@ func initWeb(
|
|||||||
upd *updater.Updater,
|
upd *updater.Updater,
|
||||||
baseLogger *slog.Logger,
|
baseLogger *slog.Logger,
|
||||||
tlsMgr *tlsManager,
|
tlsMgr *tlsManager,
|
||||||
isCustomUpdURL bool,
|
customURL bool,
|
||||||
) (web *webAPI, err error) {
|
) (web *webAPI, err error) {
|
||||||
logger := baseLogger.With(slogutil.KeyPrefix, "webapi")
|
logger := baseLogger.With(slogutil.KeyPrefix, "webapi")
|
||||||
|
|
||||||
@@ -544,7 +539,7 @@ func initWeb(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableUpdate := !isUpdateEnabled(ctx, baseLogger, &opts, isCustomUpdURL)
|
disableUpdate := !isUpdateEnabled(ctx, baseLogger, &opts, customURL)
|
||||||
|
|
||||||
webConf := &webConfig{
|
webConf := &webConfig{
|
||||||
updater: upd,
|
updater: upd,
|
||||||
@@ -650,12 +645,11 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
|
|||||||
|
|
||||||
confPath := configFilePath()
|
confPath := configFilePath()
|
||||||
|
|
||||||
updLogger := slogLogger.With(slogutil.KeyPrefix, "updater")
|
upd, customURL := newUpdater(ctx, slogLogger, globalContext.workDir, confPath, execPath, config)
|
||||||
upd, isCustomURL := newUpdater(ctx, updLogger, config, globalContext.workDir, confPath, execPath)
|
|
||||||
|
|
||||||
// TODO(e.burkov): This could be made earlier, probably as the option's
|
// TODO(e.burkov): This could be made earlier, probably as the option's
|
||||||
// effect.
|
// effect.
|
||||||
cmdlineUpdate(ctx, updLogger, opts, upd, tlsMgr)
|
cmdlineUpdate(ctx, slogLogger, opts, upd, tlsMgr)
|
||||||
|
|
||||||
if !globalContext.firstRun {
|
if !globalContext.firstRun {
|
||||||
// Save the updated config.
|
// Save the updated config.
|
||||||
@@ -674,10 +668,10 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
|
|||||||
GLMode = opts.glinetMode
|
GLMode = opts.glinetMode
|
||||||
|
|
||||||
// Init auth module.
|
// Init auth module.
|
||||||
globalContext.auth, err = initUsers()
|
globalContext.auth, err = initUsers(ctx, slogLogger)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
web, err := initWeb(ctx, opts, clientBuildFS, upd, slogLogger, tlsMgr, isCustomURL)
|
web, err := initWeb(ctx, opts, clientBuildFS, upd, slogLogger, tlsMgr, customURL)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
globalContext.web = web
|
globalContext.web = web
|
||||||
@@ -720,17 +714,16 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
|
|||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
// newUpdater creates a new AdGuard Home updater. l and conf must not be nil.
|
// newUpdater creates a new AdGuard Home updater. customURL is true if the user
|
||||||
// workDir, confPath, and execPath must not be empty. isCustomURL is true if
|
// has specified a custom version announcement URL.
|
||||||
// the user has specified a custom version announcement URL.
|
|
||||||
func newUpdater(
|
func newUpdater(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
l *slog.Logger,
|
l *slog.Logger,
|
||||||
conf *configuration,
|
|
||||||
workDir string,
|
workDir string,
|
||||||
confPath string,
|
confPath string,
|
||||||
execPath string,
|
execPath string,
|
||||||
) (upd *updater.Updater, isCustomURL bool) {
|
config *configuration,
|
||||||
|
) (upd *updater.Updater, customURL bool) {
|
||||||
// envName is the name of the environment variable that can be used to
|
// envName is the name of the environment variable that can be used to
|
||||||
// override the default version check URL.
|
// override the default version check URL.
|
||||||
const envName = "ADGUARD_HOME_TEST_UPDATE_VERSION_URL"
|
const envName = "ADGUARD_HOME_TEST_UPDATE_VERSION_URL"
|
||||||
@@ -742,14 +735,14 @@ func newUpdater(
|
|||||||
case version.Channel() == version.ChannelRelease:
|
case version.Channel() == version.ChannelRelease:
|
||||||
// Only enable custom version URL for development builds.
|
// Only enable custom version URL for development builds.
|
||||||
l.DebugContext(ctx, "custom version url is disabled for release builds")
|
l.DebugContext(ctx, "custom version url is disabled for release builds")
|
||||||
case !conf.UnsafeUseCustomUpdateIndexURL:
|
case !config.UnsafeUseCustomUpdateIndexURL:
|
||||||
l.DebugContext(ctx, "custom version url is disabled in config")
|
l.DebugContext(ctx, "custom version url is disabled in config")
|
||||||
default:
|
default:
|
||||||
versionURL, _ = url.Parse(customURLStr)
|
versionURL, _ = url.Parse(customURLStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := urlutil.ValidateHTTPURL(versionURL)
|
err := urlutil.ValidateHTTPURL(versionURL)
|
||||||
if isCustomURL = err == nil; !isCustomURL {
|
if customURL = err == nil; !customURL {
|
||||||
l.DebugContext(ctx, "parsing custom version url", slogutil.KeyError, err)
|
l.DebugContext(ctx, "parsing custom version url", slogutil.KeyError, err)
|
||||||
|
|
||||||
versionURL = updater.DefaultVersionURL()
|
versionURL = updater.DefaultVersionURL()
|
||||||
@@ -758,8 +751,7 @@ func newUpdater(
|
|||||||
l.DebugContext(ctx, "creating updater", "config_path", confPath)
|
l.DebugContext(ctx, "creating updater", "config_path", confPath)
|
||||||
|
|
||||||
return updater.NewUpdater(&updater.Config{
|
return updater.NewUpdater(&updater.Config{
|
||||||
Client: conf.Filtering.HTTPClient,
|
Client: config.Filtering.HTTPClient,
|
||||||
Logger: l,
|
|
||||||
Version: version.Version(),
|
Version: version.Version(),
|
||||||
Channel: version.Channel(),
|
Channel: version.Channel(),
|
||||||
GOARCH: runtime.GOARCH,
|
GOARCH: runtime.GOARCH,
|
||||||
@@ -770,7 +762,7 @@ func newUpdater(
|
|||||||
ConfName: confPath,
|
ConfName: confPath,
|
||||||
ExecPath: execPath,
|
ExecPath: execPath,
|
||||||
VersionCheckURL: versionURL,
|
VersionCheckURL: versionURL,
|
||||||
}), isCustomURL
|
}), customURL
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkPermissions checks and migrates permissions of the files and directories
|
// checkPermissions checks and migrates permissions of the files and directories
|
||||||
@@ -794,7 +786,8 @@ func checkPermissions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initUsers initializes context auth module. Clears config users field.
|
// initUsers initializes context auth module. Clears config users field.
|
||||||
func initUsers() (auth *Auth, err error) {
|
// baseLogger must not be nil.
|
||||||
|
func initUsers(ctx context.Context, baseLogger *slog.Logger) (auth *Auth, err error) {
|
||||||
sessFilename := filepath.Join(globalContext.getDataDir(), "sessions.db")
|
sessFilename := filepath.Join(globalContext.getDataDir(), "sessions.db")
|
||||||
|
|
||||||
var rateLimiter *authRateLimiter
|
var rateLimiter *authRateLimiter
|
||||||
@@ -807,10 +800,17 @@ func initUsers() (auth *Auth, err error) {
|
|||||||
|
|
||||||
trustedProxies := netutil.SliceSubnetSet(netutil.UnembedPrefixes(config.DNS.TrustedProxies))
|
trustedProxies := netutil.SliceSubnetSet(netutil.UnembedPrefixes(config.DNS.TrustedProxies))
|
||||||
|
|
||||||
sessionTTL := time.Duration(config.HTTPConfig.SessionTTL).Seconds()
|
auth, err = InitAuth(
|
||||||
auth = InitAuth(sessFilename, config.Users, uint32(sessionTTL), rateLimiter, trustedProxies)
|
ctx,
|
||||||
if auth == nil {
|
baseLogger,
|
||||||
return nil, errors.Error("initializing auth module failed")
|
sessFilename,
|
||||||
|
config.Users,
|
||||||
|
time.Duration(config.HTTPConfig.SessionTTL),
|
||||||
|
rateLimiter,
|
||||||
|
trustedProxies,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("initializing auth module: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Users = nil
|
config.Users = nil
|
||||||
@@ -924,7 +924,7 @@ func cleanup(ctx context.Context) {
|
|||||||
globalContext.web = nil
|
globalContext.web = nil
|
||||||
}
|
}
|
||||||
if globalContext.auth != nil {
|
if globalContext.auth != nil {
|
||||||
globalContext.auth.Close()
|
globalContext.auth.Close(ctx)
|
||||||
globalContext.auth = nil
|
globalContext.auth = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,12 +1086,12 @@ func cmdlineUpdate(
|
|||||||
//
|
//
|
||||||
// TODO(e.burkov): We could probably initialize the internal resolver
|
// TODO(e.burkov): We could probably initialize the internal resolver
|
||||||
// separately.
|
// separately.
|
||||||
err := initDNSServer(nil, nil, nil, nil, nil, nil, tlsMgr, l)
|
err := initDNSServer(nil, nil, nil, nil, nil, nil, &tlsConfigSettings{}, tlsMgr, l)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
l.InfoContext(ctx, "performing update via cli")
|
l.InfoContext(ctx, "performing update via cli")
|
||||||
|
|
||||||
info, err := upd.VersionInfo(ctx, true)
|
info, err := upd.VersionInfo(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.ErrorContext(ctx, "getting version info", slogutil.KeyError, err)
|
l.ErrorContext(ctx, "getting version info", slogutil.KeyError, err)
|
||||||
|
|
||||||
@@ -1104,7 +1104,7 @@ func cmdlineUpdate(
|
|||||||
os.Exit(osutil.ExitCodeSuccess)
|
os.Exit(osutil.ExitCodeSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = upd.Update(ctx, globalContext.firstRun)
|
err = upd.Update(globalContext.firstRun)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
err = restartService()
|
err = restartService()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/httphdr"
|
"github.com/AdguardTeam/golibs/httphdr"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
@@ -151,7 +151,7 @@ func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) {
|
|||||||
|
|
||||||
clientID := q.Get("client_id")
|
clientID := q.Get("client_id")
|
||||||
if clientID != "" {
|
if clientID != "" {
|
||||||
err = client.ValidateClientID(clientID)
|
err = dnsforward.ValidateClientID(clientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondJSONError(w, http.StatusBadRequest, err.Error())
|
respondJSONError(w, http.StatusBadRequest, err.Error())
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ type profileJSON struct {
|
|||||||
|
|
||||||
// handleGetProfile is the handler for GET /control/profile endpoint.
|
// handleGetProfile is the handler for GET /control/profile endpoint.
|
||||||
func handleGetProfile(w http.ResponseWriter, r *http.Request) {
|
func handleGetProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
name := ""
|
||||||
u := globalContext.auth.getCurrentUser(r)
|
u := globalContext.auth.getCurrentUser(r)
|
||||||
|
if u != nil {
|
||||||
|
name = string(u.Login)
|
||||||
|
}
|
||||||
|
|
||||||
var resp profileJSON
|
var resp profileJSON
|
||||||
func() {
|
func() {
|
||||||
@@ -55,7 +59,7 @@ func handleGetProfile(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer config.RUnlock()
|
defer config.RUnlock()
|
||||||
|
|
||||||
resp = profileJSON{
|
resp = profileJSON{
|
||||||
Name: u.Name,
|
Name: name,
|
||||||
Language: config.Language,
|
Language: config.Language,
|
||||||
Theme: config.Theme,
|
Theme: config.Theme,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,10 +193,7 @@ func (m *tlsManager) start(_ context.Context) {
|
|||||||
m.web.tlsConfigChanged(context.Background(), m.conf)
|
m.web.tlsConfigChanged(context.Background(), m.conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload updates the configuration and restarts the TLS manager. It logs any
|
// reload updates the configuration and restarts the TLS manager.
|
||||||
// encountered errors.
|
|
||||||
//
|
|
||||||
// TODO(s.chzhen): Consider returning an error.
|
|
||||||
func (m *tlsManager) reload(ctx context.Context) {
|
func (m *tlsManager) reload(ctx context.Context) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|||||||
@@ -204,8 +204,6 @@ func assertCertSerialNumber(tb testing.TB, conf *tlsConfigSettings, wantSN int64
|
|||||||
func TestTLSManager_Reload(t *testing.T) {
|
func TestTLSManager_Reload(t *testing.T) {
|
||||||
storeGlobals(t)
|
storeGlobals(t)
|
||||||
|
|
||||||
config.DNS.Port = 0
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logger = slogutil.NewDiscardLogger()
|
logger = slogutil.NewDiscardLogger()
|
||||||
ctx = testutil.ContextWithTimeout(t, testTimeout)
|
ctx = testutil.ContextWithTimeout(t, testTimeout)
|
||||||
@@ -262,10 +260,6 @@ func TestTLSManager_Reload(t *testing.T) {
|
|||||||
|
|
||||||
m.reload(ctx)
|
m.reload(ctx)
|
||||||
|
|
||||||
// The [tlsManager.reload] method will start the DNS server and it should be
|
|
||||||
// stopped after the test ends.
|
|
||||||
testutil.CleanupAndRequireSuccess(t, globalContext.dnsServer.Stop)
|
|
||||||
|
|
||||||
conf = m.config()
|
conf = m.config()
|
||||||
assertCertSerialNumber(t, conf, snAfter)
|
assertCertSerialNumber(t, conf, snAfter)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -13,6 +12,7 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/ioutil"
|
"github.com/AdguardTeam/golibs/ioutil"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/c2h5oh/datasize"
|
"github.com/c2h5oh/datasize"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ const maxVersionRespSize datasize.ByteSize = 64 * datasize.KB
|
|||||||
|
|
||||||
// VersionInfo downloads the latest version information. If forceRecheck is
|
// VersionInfo downloads the latest version information. If forceRecheck is
|
||||||
// false and there are cached results, those results are returned.
|
// false and there are cached results, those results are returned.
|
||||||
func (u *Updater) VersionInfo(ctx context.Context, forceRecheck bool) (vi VersionInfo, err error) {
|
func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
|
||||||
u.mu.Lock()
|
u.mu.Lock()
|
||||||
defer u.mu.Unlock()
|
defer u.mu.Unlock()
|
||||||
|
|
||||||
@@ -45,17 +45,11 @@ func (u *Updater) VersionInfo(ctx context.Context, forceRecheck bool) (vi Versio
|
|||||||
return u.prevCheckResult, u.prevCheckError
|
return u.prevCheckResult, u.prevCheckError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
vcu := u.versionCheckURL
|
vcu := u.versionCheckURL
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, vcu, nil)
|
resp, err = u.client.Get(vcu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return VersionInfo{}, fmt.Errorf("constructing request to %s: %w", vcu, err)
|
return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err)
|
||||||
}
|
|
||||||
|
|
||||||
u.logger.DebugContext(ctx, "requesting version data", "url", vcu)
|
|
||||||
|
|
||||||
resp, err := u.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return VersionInfo{}, fmt.Errorf("requesting %s: %w", vcu, err)
|
|
||||||
}
|
}
|
||||||
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
||||||
|
|
||||||
@@ -65,16 +59,16 @@ func (u *Updater) VersionInfo(ctx context.Context, forceRecheck bool) (vi Versio
|
|||||||
// ReadCloser.
|
// ReadCloser.
|
||||||
body, err := io.ReadAll(r)
|
body, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return VersionInfo{}, fmt.Errorf("reading response from %s: %w", vcu, err)
|
return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.prevCheckTime = now
|
u.prevCheckTime = now
|
||||||
u.prevCheckResult, u.prevCheckError = u.parseVersionResponse(ctx, body)
|
u.prevCheckResult, u.prevCheckError = u.parseVersionResponse(body)
|
||||||
|
|
||||||
return u.prevCheckResult, u.prevCheckError
|
return u.prevCheckResult, u.prevCheckError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) parseVersionResponse(ctx context.Context, data []byte) (VersionInfo, error) {
|
func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
|
||||||
info := VersionInfo{
|
info := VersionInfo{
|
||||||
CanAutoUpdate: aghalg.NBFalse,
|
CanAutoUpdate: aghalg.NBFalse,
|
||||||
}
|
}
|
||||||
@@ -98,7 +92,7 @@ func (u *Updater) parseVersionResponse(ctx context.Context, data []byte) (Versio
|
|||||||
info.Announcement = versionJSON["announcement"]
|
info.Announcement = versionJSON["announcement"]
|
||||||
info.AnnouncementURL = versionJSON["announcement_url"]
|
info.AnnouncementURL = versionJSON["announcement_url"]
|
||||||
|
|
||||||
packageURL, key, found := u.downloadURL(ctx, versionJSON)
|
packageURL, key, found := u.downloadURL(versionJSON)
|
||||||
if !found {
|
if !found {
|
||||||
return info, fmt.Errorf("version.json: no package URL: key %q not found in object", key)
|
return info, fmt.Errorf("version.json: no package URL: key %q not found in object", key)
|
||||||
}
|
}
|
||||||
@@ -114,10 +108,7 @@ func (u *Updater) parseVersionResponse(ctx context.Context, data []byte) (Versio
|
|||||||
// downloadURL returns the download URL for current build as well as its key in
|
// downloadURL returns the download URL for current build as well as its key in
|
||||||
// versionObj. If the key is not found, it additionally prints an informative
|
// versionObj. If the key is not found, it additionally prints an informative
|
||||||
// log message.
|
// log message.
|
||||||
func (u *Updater) downloadURL(
|
func (u *Updater) downloadURL(versionObj map[string]string) (dlURL, key string, ok bool) {
|
||||||
ctx context.Context,
|
|
||||||
versionObj map[string]string,
|
|
||||||
) (dlURL, key string, ok bool) {
|
|
||||||
if u.goarch == "arm" && u.goarm != "" {
|
if u.goarch == "arm" && u.goarm != "" {
|
||||||
key = fmt.Sprintf("download_%s_%sv%s", u.goos, u.goarch, u.goarm)
|
key = fmt.Sprintf("download_%s_%sv%s", u.goos, u.goarch, u.goarm)
|
||||||
} else if isMIPS(u.goarch) && u.gomips != "" {
|
} else if isMIPS(u.goarch) && u.gomips != "" {
|
||||||
@@ -133,7 +124,7 @@ func (u *Updater) downloadURL(
|
|||||||
|
|
||||||
keys := slices.Sorted(maps.Keys(versionObj))
|
keys := slices.Sorted(maps.Keys(versionObj))
|
||||||
|
|
||||||
u.logger.ErrorContext(ctx, "key not found", "missing", key, "got", keys)
|
log.Error("updater: key %q not found; got keys %q", key, keys)
|
||||||
|
|
||||||
return "", key, false
|
return "", key, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -59,7 +58,6 @@ func TestUpdater_VersionInfo(t *testing.T) {
|
|||||||
|
|
||||||
u := updater.NewUpdater(&updater.Config{
|
u := updater.NewUpdater(&updater.Config{
|
||||||
Client: srv.Client(),
|
Client: srv.Client(),
|
||||||
Logger: testLogger,
|
|
||||||
Version: "v0.103.0-beta.1",
|
Version: "v0.103.0-beta.1",
|
||||||
Channel: version.ChannelBeta,
|
Channel: version.ChannelBeta,
|
||||||
GOARCH: "arm",
|
GOARCH: "arm",
|
||||||
@@ -67,8 +65,7 @@ func TestUpdater_VersionInfo(t *testing.T) {
|
|||||||
VersionCheckURL: fakeURL,
|
VersionCheckURL: fakeURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
info, err := u.VersionInfo(false)
|
||||||
info, err := u.VersionInfo(ctx, false)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, counter, 1)
|
assert.Equal(t, counter, 1)
|
||||||
@@ -78,14 +75,14 @@ func TestUpdater_VersionInfo(t *testing.T) {
|
|||||||
assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
|
assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
|
||||||
|
|
||||||
t.Run("cache_check", func(t *testing.T) {
|
t.Run("cache_check", func(t *testing.T) {
|
||||||
_, err = u.VersionInfo(testutil.ContextWithTimeout(t, testTimeout), false)
|
_, err = u.VersionInfo(false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, counter, 1)
|
assert.Equal(t, counter, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("force_check", func(t *testing.T) {
|
t.Run("force_check", func(t *testing.T) {
|
||||||
_, err = u.VersionInfo(testutil.ContextWithTimeout(t, testTimeout), true)
|
_, err = u.VersionInfo(true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, counter, 2)
|
assert.Equal(t, counter, 2)
|
||||||
@@ -94,7 +91,7 @@ func TestUpdater_VersionInfo(t *testing.T) {
|
|||||||
t.Run("api_fail", func(t *testing.T) {
|
t.Run("api_fail", func(t *testing.T) {
|
||||||
srv.Close()
|
srv.Close()
|
||||||
|
|
||||||
_, err = u.VersionInfo(testutil.ContextWithTimeout(t, testTimeout), true)
|
_, err = u.VersionInfo(true)
|
||||||
var urlErr *url.Error
|
var urlErr *url.Error
|
||||||
assert.ErrorAs(t, err, &urlErr)
|
assert.ErrorAs(t, err, &urlErr)
|
||||||
})
|
})
|
||||||
@@ -133,7 +130,6 @@ func TestUpdater_VersionInfo_others(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
u := updater.NewUpdater(&updater.Config{
|
u := updater.NewUpdater(&updater.Config{
|
||||||
Client: fakeClient,
|
Client: fakeClient,
|
||||||
Logger: testLogger,
|
|
||||||
Version: "v0.103.0-beta.1",
|
Version: "v0.103.0-beta.1",
|
||||||
Channel: version.ChannelBeta,
|
Channel: version.ChannelBeta,
|
||||||
GOOS: "linux",
|
GOOS: "linux",
|
||||||
@@ -143,8 +139,7 @@ func TestUpdater_VersionInfo_others(t *testing.T) {
|
|||||||
VersionCheckURL: fakeURL,
|
VersionCheckURL: fakeURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
info, err := u.VersionInfo(false)
|
||||||
info, err := u.VersionInfo(ctx, false)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)
|
assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ import (
|
|||||||
"archive/tar"
|
"archive/tar"
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -24,14 +22,13 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/ioutil"
|
"github.com/AdguardTeam/golibs/ioutil"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Updater is the AdGuard Home updater.
|
// Updater is the AdGuard Home updater.
|
||||||
type Updater struct {
|
type Updater struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
logger *slog.Logger
|
|
||||||
|
|
||||||
version string
|
version string
|
||||||
channel string
|
channel string
|
||||||
@@ -78,48 +75,27 @@ func DefaultVersionURL() *url.URL {
|
|||||||
|
|
||||||
// Config is the AdGuard Home updater configuration.
|
// Config is the AdGuard Home updater configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Client is used to perform HTTP requests. It must not be nil.
|
|
||||||
Client *http.Client
|
Client *http.Client
|
||||||
|
|
||||||
// Logger is used for logging the update process. It must not be nil.
|
|
||||||
Logger *slog.Logger
|
|
||||||
|
|
||||||
// VersionCheckURL is URL to the latest version announcement. It must not
|
// VersionCheckURL is URL to the latest version announcement. It must not
|
||||||
// be nil, see [DefaultVersionURL].
|
// be nil, see [DefaultVersionURL].
|
||||||
VersionCheckURL *url.URL
|
VersionCheckURL *url.URL
|
||||||
|
|
||||||
// Version is the current AdGuard Home version. It must not be empty.
|
|
||||||
Version string
|
Version string
|
||||||
|
|
||||||
// Channel is the current AdGuard Home update channel. It must be a valid
|
|
||||||
// channel, see [version.ChannelBeta] and the related constants.
|
|
||||||
Channel string
|
Channel string
|
||||||
|
|
||||||
// GOARCH is the current CPU architecture. It must not be empty and must be
|
|
||||||
// one of the supported architectures.
|
|
||||||
GOARCH string
|
GOARCH string
|
||||||
|
|
||||||
// GOOS is the current operating system. It must not be empty and must be
|
|
||||||
// one of the supported OSs.
|
|
||||||
GOOS string
|
GOOS string
|
||||||
|
|
||||||
// GOARM is the current ARM variant, if any. It must either be empty or be
|
|
||||||
// a valid and supported GOARM value.
|
|
||||||
GOARM string
|
GOARM string
|
||||||
|
|
||||||
// GOMIPS is the current MIPS variant, if any. It must either be empty or
|
|
||||||
// be a valid and supported GOMIPS value.
|
|
||||||
GOMIPS string
|
GOMIPS string
|
||||||
|
|
||||||
// ConfName is the name of the current configuration file. It must not be
|
// ConfName is the name of the current configuration file. Typically,
|
||||||
// empty.
|
// "AdGuardHome.yaml".
|
||||||
ConfName string
|
ConfName string
|
||||||
|
|
||||||
// WorkDir is the working directory that is used for temporary files. It
|
// WorkDir is the working directory that is used for temporary files.
|
||||||
// must not be empty.
|
|
||||||
WorkDir string
|
WorkDir string
|
||||||
|
|
||||||
// ExecPath is path to the executable file. It must not be empty.
|
// ExecPath is path to the executable file.
|
||||||
ExecPath string
|
ExecPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +103,6 @@ type Config struct {
|
|||||||
func NewUpdater(conf *Config) *Updater {
|
func NewUpdater(conf *Config) *Updater {
|
||||||
return &Updater{
|
return &Updater{
|
||||||
client: conf.Client,
|
client: conf.Client,
|
||||||
logger: conf.Logger,
|
|
||||||
|
|
||||||
version: conf.Version,
|
version: conf.Version,
|
||||||
channel: conf.Channel,
|
channel: conf.Channel,
|
||||||
@@ -147,49 +122,49 @@ func NewUpdater(conf *Config) *Updater {
|
|||||||
|
|
||||||
// Update performs the auto-update. It returns an error if the update failed.
|
// Update performs the auto-update. It returns an error if the update failed.
|
||||||
// If firstRun is true, it assumes the configuration file doesn't exist.
|
// If firstRun is true, it assumes the configuration file doesn't exist.
|
||||||
func (u *Updater) Update(ctx context.Context, firstRun bool) (err error) {
|
func (u *Updater) Update(firstRun bool) (err error) {
|
||||||
u.mu.Lock()
|
u.mu.Lock()
|
||||||
defer u.mu.Unlock()
|
defer u.mu.Unlock()
|
||||||
|
|
||||||
u.logger.InfoContext(ctx, "staring update", "first_run", firstRun)
|
log.Info("updater: updating")
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.logger.ErrorContext(ctx, "update failed", slogutil.KeyError, err)
|
log.Info("updater: failed")
|
||||||
} else {
|
} else {
|
||||||
u.logger.InfoContext(ctx, "update finished")
|
log.Info("updater: finished successfully")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = u.prepare(ctx)
|
err = u.prepare()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("preparing: %w", err)
|
return fmt.Errorf("preparing: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer u.clean(ctx)
|
defer u.clean()
|
||||||
|
|
||||||
err = u.downloadPackageFile(ctx)
|
err = u.downloadPackageFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("downloading package file: %w", err)
|
return fmt.Errorf("downloading package file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.unpack(ctx)
|
err = u.unpack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unpacking: %w", err)
|
return fmt.Errorf("unpacking: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !firstRun {
|
if !firstRun {
|
||||||
err = u.check(ctx)
|
err = u.check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("checking config: %w", err)
|
return fmt.Errorf("checking config: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.backup(ctx, firstRun)
|
err = u.backup(firstRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("making backup: %w", err)
|
return fmt.Errorf("making backup: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.replace(ctx)
|
err = u.replace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("replacing: %w", err)
|
return fmt.Errorf("replacing: %w", err)
|
||||||
}
|
}
|
||||||
@@ -206,7 +181,7 @@ func (u *Updater) NewVersion() (nv string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepare fills all necessary fields in Updater object.
|
// prepare fills all necessary fields in Updater object.
|
||||||
func (u *Updater) prepare(ctx context.Context) (err error) {
|
func (u *Updater) prepare() (err error) {
|
||||||
u.updateDir = filepath.Join(u.workDir, fmt.Sprintf("agh-update-%s", u.newVersion))
|
u.updateDir = filepath.Join(u.workDir, fmt.Sprintf("agh-update-%s", u.newVersion))
|
||||||
|
|
||||||
_, pkgNameOnly := filepath.Split(u.packageURL)
|
_, pkgNameOnly := filepath.Split(u.packageURL)
|
||||||
@@ -225,12 +200,11 @@ func (u *Updater) prepare(ctx context.Context) (err error) {
|
|||||||
u.backupExeName = filepath.Join(u.backupDir, filepath.Base(u.execPath))
|
u.backupExeName = filepath.Join(u.backupDir, filepath.Base(u.execPath))
|
||||||
u.updateExeName = filepath.Join(u.updateDir, updateExeName)
|
u.updateExeName = filepath.Join(u.updateDir, updateExeName)
|
||||||
|
|
||||||
u.logger.InfoContext(
|
log.Debug(
|
||||||
ctx,
|
"updater: updating from %s to %s using url: %s",
|
||||||
"updating",
|
version.Version(),
|
||||||
"from", version.Version(),
|
u.newVersion,
|
||||||
"to", u.newVersion,
|
u.packageURL,
|
||||||
"package_url", u.packageURL,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
u.currentExeName = u.execPath
|
u.currentExeName = u.execPath
|
||||||
@@ -243,20 +217,23 @@ func (u *Updater) prepare(ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unpack extracts the files from the downloaded archive.
|
// unpack extracts the files from the downloaded archive.
|
||||||
func (u *Updater) unpack(ctx context.Context) (err error) {
|
func (u *Updater) unpack() error {
|
||||||
|
var err error
|
||||||
_, pkgNameOnly := filepath.Split(u.packageURL)
|
_, pkgNameOnly := filepath.Split(u.packageURL)
|
||||||
|
|
||||||
u.logger.InfoContext(ctx, "unpacking package", "package_name", pkgNameOnly)
|
log.Debug("updater: unpacking package")
|
||||||
if strings.HasSuffix(pkgNameOnly, ".zip") {
|
if strings.HasSuffix(pkgNameOnly, ".zip") {
|
||||||
u.unpackedFiles, err = u.unpackZip(ctx, u.packageName, u.updateDir)
|
u.unpackedFiles, err = zipFileUnpack(u.packageName, u.updateDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(".zip unpack failed: %w", err)
|
return fmt.Errorf(".zip unpack failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if strings.HasSuffix(pkgNameOnly, ".tar.gz") {
|
} else if strings.HasSuffix(pkgNameOnly, ".tar.gz") {
|
||||||
u.unpackedFiles, err = u.unpackTarGz(ctx, u.packageName, u.updateDir)
|
u.unpackedFiles, err = tarGzFileUnpack(u.packageName, u.updateDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(".tar.gz unpack failed: %w", err)
|
return fmt.Errorf(".tar.gz unpack failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("unknown package extension")
|
return fmt.Errorf("unknown package extension")
|
||||||
}
|
}
|
||||||
@@ -266,8 +243,8 @@ func (u *Updater) unpack(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
// check returns an error if the configuration file couldn't be used with the
|
// check returns an error if the configuration file couldn't be used with the
|
||||||
// version of AdGuard Home just downloaded.
|
// version of AdGuard Home just downloaded.
|
||||||
func (u *Updater) check(ctx context.Context) (err error) {
|
func (u *Updater) check() (err error) {
|
||||||
u.logger.InfoContext(ctx, "checking configuration")
|
log.Debug("updater: checking configuration")
|
||||||
|
|
||||||
err = copyFile(u.confName, filepath.Join(u.updateDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
|
err = copyFile(u.confName, filepath.Join(u.updateDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -291,9 +268,8 @@ func (u *Updater) check(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
// backup makes a backup of the current configuration and supporting files. It
|
// backup makes a backup of the current configuration and supporting files. It
|
||||||
// ignores the configuration file if firstRun is true.
|
// ignores the configuration file if firstRun is true.
|
||||||
func (u *Updater) backup(ctx context.Context, firstRun bool) (err error) {
|
func (u *Updater) backup(firstRun bool) (err error) {
|
||||||
u.logger.InfoContext(ctx, "backing up current configuration")
|
log.Debug("updater: backing up current configuration")
|
||||||
|
|
||||||
_ = os.Mkdir(u.backupDir, aghos.DefaultPermDir)
|
_ = os.Mkdir(u.backupDir, aghos.DefaultPermDir)
|
||||||
if !firstRun {
|
if !firstRun {
|
||||||
err = copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
|
err = copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
|
||||||
@@ -303,7 +279,7 @@ func (u *Updater) backup(ctx context.Context, firstRun bool) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wd := u.workDir
|
wd := u.workDir
|
||||||
err = u.copySupportingFiles(ctx, u.unpackedFiles, wd, u.backupDir)
|
err = copySupportingFiles(u.unpackedFiles, wd, u.backupDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", wd, u.backupDir, err)
|
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", wd, u.backupDir, err)
|
||||||
}
|
}
|
||||||
@@ -313,18 +289,13 @@ func (u *Updater) backup(ctx context.Context, firstRun bool) (err error) {
|
|||||||
|
|
||||||
// replace moves the current executable with the updated one and also copies the
|
// replace moves the current executable with the updated one and also copies the
|
||||||
// supporting files.
|
// supporting files.
|
||||||
func (u *Updater) replace(ctx context.Context) (err error) {
|
func (u *Updater) replace() error {
|
||||||
err = u.copySupportingFiles(ctx, u.unpackedFiles, u.updateDir, u.workDir)
|
err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.workDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", u.updateDir, u.workDir, err)
|
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", u.updateDir, u.workDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.logger.InfoContext(
|
log.Debug("updater: renaming: %s to %s", u.currentExeName, u.backupExeName)
|
||||||
ctx,
|
|
||||||
"backing up current executable",
|
|
||||||
"from", u.currentExeName,
|
|
||||||
"to", u.backupExeName,
|
|
||||||
)
|
|
||||||
err = os.Rename(u.currentExeName, u.backupExeName)
|
err = os.Rename(u.currentExeName, u.backupExeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -340,22 +311,14 @@ func (u *Updater) replace(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.logger.InfoContext(
|
log.Debug("updater: renamed: %s to %s", u.updateExeName, u.currentExeName)
|
||||||
ctx,
|
|
||||||
"replacing current executable",
|
|
||||||
"from", u.updateExeName,
|
|
||||||
"to", u.currentExeName,
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean removes the temporary directory itself and all it's contents.
|
// clean removes the temporary directory itself and all it's contents.
|
||||||
func (u *Updater) clean(ctx context.Context) {
|
func (u *Updater) clean() {
|
||||||
err := os.RemoveAll(u.updateDir)
|
_ = os.RemoveAll(u.updateDir)
|
||||||
if err != nil {
|
|
||||||
u.logger.WarnContext(ctx, "removing update dir", slogutil.KeyError, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxPackageFileSize is a maximum package file length in bytes. The largest
|
// MaxPackageFileSize is a maximum package file length in bytes. The largest
|
||||||
@@ -364,52 +327,34 @@ func (u *Updater) clean(ctx context.Context) {
|
|||||||
const MaxPackageFileSize = 32 * 1024 * 1024
|
const MaxPackageFileSize = 32 * 1024 * 1024
|
||||||
|
|
||||||
// Download package file and save it to disk
|
// Download package file and save it to disk
|
||||||
func (u *Updater) downloadPackageFile(ctx context.Context) (err error) {
|
func (u *Updater) downloadPackageFile() (err error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.packageURL, nil)
|
var resp *http.Response
|
||||||
|
resp, err = u.client.Get(u.packageURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("constructing package request: %w", err)
|
return fmt.Errorf("http request failed: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := u.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("requesting package: %w", err)
|
|
||||||
}
|
}
|
||||||
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
||||||
|
|
||||||
r := ioutil.LimitReader(resp.Body, MaxPackageFileSize)
|
r := ioutil.LimitReader(resp.Body, MaxPackageFileSize)
|
||||||
|
|
||||||
u.logger.InfoContext(ctx, "reading http body")
|
log.Debug("updater: reading http body")
|
||||||
|
|
||||||
// This use of ReadAll is now safe, because we limited body's Reader.
|
// This use of ReadAll is now safe, because we limited body's Reader.
|
||||||
body, err := io.ReadAll(r)
|
body, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("io.ReadAll() failed: %w", err)
|
return fmt.Errorf("io.ReadAll() failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Mkdir(u.updateDir, aghos.DefaultPermDir)
|
_ = os.Mkdir(u.updateDir, aghos.DefaultPermDir)
|
||||||
if err != nil {
|
|
||||||
// TODO(a.garipov): Consider returning this error.
|
|
||||||
u.logger.WarnContext(ctx, "creating update dir", slogutil.KeyError, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
u.logger.InfoContext(ctx, "saving package", "to", u.packageName)
|
|
||||||
|
|
||||||
|
log.Debug("updater: saving package to file")
|
||||||
err = os.WriteFile(u.packageName, body, aghos.DefaultPermFile)
|
err = os.WriteFile(u.packageName, body, aghos.DefaultPermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("writing package file: %w", err)
|
return fmt.Errorf("writing package file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpackTarGzFile unpacks one file from a .tar.gz archive into outDir. All
|
func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name string, err error) {
|
||||||
// arguments must not be empty.
|
|
||||||
func (u *Updater) unpackTarGzFile(
|
|
||||||
ctx context.Context,
|
|
||||||
outDir string,
|
|
||||||
tr *tar.Reader,
|
|
||||||
hdr *tar.Header,
|
|
||||||
) (name string, err error) {
|
|
||||||
name = filepath.Base(hdr.Name)
|
name = filepath.Base(hdr.Name)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -432,18 +377,13 @@ func (u *Updater) unpackTarGzFile(
|
|||||||
return "", fmt.Errorf("creating directory %q: %w", outName, err)
|
return "", fmt.Errorf("creating directory %q: %w", outName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.logger.InfoContext(ctx, "created directory", "name", outName)
|
log.Debug("updater: created directory %q", outName)
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if hdr.Typeflag != tar.TypeReg {
|
if hdr.Typeflag != tar.TypeReg {
|
||||||
u.logger.WarnContext(
|
log.Info("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag)
|
||||||
ctx,
|
|
||||||
"unknown file type; skipping",
|
|
||||||
"file_name", name,
|
|
||||||
"type", hdr.Typeflag,
|
|
||||||
)
|
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -460,19 +400,16 @@ func (u *Updater) unpackTarGzFile(
|
|||||||
return "", fmt.Errorf("io.Copy(): %w", err)
|
return "", fmt.Errorf("io.Copy(): %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.logger.InfoContext(ctx, "created file", "name", outName)
|
log.Debug("updater: created file %q", outName)
|
||||||
|
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpackTarGz unpack all files from a .tar.gz archive to outDir. Existing
|
// Unpack all files from .tar.gz file to the specified directory
|
||||||
// files are overwritten. All files are created inside outDir. files are the
|
// Existing files are overwritten
|
||||||
// list of created files.
|
// All files are created inside outDir, subdirectories are not created
|
||||||
func (u *Updater) unpackTarGz(
|
// Return the list of files (not directories) written
|
||||||
ctx context.Context,
|
func tarGzFileUnpack(tarfile, outDir string) (files []string, err error) {
|
||||||
tarfile string,
|
|
||||||
outDir string,
|
|
||||||
) (files []string, err error) {
|
|
||||||
f, err := os.Open(tarfile)
|
f, err := os.Open(tarfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("os.Open(): %w", err)
|
return nil, fmt.Errorf("os.Open(): %w", err)
|
||||||
@@ -500,7 +437,7 @@ func (u *Updater) unpackTarGz(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var name string
|
var name string
|
||||||
name, err = u.unpackTarGzFile(ctx, outDir, tarReader, hdr)
|
name, err = tarGzFileUnpackOne(outDir, tarReader, hdr)
|
||||||
|
|
||||||
if name != "" {
|
if name != "" {
|
||||||
files = append(files, name)
|
files = append(files, name)
|
||||||
@@ -510,13 +447,7 @@ func (u *Updater) unpackTarGz(
|
|||||||
return files, err
|
return files, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpackZipFile unpacks one file from a .zip archive into outDir. All
|
func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
||||||
// arguments must not be empty.
|
|
||||||
func (u *Updater) unpackZipFile(
|
|
||||||
ctx context.Context,
|
|
||||||
outDir string,
|
|
||||||
zf *zip.File,
|
|
||||||
) (name string, err error) {
|
|
||||||
var rc io.ReadCloser
|
var rc io.ReadCloser
|
||||||
rc, err = zf.Open()
|
rc, err = zf.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -535,8 +466,7 @@ func (u *Updater) unpackZipFile(
|
|||||||
if name == "AdGuardHome" {
|
if name == "AdGuardHome" {
|
||||||
// Top-level AdGuardHome/. Skip it.
|
// Top-level AdGuardHome/. Skip it.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): See the similar TODO in
|
// TODO(a.garipov): See the similar todo in tarGzFileUnpack.
|
||||||
// [Updater.unpackTarGzFile].
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,7 +475,7 @@ func (u *Updater) unpackZipFile(
|
|||||||
return "", fmt.Errorf("creating directory %q: %w", outputName, err)
|
return "", fmt.Errorf("creating directory %q: %w", outputName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.logger.InfoContext(ctx, "created directory", "name", outputName)
|
log.Debug("updater: created directory %q", outputName)
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -562,19 +492,16 @@ func (u *Updater) unpackZipFile(
|
|||||||
return "", fmt.Errorf("io.Copy(): %w", err)
|
return "", fmt.Errorf("io.Copy(): %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.logger.InfoContext(ctx, "created file", "name", outputName)
|
log.Debug("updater: created file %q", outputName)
|
||||||
|
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpackZip unpack all files from a .zip archive to outDir. Existing files are
|
// Unpack all files from .zip file to the specified directory
|
||||||
// overwritten. All files are created inside outDir. files are the list of
|
// Existing files are overwritten
|
||||||
// created files.
|
// All files are created inside 'outDir', subdirectories are not created
|
||||||
func (u *Updater) unpackZip(
|
// Return the list of files (not directories) written
|
||||||
ctx context.Context,
|
func zipFileUnpack(zipfile, outDir string) (files []string, err error) {
|
||||||
zipfile string,
|
|
||||||
outDir string,
|
|
||||||
) (files []string, err error) {
|
|
||||||
zrc, err := zip.OpenReader(zipfile)
|
zrc, err := zip.OpenReader(zipfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("zip.OpenReader(): %w", err)
|
return nil, fmt.Errorf("zip.OpenReader(): %w", err)
|
||||||
@@ -583,7 +510,7 @@ func (u *Updater) unpackZip(
|
|||||||
|
|
||||||
for _, zf := range zrc.File {
|
for _, zf := range zrc.File {
|
||||||
var name string
|
var name string
|
||||||
name, err = u.unpackZipFile(ctx, outDir, zf)
|
name, err = zipFileUnpackOne(outDir, zf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -616,12 +543,7 @@ func copyFile(src, dst string, perm fs.FileMode) (err error) {
|
|||||||
// copySupportingFiles copies each file specified in files from srcdir to
|
// copySupportingFiles copies each file specified in files from srcdir to
|
||||||
// dstdir. If a file specified as a path, only the name of the file is used.
|
// dstdir. If a file specified as a path, only the name of the file is used.
|
||||||
// It skips AdGuardHome, AdGuardHome.exe, and AdGuardHome.yaml.
|
// It skips AdGuardHome, AdGuardHome.exe, and AdGuardHome.yaml.
|
||||||
func (u *Updater) copySupportingFiles(
|
func copySupportingFiles(files []string, srcdir, dstdir string) error {
|
||||||
ctx context.Context,
|
|
||||||
files []string,
|
|
||||||
srcdir string,
|
|
||||||
dstdir string,
|
|
||||||
) (err error) {
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
_, name := filepath.Split(f)
|
_, name := filepath.Split(f)
|
||||||
if name == "AdGuardHome" || name == "AdGuardHome.exe" || name == "AdGuardHome.yaml" {
|
if name == "AdGuardHome" || name == "AdGuardHome.exe" || name == "AdGuardHome.yaml" {
|
||||||
@@ -631,12 +553,12 @@ func (u *Updater) copySupportingFiles(
|
|||||||
src := filepath.Join(srcdir, name)
|
src := filepath.Join(srcdir, name)
|
||||||
dst := filepath.Join(dstdir, name)
|
dst := filepath.Join(dstdir, name)
|
||||||
|
|
||||||
err = copyFile(src, dst, aghos.DefaultPermFile)
|
err := copyFile(src, dst, aghos.DefaultPermFile)
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.logger.InfoContext(ctx, "copied", "from", src, "to", dst)
|
log.Debug("updater: copied: %q to %q", src, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -59,7 +55,6 @@ func TestUpdater_internal(t *testing.T) {
|
|||||||
|
|
||||||
u := NewUpdater(&Config{
|
u := NewUpdater(&Config{
|
||||||
Client: fakeClient,
|
Client: fakeClient,
|
||||||
Logger: slogutil.NewDiscardLogger(),
|
|
||||||
GOOS: tc.os,
|
GOOS: tc.os,
|
||||||
Version: "v0.103.0",
|
Version: "v0.103.0",
|
||||||
ExecPath: exePath,
|
ExecPath: exePath,
|
||||||
@@ -73,13 +68,13 @@ func TestUpdater_internal(t *testing.T) {
|
|||||||
u.newVersion = "v0.103.1"
|
u.newVersion = "v0.103.1"
|
||||||
u.packageURL = fakeURL.String()
|
u.packageURL = fakeURL.String()
|
||||||
|
|
||||||
require.NoError(t, u.prepare(newCtx(t)))
|
require.NoError(t, u.prepare())
|
||||||
require.NoError(t, u.downloadPackageFile(newCtx(t)))
|
require.NoError(t, u.downloadPackageFile())
|
||||||
require.NoError(t, u.unpack(newCtx(t)))
|
require.NoError(t, u.unpack())
|
||||||
require.NoError(t, u.backup(newCtx(t), false))
|
require.NoError(t, u.backup(false))
|
||||||
require.NoError(t, u.replace(newCtx(t)))
|
require.NoError(t, u.replace())
|
||||||
|
|
||||||
u.clean(newCtx(t))
|
u.clean()
|
||||||
|
|
||||||
require.True(t, t.Run("backup", func(t *testing.T) {
|
require.True(t, t.Run("backup", func(t *testing.T) {
|
||||||
var d []byte
|
var d []byte
|
||||||
@@ -118,8 +113,3 @@ func TestUpdater_internal(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCtx is a helper that returns a new context with a timeout.
|
|
||||||
func newCtx(tb testing.TB) (ctx context.Context) {
|
|
||||||
return testutil.ContextWithTimeout(tb, 1*time.Second)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,21 +10,17 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testTimeout is the common timeout for tests.
|
func TestMain(m *testing.M) {
|
||||||
const testTimeout = 1 * time.Second
|
testutil.DiscardLogOutput(m)
|
||||||
|
}
|
||||||
// testLogger is the common logger for tests.
|
|
||||||
var testLogger = slogutil.NewDiscardLogger()
|
|
||||||
|
|
||||||
func TestUpdater_Update(t *testing.T) {
|
func TestUpdater_Update(t *testing.T) {
|
||||||
const jsonData = `{
|
const jsonData = `{
|
||||||
@@ -77,7 +73,6 @@ func TestUpdater_Update(t *testing.T) {
|
|||||||
|
|
||||||
u := updater.NewUpdater(&updater.Config{
|
u := updater.NewUpdater(&updater.Config{
|
||||||
Client: srv.Client(),
|
Client: srv.Client(),
|
||||||
Logger: testLogger,
|
|
||||||
GOARCH: "amd64",
|
GOARCH: "amd64",
|
||||||
GOOS: "linux",
|
GOOS: "linux",
|
||||||
Version: "v0.103.0",
|
Version: "v0.103.0",
|
||||||
@@ -87,12 +82,10 @@ func TestUpdater_Update(t *testing.T) {
|
|||||||
VersionCheckURL: versionCheckURL,
|
VersionCheckURL: versionCheckURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
_, err = u.VersionInfo(false)
|
||||||
_, err = u.VersionInfo(ctx, false)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ctx = testutil.ContextWithTimeout(t, testTimeout)
|
err = u.Update(true)
|
||||||
err = u.Update(ctx, true)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// check backup files
|
// check backup files
|
||||||
@@ -131,15 +124,14 @@ func TestUpdater_Update(t *testing.T) {
|
|||||||
t.Skip("skipping config check test on windows")
|
t.Skip("skipping config check test on windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.Update(testutil.ContextWithTimeout(t, testTimeout), false)
|
err = u.Update(false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("api_fail", func(t *testing.T) {
|
t.Run("api_fail", func(t *testing.T) {
|
||||||
srv.Close()
|
srv.Close()
|
||||||
|
|
||||||
err = u.Update(testutil.ContextWithTimeout(t, testTimeout), true)
|
err = u.Update(true)
|
||||||
|
|
||||||
var urlErr *url.Error
|
var urlErr *url.Error
|
||||||
assert.ErrorAs(t, err, &urlErr)
|
assert.ErrorAs(t, err, &urlErr)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -980,8 +980,7 @@
|
|||||||
- 'clients'
|
- 'clients'
|
||||||
'operationId': 'clientsSearch'
|
'operationId': 'clientsSearch'
|
||||||
'summary': >
|
'summary': >
|
||||||
Retrieve information about clients by performing an exact match search
|
Get information about clients by their IP addresses, CIDRs, MAC addresses, or ClientIDs.
|
||||||
using IP addresses, CIDRs, MAC addresses, or ClientIDs.
|
|
||||||
'requestBody':
|
'requestBody':
|
||||||
'content':
|
'content':
|
||||||
'application/json':
|
'application/json':
|
||||||
|
|||||||
Reference in New Issue
Block a user