Compare commits

..

4 Commits

Author SHA1 Message Date
Stanislav Chzhen
6109e3575f home: add tests 2025-05-13 22:23:41 +03:00
Stanislav Chzhen
88706e9cf2 home: auth tests 2025-05-07 15:46:22 +03:00
Eugene Burkov
b5c47054ab Pull request 2407: Update i18n
Merge in DNS/adguard-home from upd-all to master

Squashed commit of the following:

commit cb4a2379ee2543391ab85c6dd29bffc083544b2c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue May 6 17:25:50 2025 +0300

    client: upd i18n
2025-05-06 17:50:19 +03:00
Stanislav Chzhen
4776255604 Pull request 2402: AG-40703-fix-custom-cache
Merge in DNS/adguard-home from AG-40703-fix-custom-cache to master

Squashed commit of the following:

commit e9b9aa34d6969e87cc151573912c2f22a1b81cea
Merge: b8ec40b3d e5d0f0b11
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue May 6 16:58:37 2025 +0300

    Merge branch 'master' into AG-40703-fix-custom-cache

commit b8ec40b3dd9f59124bbf5cfc2b303a37750f7497
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue May 6 16:20:48 2025 +0300

    all: upd proxy

commit 026624543c319c022cf5d57d958cc5127cf2a629
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 28 15:46:28 2025 +0300

    all: fix custom cache
2025-05-06 17:07:57 +03:00
28 changed files with 453 additions and 499 deletions

View File

@@ -1,24 +1,24 @@
{
"client_settings": "Налады кліентаў",
"example_upstream_reserved": "upstream <0>для канкрэтных даменаў</0>;",
"example_multiple_upstreams_reserved": "некалькі DNS-сервераў <0>для канкрэтных даменаў</0>;",
"example_multiple_upstreams_reserved": "некалькі сервер DNSаў <0>для канкрэтных даменаў</0>;",
"example_upstream_comment": "каментар.",
"upstream_parallel": "Ужыць адначасныя запыты да ўсіх сервераў для паскарэння апрацоўкі запыту",
"parallel_requests": "Паралельныя запыты",
"load_balancing": "Размеркаванне нагрузкі",
"load_balancing_desc": "Запытвайце па адным серверы за раз. AdGuard Home будзе выкарыстоўваць выпадковы алгарытм для выбару сервера, так што самы хуткі сервер будзе выкарыстоўвацца часцей.",
"bootstrap_dns": "Bootstrap DNS-серверы",
"bootstrap_dns_desc": "IP-адрасы DNS-сервераў, якія выкарыстоўваюцца для вырашэння IP-адрасоў распознавальнікаў DoH/DoT, якія вы ўказваеце ў якасці перадачы. Каментары не дапускаюцца.",
"fallback_dns_title": "Рэзервовыя DNS-серверы",
"fallback_dns_desc": "Спіс рэзервовых DNS-сервераў, якія выкарыстоўваюцца, калі вышэйшыя DNS-серверы не адказваюць. Сінтаксіс такі ж, як і ў галоўным полі ўверх.",
"bootstrap_dns": "Bootstrap сервер DNSы",
"bootstrap_dns_desc": "IP-адрасы сервер DNSаў, якія выкарыстоўваюцца для вырашэння IP-адрасоў распознавальнікаў DoH/DoT, якія вы ўказваеце ў якасці перадачы. Каментары не дапускаюцца.",
"fallback_dns_title": "Рэзервовыя сервер DNSы",
"fallback_dns_desc": "Спіс рэзервовых сервер DNSаў, якія выкарыстоўваюцца, калі вышэйшыя сервер 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_default_resolver": "Па змаўчанні AdGuard Home выкарыстоўвае наступныя зваротныя DNS-рэзолверы: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home не змог вызначыць прыдатныя прыватныя адваротныя DNS-рэзолверы для гэтай сістэмы.",
"local_ptr_placeholder": "Увядзіце па адным адрасе на радок",
"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_desc": "Пасылаць адваротныя DNS-запыты для лакальна абслугоўных адрасоў на паказаныя серверы. Калі адключана, AdGuard Home будзе адказваць NXDOMAIN на ўсе падобныя PTR-запыты, апроч запытаў пра кліентаў, ужо вядомых па DHCP, /etc/hosts і гэтак далей.",
"check_dhcp_servers": "Праверыць DHCP-серверы",
@@ -101,13 +101,13 @@
"compact": "Компактный",
"nothing_found": "Нічога не знойдзена",
"faq": "FAQ",
"version": "версія",
"version": "Версія",
"address": "Адрас",
"protocol": "Пратакол",
"on": "УКЛ",
"off": "Выкл",
"copyright": "Усе правы захаваныя",
"homepage": "Галоўная",
"homepage": "Хатняя старонка",
"report_an_issue": "Паведаміць пра праблему",
"privacy_policy": "Палітыка прыватнасці",
"enable_protection": "Уключыць абарону",
@@ -165,8 +165,8 @@
"custom_filtering_rules": "Карыстальніцкія правілы фільтрацыі",
"encryption_settings": "Налады шыфравання",
"dhcp_settings": "Налады DHCP",
"upstream_dns": "Upstream DNS-серверы",
"upstream_dns_help": "Увядзіце адрасы сервераў па адным у радку. <a>Даведацца больш </a> пра наладжванне DNS-сервераў.",
"upstream_dns": "Upstream сервер DNSы",
"upstream_dns_help": "Увядзіце адрасы сервераў па адным у радку. <a>Даведацца больш </a> пра наладжванне сервер DNSаў.",
"upstream_dns_configured_in_file": "Наладжаны ў {{path}}",
"test_upstream_btn": "Тэст upstream сервераў",
"upstreams": "Upstreams",
@@ -182,7 +182,7 @@
"enabled_save_search_toast": "Уключаны бяспечны пошук",
"updated_save_search_toast": "Налады бяспечнага пошуку абноўлены",
"enabled_table_header": "УКЛ.",
"name_table_header": "Імя",
"name_table_header": "Назва",
"list_url_table_header": "URL-адрас спіса",
"rules_count_table_header": "Колькасць правілаў:",
"last_time_updated_table_header": "Апошняе абнаўленне",
@@ -196,7 +196,7 @@
"no_whitelist_added": "Белыя спісы не дададзены",
"add_blocklist": "Дадаць чорны спіс",
"add_allowlist": "Дадаць белы спіс",
"cancel_btn": "Адмена",
"cancel_btn": "Скасаваць",
"enter_name_hint": "Увядзіце імя",
"enter_url_or_path_hint": "Увядзіце URL-адрас ці абсалютны шлях да спіса",
"check_updates_btn": "Праверыць абнаўленні",
@@ -219,7 +219,7 @@
"example_meaning_host_block": "адказаць 127.0.0.1 для example.org (але не для яго паддаменаў);",
"example_comment": "! Так можна дадаваць апісанне.",
"example_comment_meaning": "каментар;",
"example_comment_hash": "# І вось так таксама.",
"example_comment_hash": "# Таксама каментарый.",
"example_regex_meaning": "блакаваць доступ да даменаў, якія адпавядаюць зададзенаму рэгулярнаму выразу.",
"example_upstream_regular": "звычайны DNS (наўзверх UDP);",
"example_upstream_regular_port": "звычайны DNS (праз UDP, імя хаста);",
@@ -233,13 +233,13 @@
"example_upstream_tcp_port": "звычайны DNS (праз TCP, імя хаста);",
"example_upstream_tcp_hostname": "звычайны DNS (праз TCP, імя хаста);",
"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_not_ok_toast": "Сервер «{{key}}»: немагчыма выкарыстоўваць, праверце слушнасць напісання",
"dns_test_parsing_error_toast": "Раздзел {{section}}: радок {{line}}: немагчыма выкарыстоўваць, праверце слушнасць напісання",
"dns_test_warning_toast": "Upstream «{{key}}» не адказвае на тэставыя запыты і можа не працаваць належным чынам",
"unblock": "Адблакаваць",
"block": "Заблакаваць",
"block": "Заблакіраваць",
"disallow_this_client": "Забараніць доступ гэтаму кліенту",
"allow_this_client": "Дазволіць доступ гэтаму кліенту",
"block_for_this_client_only": "Заблакаваць толькі для гэтага кліента",
@@ -259,7 +259,7 @@
"no_logs_found": "Логі не знойдзены",
"refresh_btn": "Абнавіць",
"previous_btn": "Назад",
"next_btn": "Наперад",
"next_btn": "Далей",
"loading_table_status": "Загрузка...",
"page_table_footer_text": "Старонка",
"rows_table_footer_text": "радкоў",
@@ -280,7 +280,7 @@
"query_log_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання запытаў? Пры памяншэнні інтэрвалу, некаторыя даныя могуць быць страчаны",
"anonymize_client_ip": "Ананімізацыя IP-адрасы кліента",
"anonymize_client_ip_desc": "Не захоўвайце поўныя IP-адрасы гэтых удзельнікаў у часопісах або статыстыцы",
"dns_config": "Налады DNS-сервера",
"dns_config": "Налады сервер DNSа",
"dns_cache_config": "Налада кэша DNS",
"dns_cache_config_desc": "Тут можна наладзіць кэш DNS",
"blocking_mode": "Рэжым блакавання",
@@ -342,14 +342,14 @@
"unknown_filter": "Невядомы фільтр {{filterId}}",
"known_tracker": "Вядомы трэкер",
"install_welcome_title": "Сардэчна запрашаем у AdGuard Home!",
"install_welcome_desc": "AdGuard Home гэта DNS-сервер, што блакуе рэкламу і трэкінг. Яго мэта даць вам магчымасць кантраляваць усю ваша сеціва і ўсе падлучаныя прылады. Ён не патрабуе ўсталёўкі кліенцкіх праграм.",
"install_welcome_desc": "AdGuard Home гэта сервер DNS, што блакуе рэкламу і трэкінг. Яго мэта даць вам магчымасць кантраляваць усю ваша сеціва і ўсе падлучаныя прылады. Ён не патрабуе ўсталёўкі кліенцкіх праграм.",
"install_settings_title": "Ўэб-інтэрфейс адміністравання",
"install_settings_listen": "Інтэрфейс сеціва",
"install_settings_port": "Порт",
"install_settings_interface_link": "Ваш ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны па наступных адрасах:",
"form_error_port": "Увядзіце карэктны нумар порта",
"install_settings_dns": "DNS-сервер",
"install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне DNS-сервера на адным з наступных адрасоў:",
"install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне сервер DNSа на адным з наступных адрасоў:",
"install_settings_all_interfaces": "Усе інтэрфейсы",
"install_auth_title": "Аўтарызацыя",
"install_auth_desc": "Настойліва рэкамендуецца наладзіць аўтэнтыфікацыю паролем для ўэб-інтэрфейсу AdGuard Home. Нават калі ён даступны толькі ў вашай лакальнай сетцы, важна абараніць яго ад неабмежаванага доступу.",
@@ -365,17 +365,17 @@
"install_submit_desc": "Працэдура налады завершана і вы гатовы пачаць выкарыстанне AdGuard Home.",
"install_devices_router": "Роўтар",
"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_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары «DNS» поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.",
"install_devices_router_list_3": "Увядзіце туды адрас вашага AdGuard Home.",
"install_devices_router_list_4": "Вы не можаце ўсталяваць уласны DNS-сервер на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе DNS-сервераў для вашай пэўнай мадэлі маршрутызатара.",
"install_devices_router_list_4": "Вы не можаце ўсталяваць уласны сервер DNS на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе сервер DNSаў для вашай пэўнай мадэлі маршрутызатара.",
"install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню «Пуск» ці праз пошук Windows.",
"install_devices_windows_list_2": "Перайдзіце ў «Сеціва і інтэрнэт», а потым у «Цэнтр кіравання сеціва і агульным доступам».",
"install_devices_windows_list_3": "У левым боку экрана клікніце «Змена параметраў адаптара».",
"install_devices_windows_list_4": "Пстрыкніце правай кнопкай мышы ваша актыўнае злучэнне і абярыце Уласцівасці.",
"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_2": "Клікніце па іконцы Сеціва.",
"install_devices_macos_list_3": "Абярыце першае падлучэнне ў спісе і націсніце кнопку «Дадаткова».",
@@ -415,7 +415,7 @@
"encryption_key": "Прыватны ключ",
"encryption_key_input": "Скапіюйце сюды прыватны ключ у PEM-кадоўцы.",
"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_invalid": "Ланцужок сертыфікатаў не валідны",
"encryption_key_valid": "Валідны {{type}} прыватны ключ",
@@ -435,8 +435,8 @@
"update_announcement": "AdGuard Home {{version}} ужо даступная! <0>Націсніце сюды</0>, каб даведацца больш.",
"setup_guide": "Інструкцыя па наладзе",
"dns_addresses": "Адрасы DNS",
"dns_start": "DNS-сервер запускаецца",
"dns_status_error": "Памылка праверкі стану DNS-сервера",
"dns_start": "сервер DNS запускаецца",
"dns_status_error": "Памылка праверкі стану сервер DNSа",
"down": "Уніз",
"fix": "Выправіць",
"dns_providers": "<0>Спіс вядомых DNS-правайдараў</0> на выбар.",
@@ -449,7 +449,7 @@
"settings_global": "Глабальныя",
"settings_custom": "Свае",
"table_client": "Кліент",
"table_name": "Імя",
"table_name": "Назва",
"save_btn": "Захаваць",
"client_add": "Дадаць кліента",
"client_new": "Новы кліент",
@@ -475,7 +475,7 @@
"auto_clients_title": "Кліенты (runtime)",
"auto_clients_desc": "Інфармацыя аб IP-адрасах прылад, якія выкарыстоўваюць або могуць выкарыстоўваць AdGuard Home. Гэтая інфармацыя збіраецца з некалькіх крыніц, уключаючы файлы хостаў, зваротны DNS і г.д.",
"access_title": "Налады доступу",
"access_desc": "Тут вы можаце наладзіць правілы доступу да DNS-серверу AdGuard Home",
"access_desc": "Тут вы можаце наладзіць правілы доступу да сервер DNSу AdGuard Home",
"access_allowed_title": "Дазволеныя кліенты",
"access_allowed_desc": "Спіс CIDR, IP-адрасоў або <a>ClientID</a>. Калі ў гэтым спісе ёсць запісы, AdGuard Home будзе прымаць запыты толькі ад гэтых кліентаў.",
"access_disallowed_title": "Забароненыя кліенты",
@@ -596,7 +596,7 @@
"disable_ipv6_desc": "Ігнараваць усе запыты DNS для адрасоў IPv6 (тып AAAA) і выдаленне дадзеных IPv6 з адказаў тыпу HTTPS.",
"fastest_addr": "Найхуткі IP-адрас",
"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_result": "У выніку ўсе DNS-запыты ад вашай сістэмы будуць па змаўчанні апрацоўвацца AdGuard Home.\n",
"tags_title": "Тэгі",
@@ -634,12 +634,12 @@
"validated_with_dnssec": "Проверено с помощью DNSSEC",
"all_queries": "Усе запыты",
"show_blocked_responses": "Заблакавана",
"show_whitelisted_responses": "Белы спіс",
"show_whitelisted_responses": "У белым спісе",
"show_processed_responses": "Апрацавана",
"blocked_safebrowsing": "Заблакіравана згодна з базай даных Safe Browsing",
"blocked_adult_websites": "Заблакавана Бацькоўскім кантролем",
"blocked_threats": "Заблакавана пагроз",
"allowed": "Дазволены",
"allowed": "У белым спісе",
"filtered": "Адфільтраваныя",
"rewritten": "Перапісаныя",
"safe_search": "Бяспечны пошук",
@@ -738,7 +738,7 @@
"thursday_short": "Чц.",
"friday_short": "Пт.",
"saturday_short": "Сб.",
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream DNS-сервераў",
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream сервер DNSаў",
"enable_upstream_dns_cache": "Ўключыць кэшаванне для карыстацкай канфігурацыі upstream-сервераў гэтага кліента",
"dns_cache_size": "Памер кэша DNS, у байтах"
}

View File

@@ -656,7 +656,7 @@
"blocklist": "Zakázaný",
"milliseconds_abbreviation": "ms",
"cache_size": "Velikost mezipaměti",
"cache_size_desc": "Velikost mezipaměti DNS (v bajtech). Chcete-li ukládání do mezipaměti zakázat, ponechte prázdné.",
"cache_size_desc": "Velikost mezipaměti DNS (v bajtech). Chcete-li ukládání do mezipaměti zakázat, nastavte 0.",
"cache_ttl_min_override": "Přepsat minimální hodnotu TTL",
"cache_ttl_max_override": "Přepsat maximální hodnotu TTL",
"enter_cache_size": "Zadejte velikost mezipaměti (v bajtech)",

View File

@@ -656,7 +656,7 @@
"blocklist": "Sortliste",
"milliseconds_abbreviation": "ms",
"cache_size": "Cache-størrelse",
"cache_size_desc": "DNS cache-størrelse (i bytes). Lad stå tomt for at deaktivere cache.",
"cache_size_desc": "DNS cache-størrelse (i bytes). Sæt til 0 for at deaktivere cache.",
"cache_ttl_min_override": "Tilsidesæt minimum TTL",
"cache_ttl_max_override": "Tilsidesæt maksimal TTL",
"enter_cache_size": "Angiv cache-størrelse (bytes)",

View File

@@ -656,7 +656,7 @@
"blocklist": "Sperrliste",
"milliseconds_abbreviation": "ms",
"cache_size": "Größe des Cache",
"cache_size_desc": "Größe des DNS-Zwischenspeichers (in Bytes)",
"cache_size_desc": "Größe des DNS-Cache (in Bytes). Um das Caching zu deaktivieren, setzen Sie den Wert auf 0.",
"cache_ttl_min_override": "TTL-Minimalwert überschreiben",
"cache_ttl_max_override": "TTL-Höchstwert überschreiben",
"enter_cache_size": "Größe des Cache (Bytes) eingeben",

View File

@@ -656,7 +656,7 @@
"blocklist": "Lista de bloqueo",
"milliseconds_abbreviation": "ms",
"cache_size": "Tamaño de la caché",
"cache_size_desc": "Tamaño de la caché DNS (en bytes). Para deshabilitar el almacenamiento en caché, déjalo vacío.",
"cache_size_desc": "Tamaño de la caché DNS (en bytes). Para desactivar el almacenamiento en caché, configúralo en 0.",
"cache_ttl_min_override": "Anular TTL mínimo",
"cache_ttl_max_override": "Anular TTL máximo",
"enter_cache_size": "Ingresa el tamaño de la caché (bytes)",

View File

@@ -656,7 +656,7 @@
"blocklist": "Liste de blocage",
"milliseconds_abbreviation": "ms",
"cache_size": "Taille du cache",
"cache_size_desc": "Taille du cache DNS (en octets). Pour désactiver la mise en cache, laissez vide.",
"cache_size_desc": "Taille du cache DNS (en octets). Pour désactiver la mise en cache, mettez la valeur sur 0.",
"cache_ttl_min_override": "Remplacer le TTL minimum",
"cache_ttl_max_override": "Remplacer le TTL maximum",
"enter_cache_size": "Entrer la taille du cache (octets)",

View File

@@ -656,7 +656,7 @@
"blocklist": "Lista nera",
"milliseconds_abbreviation": "ms",
"cache_size": "Dimensioni cache",
"cache_size_desc": "Dimensione della cache DNS (in byte). Per disabilitare la memorizzazione nella cache, lascia vuoto.",
"cache_size_desc": "Dimensione della cache DNS (in byte). Per disabilitare la cache, impostare su 0.",
"cache_ttl_min_override": "Sovrascrivi TTL minimo",
"cache_ttl_max_override": "Sovrascrivi TTL massimo",
"enter_cache_size": "Immetti dimensioni cache (in byte)",

View File

@@ -656,7 +656,7 @@
"blocklist": "ブロックリスト",
"milliseconds_abbreviation": "ms",
"cache_size": "キャッシュサイズ",
"cache_size_desc": "DNSキャッシュサイズバイト単位※キャッシュを無効化するには、この欄を空してください。",
"cache_size_desc": "DNSキャッシュサイズバイト単位※キャッシュを無効化するには、「0」ゼロしてください。",
"cache_ttl_min_override": "最小TTLの上書き秒単位",
"cache_ttl_max_override": "最大TTLの上書き秒単位",
"enter_cache_size": "キャッシュサイズ(バイト単位)を入力してください",

View File

@@ -656,7 +656,7 @@
"blocklist": "차단 목록",
"milliseconds_abbreviation": "ms",
"cache_size": "캐시 크기",
"cache_size_desc": "DNS 캐시 크기(바이트). 캐싱을 비활성화하려면 비워 둡니다.",
"cache_size_desc": "DNS 캐시 크기(바이트). 캐싱을 사용하지 않으려면 0으로 설정합니다.",
"cache_ttl_min_override": "최소 TTL (초) 무시",
"cache_ttl_max_override": "최대 TTL (초) 무시",
"enter_cache_size": "캐시 크기를 입력하세요",

View File

@@ -656,7 +656,7 @@
"blocklist": "Blokkeerlijst",
"milliseconds_abbreviation": "ms",
"cache_size": "Cache grootte",
"cache_size_desc": "DNS-cachegrootte (in bytes). Leeg laten om caching uit te schakelen.",
"cache_size_desc": "DNS-cachegrootte (in bytes). Om caching uit te schakelen, stel deze in op 0.",
"cache_ttl_min_override": "Minimale TTL overschrijven",
"cache_ttl_max_override": "Maximale TTL overschrijven",
"enter_cache_size": "Cache grootte invoeren (bytes)",

View File

@@ -656,7 +656,7 @@
"blocklist": "Lista de bloqueio",
"milliseconds_abbreviation": "ms",
"cache_size": "Tamanho do cache",
"cache_size_desc": "Tamanho do cache do DNS (em bytes). Para desativar o cache, deixe em branco.",
"cache_size_desc": "Tamanho do cache do DNS (em bytes). Para desativar o cache, defina como 0.",
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
"cache_ttl_max_override": "Sobrepor o TTL máximo",
"enter_cache_size": "Digite o tamanho do cache (bytes)",

View File

@@ -656,7 +656,7 @@
"blocklist": "Lista de bloqueio",
"milliseconds_abbreviation": "ms",
"cache_size": "Tamanho do cache",
"cache_size_desc": "Tamanho do cache DNS (em bytes). Para desativar o cache, deixar o campo vazio.",
"cache_size_desc": "Tamanho do cache DNS (em bytes). Para desativar o cache, defina como 0.",
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
"cache_ttl_max_override": "Sobrepor o TTL máximo",
"enter_cache_size": "Digite o tamanho do cache (bytes)",

View File

@@ -656,7 +656,7 @@
"blocklist": "Чёрный список",
"milliseconds_abbreviation": "мс",
"cache_size": "Размер кеша",
"cache_size_desc": "Размера кеша DNS (в байтах). Чтобы отключить кэширование, оставьте поле пустым.",
"cache_size_desc": "Размер кеша DNS (в байтах). Чтобы отключить кеширование, установите значение 0.",
"cache_ttl_min_override": "Переопределить минимальный TTL",
"cache_ttl_max_override": "Переопределить максимальный TTL",
"enter_cache_size": "Введите размер кеша (в байтах)",

View File

@@ -656,7 +656,7 @@
"blocklist": "Zoznam blokovaní",
"milliseconds_abbreviation": "ms",
"cache_size": "Veľkosť cache",
"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_size_desc": "Veľkosť vyrovnávacej pamäte DNS (v bajtoch). Ak chcete vypnúť ukladanie do vyrovnávacej pamäte, nastavte hodnotu 0.",
"cache_ttl_min_override": "Prepísať minimálne TTL",
"cache_ttl_max_override": "Prepísať maximálne TTL",
"enter_cache_size": "Zadať veľkosť cache (v bajtoch)",

View File

@@ -656,7 +656,7 @@
"blocklist": "Engel listesi",
"milliseconds_abbreviation": "ms",
"cache_size": "Önbellek boyutu",
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden). Önbelleğe almayı devre dışı bırakmak için boş bırakın.",
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden). Önbelleği devre dışı bırakmak için 0 olarak ayarlayın.",
"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",
"enter_cache_size": "Önbellek boyutunu girin (bayt)",

View File

@@ -656,7 +656,7 @@
"blocklist": "黑名单",
"milliseconds_abbreviation": "毫秒",
"cache_size": "缓存大小",
"cache_size_desc": "DNS 缓存大小(单位:字节)。若要关闭缓存,请留空。",
"cache_size_desc": "DNS 缓存大小(单位:字节)。若要禁用缓存,请设置为 0。",
"cache_ttl_min_override": "覆盖最小 TTL 值",
"cache_ttl_max_override": "覆盖最大 TTL 值",
"enter_cache_size": "输入缓存大小(字节)",

View File

@@ -656,7 +656,7 @@
"blocklist": "封鎖清單",
"milliseconds_abbreviation": "ms",
"cache_size": "快取大小",
"cache_size_desc": "DNS 快取大小 (位元組)。若要停用快取,請留空。",
"cache_size_desc": "DNS 快取大小位元組。若要停用快取,請設為 0。",
"cache_ttl_min_override": "覆寫最小的存活時間TTL",
"cache_ttl_max_override": "覆寫最大的存活時間TTL",
"enter_cache_size": "輸入快取大小(位元組)",

2
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
go 1.24.2
require (
github.com/AdguardTeam/dnsproxy v0.75.3
github.com/AdguardTeam/dnsproxy v0.75.4
github.com/AdguardTeam/golibs v0.32.8
github.com/AdguardTeam/urlfilter v0.20.0
github.com/NYTimes/gziphandler v1.1.1

4
go.sum
View File

@@ -10,8 +10,8 @@ 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/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
github.com/AdguardTeam/dnsproxy v0.75.3 h1:pxlMNO+cP1A3px40PY/old6SAE82pkdLPUA2P3KY8u0=
github.com/AdguardTeam/dnsproxy v0.75.3/go.mod h1:50OyTHao+uQzUJiXay08hgfvWQ3o2Q2WV99W8u8ypDE=
github.com/AdguardTeam/dnsproxy v0.75.4 h1:hTnHh9HoTYKKhKqePpIxCzfecl7dAXykZTw2gcj0I5U=
github.com/AdguardTeam/dnsproxy v0.75.4/go.mod h1:50OyTHao+uQzUJiXay08hgfvWQ3o2Q2WV99W8u8ypDE=
github.com/AdguardTeam/golibs v0.32.8 h1:O3mc3kYcPkW3kbmd+gqzFNgUka13a+iBgFLThwOYSQE=
github.com/AdguardTeam/golibs v0.32.8/go.mod h1:McV1QFFlKLElKa306V4OL/T2kr7564PhsayfvTWYBVs=
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=

View File

@@ -1,127 +0,0 @@
package dhcpsvc
import (
"context"
"fmt"
"net/netip"
"github.com/AdguardTeam/golibs/errors"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
// serveV4 handles the ethernet packet of IPv4 type.
func (srv *DHCPServer) serveV4(
ctx context.Context,
rw responseWriter4,
pkt gopacket.Packet,
) (err error) {
defer func() { err = errors.Annotate(err, "serving dhcpv4: %w") }()
req, ok := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
if !ok {
srv.logger.DebugContext(ctx, "skipping non-dhcpv4 packet")
return nil
}
// TODO(e.burkov): Handle duplicate Xid.
if req.Operation != layers.DHCPOpRequest {
srv.logger.DebugContext(ctx, "skipping non-request dhcpv4 packet")
return nil
}
typ, ok := msg4Type(req)
if !ok {
// The "DHCP message type" option - must be included in every DHCP
// message.
//
// See https://datatracker.ietf.org/doc/html/rfc2131#section-3.
return fmt.Errorf("dhcpv4: message type: %w", errors.ErrNoValue)
}
return srv.handleDHCPv4(ctx, rw, typ, req)
}
// handleDHCPv4 handles the DHCPv4 message of the given type.
func (srv *DHCPServer) handleDHCPv4(
ctx context.Context,
rw responseWriter4,
typ layers.DHCPMsgType,
req *layers.DHCPv4,
) (err error) {
// Each interface should handle the DISCOVER and REQUEST messages offer and
// allocate the available leases. The RELEASE and DECLINE messages should
// be handled by the server itself as it should remove the lease.
switch typ {
case layers.DHCPMsgTypeDiscover:
srv.handleDiscover(ctx, rw, req)
case layers.DHCPMsgTypeRequest:
srv.handleRequest(ctx, rw, req)
case layers.DHCPMsgTypeRelease:
// TODO(e.burkov): !! Remove the lease, either allocated or offered.
case layers.DHCPMsgTypeDecline:
// TODO(e.burkov): !! Remove the allocated lease. RFC tells it only
// possible if the client found the address already in use.
default:
// TODO(e.burkov): Handle DHCPINFORM.
return fmt.Errorf("dhcpv4: request type: %w: %v", errors.ErrBadEnumValue, typ)
}
return nil
}
// handleDiscover handles the DHCPv4 message of discover type.
func (srv *DHCPServer) handleDiscover(ctx context.Context, rw responseWriter4, req *layers.DHCPv4) {
// TODO(e.burkov): Check existing leases, either allocated or offered.
for _, iface := range srv.interfaces4 {
go iface.handleDiscover(ctx, rw, req)
}
}
// handleRequest handles the DHCPv4 message of request type.
func (srv *DHCPServer) handleRequest(ctx context.Context, rw responseWriter4, req *layers.DHCPv4) {
srvID, hasSrvID := serverID4(req)
reqIP, hasReqIP := requestedIPv4(req)
switch {
case hasSrvID && !srvID.IsUnspecified():
// If the DHCPREQUEST message contains a server identifier option, the
// message is in response to a DHCPOFFER message. Otherwise, the
// message is a request to verify or extend an existing lease.
iface, hasIface := srv.interfaces4.findInterface(srvID)
if !hasIface {
srv.logger.DebugContext(ctx, "skipping selecting request", "serverid", srvID)
return
}
iface.handleSelecting(ctx, rw, req, reqIP)
case hasReqIP && !reqIP.IsUnspecified():
// Requested IP address option MUST be filled in with client's notion of
// its previously assigned address.
iface, hasIface := srv.interfaces4.findInterface(reqIP)
if !hasIface {
srv.logger.DebugContext(ctx, "skipping init-reboot request", "requestedip", reqIP)
return
}
iface.handleInitReboot(ctx, rw, req, reqIP)
default:
// Server identifier MUST NOT be filled in, requested IP address option
// MUST NOT be filled in.
ip, _ := netip.AddrFromSlice(req.ClientIP.To4())
iface, hasIface := srv.interfaces4.findInterface(ip)
if !hasIface {
srv.logger.DebugContext(ctx, "skipping init-reboot request", "clientip", ip)
return
}
iface.handleRenew(ctx, rw, req)
}
}

View File

@@ -1,57 +0,0 @@
package dhcpsvc
import (
"context"
"fmt"
"github.com/AdguardTeam/golibs/errors"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
// serveV6 handles the ethernet packet of IPv6 type.
func (srv *DHCPServer) serveV6(
ctx context.Context,
rw responseWriter4,
pkt gopacket.Packet,
) (err error) {
defer func() { err = errors.Annotate(err, "serving dhcpv6: %w") }()
msg, ok := pkt.Layer(layers.LayerTypeDHCPv6).(*layers.DHCPv6)
if !ok {
srv.logger.DebugContext(ctx, "skipping non-dhcpv6 packet")
return nil
}
// TODO(e.burkov): Handle duplicate TransactionID.
typ := msg.MsgType
return srv.handleDHCPv6(ctx, rw, typ, msg)
}
// handleDHCPv6 handles the DHCPv6 message of the given type.
func (srv *DHCPServer) handleDHCPv6(
_ context.Context,
_ responseWriter4,
typ layers.DHCPv6MsgType,
_ *layers.DHCPv6,
) (err error) {
switch typ {
case
layers.DHCPv6MsgTypeSolicit,
layers.DHCPv6MsgTypeRequest,
layers.DHCPv6MsgTypeConfirm,
layers.DHCPv6MsgTypeRenew,
layers.DHCPv6MsgTypeRebind,
layers.DHCPv6MsgTypeInformationRequest,
layers.DHCPv6MsgTypeRelease,
layers.DHCPv6MsgTypeDecline:
// TODO(e.burkov): Handle messages.
default:
return fmt.Errorf("dhcpv6: request type: %w: %v", errors.ErrBadEnumValue, typ)
}
return nil
}

View File

@@ -45,6 +45,17 @@ type netInterface struct {
leaseTTL time.Duration
}
// newNetInterface creates a new netInterface with the given name, leaseTTL, and
// logger.
func newNetInterface(name string, l *slog.Logger, leaseTTL time.Duration) (iface *netInterface) {
return &netInterface{
logger: l,
leases: map[macKey]*Lease{},
name: name,
leaseTTL: leaseTTL,
}
}
// reset clears all the slices in iface for reuse.
func (iface *netInterface) reset() {
clear(iface.leases)

View File

@@ -1,50 +0,0 @@
package dhcpsvc
import (
"context"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/google/gopacket/layers"
)
// responseWriter4 writes DHCPv4 response to the client.
type responseWriter4 interface {
// write writes the DHCPv4 response to the client.
write(ctx context.Context, pkt *layers.DHCPv4) (err error)
}
// serve handles the incoming packets and dispatches them to the appropriate
// handler based on the Ethernet type. It's used to run in a separate goroutine
// as it blocks until packets channel is closed.
func (srv *DHCPServer) serve(ctx context.Context) {
defer slogutil.RecoverAndLog(ctx, srv.logger)
for pkt := range srv.packetSource.Packets() {
etherLayer, ok := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
if !ok {
srv.logger.DebugContext(ctx, "skipping non-ethernet packet")
continue
}
var err error
// TODO(e.burkov): Set the response writer.
var rw responseWriter4
switch typ := etherLayer.EthernetType; typ {
case layers.EthernetTypeIPv4:
err = srv.serveV4(ctx, rw, pkt)
case layers.EthernetTypeIPv6:
err = srv.serveV6(ctx, rw, pkt)
default:
srv.logger.DebugContext(ctx, "skipping ethernet packet", "type", typ)
continue
}
if err != nil {
srv.logger.ErrorContext(ctx, "serving", slogutil.KeyError, err)
}
}
}

View File

@@ -13,13 +13,9 @@ import (
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/google/gopacket"
)
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
//
// TODO(e.burkov): Rename to Default.
type DHCPServer struct {
// enabled indicates whether the DHCP server is enabled and can provide
// information about its clients.
@@ -28,9 +24,6 @@ type DHCPServer struct {
// logger logs common DHCP events.
logger *slog.Logger
// TODO(e.burkov): Implement and set.
packetSource gopacket.PacketSource
// localTLD is the top-level domain name to use for resolving DHCP clients'
// hostnames.
localTLD string
@@ -105,7 +98,7 @@ func New(ctx context.Context, conf *Config) (srv *DHCPServer, err error) {
// their configurations.
func newInterfaces(
ctx context.Context,
baseLogger *slog.Logger,
l *slog.Logger,
ifaces map[string]*InterfaceConfig,
) (v4 dhcpInterfacesV4, v6 dhcpInterfacesV6, err error) {
defer func() { err = errors.Annotate(err, "creating interfaces: %w") }()
@@ -117,27 +110,18 @@ func newInterfaces(
var errs []error
for _, name := range slices.Sorted(maps.Keys(ifaces)) {
iface := ifaces[name]
iface4, v4Err := newDHCPInterfaceV4(
ctx,
baseLogger.With(keyInterface, name, keyFamily, netutil.AddrFamilyIPv4),
name,
iface.IPv4,
)
if v4Err != nil {
v4Err = fmt.Errorf("interface %q: %s: %w", name, netutil.AddrFamilyIPv4, v4Err)
errs = append(errs, v4Err)
} else {
v4 = append(v4, iface4)
var i4 *dhcpInterfaceV4
i4, err = newDHCPInterfaceV4(ctx, l, name, iface.IPv4)
if err != nil {
errs = append(errs, fmt.Errorf("interface %q: ipv4: %w", name, err))
} else if i4 != nil {
v4 = append(v4, i4)
}
iface6 := newDHCPInterfaceV6(
ctx,
baseLogger.With(keyInterface, name, keyFamily, netutil.AddrFamilyIPv6),
name,
iface.IPv6,
)
v6 = append(v6, iface6)
i6 := newDHCPInterfaceV6(ctx, l, name, iface.IPv6)
if i6 != nil {
v6 = append(v6, i6)
}
}
if err = errors.Join(errs...); err != nil {
@@ -152,25 +136,6 @@ func newInterfaces(
// TODO(e.burkov): Uncomment when the [Interface] interface is implemented.
// var _ Interface = (*DHCPServer)(nil)
// Start implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) Start(ctx context.Context) (err error) {
srv.logger.DebugContext(ctx, "starting dhcp server")
// TODO(e.burkov): Listen to configured interfaces.
go srv.serve(context.Background())
return nil
}
func (srv *DHCPServer) Shutdown(ctx context.Context) (err error) {
srv.logger.DebugContext(ctx, "shutting down dhcp server")
// TODO(e.burkov): Close the packet source.
return nil
}
// Enabled implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) Enabled() (ok bool) {
return srv.enabled.Load()
@@ -370,50 +335,6 @@ func (srv *DHCPServer) RemoveLease(ctx context.Context, l *Lease) (err error) {
return nil
}
// removeLeaseByAddr removes the lease with the given IP address from the
// server. It returns an error if the lease can't be removed.
//
// TODO(e.burkov): !! Use.
func (srv *DHCPServer) removeLeaseByAddr(ctx context.Context, addr netip.Addr) (err error) {
defer func() { err = errors.Annotate(err, "removing lease by address: %w") }()
iface, err := srv.ifaceForAddr(addr)
if err != nil {
// Don't wrap the error since it's already informative enough as is.
return err
}
srv.leasesMu.Lock()
defer srv.leasesMu.Unlock()
l, ok := srv.leases.leaseByAddr(addr)
if !ok {
return fmt.Errorf("no lease for ip %s", addr)
}
err = srv.leases.remove(l, iface)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
err = srv.dbStore(ctx)
if err != nil {
// Don't wrap the error since it's already informative enough as is.
return err
}
iface.logger.DebugContext(
ctx, "removed lease",
"hostname", l.Hostname,
"ip", l.IP,
"mac", l.HWAddr,
"static", l.IsStatic,
)
return nil
}
// ifaceForAddr returns the handled network interface for the given IP address,
// or an error if no such interface exists.
func (srv *DHCPServer) ifaceForAddr(addr netip.Addr) (iface *netInterface, err error) {

View File

@@ -91,7 +91,7 @@ type dhcpInterfaceV4 struct {
// gateway is the IP address of the network gateway.
gateway netip.Addr
// subnet is the network subnet of the interface.
// subnet is the network subnet.
subnet netip.Prefix
// addrSpace is the IPv4 address space allocated for leasing.
@@ -115,7 +115,12 @@ func newDHCPInterfaceV4(
l *slog.Logger,
name string,
conf *IPv4Config,
) (iface *dhcpInterfaceV4, err error) {
) (i *dhcpInterfaceV4, err error) {
l = l.With(
keyInterface, name,
keyFamily, netutil.AddrFamilyIPv4,
)
if !conf.Enabled {
l.DebugContext(ctx, "disabled")
@@ -139,20 +144,31 @@ func newDHCPInterfaceV4(
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
}
iface = &dhcpInterfaceV4{
i = &dhcpInterfaceV4{
gateway: conf.GatewayIP,
subnet: subnet,
addrSpace: addrSpace,
common: &netInterface{
logger: l,
leases: map[macKey]*Lease{},
name: name,
leaseTTL: conf.LeaseDuration,
},
common: newNetInterface(name, l, conf.LeaseDuration),
}
iface.implicitOpts, iface.explicitOpts = conf.options(ctx, l)
i.implicitOpts, i.explicitOpts = conf.options(ctx, l)
return iface, nil
return i, nil
}
// dhcpInterfacesV4 is a slice of network interfaces of IPv4 address family.
type dhcpInterfacesV4 []*dhcpInterfaceV4
// find returns the first network interface within ifaces containing ip. It
// returns false if there is no such interface.
func (ifaces dhcpInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
i := slices.IndexFunc(ifaces, func(iface *dhcpInterfaceV4) (contains bool) {
return iface.subnet.Contains(ip)
})
if i < 0 {
return nil, false
}
return ifaces[i].common, true
}
// options returns the implicit and explicit options for the interface. The two
@@ -345,104 +361,3 @@ func (c *IPv4Config) options(ctx context.Context, l *slog.Logger) (imp, exp laye
func compareV4OptionCodes(a, b layers.DHCPOption) (res int) {
return int(a.Type) - int(b.Type)
}
// msg4Type returns the message type of msg, if it's present within the options.
func msg4Type(msg *layers.DHCPv4) (typ layers.DHCPMsgType, ok bool) {
for _, opt := range msg.Options {
if opt.Type == layers.DHCPOptMessageType && len(opt.Data) > 0 {
return layers.DHCPMsgType(opt.Data[0]), true
}
}
return 0, false
}
// requestedIPv4 returns the IPv4 address, requested by client in the DHCP
// message, if any.
func requestedIPv4(msg *layers.DHCPv4) (ip netip.Addr, ok bool) {
for _, opt := range msg.Options {
if opt.Type == layers.DHCPOptRequestIP && len(opt.Data) == net.IPv4len {
return netip.AddrFromSlice(opt.Data)
}
}
return netip.Addr{}, false
}
// serverID4 returns the server ID of the DHCP message, if any.
func serverID4(msg *layers.DHCPv4) (ip netip.Addr, ok bool) {
for _, opt := range msg.Options {
if opt.Type == layers.DHCPOptServerID && len(opt.Data) == net.IPv4len {
return netip.AddrFromSlice(opt.Data)
}
}
return netip.Addr{}, false
}
// handleDiscover handles messages of type discover.
func (iface *dhcpInterfaceV4) handleDiscover(
ctx context.Context,
rw responseWriter4,
msg *layers.DHCPv4,
) {
// TODO(e.burkov): !! Implement.
}
// handleSelecting handles messages of type request in SELECTING state.
func (iface *dhcpInterfaceV4) handleSelecting(
ctx context.Context,
rw responseWriter4,
msg *layers.DHCPv4,
reqIP netip.Addr,
) {
// TODO(e.burkov): !! Implement.
}
// handleSelecting handles messages of type request in INIT-REBOOT state.
func (iface *dhcpInterfaceV4) handleInitReboot(
ctx context.Context,
rw responseWriter4,
msg *layers.DHCPv4,
reqIP netip.Addr,
) {
// TODO(e.burkov): !! Implement.
}
// handleRenew handles messages of type request in RENEWING or REBINDING state.
func (iface *dhcpInterfaceV4) handleRenew(
ctx context.Context,
rw responseWriter4,
req *layers.DHCPv4,
) {
// TODO(e.burkov): !! Implement.
}
// dhcpInterfacesV4 is a slice of network interfaces of IPv4 address family.
type dhcpInterfacesV4 []*dhcpInterfaceV4
// find returns the first network interface within ifaces containing ip. It
// returns false if there is no such interface.
func (ifaces dhcpInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
i := slices.IndexFunc(ifaces, func(iface *dhcpInterfaceV4) (contains bool) {
return iface.subnet.Contains(ip)
})
if i < 0 {
return nil, false
}
return ifaces[i].common, true
}
// findInterface returns the first DHCPv4 interface within ifaces containing
// ip. It returns false if there is no such interface.
func (ifaces dhcpInterfacesV4) findInterface(ip netip.Addr) (iface *dhcpInterfaceV4, ok bool) {
i := slices.IndexFunc(ifaces, func(iface *dhcpInterfaceV4) (contains bool) {
return iface.subnet.Contains(ip)
})
if i < 0 {
return nil, false
}
return ifaces[i], true
}

View File

@@ -97,27 +97,23 @@ func newDHCPInterfaceV6(
l *slog.Logger,
name string,
conf *IPv6Config,
) (iface *dhcpInterfaceV6) {
) (i *dhcpInterfaceV6) {
l = l.With(keyInterface, name, keyFamily, netutil.AddrFamilyIPv6)
if !conf.Enabled {
l.DebugContext(ctx, "disabled")
return nil
}
iface = &dhcpInterfaceV6{
rangeStart: conf.RangeStart,
common: &netInterface{
logger: l,
leases: map[macKey]*Lease{},
name: name,
leaseTTL: conf.LeaseDuration,
},
i = &dhcpInterfaceV6{
rangeStart: conf.RangeStart,
common: newNetInterface(name, l, conf.LeaseDuration),
raSLAACOnly: conf.RASLAACOnly,
raAllowSLAAC: conf.RAAllowSLAAC,
}
iface.implicitOpts, iface.explicitOpts = conf.options(ctx, l)
i.implicitOpts, i.explicitOpts = conf.options(ctx, l)
return iface
return i
}
// dhcpInterfacesV6 is a slice of network interfaces of IPv6 address family.

View File

@@ -1,19 +1,355 @@
package home
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/netip"
"net/textproto"
"net/url"
"path/filepath"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
)
// TODO(s.chzhen): !! Add more tests.
func TestAuth_ServeHTTP_first_run(t *testing.T) {
storeGlobals(t)
globalContext.firstRun = true
mux := http.NewServeMux()
globalContext.mux = mux
var (
logger = slogutil.NewDiscardLogger()
ctx = testutil.ContextWithTimeout(t, testTimeout)
err error
)
web, err := initWeb(ctx, options{}, nil, nil, logger, nil, false)
require.NoError(t, err)
globalContext.web = web
testCases := []struct {
url string
method string
code int
}{{
url: "/",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/apple/doh.mobileconfig",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/apple/dot.mobileconfig",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/control/i18n/change_language",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/control/i18n/current_language",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/control/install/check_config",
method: http.MethodPost,
code: http.StatusBadRequest,
}, {
url: "/control/install/configure",
method: http.MethodPost,
code: http.StatusBadRequest,
}, {
url: "/control/install/get_addresses",
method: http.MethodGet,
code: http.StatusOK,
}, {
url: "/control/login",
method: http.MethodPost,
code: http.StatusFound,
}, {
url: "/control/logout",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/control/profile",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/control/profile/update",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/control/status",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/control/update",
method: http.MethodGet,
code: http.StatusFound,
}, {
url: "/control/version.json",
method: http.MethodGet,
code: http.StatusFound,
}}
for _, tc := range testCases {
t.Run(tc.url, func(t *testing.T) {
r := httptest.NewRequest(tc.method, tc.url, nil)
h, pattern := mux.Handler(r)
require.NotEmpty(t, pattern)
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
assert.Equal(t, tc.code, w.Code)
})
}
}
func TestAuth_ServeHTTP(t *testing.T) {
storeGlobals(t)
const (
authNone = iota
authBasic
authCookie
)
const (
testTTL = 60
userName = "name"
userPassword = "password"
)
var (
logger = slogutil.NewDiscardLogger()
ctx = testutil.ContextWithTimeout(t, testTimeout)
err error
)
passwordHash, err := bcrypt.GenerateFromPassword([]byte(userPassword), bcrypt.DefaultCost)
require.NoError(t, err)
sessionsDB := filepath.Join(t.TempDir(), "sessions.db")
users := []webUser{{
Name: userName,
PasswordHash: string(passwordHash),
}}
auth := InitAuth(sessionsDB, users, testTTL, nil, nil)
globalContext.auth = auth
mux := http.NewServeMux()
globalContext.mux = mux
tlsMgr, err := newTLSManager(ctx, &tlsManagerConfig{
logger: logger,
configModified: func() {},
})
require.NoError(t, err)
web, err := initWeb(ctx, options{}, nil, nil, logger, tlsMgr, false)
require.NoError(t, err)
globalContext.web = web
creds, err := json.Marshal(&loginJSON{Name: userName, Password: userPassword})
require.NoError(t, err)
r := httptest.NewRequest(http.MethodPost, "/control/login", bytes.NewReader(creds))
r.Header.Set(httphdr.ContentType, aghhttp.HdrValApplicationJSON)
w := httptest.NewRecorder()
mux.ServeHTTP(w, r)
var loginCookie *http.Cookie
for _, c := range w.Result().Cookies() {
if c.Name == sessionCookieName {
loginCookie = c
}
}
require.NotNil(t, loginCookie)
testCases := []struct {
url string
method string
authMethod int
wantCode int
}{{
url: "/",
method: http.MethodGet,
authMethod: authNone,
wantCode: http.StatusFound,
}, {
url: "/control/i18n/change_language",
method: http.MethodPost,
authMethod: authNone,
wantCode: http.StatusForbidden,
}, {
url: "/control/i18n/change_language",
method: http.MethodPost,
authMethod: authBasic,
wantCode: http.StatusInternalServerError,
}, {
url: "/control/i18n/change_language",
method: http.MethodPost,
authMethod: authCookie,
wantCode: http.StatusInternalServerError,
}, {
url: "/control/i18n/current_language",
method: http.MethodGet,
authMethod: authNone,
wantCode: http.StatusForbidden,
}, {
url: "/control/i18n/current_language",
method: http.MethodGet,
authMethod: authBasic,
wantCode: http.StatusOK,
}, {
url: "/control/i18n/current_language",
method: http.MethodGet,
authMethod: authCookie,
wantCode: http.StatusOK,
}, {
url: "/control/logout",
method: http.MethodGet,
authMethod: authNone,
wantCode: http.StatusForbidden,
}, {
url: "/control/logout",
method: http.MethodGet,
authMethod: authBasic,
wantCode: http.StatusFound,
}, {
url: "/control/profile",
method: http.MethodGet,
authMethod: authNone,
wantCode: http.StatusForbidden,
}, {
url: "/control/profile",
method: http.MethodGet,
authMethod: authBasic,
wantCode: http.StatusOK,
}, {
url: "/control/profile",
method: http.MethodGet,
authMethod: authCookie,
wantCode: http.StatusOK,
}, {
url: "/control/profile/update",
method: http.MethodPut,
authMethod: authNone,
wantCode: http.StatusForbidden,
}, {
url: "/control/profile/update",
method: http.MethodPut,
authMethod: authBasic,
wantCode: http.StatusBadRequest,
}, {
url: "/control/profile/update",
method: http.MethodPut,
authMethod: authCookie,
wantCode: http.StatusBadRequest,
}, {
url: "/control/status",
method: http.MethodGet,
authMethod: authNone,
wantCode: http.StatusForbidden,
}, {
url: "/control/status",
method: http.MethodGet,
authMethod: authBasic,
wantCode: http.StatusOK,
}, {
url: "/control/status",
method: http.MethodGet,
authMethod: authCookie,
wantCode: http.StatusOK,
}, {
url: "/control/update",
method: http.MethodPost,
authMethod: authNone,
wantCode: http.StatusForbidden,
}, {
url: "/control/version.json",
method: http.MethodGet,
authMethod: authNone,
wantCode: http.StatusForbidden,
}, {
url: "/control/version.json",
method: http.MethodGet,
authMethod: authBasic,
wantCode: http.StatusOK,
}, {
url: "/control/version.json",
method: http.MethodGet,
authMethod: authCookie,
wantCode: http.StatusOK,
}}
for _, tc := range testCases {
t.Run(tc.url, func(t *testing.T) {
r = httptest.NewRequest(tc.method, tc.url, nil)
switch tc.authMethod {
case authNone:
// Go on.
case authBasic:
r.SetBasicAuth(userName, userPassword)
case authCookie:
r.AddCookie(loginCookie)
default:
panic("unrecognized auth method")
}
h, pattern := mux.Handler(r)
require.NotEmpty(t, pattern)
w = httptest.NewRecorder()
h.ServeHTTP(w, r)
assert.Equal(t, tc.wantCode, w.Code)
})
}
t.Run("logout", func(t *testing.T) {
r = httptest.NewRequest(http.MethodGet, "/control/status", nil)
r.AddCookie(loginCookie)
w = httptest.NewRecorder()
mux.ServeHTTP(w, r)
assert.Equal(t, http.StatusOK, w.Code)
r = httptest.NewRequest(http.MethodGet, "/control/logout", nil)
r.AddCookie(loginCookie)
w = httptest.NewRecorder()
mux.ServeHTTP(w, r)
assert.Equal(t, http.StatusFound, w.Code)
r = httptest.NewRequest(http.MethodGet, "/control/status", nil)
r.AddCookie(loginCookie)
w = httptest.NewRecorder()
mux.ServeHTTP(w, r)
assert.Equal(t, http.StatusForbidden, w.Code)
})
}
// implements http.ResponseWriter
type testResponseWriter struct {
hdr http.Header

View File

@@ -113,10 +113,13 @@ func TestValidateCertificates(t *testing.T) {
// restores them once the test is complete.
//
// The global variables are:
// - [configuration.dns]
// - [configuration]
// - [homeContext.auth]
// - [homeContext.clients.storage]
// - [homeContext.dnsServer]
// - [homeContext.firstRun]
// - [homeContext.mux]
// - [homeContext.web]
//
// TODO(s.chzhen): Remove this once the TLS manager no longer accesses global
// variables. Make tests that use this helper concurrent.
@@ -124,15 +127,21 @@ func storeGlobals(tb testing.TB) {
tb.Helper()
prevConfig := config
auth := globalContext.auth
storage := globalContext.clients.storage
dnsServer := globalContext.dnsServer
firstRun := globalContext.firstRun
mux := globalContext.mux
web := globalContext.web
tb.Cleanup(func() {
config = prevConfig
globalContext.auth = auth
globalContext.clients.storage = storage
globalContext.dnsServer = dnsServer
globalContext.firstRun = firstRun
globalContext.mux = mux
globalContext.web = web
})
}