Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
344c66f7ab | ||
|
|
83be002b41 | ||
|
|
9945cd3991 | ||
|
|
667263a3a8 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -1,7 +1,7 @@
|
||||
'name': 'build'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.22.2'
|
||||
'GO_VERSION': '1.22.3'
|
||||
'NODE_VERSION': '16'
|
||||
|
||||
'on':
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -1,7 +1,7 @@
|
||||
'name': 'lint'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.22.2'
|
||||
'GO_VERSION': '1.22.3'
|
||||
|
||||
'on':
|
||||
'push':
|
||||
|
||||
76
CHANGELOG.md
76
CHANGELOG.md
@@ -14,11 +14,11 @@ and this project adheres to
|
||||
<!--
|
||||
## [v0.108.0] - TBA
|
||||
|
||||
## [v0.107.49] - 2024-04-24 (APPROX.)
|
||||
## [v0.107.50] - 2024-04-24 (APPROX.)
|
||||
|
||||
See also the [v0.107.49 GitHub milestone][ms-v0.107.49].
|
||||
See also the [v0.107.50 GitHub milestone][ms-v0.107.50].
|
||||
|
||||
[ms-v0.107.49]: https://github.com/AdguardTeam/AdGuardHome/milestone/84?closed=1
|
||||
[ms-v0.107.50]: https://github.com/AdguardTeam/AdGuardHome/milestone/85?closed=1
|
||||
|
||||
NOTE: Add new changes BELOW THIS COMMENT.
|
||||
-->
|
||||
@@ -29,13 +29,74 @@ NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
|
||||
|
||||
|
||||
## [v0.107.49] - 2024-05-21
|
||||
|
||||
See also the [v0.107.49 GitHub milestone][ms-v0.107.49].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the Go
|
||||
vulnerabilities fixed in [Go 1.22.3][go-1.22.3].
|
||||
|
||||
### Added
|
||||
|
||||
- Support for comments in the ipset file ([#5345]).
|
||||
|
||||
### Changed
|
||||
|
||||
- Private rDNS resolution now also affects `SOA` and `NS` requests ([#6882]).
|
||||
- Rewrite rules mechanics was changed due to improve resolving in safe search.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Currently, AdGuard Home skips persistent clients that have duplicate fields
|
||||
when reading them from the configuration file. This behaviour is deprecated
|
||||
and will cause errors on startup in a future release.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Acceptance of duplicate UIDs for persistent clients at startup. See also the
|
||||
section on client settings on the [Wiki page][wiki-config].
|
||||
- Domain specifications for top-level domains not considered for requests to
|
||||
unqualified domains ([#6744]).
|
||||
- Support for link-local subnets, i.e. `fe80::/16`, as client identifiers
|
||||
([#6312]).
|
||||
- Issues with QUIC and HTTP/3 upstreams on older Linux kernel versions
|
||||
([#6422]).
|
||||
- YouTube restricted mode is not enforced by HTTPS queries on Firefox.
|
||||
- Support for link-local subnets, i.e. `fe80::/16`, in the access settings
|
||||
([#6192]).
|
||||
- The ability to apply an invalid configuration for private rDNS, which led to
|
||||
server not starting.
|
||||
- Ignoring query log for clients with ClientID set ([#5812]).
|
||||
- Subdomains of `in-addr.arpa` and `ip6.arpa` containing zero-length prefix
|
||||
incorrectly considered invalid when specified for private rDNS upstream
|
||||
servers ([#6854]).
|
||||
- Unspecified IP addresses aren't checked when using "Fastest IP address" mode
|
||||
([#6875]).
|
||||
|
||||
[#5345]: https://github.com/AdguardTeam/AdGuardHome/issues/5345
|
||||
[#5812]: https://github.com/AdguardTeam/AdGuardHome/issues/5812
|
||||
[#6192]: https://github.com/AdguardTeam/AdGuardHome/issues/6192
|
||||
[#6312]: https://github.com/AdguardTeam/AdGuardHome/issues/6312
|
||||
[#6422]: https://github.com/AdguardTeam/AdGuardHome/issues/6422
|
||||
[#6744]: https://github.com/AdguardTeam/AdGuardHome/issues/6744
|
||||
[#6854]: https://github.com/AdguardTeam/AdGuardHome/issues/6854
|
||||
[#6875]: https://github.com/AdguardTeam/AdGuardHome/issues/6875
|
||||
[#6882]: https://github.com/AdguardTeam/AdGuardHome/issues/6882
|
||||
|
||||
[go-1.22.3]: https://groups.google.com/g/golang-announce/c/wkkO4P9stm0
|
||||
[ms-v0.107.49]: https://github.com/AdguardTeam/AdGuardHome/milestone/84?closed=1
|
||||
|
||||
|
||||
|
||||
## [v0.107.48] - 2024-04-05
|
||||
|
||||
See also the [v0.107.48 GitHub milestone][ms-v0.107.48].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Access settings not being applied to encrypted protocols ([#6890])
|
||||
- Access settings not being applied to encrypted protocols ([#6890]).
|
||||
|
||||
[#6890]: https://github.com/AdguardTeam/AdGuardHome/issues/6890
|
||||
|
||||
@@ -2910,11 +2971,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.49...HEAD
|
||||
[v0.107.49]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.48...v0.107.49
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.50...HEAD
|
||||
[v0.107.50]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.49...v0.107.50
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.48...HEAD
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.49...HEAD
|
||||
[v0.107.49]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.48...v0.107.49
|
||||
[v0.107.48]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.47...v0.107.48
|
||||
[v0.107.47]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.46...v0.107.47
|
||||
[v0.107.46]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.45...v0.107.46
|
||||
|
||||
2
Makefile
2
Makefile
@@ -27,7 +27,7 @@ DIST_DIR = dist
|
||||
GOAMD64 = v1
|
||||
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
|
||||
GOSUMDB = sum.golang.google.cn
|
||||
GOTOOLCHAIN = go1.22.2
|
||||
GOTOOLCHAIN = go1.22.3
|
||||
GPG_KEY = devteam@adguard.com
|
||||
GPG_KEY_PASSPHRASE = not-a-real-password
|
||||
NPM = npm
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.2--1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
|
||||
'stages':
|
||||
- 'Build frontend':
|
||||
@@ -249,7 +249,7 @@
|
||||
'recipients':
|
||||
- 'webhook':
|
||||
'name': 'Build webhook'
|
||||
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa'
|
||||
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa-dns-builds'
|
||||
|
||||
'labels': []
|
||||
'other':
|
||||
@@ -266,7 +266,7 @@
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.2--1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
# release-vX.Y.Z branches are the branches from which the actual final
|
||||
# release is built.
|
||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||
@@ -282,4 +282,4 @@
|
||||
'variables':
|
||||
'channel': 'release'
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.2--1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
'recipients':
|
||||
- 'webhook':
|
||||
'name': 'Build webhook'
|
||||
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa'
|
||||
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa-dns-builds'
|
||||
|
||||
'labels': []
|
||||
'other':
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'name': 'AdGuard Home - Build and run tests'
|
||||
'variables':
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.2--1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
'channel': 'development'
|
||||
|
||||
'stages':
|
||||
@@ -195,5 +195,5 @@
|
||||
# may need to build a few of these.
|
||||
'variables':
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.2--1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
'channel': 'candidate'
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
{
|
||||
"client_settings": "إعدادات العميل",
|
||||
"example_upstream_reserved": "يمكنك تحديد <0> DNS upstream لنطاق معين (نطاقات) </0>",
|
||||
"example_upstream_comment": "يمكنك تحديد تعليق",
|
||||
"upstream_parallel": "استخدام الاستعلامات المتوازية لتسريع الحل عن طريق الاستعلام في وقت واحد عن جميع خوادم المنبع",
|
||||
"parallel_requests": "طلبات موازية",
|
||||
"load_balancing": "توزيع الحمل",
|
||||
"example_upstream_reserved": "من المنبع <0>لمجالات محددة</0>;",
|
||||
"example_multiple_upstreams_reserved": "منابع متعددة <0>لمجالات محددة</0>;",
|
||||
"example_upstream_comment": "تعليق.",
|
||||
"upstream_parallel": "استخدم الاستعلامات المتوازية لتسريع عملية الحل عن طريق الاستعلام عن جميع الخوادم المنبع في وقت واحد.",
|
||||
"parallel_requests": "الطلبات الموازية",
|
||||
"load_balancing": "موازنة الأحمال",
|
||||
"load_balancing_desc": "الاستعلام عن خادم واحد في كل مرة سيستخدم AdGuard الرئيسية الخوارزمية العشوائية الموزونة لاختيار الخادم بحيث يتم استخدام أسرع خادم في كثير من الأحيان",
|
||||
"bootstrap_dns": "خوادم Bootstrap DNS",
|
||||
"bootstrap_dns_desc": "عناوين IP لخوادم DNS المستخدمة لحل عناوين IP الخاصة بمحللات DoH/DoT التي تحددها كمصدرين رئيسيين. التعليقات غير مسموح بها.",
|
||||
"fallback_dns_title": "خوادم DNS الاحتياطية",
|
||||
"fallback_dns_desc": "قائمة الخوادم الاحتياطية المستخدمة في حالة عدم الاستجابة من خوادم DNS الرئيسية. تمتلك تلك الخوادم والخوادم الرئيسية نفس الأوامر.",
|
||||
"fallback_dns_placeholder": "أدخل خادم DNS احتياطي واحد لكل سطر",
|
||||
"local_ptr_title": "خوادم DNS العكسية الخاصة",
|
||||
"local_ptr_desc": "خوادم DNS التي يستخدمها AdGuard Home لاستعلامات PTR المحلية. تُستخدم هذه الخوادم لحل أسماء المضيفين للعملاء بعناوين IP خاصة ، على سبيل المثال \"192.168.12.34\" ، باستخدام DNS العكسي. في حالة عدم التعيين ، يستخدم AdGuard Home عناوين محللات DNS الافتراضية لنظام التشغيل الخاص بك باستثناء عناوين AdGuard Home نفسها.",
|
||||
"local_ptr_default_resolver": "بشكل افتراضي ، يستخدم AdGuard Home محللات DNS العكسية التالية: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "لم يتمكن AdGuard Home من تحديد محللات DNS العكسية المناسبة لهذا النظام.",
|
||||
"local_ptr_placeholder": "أدخل عنوان خادم واحد لكل سطر",
|
||||
"local_ptr_placeholder": "أدخل عنوان IP واحد لكل سطر",
|
||||
"resolve_clients_title": "تفعيل التحليل العكسي لعناوين IP للعملاء",
|
||||
"resolve_clients_desc": "حل عكسيًا لعناوين IP للعملاء في أسماء مضيفيهم عن طريق إرسال استعلامات PTR إلى أدوات الحل المقابلة (خوادم DNS الخاصة للعملاء المحليين ، والخوادم الأولية للعملاء الذين لديهم عناوين IP عامة).",
|
||||
"use_private_ptr_resolvers_title": "استخدم محللات DNS العكسية الخاصة",
|
||||
@@ -34,7 +38,7 @@
|
||||
"dhcp_leases_not_found": "لم يتم العثور على عقود إيجار DHCP",
|
||||
"dhcp_config_saved": "الإعدادات محفوظة لخادم DHCP",
|
||||
"dhcp_ipv4_settings": "DHCP IPv4 إعدادات",
|
||||
"dhcp_ipv6_settings": "DHCP IPv6 إعدادات",
|
||||
"dhcp_ipv6_settings": "إعدادات DHCP IPv6",
|
||||
"form_error_required": "الحقل مطلوب",
|
||||
"form_error_ip4_format": "عنوان IPv4 غير صالح",
|
||||
"form_error_ip4_gateway_format": "عنوان IPv4 غير صالح للبوابة",
|
||||
@@ -63,14 +67,16 @@
|
||||
"dhcp_ip_addresses": "عناوين الـIP",
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "اسم المضيف",
|
||||
"dhcp_table_expires": "يتنهي في",
|
||||
"dhcp_table_expires": "تنتهي",
|
||||
"dhcp_warning": "إذا كنت تريد تمكين خادم DHCP على أي حال ، فتأكد من عدم وجود خادم DHCP نشط آخر في شبكتك. خلاف ذلك ، يمكن أن يعطل خدمة الإنترنت للأجهزة المتصلة!",
|
||||
"dhcp_error": "لم نتمكن من تحديد ما إذا كان هناك خادم DHCP آخر في الشبكة.",
|
||||
"dhcp_static_ip_error": "من أجل استخدام خادم DHCP ، يجب تعيين عنوان IP ثابت. فشلنا في تحديد ما إذا تم تكوين واجهة الشبكة هذه باستخدام عنوان IP ثابت. يرجى تعيين عنوان IP ثابت يدويًا.",
|
||||
"dhcp_dynamic_ip_found": "يستخدم نظامك عنوان IP الديناميكي للواجهة <0>{{interfaceName}}</0>. من أجل استعمال خادم DHCP ، يجب تعيين عنوان IP ثابت. عنوان IP الحالي الخاص بك هو <0>{{ipAddress}}</0>. إذا ضغطت على زر تفعيل DHCP سنقوم تلقائيًا بتعيين عنوان الIP هذا على أنه ثابت.",
|
||||
"dhcp_lease_added": "تمت أضافة مدة الايجار \"{{key}}\" بنجاح",
|
||||
"dhcp_lease_deleted": "تمت ازالة مدة الايجار \"{{key}}\" بنجاح",
|
||||
"dhcp_lease_updated": "Static lease \"{{key}}\" تمّ التحديث بنجاح",
|
||||
"dhcp_new_static_lease": "عقد إيجار ثابت جديد",
|
||||
"dhcp_edit_static_lease": "تحرير عقد الإيجار الثابت",
|
||||
"dhcp_static_leases_not_found": "لم يتم العثور على عقود إيجار ثابتة DHCP",
|
||||
"dhcp_add_static_lease": "إضافة عقد إيجار ثابت",
|
||||
"dhcp_reset_leases": "إعادة تعيين كافة عقود الإيجار",
|
||||
@@ -83,7 +89,7 @@
|
||||
"form_enter_hostname": "أدخل اسم الhostname",
|
||||
"error_details": "مزيد من التفاصيل حول الخطأ",
|
||||
"response_details": "تفاصيل الاستجابة",
|
||||
"request_details": "تفاصيل الطلب",
|
||||
"request_details": "طلب التفاصيل",
|
||||
"client_details": "تفاصيل العميل",
|
||||
"details": "التفاصيل",
|
||||
"back": "رجوع",
|
||||
@@ -93,13 +99,13 @@
|
||||
"filter": "فلتر",
|
||||
"query_log": "سجل الQuery",
|
||||
"compact": "المدمج",
|
||||
"nothing_found": "لم يتم العثور علي شيء...",
|
||||
"faq": "أسئلة مكررة",
|
||||
"nothing_found": "لم يتم العثور على شيء",
|
||||
"faq": "الأسئلة المتداولة",
|
||||
"version": "الإصدار",
|
||||
"address": "العناوين",
|
||||
"address": "العنوان",
|
||||
"protocol": "البروتوكول",
|
||||
"on": "ON",
|
||||
"off": "OFF",
|
||||
"on": "قيد التشغيل",
|
||||
"off": "قيد الإيقاف",
|
||||
"copyright": "حقوق النشر",
|
||||
"homepage": "الصفحة الرئيسية",
|
||||
"report_an_issue": "الإبلاغ عن مشكلة",
|
||||
@@ -114,7 +120,8 @@
|
||||
"stats_malware_phishing": "حسر البرامج الضارة / والتصيّد",
|
||||
"stats_adult": "حظر مواقع الويب الخاصة بالبالغين",
|
||||
"stats_query_domain": "اعلى النطاقات التي تم الاستعلام عنها",
|
||||
"for_last_24_hours": "لأخر 24 ساعة",
|
||||
"for_last_hours": "لآخر {{count}} ساعة",
|
||||
"for_last_hours_plural": "لآخر {{count}} ساعة",
|
||||
"for_last_days": "لآخر {{value}} يوم",
|
||||
"for_last_days_plural": "لآخر {{count}} ايام",
|
||||
"stats_disabled": "تم تعطيل الإحصائيات. يمكنك تشغيله من <0> صفحة الإعدادات </0>.",
|
||||
@@ -129,13 +136,16 @@
|
||||
"no_upstreams_data_found": "لم يتم العثور على بيانات خوادم upstream",
|
||||
"number_of_dns_query_days": "عدد استعلامات DNS التي تمت معالجتها لآخر {{count}} يوم",
|
||||
"number_of_dns_query_days_plural": "عدد استعلامات DNS التي تمت معالجتها لآخر {{count}} أيام",
|
||||
"number_of_dns_query_24_hours": "عدد استعلامات DNS التي تمت معالجتها لآخر 24 ساعة",
|
||||
"number_of_dns_query_hours": "عدد استفسارات DNS التي تمت معالجتها لآخر {{count}} ساعة",
|
||||
"number_of_dns_query_hours_plural": "عدد استعلامات DNS التي تمت معالجتها خلال آخر {{count}} ساعة",
|
||||
"number_of_dns_query_blocked_24_hours": "عدد طلبات DNS المحظورة بواسطة فلاتر adblock وقوائم حظر المضيفين",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "عدد طلبات DNS التي تم حظرها من قبل وحدة أمان التصفح AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "عدد من المواقع (الإباحية) للبالغين تم حجبها",
|
||||
"enforced_save_search": "فرض البحث الآمن",
|
||||
"number_of_dns_query_to_safe_search": "عدد طلبات DNS لمحركات البحث التي تم فرض البحث الآمن عنها",
|
||||
"average_processing_time": "متوسط وقت المعالجة",
|
||||
"average_upstream_response_time": "متوسط وقت استجابة المنبع",
|
||||
"response_time": "وقت الاستجابة",
|
||||
"average_processing_time_hint": "متوسط الوقت بالمللي ثانية عند معالجة طلب DNS",
|
||||
"block_domain_use_filters_and_hosts": "حظر النطاقات باستخدام عوامل التصفية وملفات المضيفين",
|
||||
"filters_block_toggle_hint": "يمكنك إعداد قواعد حظر في <a>المرشحات</a> اعدادات.",
|
||||
@@ -170,8 +180,9 @@
|
||||
"enabled_parental_toast": "تفعيل الرقابة الأبوية",
|
||||
"disabled_safe_search_toast": "تعطيل البحث الآمن",
|
||||
"enabled_save_search_toast": "تفعيل البحث الآمن",
|
||||
"enabled_table_header": "تمكين",
|
||||
"name_table_header": "الاسم",
|
||||
"updated_save_search_toast": "تم تحديث إعدادات البحث الآمن",
|
||||
"enabled_table_header": "قيد التشغيل",
|
||||
"name_table_header": "الاِسْم",
|
||||
"list_url_table_header": "قائمة الروابط",
|
||||
"rules_count_table_header": "عدد القواعد",
|
||||
"last_time_updated_table_header": "آخر تحديث",
|
||||
@@ -225,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "تم حفظ خوادم Upstream بنجاح",
|
||||
"dns_test_ok_toast": "تعمل خوادم DNS المحددة بشكل صحيح",
|
||||
"dns_test_not_ok_toast": "خادم \"{{key}}\": لا يمكن استخدامه يرجى التحقق من كتابته بشكل صحيح",
|
||||
"dns_test_parsing_error_toast": "القسم {{section}}: السطر {{line}}: لا يمكن استخدامه، يرجى التحقق من أنك قد كتبته بشكل صحيح",
|
||||
"dns_test_warning_toast": "المنبع \"{{key}}\" لا يستجيب لطلبات الاختبار وقد لا يعمل بشكل صحيح",
|
||||
"unblock": "إلغاء الحظر",
|
||||
"block": "حظر",
|
||||
@@ -232,7 +244,8 @@
|
||||
"allow_this_client": "السماح لهذا العميل",
|
||||
"block_for_this_client_only": "احجب هذا العميل فقط",
|
||||
"unblock_for_this_client_only": "إلغاء حجب هذا العميل فقط",
|
||||
"time_table_header": "وقت",
|
||||
"add_persistent_client": "إضافة كعميل دائم",
|
||||
"time_table_header": "الوقت",
|
||||
"date": "التاريخ",
|
||||
"domain_name_table_header": "اسم النطاق",
|
||||
"domain_or_client": "الدومين أو العميل",
|
||||
@@ -247,7 +260,7 @@
|
||||
"refresh_btn": "تحديث",
|
||||
"previous_btn": "السابق",
|
||||
"next_btn": "التالي",
|
||||
"loading_table_status": "جار التحميل...",
|
||||
"loading_table_status": "التحميل جارٍ...",
|
||||
"page_table_footer_text": "الصفحة",
|
||||
"rows_table_footer_text": "صفوف",
|
||||
"updated_custom_filtering_toast": "تحديث قواعد الفلترة المخصصة",
|
||||
@@ -259,12 +272,12 @@
|
||||
"query_log_cleared": "تم مسح سجل الاستعلام بنجاح",
|
||||
"query_log_updated": "تم تحديث سجل الاستعلام بنجاح",
|
||||
"query_log_clear": "مسح سجلات الاستعلام",
|
||||
"query_log_retention": "الاحتفاظ بسجلات الاستعلام",
|
||||
"query_log_retention": "تناوب سجلات الاستعلام",
|
||||
"query_log_enable": "تمكين السجل",
|
||||
"query_log_configuration": "تكوين السجلات",
|
||||
"query_log_disabled": "سجل الاستعلام معطل ويمكن تهيئته من<0>الاعدادات</0>",
|
||||
"query_log_strict_search": "استخدم علامات الاقتباس المزدوجة للبحث الدقيق",
|
||||
"query_log_retention_confirm": "هل أنت متأكد من أنك تريد تغيير الاحتفاظ بسجل الاستعلام؟ إذا قمت بتقليل قيمة الفاصل الزمني سيتم فقدان بعض البيانات",
|
||||
"query_log_retention_confirm": "هل أنت متيقِّن من أنك تريد تغيير دوران سجل الاستعلام؟ إذا قمت بتقليل قيمة الفاصل الزمني، ستفقد بعض البيانات",
|
||||
"anonymize_client_ip": "إخفاء عنوان IP العميل",
|
||||
"anonymize_client_ip_desc": "لا تقم بحفظ كامل عنوان IP العميل في السجلات والإحصائيات",
|
||||
"dns_config": "إعداد خادم DNS",
|
||||
@@ -279,6 +292,8 @@
|
||||
"blocking_ipv4": "حجب عنوان IPv4",
|
||||
"blocking_ipv6": "حجب عنوان IPv6",
|
||||
"blocked_response_ttl": "زمن حظر الاستجابة",
|
||||
"blocked_response_ttl_desc": "تحديد عدد الثواني التي يجب على العملاء تخزين الاستجابة التي تمت تصفيتها مؤقتًا",
|
||||
"form_enter_blocked_response_ttl": "أدخل وقت الاستجابة المحظورة TTL (بالثواني)",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
@@ -294,7 +309,19 @@
|
||||
"rate_limit": "حدود التقييم",
|
||||
"edns_enable": "فعل EDNS client subnet",
|
||||
"edns_cs_desc": "أضف EDNS الشبكة الفرعية للعميل (ECS) إلى الطلبات الأولية وقم بتسجيل القيم المرسلة من قبل العملاء في سجل الاستعلام.",
|
||||
"edns_use_custom_ip": "استخدام IP مخصص لـ EDNS",
|
||||
"edns_use_custom_ip_desc": "السماح باستخدام IP مخصص لـ EDNS",
|
||||
"rate_limit_desc": "عدد الطلبات في الثانية المسموح بها لكل عميل. جعله على 0 يعني عدم وجود حد.",
|
||||
"rate_limit_subnet_len_ipv4": "طول بادئة الشبكة لعناوين IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "طول بادئة الشبكة لعناوين IPv4 المستخدمة لتحديد معدل الحد الأقصى. الافتراضي هو 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "يجب أن يكون طول بادئة الشبكة IPv4 بين 0 و 32",
|
||||
"rate_limit_subnet_len_ipv6": "طول بادئة الشبكة لعناوين IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "طول بادئة الشبكة لعناوين IPv6 المستخدمة لتحديد معدل الحد الأقصى. الافتراضي هو 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "يجب أن يكون طول بادئة الشبكة IPv6 بين 0 و 128",
|
||||
"form_enter_rate_limit_subnet_len": "أدخل طول بادئة الشبكة الفرعية لتحديد معدل الحد الأقصى",
|
||||
"rate_limit_whitelist": "قائمة السماح بتحديد معدل الحد الأقصى",
|
||||
"rate_limit_whitelist_desc": "عناوين IP المستبعدة من تحديد معدل الحد الأقصى",
|
||||
"rate_limit_whitelist_placeholder": "أدخل عنوان IP واحد لكل سطر",
|
||||
"blocking_ipv4_desc": "سيتم إرجاع عنوان IP لطلب محظور",
|
||||
"blocking_ipv6_desc": "سيتم إرجاع عنوان IP لطلب AAAA محظور",
|
||||
"blocking_mode_default": "الافتراضي: الرد بعنوان IP صفري (0.0.0.0 لـ A ؛ :: لـ AAAA) عند حظره بواسطة قاعدة نمط Adblock ؛ الرد بعنوان IP المحدد في القاعدة عند حظره بواسطة / etc / hosts-style rule",
|
||||
@@ -302,14 +329,15 @@
|
||||
"blocking_mode_nxdomain": "NXDOMAIN: الرد باستخدام رمز NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP Null: الاستجابة بعنوان IP صفري (0.0.0.0 لـ A ؛ :: لـ AAAA)",
|
||||
"blocking_mode_custom_ip": "استجابة IP مخصصة بعنوان IP تم تعيينه يدويًا",
|
||||
"theme_auto": "تلقائي",
|
||||
"theme_light": "فاتح",
|
||||
"theme_dark": "ليلي",
|
||||
"theme_dark": "داكن",
|
||||
"upstream_dns_client_desc": "إذا احتفظت بهذا الحقل فارغًا ، فسيستخدم AdGuard Home الخوادم التي تم تكوينها في<0>DNS إعدادات</0>.",
|
||||
"tracker_source": "مصدر المتعقب",
|
||||
"source_label": "المصدر",
|
||||
"found_in_known_domain_db": "تم العثور عليه في قاعدة بيانات دومينات معروفة.",
|
||||
"category_label": "الفئة",
|
||||
"rule_label": "قواعد",
|
||||
"rule_label": "قاعدة (قواعد)",
|
||||
"list_label": "قائمه",
|
||||
"unknown_filter": "فلتر غير معروف {{filterId}}",
|
||||
"known_tracker": "متعقب معروف",
|
||||
@@ -320,14 +348,14 @@
|
||||
"install_settings_port": "المنفذ",
|
||||
"install_settings_interface_link": "ستكون واجهة الويب الخاصة بمسؤول AdGuard Home متاحة على العناوين التالية:",
|
||||
"form_error_port": "أدخل رقم منفذ صالح",
|
||||
"install_settings_dns": "خادم DNS",
|
||||
"install_settings_dns": "خَادِم DNS",
|
||||
"install_settings_dns_desc": "ستحتاج إلى ضبط أجهزتك أو جهاز التوجيه الخاص بك لاستخدام خادم DNS على العناوين التالية:",
|
||||
"install_settings_all_interfaces": "جميع الواجهات",
|
||||
"install_auth_title": "المصادقة",
|
||||
"install_auth_desc": "يجب إعداد مصادقة كلمة المرور لواجهة ويب مسؤول AdGuard Home. في حال كان AdGuard Home لا يمكن الوصول إليه إلا في شبكتك المحلية ، فلا يزال من المهم حمايته من الوصول غير المقيد.",
|
||||
"install_auth_username": "اسم المستخدم",
|
||||
"install_auth_password": "الكلمة السرية",
|
||||
"install_auth_confirm": "تاكيد كلمه المرور",
|
||||
"install_auth_confirm": "تأكيد كلمة المرور",
|
||||
"install_auth_username_enter": "أدخل اسم المستخدم",
|
||||
"install_auth_password_enter": "أدخل كلمة المرور",
|
||||
"install_step": "خطوة",
|
||||
@@ -397,6 +425,9 @@
|
||||
"encryption_hostnames": "اسم المستضيف",
|
||||
"encryption_reset": "هل أنت متأكد أنك تريد إعادة تعيين إعدادات التشفير؟",
|
||||
"encryption_warning": "تحذير",
|
||||
"encryption_plain_dns_enable": "تمكين DNS العادي",
|
||||
"encryption_plain_dns_desc": "الـDNS العادي مفعل افتراضيًا. يمكنك تعطيله لإجبار جميع الأجهزة على استخدام DNS المشفر. للقيام بذلك، يجب عليك تفعيل بروتوكول DNS المشفر على الأقل",
|
||||
"encryption_plain_dns_error": "لتعطيل DNS العادي، قم بتمكين بروتوكول DNS المشفر على الأقل",
|
||||
"topline_expiring_certificate": "شهادة SSL الخاصة بك على وشك الانتهاء. قم بتحديث <0>إعدادات التشفير</0>.",
|
||||
"topline_expired_certificate": "انتهت صلاحية شهادة SSL الخاصة بك. قم بتحديث <0>إعدادات التشفير</0>.",
|
||||
"form_error_port_range": "أدخل رقم المنفذ في النطاق 80-65535",
|
||||
@@ -436,6 +467,7 @@
|
||||
"form_add_id": "أضافة معّرف",
|
||||
"form_client_name": "ادخل اسم العميل",
|
||||
"name": "اسم",
|
||||
"client_name": "العميل {{id}}",
|
||||
"client_global_settings": "استخدم إعدادات عالمية",
|
||||
"client_deleted": "تم حذف العميل \"{{key}}\" بنجاح",
|
||||
"client_added": "تم اضافة العميل \"{{key}}\" بنجاح",
|
||||
@@ -444,7 +476,7 @@
|
||||
"client_confirm_delete": "هل أنت متأكد من أنك تريد حذف العميل \"{{key}}\"?",
|
||||
"list_confirm_delete": "هل أنت متأكد أنك تريد حذف هذه القائمة؟",
|
||||
"auto_clients_title": "Runtime clients",
|
||||
"auto_clients_desc": "الأجهزة غير المدرجة في قائمة العملاء الدائمين الذين قد لا يزالون يستخدمون AdGuard Home",
|
||||
"auto_clients_desc": "معلومات حول عناوين IP للأجهزة التي تستخدم أو قد تستخدم AdGuard Home. يتم جمع هذه المعلومات من عدة مصادر، بما في ذلك ملفات المضيفين، و DNS العكسي، إلخ.",
|
||||
"access_title": "إعدادات الوصول",
|
||||
"access_desc": "هنا يمكنك ضبط قواعد الوصول لخادم AdGuard Home DNS",
|
||||
"access_allowed_title": "العملاء المسموحين",
|
||||
@@ -457,6 +489,7 @@
|
||||
"updates_checked": "يتوفر إصدار جديد من AdGuard Home",
|
||||
"updates_version_equal": "AdGuard Home محدث",
|
||||
"check_updates_now": "تحقق من وجود تحديثات الآن",
|
||||
"version_request_error": "فشل التحقق من التحديث. يرجى التحقق من اتصالك بالإنترنت.",
|
||||
"dns_privacy": "خصوصية DNS",
|
||||
"setup_dns_privacy_1": "<0> DNS-over-TLS: </0> استخدم سلسلة <1> {{address}} </1>.",
|
||||
"setup_dns_privacy_2": "<0> DNS-over-HTTPS: </0> استخدم سلسلة <1> {{address}} </1>.",
|
||||
@@ -477,7 +510,9 @@
|
||||
"setup_dns_notice": "من أجل استخدام <0> DNS-over-HTTPS </0> أو <1> DNS-over-TLS </1> ، تحتاج إلى <1> تكوين التشفير </1> في إعدادات AdGuard Home.",
|
||||
"rewrite_added": "تمت إضافة إعادة كتابة DNS لـ \"{{key}}\" بنجاح",
|
||||
"rewrite_deleted": "تم حذف إعادة كتابة DNS لـ \"{{key}}\" بنجاح",
|
||||
"rewrite_updated": "تم تحديث إعادة كتابة DNS بنجاح",
|
||||
"rewrite_add": "إضافة إعادة كتابة DNS",
|
||||
"rewrite_edit": "تحرير إعادة كتابة DNS",
|
||||
"rewrite_not_found": "لم يتم العثور على إعادة كتابة DNS",
|
||||
"rewrite_confirm_delete": "هل أنت متأكد من أنك تريد حذف إعادة كتابة DNS لـ \"{{key}}\"؟",
|
||||
"rewrite_desc": "يسمح بتكوين استجابة DNS المخصصة بسهولة لاسم نطاق معين.",
|
||||
@@ -506,7 +541,7 @@
|
||||
"encryption_key_source_content": "الصق محتويات المفتاح الخاص",
|
||||
"stats_params": "ضبط الاحصائيات",
|
||||
"config_successfully_saved": "تم حفظ الاعدادات بنجاح",
|
||||
"interval_6_hour": "ساعات6",
|
||||
"interval_6_hour": "6 ساعات",
|
||||
"interval_24_hour": "24 ساعة",
|
||||
"interval_days": "{{count}} يوم",
|
||||
"interval_days_plural": "{{count}} الأيام",
|
||||
@@ -517,14 +552,18 @@
|
||||
"filter_added_successfully": "تم إضافة القائمة بنجاح",
|
||||
"filter_removed_successfully": "تم ازالته من القائمة بنجاح",
|
||||
"filter_updated": "تم تحديث القائمة بنجاح",
|
||||
"statistics_configuration": "ضبط الاحصائيات",
|
||||
"statistics_configuration": "تكوين الإحصائيات",
|
||||
"statistics_retention": "الاحتفاظ بالإحصاءات",
|
||||
"statistics_retention_desc": "إذا قمت بتقليل قيمة الفاصل الزمني ، فستفقد بعض البيانات",
|
||||
"statistics_clear": "إعادة تعيين الإحصائيات",
|
||||
"statistics_clear_confirm": "هل أنت متأكد من أنك تريد مسح الإحصاءات؟",
|
||||
"statistics_clear_confirm": "هل أنت متيقِّن من أنك تريد مسح الإحصائيات؟",
|
||||
"statistics_retention_confirm": "هل أنت متأكد أنك تريد تغيير الاحتفاظ بالإحصاءات؟ إذا قمت بتقليل قيمة الفاصل الزمني ، فستفقد بعض البيانات",
|
||||
"statistics_cleared": "تم مسح الإحصائيات بنجاح",
|
||||
"statistics_enable": "تفعيل الاحصائيات",
|
||||
"ignore_domains": "المجالات التي تم تجاهلها (مفصولة بسطر جديد)",
|
||||
"ignore_domains_title": "نطاقات تم تجاهلها",
|
||||
"ignore_domains_desc_stats": "لا تتم كتابة الاستعلامات المطابقة لهذه القواعد في الإحصائيات",
|
||||
"ignore_domains_desc_query": "لا تتم كتابة الاستعلامات المطابقة لهذه القواعد في سجل الاستعلامات",
|
||||
"interval_hours": "{{count}} ساعة",
|
||||
"interval_hours_plural": "{{count}} ساعات",
|
||||
"filters_configuration": "اضبط الفلاتر",
|
||||
@@ -597,20 +636,20 @@
|
||||
"dnssec_enable_desc": "قم بتعيين علامة DNSSEC في استعلامات DNS الواردة وتحقق من النتيجة (مطلوب محلل يدعم DNSSEC).",
|
||||
"validated_with_dnssec": "تم التحقق من صحتها باستخدام DNSSEC",
|
||||
"all_queries": "كافة الاستفسارات",
|
||||
"show_blocked_responses": "حظر",
|
||||
"show_whitelisted_responses": "القائمة البيضاء",
|
||||
"show_processed_responses": "معالجة",
|
||||
"show_blocked_responses": "ما تمّ حظره",
|
||||
"show_whitelisted_responses": "المسموح به",
|
||||
"show_processed_responses": "تمت معالجتها",
|
||||
"blocked_safebrowsing": "محظور بواسطة التصفح الآمن",
|
||||
"blocked_adult_websites": "محظور بواسطة الرقابة الأبوية",
|
||||
"blocked_adult_websites": "محظور بواسطة الرِّقابة الأبوية",
|
||||
"blocked_threats": "التهديدات المحظورة",
|
||||
"allowed": "القائمة البيضاء",
|
||||
"allowed": "المسموح به",
|
||||
"filtered": "تمت الفلترة",
|
||||
"rewritten": "أعيدت كتابته",
|
||||
"safe_search": "البحث الأمن",
|
||||
"safe_search": "البحث الآمن",
|
||||
"blocklist": "قائمة الحظر",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "حجم ذاكرة التخزين المؤقت",
|
||||
"cache_size_desc": "حجم ذاكرة التخزين المؤقت لنظام أسماء النطاقات (بالبايت).",
|
||||
"cache_size_desc": "حجم ذاكرة التخزين المؤقت لنظام أسماء النطاق (بالبايت). لتعطيل التخزين المؤقت، اتركه فارغًا.",
|
||||
"cache_ttl_min_override": "تجاوز الحد الأدنى من مدة البقاء TTL",
|
||||
"cache_ttl_max_override": "تجاوز الحد الاقصى من مدة البقاء TTL",
|
||||
"enter_cache_size": "أدخل حجم ذاكرة التخزين المؤقت (بايت)",
|
||||
@@ -621,14 +660,14 @@
|
||||
"ttl_cache_validation": "يجب أن يكون الحد الأدنى لتجاوز TTL لذاكرة التخزين المؤقت أقل من أو يساوي الحد الأقصى",
|
||||
"cache_optimistic": "متفائل التخزين المؤقت",
|
||||
"cache_optimistic_desc": "اجعل AdGuard Home يستجيب من ذاكرة التخزين المؤقت حتى عندما تنتهي صلاحية الإدخالات وحاول أيضًا تحديثها.",
|
||||
"filter_category_general": "General",
|
||||
"filter_category_security": "الامان",
|
||||
"filter_category_general": "عام",
|
||||
"filter_category_security": "الأمان",
|
||||
"filter_category_regional": "إقليمي",
|
||||
"filter_category_other": "أخرى",
|
||||
"filter_category_general_desc": "القوائم التي تمنع التتبع والإعلان على معظم الأجهزة",
|
||||
"filter_category_general_desc": "القوائم التي تحظر التتبع والإعلانات على معظم الأجهزة",
|
||||
"filter_category_security_desc": "القوائم المصممة خصيصًا لحظر النطاقات الخبيثة والتصيد الاحتيالي والخداع",
|
||||
"filter_category_regional_desc": "القوائم التي تركز على الإعلانات الإقليمية وخوادم التتبع",
|
||||
"filter_category_other_desc": "قوائم حظر أخرى",
|
||||
"filter_category_other_desc": "قوائم الحظر الأخرى",
|
||||
"setup_config_to_enable_dhcp_server": "أضبط الاعدادات لتمكين خادم DHCP",
|
||||
"original_response": "الرد الأصلي",
|
||||
"click_to_view_queries": "انقر لعرض الـ queries",
|
||||
@@ -637,16 +676,72 @@
|
||||
"filter_allowlist": "تحذير: سيؤدي هذا الإجراء أيضًا إلى استبعاد القاعدة \"{{disallowed_rule}}\" من قائمة العملاء المسموح لهم.",
|
||||
"last_rule_in_allowlist": "لا يمكن منع هذا العميل لأن استبعاد القاعدة \"{{disallowed_rule}}\" سيؤدي إلى تعطيل قائمة \"العملاء المسموح لهم\".",
|
||||
"use_saved_key": "استخدم المفتاح المحفوظ مسبقًا",
|
||||
"parental_control": "الرقابة الابويه",
|
||||
"parental_control": "الرِّقابة الأبوية",
|
||||
"safe_browsing": "تصفح آمن",
|
||||
"served_from_cache": "{{value}} <i>(يتم تقديمه من ذاكرة التخزين المؤقت)</i>",
|
||||
"form_error_password_length": "يجب أن تتكون كلمة المرور من {{value}} من الأحرف على الأقل",
|
||||
"served_from_cache_label": "يتم تقديمه من ذاكرة التخزين المؤقت",
|
||||
"form_error_password_length": "يجب أن تتكون كلمة المرور من {{min}} إلى {{max}} من الأحرف في الأقل",
|
||||
"anonymizer_notification": "<0>ملاحظة:</0> تم تمكين إخفاء هُوِيَّة IP. يمكنك تعطيله في <1>الإعدادات العامة</1>.",
|
||||
"confirm_dns_cache_clear": "هل تريد بالتأكيد مسح ذاكرة التخزين المؤقت لنظام أسماء النطاقات DNS؟",
|
||||
"cache_cleared": "تم مسح ذاكرة التخزين المؤقت لنظام أسماء النطاق بنجاح",
|
||||
"clear_cache": "مسح ذاكرة التخزين المؤقت",
|
||||
"make_static": "اجعلها ثابتة",
|
||||
"theme_auto_desc": "تلقائي (بناءً على نظام ألوان جهازك)",
|
||||
"theme_dark_desc": "المظهر الداكن",
|
||||
"theme_light_desc": "المظهر فاتح",
|
||||
"disable_for_seconds": "لـ {{count}} ثانية",
|
||||
"disable_for_seconds_plural": "لمدة {{count}} ثانية",
|
||||
"disable_for_minutes": "لمدة {{count}} دقيقة",
|
||||
"disable_for_minutes_plural": "لمدة {{count}} دقيقة",
|
||||
"disable_for_hours": "لمدة {{count}} ساعة",
|
||||
"disable_for_hours_plural": "لمدة {{count}} ساعات",
|
||||
"disable_until_tomorrow": "حتى الغد",
|
||||
"disable_notify_for_seconds": "تعطيل الحماية لـ {{count}} ثانية",
|
||||
"disable_notify_for_seconds_plural": "تعطيل الحماية ل {{count}} ثواني",
|
||||
"disable_notify_for_minutes": "تعطيل الحماية لـ {{count}} دقيقة",
|
||||
"disable_notify_for_minutes_plural": "تعطيل الحماية لـ {{count}} دقائق",
|
||||
"disable_notify_for_hours": "تعطيل الحماية لـ {{count}} ساعة",
|
||||
"disable_notify_for_hours_plural": "تعطيل الحماية لـ {{count}} ساعات",
|
||||
"disable_notify_until_tomorrow": "تعطيل الحماية حتى الغد",
|
||||
"enable_protection_timer": "سيتم تمكين الحماية في {{time}}",
|
||||
"custom_retention_input": "أدخل الاحتفاظ بالساعات",
|
||||
"custom_rotation_input": "أدخل التناوب بالساعات",
|
||||
"protection_section_label": "الحماية",
|
||||
"log_and_stats_section_label": "سجل الاستعلام والإحصائيات",
|
||||
"ignore_query_log": "تجاهل هذا العميل في سجل الاستعلام",
|
||||
"ignore_statistics": "تجاهل هذا العميل في الإحصائيات",
|
||||
"schedule_services": "إيقاف حظر الخدمة مؤقتًا",
|
||||
"schedule_services_desc": "تهيئة جدول إيقاف فلتر خدمة الحظر",
|
||||
"schedule_services_desc_client": "تهيئة جدول إيقاف فلتر خدمة الحظر لهذا العميل",
|
||||
"schedule_desc": "تعيين فترات عدم النشاط للخدمات المحظورة",
|
||||
"schedule_invalid_select": "يجب أن يكون وقت البَدْء قبل وقت الانتهاء",
|
||||
"schedule_select_days": "اختر الأيام",
|
||||
"schedule_timezone": "قم باختيار منطقة زمنية",
|
||||
"schedule_current_timezone": "المنطقة الزمنية الحالية: {{value}}",
|
||||
"schedule_time_all_day": "طوال اليوم",
|
||||
"schedule_modal_description": "سيحل هذا الجدول الزمني محل أي جداول موجودة لنفس اليوم من الأسبوع. يمكن أن يكون لكل يوم من أيام الأسبوع مدّة خمول واحدة فقط.",
|
||||
"schedule_modal_time_off": "لا يوجد حظر للخدمة:",
|
||||
"schedule_new": "جدول زمني جديد",
|
||||
"schedule_edit": "تحرير الجدول الزمني",
|
||||
"schedule_save": "حفظ الجدول الزمني",
|
||||
"schedule_add": "إضافة جدول زمني",
|
||||
"schedule_remove": "إزالة الجدول الزمني",
|
||||
"schedule_from": "من",
|
||||
"schedule_to": "إلى",
|
||||
"sunday": "الأحد",
|
||||
"monday": "الإثنين",
|
||||
"tuesday": "الثلاثاء",
|
||||
"wednesday": "الأربعاء",
|
||||
"thursday": "الخميس",
|
||||
"friday": "الجمعة",
|
||||
"saturday": "السبت",
|
||||
"sunday_short": "الاحد",
|
||||
"monday_short": "الإثنين",
|
||||
"tuesday_short": "الثلاثاء",
|
||||
"wednesday_short": "الاربعاء",
|
||||
"thursday_short": "الخميس",
|
||||
"friday_short": "الجمعة",
|
||||
"saturday_short": "السبت"
|
||||
"saturday_short": "السبت",
|
||||
"upstream_dns_cache_configuration": "تهيئة ذاكرة التخزين المؤقت لنظام أسماء النطاقات المستقبلي",
|
||||
"enable_upstream_dns_cache": "تمكين التخزين المؤقت لنظام أسماء النطاقات DNS لتكوين المنبع المخصص لهذا العميل",
|
||||
"dns_cache_size": "حجم ذاكرة التخزين المؤقت لنظام أسماء النطاقات، بالبايت"
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Seznam záložních DNS serverů používaných v případě, že odchozí DNS servery neodpovídají. Syntaxe je stejná jako v hlavním poli pro odchozí servery výše.",
|
||||
"fallback_dns_placeholder": "Zadejte jeden záložní DNS server na řádek",
|
||||
"local_ptr_title": "Soukromé reverzní DNS servery",
|
||||
"local_ptr_desc": "Servery DNS, které AdGuard Home používá pro lokální dotazy PTR. Tyto servery se používají k řešení požadavků PTR na adresy v soukromých rozmezích IP, například \"192.168.12.34\", pomocí reverzního DNS. Pokud není nastaveno, AdGuard Home automaticky použije výchozí řešitele vašeho OS s výjimkou adres samotného AdGuard Home.",
|
||||
"local_ptr_desc": "DNS servery používané AdGuard Home pro soukromé požadavky PTR, SOA a NS. Požadavek je považován za soukromý, pokud požaduje doménu ARPA obsahující podsíť v rámci soukromých IP rozsahů (například \"192.168.12.34\") a pochází od klienta se soukromou IP adresou. Pokud není nastaveno, budou použity výchozí DNS řešitele vašeho operačního systému, s výjimkou IP adres AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Ve výchozím nastavení používá AdGuard Home následující reverzní DNS řešitele: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home nemohl určit vhodné soukromé reverzní DNS řešitele pro tento systém.",
|
||||
"local_ptr_placeholder": "Zadejte jednu IP adresu na řádek",
|
||||
"resolve_clients_title": "Povolit zpětné řešení IP adres klientů",
|
||||
"resolve_clients_desc": "Obráceně vyřešit IP adresy klientů na jejich názvy hostitelů zasláním dotazů PTR příslušným řešitelům (soukromé DNS servery pro místní klienty, odchozí servery pro klienty s veřejnou IP adresou).",
|
||||
"use_private_ptr_resolvers_title": "Použít soukromé reverzní rDNS řešitele",
|
||||
"use_private_ptr_resolvers_desc": "Realizuje reverzní DNS vyhledávání pro lokální adresy pomocí těchto odchozích serverů. Pokud je funkce vypnuta, Adguard Home reaguje s NXDOMAIN na všechny takové PTR dotazy kromě klientů známých z DHCP, /etc/hosts, atd.",
|
||||
"use_private_ptr_resolvers_desc": "Řešení požadavků PTR, SOA a NS pro domény ARPA obsahující soukromé IP adresy prostřednictvím soukromých odchozích serverů, DHCP, /etc/hosts atd. Pokud je zakázáno, AdGuard Home bude na všechny takové požadavky odpovídat pomocí NXDOMAIN.",
|
||||
"check_dhcp_servers": "Zkontrolovat DHCP servery",
|
||||
"save_config": "Uložit konfiguraci",
|
||||
"enabled_dhcp": "DHCP server zapnutý",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Liste over reserve (fallback) DNS-servere, som bruges, når upstream DNS-servere ikke reagerer. Samme syntaks som i upstream-hovedfeltet ovenfor.",
|
||||
"fallback_dns_placeholder": "Angiv én reserve DNS-server pr. linje",
|
||||
"local_ptr_title": "Private reverse DNS-servere",
|
||||
"local_ptr_desc": "DNS-servere brugt af AdGuard Home til lokale PTR-forespørgsler. Disse servere bruges til at opløse PTR-forespørgsler fra private IP-adresseområder, f.eks. \"192.168.12.34\", vha. reverse DNS. Hvis ikke opsat, bruger AdGuard Home operativsystems standard DNS-opløsere undtagen for sine egne adresser.",
|
||||
"local_ptr_desc": "DNS-serverne brugt af AdGuard Home til private PTR-, SOA- og NS-forespørgsler. En forespørgsel anses som privat, hvis den omhandler et ARPA-domæne indeholdende et undernet i et privat IP-områder, (såsom \"192.168.12.34\") og kommer fra en klient med en privat adresse. Hvis ikke opsat, bruger AdGuard Home OS'ets adresser på standard DNS-opløserne, bortset fra AdGuard Home-adresserne.",
|
||||
"local_ptr_default_resolver": "AdGuard Home bruger som standard flg. reverse DNS-opløsere: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home kunne ikke fastslå egnede private reverse DNS-opløsere for dette system.",
|
||||
"local_ptr_placeholder": "Angiv én IP-adresse pr. linje",
|
||||
"resolve_clients_title": "Aktivér omvendt løsning af klienters IP-adresser",
|
||||
"resolve_clients_desc": "Opløs klienters IP-adresser reverseret til deres værtsnavne ved at sende PTR-forespørgsler til korresponderende opløsere (private DNS-servere til lokale klienter, upstream-servere til klienter med offentlige IP-adresser).",
|
||||
"use_private_ptr_resolvers_title": "Brug private reverse DNS-opløsere",
|
||||
"use_private_ptr_resolvers_desc": "Udfør reverse DNS-opslag for lokalt leverede adresser vha. disse upstream-servere. Hvis deaktiveret, svarer AdGuard Home med NXDOMAIN på alle sådanne PTR-forespørgsler bortset fra for klienter kendt via DHCP, /etc/hosts mv.",
|
||||
"use_private_ptr_resolvers_desc": "Opløs PTR-, SOA- og NS-forespørgsler til ARPA-domæner indeholdende private adresser vha. private upstream-servere, DHCP, /etc/hosts mv. Hvis deaktiveret, besvarer AdGuard Home sådanne forespørgsler med NXDOMAIN.",
|
||||
"check_dhcp_servers": "Søg efter DHCP-servere",
|
||||
"save_config": "Gem opsætning",
|
||||
"enabled_dhcp": "DHCP-server aktiveret",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Liste der Fallback-DNS-Server, die verwendet werden, wenn die Upstream-DNS-Server nicht antworten. Die Syntax ist die gleiche wie im Hauptfeld für Upstream-Server oben.",
|
||||
"fallback_dns_placeholder": "Geben Sie einen Fallback-DNS-Server pro Zeile ein",
|
||||
"local_ptr_title": "Private inverse DNS-Server",
|
||||
"local_ptr_desc": "Die DNS-Server, die AdGuard Home für lokale PTR-Abfragen verwendet. Diese Server werden verwendet, um die Hostnamen von Clients mit privaten IP-Adressen, z. B. „192.168.12.34“, per inverse DNS-Anfragen aufzulösen. Wenn nicht festgelegt, verwendet AdGuard Home die Adressen der Standard-DNS-Auflöser Ihres Betriebssystems mit Ausnahme der Adressen von AdGuard Home selbst.",
|
||||
"local_ptr_desc": "Von AdGuard Home verwendete DNS-Server für private PTR-, SOA- und NS-Anfragen. Eine Anfrage gilt als privat, wenn sie nach einer ARPA-Domain fragt, die ein Subnetz innerhalb privater IP-Bereiche enthält (z. B. „192.168.12.34“) und von einem Client mit privater IP-Adresse stammt. Wenn nicht eingestellt, werden die Standard-DNS-Auflöser Ihres Betriebssystems verwendet, außer für die IP-Adressen von AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Standardmäßig verwendet AdGuard Home die folgenden inversen DNS-Resolver: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home konnte keine geeigneten privaten Invers-DNS-Resolver für dieses System ermitteln.",
|
||||
"local_ptr_placeholder": "Geben Sie eine IP-Adresse pro Zeile ein",
|
||||
"resolve_clients_title": "Hostnamenauflösung der Clients aktivieren",
|
||||
"resolve_clients_desc": "Inverses Auflösen der IP-Adressen der Clients in ihre Hostnamen durch Senden von PTR-Anfragen an die entsprechenden Resolver (private DNS-Server für lokale Kunden, Upstream-Server für Kunden mit öffentlichen IP-Adressen).",
|
||||
"use_private_ptr_resolvers_title": "Private Reverse-DNS-Resolver verwenden",
|
||||
"use_private_ptr_resolvers_desc": "Führt inverse DNS-Abfragen für lokal bereitgestellte Adressen mit diesen Upstream-Servern durch. Wenn deaktiviert, antwortet AdGuard Home mit NXDOMAIN auf alle solchen PTR-Anfragen, außer für Clients, die über DHCP, /etc/hosts usw. bekannt sind.",
|
||||
"use_private_ptr_resolvers_desc": "Löst PTR-, SOA- und NS-Anfragen für ARD-Domains mit privaten IP-Adressen über private Upstream-Server, DHCP, /etc/hosts usw. auf. Ist diese Option deaktiviert, antwortet AdGuard Home auf alle derartigen Anfragen mit NXDOMAIN.",
|
||||
"check_dhcp_servers": "Auf DHCP-Server prüfen",
|
||||
"save_config": "Konfiguration speichern",
|
||||
"enabled_dhcp": "DHCP-Server aktiviert",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "List of fallback DNS servers used when upstream DNS servers are not responding. The syntax is the same as in the main upstreams field above.",
|
||||
"fallback_dns_placeholder": "Enter one fallback DNS server per line",
|
||||
"local_ptr_title": "Private reverse DNS servers",
|
||||
"local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve PTR requests for addresses in private IP ranges, for example \"192.168.12.34\", using reverse DNS. If not set, AdGuard Home uses the addresses of the default DNS resolvers of your OS except for the addresses of AdGuard Home itself.",
|
||||
"local_ptr_desc": "DNS servers used by AdGuard Home for private PTR, SOA, and NS requests. A request is considered private if it asks for an ARPA domain containing a subnet within private IP ranges (such as \"192.168.12.34\") and comes from a client with a private IP address. If not set, the default DNS resolvers of your OS will be used, except for the AdGuard Home IP addresses.",
|
||||
"local_ptr_default_resolver": "By default, AdGuard Home uses the following reverse DNS resolvers: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home could not determine suitable private reverse DNS resolvers for this system.",
|
||||
"local_ptr_placeholder": "Enter one IP address per line",
|
||||
"resolve_clients_title": "Enable reverse resolving of clients' IP addresses",
|
||||
"resolve_clients_desc": "Reversely resolve clients' IP addresses into their hostnames by sending PTR queries to corresponding resolvers (private DNS servers for local clients, upstream servers for clients with public IP addresses).",
|
||||
"use_private_ptr_resolvers_title": "Use private reverse DNS resolvers",
|
||||
"use_private_ptr_resolvers_desc": "Perform reverse DNS lookups for locally served addresses using these upstream servers. If disabled, AdGuard Home responds with NXDOMAIN to all such PTR requests except for clients known from DHCP, /etc/hosts, and so on.",
|
||||
"use_private_ptr_resolvers_desc": "Resolve PTR, SOA, and NS requests for ARPA domains containing private IP addresses through private upstream servers, DHCP, /etc/hosts, etc. If disabled, AdGuard Home will respond to all such requests with NXDOMAIN.",
|
||||
"check_dhcp_servers": "Check for DHCP servers",
|
||||
"save_config": "Save configuration",
|
||||
"enabled_dhcp": "DHCP server enabled",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lista de servidores DNS alternativos utilizados cuando los servidores DNS de subida no responden. La sintaxis es la misma que en el campo de los principales DNS de subida anterior.",
|
||||
"fallback_dns_placeholder": "Ingresa un servidor DNS alternativo por línea",
|
||||
"local_ptr_title": "Servidores DNS inversos y privados",
|
||||
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver las peticiones PTR de direcciones en rangos de IP privadas, por ejemplo \"192.168.12.34\", utilizando DNS inverso. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
|
||||
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para consultas PTR, SOA y NS privadas. La petición se considera privada si solicita un dominio ARPA que contiene una subred dentro de rangos IP privados, por ejemplo \"192.168.12.34\", y procede de un cliente con dirección privada. Si no se configura, AdGuard Home utiliza las direcciones de los resolvedores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Por defecto, AdGuard Home utiliza los siguientes resolutores DNS inversos: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home no pudo determinar los resolutores DNS inversos y privados adecuados para este sistema.",
|
||||
"local_ptr_placeholder": "Ingresa una dirección IP por línea",
|
||||
"resolve_clients_title": "Habilitar la resolución inversa de las direcciones IP de clientes",
|
||||
"resolve_clients_desc": "Resolve de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, servidores DNS de subida para clientes con direcciones IP públicas).",
|
||||
"use_private_ptr_resolvers_title": "Usar resolutores DNS inversos y privados",
|
||||
"use_private_ptr_resolvers_desc": "Realiza búsquedas DNS inversas para direcciones servidas localmente utilizando estos servidores DNS de subida. Si está deshabilitado, AdGuard Home responderá con NXDOMAIN a todas las peticiones PTR de este tipo, excepto para los clientes conocidos por DHCP, /etc/hosts, etc.",
|
||||
"use_private_ptr_resolvers_desc": "Resolver las peticiones PTR, SOA y NS para dominios ARPA que contienen direcciones privadas utilizando servidores upstream privados, DHCP, /etc/hosts, etc. Si se desactiva, AdGuard Home responde a todas estas consultas con NXDOMAIN.",
|
||||
"check_dhcp_servers": "Comprobar si hay servidores DHCP",
|
||||
"save_config": "Guardar configuración",
|
||||
"enabled_dhcp": "Servidor DHCP habilitado",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Liste des serveurs DNS de repli utilisés lorsque les serveurs DNS en amont ne répondent pas. La syntaxe est la même que dans le champ principal en amont ci-dessus.",
|
||||
"fallback_dns_placeholder": "Saisissez un serveur DNS de repli par ligne",
|
||||
"local_ptr_title": "Serveurs DNS privés inverses",
|
||||
"local_ptr_desc": "Les serveurs DNS que AdGuard Home utilise pour les requêtes PTR locales. Ces serveurs sont utilisés pour résoudre les noms d'hôte des clients avec des adresses IP privées, par exemple « 192.168.12.34 », en utilisant le DNS inversé. Si ce paramètre n'est pas défini, AdGuard Home utilise les adresses des résolveurs DNS par défaut de votre système d'exploitation, à l'exception des adresses d'AdGuard Home lui-même.",
|
||||
"local_ptr_desc": "Les serveurs DNS utilisés par AdGuard Home pour les requêtes privées PTR, SOA et NS. Une requête est considérée privée si elle demande un domaine ARPA contenant un sous-réseau entre les plages IP privées (par exemple \"192.168.12.34\") et provient d'un client avec une adresse IP privée. Sans réglages additionnels, les résolveurs DNS par défaut de votre système d'exploitation seront utilisés, sauf pour les adresses IP d'AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Par défaut, AdGuard Home utilise les résolveurs DNS inversés suivants : {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home n'a pas pu déterminer de résolveurs DNS inversés privés appropriés pour ce système.",
|
||||
"local_ptr_placeholder": "Saisissez une adresse IP par ligne",
|
||||
"resolve_clients_title": "Activer la résolution inverse des adresses IP des clients",
|
||||
"resolve_clients_desc": "Résoudre inversement les adresses IP des clients en leurs noms d'hôtes en envoyant des requêtes PTR aux résolveurs correspondants (serveurs DNS privés pour les clients locaux, serveurs en amont pour les clients ayant une adresse IP publique).",
|
||||
"use_private_ptr_resolvers_title": "Utiliser des résolveurs DNS inversés privés",
|
||||
"use_private_ptr_resolvers_desc": "Effectuer des recherches DNS inversées pour les adresses servies localement en utilisant ces serveurs en amont. S'il est désactivé, AdGuard Home répond avec NXDOMAIN à toutes les requêtes PTR de ce type, sauf pour les clients connus par DHCP, /etc/hosts, etc.",
|
||||
"use_private_ptr_resolvers_desc": "Résolvez les requêtes PTR, SOA et NS pour les domaines ARPA contenant des adresses IP privées par aide des serveurs privés en amont, DHCP, /etc/hosts, etc. S'il est désactivé, AdGuard Home répondra à toutes ces requêtes avec NXDOMAIN.",
|
||||
"check_dhcp_servers": "Rechercher les serveurs DHCP",
|
||||
"save_config": "Sauvegarder la configuration",
|
||||
"enabled_dhcp": "Serveur DHCP activé",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Elenco dei server DNS fallback utilizzati quando i server DNS upstream non rispondono. La sintassi è la stessa del campo principale upstream sopra.",
|
||||
"fallback_dns_placeholder": "Inserisci un server DNS fallback per riga",
|
||||
"local_ptr_title": "Server DNS privati inversi",
|
||||
"local_ptr_desc": "I server DNS che AdGuard Home utilizza per le richieste PTR locali. Questi server vengono utilizzati per risolvere i nomi host dei client con indirizzi IP privati, ad esempio \"192.168.12.34\", utilizzando il DNS inverso. Se non è impostato, AdGuard Home utilizzerà gli indirizzi dei resolutori DNS predefiniti del tuo sistema operativo ad eccezione degli indirizzi di AdGuard Home stesso.",
|
||||
"local_ptr_desc": "I server DNS usati da AdGuard Home per richieste private PTR, SOA e NS. Una richiesta è considerata privata se richiede un dominio ARPA contenente una sottorete all'interno di intervalli IP privati (come \"192.168.12.34\") e proviene da un client con un indirizzo IP privato. Se non impostato, saranno usati i risolutori DNS predefiniti del tuo sistema operativo, ad eccezione degli indirizzi IP di AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Per impostazione predefinita, AdGuard Home utilizzerà i seguenti risolutori DNS inversi: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home non è stato in grado di determinare i risolutori DNS inversi privati adatti per questo sistema.",
|
||||
"local_ptr_placeholder": "Inserisci un indirizzo IP per riga",
|
||||
"resolve_clients_title": "Attiva la risoluzione inversa degli indirizzi IP dei client",
|
||||
"resolve_clients_desc": "Risolve inversamente gli indirizzi IP dei client nei loro nomi host inviando richieste PTR ai risolutori corrispondenti (server DNS privati per client locali, server upstream per client con indirizzi IP pubblici).",
|
||||
"use_private_ptr_resolvers_title": "Utilizza dei resolver rDNS privati",
|
||||
"use_private_ptr_resolvers_desc": "Esegue ricerche DNS inverse per indirizzi locali utilizzando questi server upstream. Se disattivata, AdGuard Home risponderà con NXDOMAIN a tutte le richieste PTR ad eccezione dei client noti da DHCP, /etc/hosts, e così via.",
|
||||
"use_private_ptr_resolvers_desc": "Risolvi le richieste PTR, SOA e NS per domini ARPA contenenti indirizzi IP privati tramite server upstream privati, DHCP, /etc/hosts, ecc. Se disabilitato, AdGuard Home risponderà a tutte queste richieste con NXDOMAIN.",
|
||||
"check_dhcp_servers": "Controlla la presenza di server DHCP",
|
||||
"save_config": "Salva configurazione",
|
||||
"enabled_dhcp": "Server DHCP attivo",
|
||||
@@ -341,7 +341,7 @@
|
||||
"list_label": "Elenco",
|
||||
"unknown_filter": "Filtro sconosciuto {{filterId}}",
|
||||
"known_tracker": "Tracciatore noto",
|
||||
"install_welcome_title": "Benvenuto nella Home di AdGuard!",
|
||||
"install_welcome_title": "Benvenuto in AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home è un server DNS che blocca annunci e tracciatori a livello di rete. Il suo scopo è quello di permetterti il controllo dell'intera rete e di tutti i dispositivi, e non richiede l'utilizzo di un programma lato client.",
|
||||
"install_settings_title": "Interfaccia Web dell'Admin",
|
||||
"install_settings_listen": "Interfaccia d'ascolto",
|
||||
@@ -446,7 +446,7 @@
|
||||
"update_now": "Aggiorna ora",
|
||||
"update_failed": "Aggiornamento automatico non riuscito. Ti suggeriamo di <a>seguire questi passaggi</a> per aggiornare manualmente.",
|
||||
"manual_update": "Ti invitiamo a <a>seguire questi passaggi</a> per aggiornare manualmente.",
|
||||
"processing_update": "Perfavore aspetta, AdGuard Home si sta aggiornando",
|
||||
"processing_update": "Attendi per favore, AdGuard Home si sta aggiornando",
|
||||
"clients_title": "Client persistenti",
|
||||
"clients_desc": "Configura le registrazioni dei client persistenti per i dispositivi connessi ad AdGuard Home",
|
||||
"settings_global": "Globale",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "업스트림 DNS 서버가 응답하지 않을 때 사용되는 폴백 DNS 서버 목록입니다. 구문은 위의 기본 업스트림 필드와 동일합니다.",
|
||||
"fallback_dns_placeholder": "한 줄에 하나의 폴백 DNS 서버를 입력하세요.",
|
||||
"local_ptr_title": "프라이빗 역방향 DNS 서버",
|
||||
"local_ptr_desc": "AdGuard Home이 로컬 PTR 쿼리에 사용하는 DNS 서버입니다. 이러한 서버는 역방향 DNS를 사용하여 개인 IP 주소(예: '192.168.12.34')가 있는 클라이언트의 호스트 이름을 확인하는 데 사용됩니다. 설정되지 않은 경우, AdGuard Home은 AdGuard Home의 주소를 제외하고 운영 체제의 기본 DNS 리졸버의 주소를 사용합니다.",
|
||||
"local_ptr_desc": "AdGuard Home에서 비공개 PTR, SOA 및 NS 요청에 사용하는 DNS 서버입니다. 요청이 비공개 IP 범위 내의 서브넷(예: \"192.168.12.34\")을 포함하는 ARPA 도메인을 요청하고 비공개 IP 주소를 가진 클라이언트로부터 오는 경우 비공개로 간주됩니다. 설정하지 않으면 AdGuard Home IP 주소를 제외한 OS의 기본 DNS 리졸버가 사용됩니다.",
|
||||
"local_ptr_default_resolver": "기본적으로 AdGuard Home에서는 {{ip}} 역방향 DNS 서버를 이용합니다.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home에서 이 시스템에 적합한 사설 역방향 프라이빗 DNS 서버를 결정할 수 없습니다.",
|
||||
"local_ptr_placeholder": "한 줄에 하나씩 IP 주소를 입력하세요.",
|
||||
"resolve_clients_title": "클라이언트 IP 주소에 대한 호스트명 확인 활성화",
|
||||
"resolve_clients_desc": "해당 서버에 대한 PTR 쿼리를 통해 클라이언트의 도메인 이름을 정의합니다. (로컬 클라이언트의 경우 프라이빗 DNS 서버, 공용 IP 주소가 있는 클라이언트의 경우 업스트림 서버).",
|
||||
"use_private_ptr_resolvers_title": "프라이빗 역방향 DNS 리졸버 사용",
|
||||
"use_private_ptr_resolvers_desc": "업스트림 서버를 사용해 로컬로 제공되는 주소의 역방향 DNS를 조회합니다. 끄는 경우, AdGuard Home은 DHCP, /etc/hosts 등에서 알려진 클라이언트를 제외한 모든 PTR 요청에 NXDOMAIN으로 응답합니다.",
|
||||
"use_private_ptr_resolvers_desc": "사설 업스트림 서버, DHCP, / etc/hosts 등을 통해 사설 IP 주소가 포함된 ARPA 도메인에 대한 PTR, SOA 및 NS 요청을 처리합니다. 비활성화하면 AdGuard Home은 NXDOMAIN을 사용하여 이러한 모든 요청에 응답합니다.",
|
||||
"check_dhcp_servers": "DHCP 서버 체크",
|
||||
"save_config": "구성 저장",
|
||||
"enabled_dhcp": "DHCP 서버 활성화됨",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lijst met DNS-back-up-noodservers die worden gebruikt wanneer upstream DNS-servers niet reageren. De syntaxis is hetzelfde als in het veld hoofdstroomopwaarts hierboven.",
|
||||
"fallback_dns_placeholder": "Voer één DNS-back-upserver per regel in",
|
||||
"local_ptr_title": "Private omgekeerde DNS-servers",
|
||||
"local_ptr_desc": "De DNS-servers die AdGuard Home gebruikt voor lokale PTR-zoekopdrachten. Deze servers worden gebruikt om PTR-verzoeken voor adressen in privé-IP-bereiken op te lossen, bijvoorbeeld \"192.168.12.34\", met behulp van reverse DNS. Indien niet ingesteld, gebruikt AdGuard Home de adressen van de standaard DNS-resolvers van uw besturingssysteem, behalve de adressen van AdGuard Home zelf.",
|
||||
"local_ptr_desc": "DNS-servers die door AdGuard Home worden gebruikt voor privé PTR-, SOA- en NS-verzoeken. Een verzoek wordt als privé beschouwd als het vraagt om een ARPA-domein dat een subnet binnen privé-IP-bereiken bevat (zoals \"192.168.12.34\") en afkomstig is van een client met een privé-IP-adres. Indien niet ingesteld, zullen de standaard DNS-resolvers van je besturingssysteem worden gebruikt, behalve de AdGuard Home IP-adressen.",
|
||||
"local_ptr_default_resolver": "Standaard gebruikt AdGuard Home de volgende omgekeerde DNS-resolvers: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home kon voor dit systeem geen geschikte private omgekeerde DNS-resolvers bepalen.",
|
||||
"local_ptr_placeholder": "Voer één IP-adres per regel in",
|
||||
"resolve_clients_title": "Omzetten van hostnamen van clients inschakelen",
|
||||
"resolve_clients_desc": "Indien ingeschakeld, zal AdGuard Home proberen om IP-adressen van apparaten te converteren in hun hostnamen door PTR-verzoeken te sturen naar overeenkomstige resolvers (privé-DNS-servers voor lokale apparaten, upstream-server voor apparaten met een openbaar IP-adres).",
|
||||
"use_private_ptr_resolvers_title": "Private omgekeerde DNS-resolvers gebruiken",
|
||||
"use_private_ptr_resolvers_desc": "Omgekeerde DNS opzoekingen uitvoeren voor locale adressen door deze upstream servers te gebruiken. Indien uitgeschakeld, reageert AdGuard Home met NXDOMAIN op al dergelijke PTR-verzoeken, uitgezonderd voor apparaten gekend van DHCP, /etc/hosts, enz.",
|
||||
"use_private_ptr_resolvers_desc": "PTR-, SOA- en NS-verzoeken voor ARPA-domeinen die privé-IP-adressen bevatten oplossen via privé-upstreamservers, DHCP, /etc/hosts, enz. Indien uitgeschakeld, zal AdGuard Home op al dergelijke verzoeken reageren met NXDOMAIN.",
|
||||
"check_dhcp_servers": "Zoek achter DHCP servers",
|
||||
"save_config": "Configuratie opslaan",
|
||||
"enabled_dhcp": "DHCP server inschakelen",
|
||||
@@ -254,8 +254,8 @@
|
||||
"response_code": "Reactiecode",
|
||||
"client_table_header": "Gebruiker",
|
||||
"empty_response_status": "Leeg",
|
||||
"show_all_filter_type": "Toon alles",
|
||||
"show_filtered_type": "Toon gefilterde",
|
||||
"show_all_filter_type": "Alles weergeven",
|
||||
"show_filtered_type": "Gefilterde weergeven",
|
||||
"no_logs_found": "Geen logboeken gevonden",
|
||||
"refresh_btn": "Verversen",
|
||||
"previous_btn": "Vorige",
|
||||
@@ -532,7 +532,7 @@
|
||||
"blocked_services_global": "Gebruik algemeen geblokkeerde services",
|
||||
"blocked_service": "Geblokkeerde service",
|
||||
"block_all": "Blokkeer alles",
|
||||
"unblock_all": "Deblokkeer alles",
|
||||
"unblock_all": "Alles deblokkeren",
|
||||
"encryption_certificate_path": "Certificaat pad",
|
||||
"encryption_private_key_path": "Privé sleutel pad",
|
||||
"encryption_certificates_source_path": "Certificaten bestandspad instellen",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lista rezerwowych serwerów DNS używanych, gdy nadrzędne serwery DNS nie odpowiadają. Składnia jest taka sama jak w głównym polu powyżej.",
|
||||
"fallback_dns_placeholder": "Wprowadź jeden rezerwowy serwer DNS w każdym wierszu",
|
||||
"local_ptr_title": "Prywatne odwrotne serwery DNS",
|
||||
"local_ptr_desc": "Serwery DNS, których AdGuard Home używa do lokalnych zapytań PTR. Serwery te są używane do rozpoznawania nazw hostów klientów z prywatnymi adresami IP, na przykład „192.168.12.34”, przy użyciu odwrotnego DNS. Jeśli nie jest ustawiona, AdGuard Home używa adresów domyślnych resolwerów DNS systemu operacyjnego, z wyjątkiem adresów samego AdGuard Home.",
|
||||
"local_ptr_desc": "Serwery DNS używane przez AdGuard Home do prywatnych żądań PTR, SOA i NS. Żądanie jest uważane za prywatne, jeśli prosi o domenę ARPA zawierającą podsieć w prywatnym zakresie adresów IP (np. „192.168.12.34”) i pochodzi od klienta z prywatnym adresem IP. Jeśli nie zostanie ustawione, zostaną użyte domyślne programy rozpoznawania nazw DNS Twojego systemu operacyjnego, z wyjątkiem domowych adresów IP AdGuard.",
|
||||
"local_ptr_default_resolver": "Domyślnie AdGuard Home używa następujących odwrotnych resolwerów DNS: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home nie mógł określić odpowiednich prywatnych resolwerów DNS dla tego systemu.",
|
||||
"local_ptr_placeholder": "Wprowadź po jednym adresie IP w każdym wierszu",
|
||||
"resolve_clients_title": "Włącz odwrotne rozpoznawanie adresów IP klientów",
|
||||
"resolve_clients_desc": "Odwróć adresy IP klientów na ich nazwy hostów, wysyłając zapytania PTR do odpowiednich programów tłumaczących (prywatne serwery DNS dla klientów lokalnych, serwery nadrzędne dla klientów z publicznymi adresami IP).",
|
||||
"use_private_ptr_resolvers_title": "Użyj prywatnych odwrotnych resolwerów DNS",
|
||||
"use_private_ptr_resolvers_desc": "Wykonuj odwrotne wyszukiwania DNS dla adresów obsługiwanych lokalnie przy użyciu tych serwerów nadrzędnych. Po wyłączeniu AdGuard Home odpowiada za pomocą NXDOMAIN na wszystkie takie żądania PTR, z wyjątkiem klientów znanych z DHCP, /etc/hosts i tak dalej.",
|
||||
"use_private_ptr_resolvers_desc": "Rozwiązuj żądania PTR, SOA i NS dla domen ARPA zawierających prywatne adresy IP za pośrednictwem prywatnych serwerów nadrzędnych, DHCP, /etc/hosts itp. Jeśli ta opcja jest wyłączona, AdGuard Home będzie odpowiadać na wszystkie takie żądania za pomocą NXDOMAIN.",
|
||||
"check_dhcp_servers": "Sprawdź serwery DHCP",
|
||||
"save_config": "Zapisz konfigurację",
|
||||
"enabled_dhcp": "Serwer DHCP włączony",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lista de servidores DNS Fallback usados quando os servidores DNS primários não estão respondendo. A sintaxe é a mesma dos campos de servidores principais na seção acima.",
|
||||
"fallback_dns_placeholder": "Insira um servidor DNS fallback por linha",
|
||||
"local_ptr_title": "Servidores DNS reversos privados",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver solicitações de PTR para endereços em intervalos de IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não estiver definido, o AdGuard Home usa os endereços dos resolvedores de DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home utiliza para consultas privadas de PTR, SOA e NS. A solicitação é considerada privada se solicitar um domínio ARPA contendo uma sub-rede dentro de intervalos de IP privados, por exemplo \"192.168.12.34\", e vier de um cliente com endereço privado. Se não for definido, o AdGuard Home usará os endereços dos resolvedores DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Por padrão, o AdGuard Home usa os seguintes resolvedores de DNS reverso: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "A página inicial do AdGuard não conseguiu determinar resolvedores DNS reversos privados adequados para este sistema.",
|
||||
"local_ptr_placeholder": "Insira um endereço IP por linha",
|
||||
"resolve_clients_title": "Ativar resolução reversa de endereços IP de clientes",
|
||||
"resolve_clients_desc": "Resolva reversamente os endereços IP dos clientes em seus nomes de host, enviando consultas PTR aos resolvedores correspondentes (servidores DNS privados para clientes locais, servidores upstream para clientes com endereços IP públicos).",
|
||||
"use_private_ptr_resolvers_title": "Usar resolvedores DNS reversos privados",
|
||||
"use_private_ptr_resolvers_desc": "Execute pesquisas reversas de DNS para endereços servidos localmente usando esses servidores DNS primário. Se desativado, o AdGuard Home responde com NXDOMAIN a todas essas solicitações PTR, exceto para clientes conhecidos de DHCP, /etc/hosts e assim por diante.",
|
||||
"use_private_ptr_resolvers_desc": "Resolver solicitações PTR, SOA e NS para domínios ARPA contendo endereços privados usando servidores upstream privados, DHCP, /etc/hosts e assim por diante. Se desativado, o AdGuard Home responde a todas essas consultas com NXDOMAIN.",
|
||||
"check_dhcp_servers": "Verificar por servidores DHCP",
|
||||
"save_config": "Salvar configuração",
|
||||
"enabled_dhcp": "Servidor DHCP ativado",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lista de servidores DNS de fallback usados quando os servidores DNS upstream não estão respondendo. A sintaxe é a mesma do campo principal de upstreams acima.",
|
||||
"fallback_dns_placeholder": "Insira um servidor DNS de fallback por linha",
|
||||
"local_ptr_title": "Servidores DNS reversos privados",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver solicitações de PTR para endereços em intervalos de IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não estiver definido, o AdGuard Home usa os endereços dos resolvedores de DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home utiliza para consultas privadas de PTR, SOA e NS. A solicitação é considerada privada se solicitar um domínio ARPA contendo uma sub-rede dentro de intervalos de IP privados, por exemplo \"192.168.12.34\", e vier de um cliente com endereço privado. Se não for definido, o AdGuard Home usará os endereços dos resolvedores DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Por predefinição, o AdGuard Home usa os seguintes resolvedores de DNS reverso: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "A página inicial do AdGuard não conseguiu determinar resolvedores DNS reversos privados adequados para este sistema.",
|
||||
"local_ptr_placeholder": "Insira um endereço IP por linha",
|
||||
"resolve_clients_title": "Ativar resolução reversa de endereços IP de clientes",
|
||||
"resolve_clients_desc": "Resolva reversamente os endereços IP dos clientes em seus nomes de host, enviando consultas PTR aos resolvedores correspondentes (servidores DNS privados para clientes locais, servidores upstream para clientes com endereços IP públicos).",
|
||||
"use_private_ptr_resolvers_title": "Usar resolvedores DNS reversos privados",
|
||||
"use_private_ptr_resolvers_desc": "Execute pesquisas reversas de DNS para endereços servidos localmente usando esses servidores DNS primário. Se desativado, o AdGuard Home responde com NXDOMAIN a todas essas solicitações PTR, exceto para clientes conhecidos de DHCP, /etc/hosts e assim por diante.",
|
||||
"use_private_ptr_resolvers_desc": "Resolver solicitações PTR, SOA e NS para domínios ARPA contendo endereços privados usando servidores upstream privados, DHCP, /etc/hosts e assim por diante. Se desativado, o AdGuard Home responde a todas essas consultas com NXDOMAIN.",
|
||||
"check_dhcp_servers": "Verificar por servidores DHCP",
|
||||
"save_config": "Guardar definição",
|
||||
"enabled_dhcp": "Servidor DHCP ativado",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Список резервных DNS-серверов, используемых в тех случаях, когда вышестоящие DNS-серверы недоступны. Синтаксис такой же, как и в поле Upstream DNS-серверы выше.",
|
||||
"fallback_dns_placeholder": "Введите один резервный DNS-сервер в каждой строке",
|
||||
"local_ptr_title": "Приватные серверы для обратного DNS",
|
||||
"local_ptr_desc": "DNS-серверы, которые AdGuard Home использует для локальных PTR-запросов. Эти серверы используются, чтобы получить доменные имена клиентов с приватными IP-адресами, например «192.168.12.34», с помощью обратного DNS. Если список пуст, AdGuard Home использует DNS-серверы по умолчанию вашей ОС.",
|
||||
"local_ptr_desc": "DNS-серверы, которые AdGuard Home использует для локальных PTR, SOA и NS-запросов. Запрос считается локальным, если он запрашивает информацию об ARPA-домене, подсеть которого в локальном IP-диапазоне (например, «192.168.12.34»), и если при этом запрос пришел от клиента с локальным адресом. Если значение не установлено, AdGuard Home использует адреса DNS-серверы по умолчанию в вашей ОС, за исключением адресов самого AdGuard Home.",
|
||||
"local_ptr_default_resolver": "По умолчанию AdGuard Home использует следующие обратные DNS-резолверы: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home не смог определить подходящие приватные обратные DNS-резолверы для этой системы.",
|
||||
"local_ptr_placeholder": "Введите по одному адресу на строчку",
|
||||
"resolve_clients_title": "Включить запрашивание доменных имён для IP-адресов клиентов",
|
||||
"resolve_clients_desc": "Определять доменные имена клиентов через PTR-запросы к соответствующим серверам (приватные DNS-серверы для локальных клиентов, upstream-серверы для клиентов с публичным IP-адресом).",
|
||||
"use_private_ptr_resolvers_title": "Использовать приватные обратные DNS-резолверы",
|
||||
"use_private_ptr_resolvers_desc": "Посылать обратные DNS-запросы для локально обслуживаемых адресов на указанные серверы. Если отключено, AdGuard Home будет отвечать NXDOMAIN на все подобные PTR-запросы, кроме запросов о клиентах, уже известных по DHCP, /etc/hosts и так далее.",
|
||||
"use_private_ptr_resolvers_desc": "Посылать PTR, SOA и NS-запросы для ARPA-доменов, содержащих локальные адреса, с помощью указанных upstream-серверов, DHCP, /etc/hosts и так далее. Если отключено, AdGuard Home отвечает NXDOMAIN на все подобные запросы.",
|
||||
"check_dhcp_servers": "Проверить DHCP-серверы",
|
||||
"save_config": "Сохранить конфигурацию",
|
||||
"enabled_dhcp": "DHCP-сервер включён",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Zoznam záložných serverov DNS, ktoré sa používajú, keď nadradený servery DNS neodpovedajú. Syntax je rovnaká ako v hlavnom poli vyššie.",
|
||||
"fallback_dns_placeholder": "Zadajte jeden záložný server DNS na riadok",
|
||||
"local_ptr_title": "Súkromné reverzné DNS servery",
|
||||
"local_ptr_desc": "DNS servery, ktoré AdGuard Home používa pre miestne PTR dopyty. Tieto servery sa používajú na rozlíšenie názvov hostiteľov klientov so súkromnými adresami IP, napríklad \"192.168.12.34\", pomocou reverzného DNS. Ak nie je nastavené inak, AdGuard Home použije adresy predvolených prekladačov DNS Vášho operačného systému okrem adries samotného AdGuard Home.",
|
||||
"local_ptr_desc": "Servery DNS, ktoré používa AdGuard Home na súkromné dopyty PTR, SOA a NS. Dopyt sa považuje za súkromný, ak požaduje doménu ARPA obsahujúcu podsieť v rozsahu súkromnej IP adresy (napríklad „192.168.12.34“) a pochádza od klienta so súkromnou IP adresou. Ak nie je nastavené, použijú sa predvolené DNS resolvery Vášho operačného systému, okrem AdGuard Home IP adries.",
|
||||
"local_ptr_default_resolver": "V predvolenom nastavení používa AdGuard Home nasledujúce reverzné DNS prekladače: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home nemohol určiť vhodné súkromné reverzné DNS prekladače pre tento systém.",
|
||||
"local_ptr_placeholder": "Na každý riadok zadajte IP adresu jedného servera",
|
||||
"resolve_clients_title": "Povoliť spätný preklad IP adries klientov",
|
||||
"resolve_clients_desc": "Reverzne rozlišuje adresy IP klientov na ich názvy hostiteľov odosielaním PTR dopytov príslušným prekladačom (súkromné DNS servery pre miestnych klientov, servery typu upstream pre klientov s verejnými IP adresami).",
|
||||
"use_private_ptr_resolvers_title": "Použiť súkromné reverzné DNS resolvery",
|
||||
"use_private_ptr_resolvers_desc": "Realizuje reverzné vyhľadávanie DNS pre lokálne adresy pomocou týchto upstream serverov. Ak je funkcia vypnutá, Adguard Home reaguje s NXDOMAIN na všetky takéto PTR dopyty okrem klientov známych z DHCP, /etc/hosts, a tak ďalej.",
|
||||
"use_private_ptr_resolvers_desc": "Riešenie dopytov PTR, SOA a NS pre domény ARPA obsahujúce súkromné IP adresy prostredníctvom súkromných upstream serverov, DHCP, /etc/hosts atď. Ak je vypnuté, AdGuard Home bude na všetky takéto dopyty odpovedať pomocou NXDOMAIN.",
|
||||
"check_dhcp_servers": "Skontrolovať DHCP servery",
|
||||
"save_config": "Uložiť konfiguráciu",
|
||||
"enabled_dhcp": "DHCP server zapnutý",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Yukarı akış DNS sunucuları yanıt vermediğinde kullanılan yedek DNS sunucularının listesi. Söz dizimi yukarıdaki ana üst kaynak alanıyla aynıdır.",
|
||||
"fallback_dns_placeholder": "Her satıra bir yedek DNS sunucusu girin",
|
||||
"local_ptr_title": "Özel ters DNS sunucuları",
|
||||
"local_ptr_desc": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, rDNS kullanarak, örneğin \"192.168.12.34\" gibi özel IP aralıklarındaki adresler için PTR isteklerini çözmek için kullanılır. Ayarlanmadığı durumda AdGuard Home, işletim sisteminizin varsayılan DNS çözümleme adreslerini kullanır.",
|
||||
"local_ptr_desc": "AdGuard Home tarafından özel PTR, SOA ve NS istekleri için kullanılan DNS sunucuları. Bir istek, özel IP aralıkları (\"192.168.12.34\" gibi) içinde bir alt ağ içeren bir ARPA alan adı ister ve özel IP adresine sahip bir istemciden gelirse özel olarak kabul edilir. Ayarlanmadığı durumda AdGuard Home, IP adresleri dışında işletim sisteminizin varsayılan DNS çözümleyicileri kullanılır.",
|
||||
"local_ptr_default_resolver": "AdGuard Home, varsayılan olarak aşağıdaki ters DNS çözümleyicilerini kullanır: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home, bu sistem için uygun olan özel ters DNS çözümleyicilerini belirleyemedi.",
|
||||
"local_ptr_placeholder": "Her satıra bir IP adresi girin",
|
||||
"resolve_clients_title": "İstemcilerin IP adreslerinin ters çözümlenmesini etkinleştir",
|
||||
"resolve_clients_desc": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunucuları) PTR sorguları göndererek istemcilerin IP adreslerini ana makine adlarının tersine çözün.",
|
||||
"use_private_ptr_resolvers_title": "Özel ters DNS çözümleyicileri kullan",
|
||||
"use_private_ptr_resolvers_desc": "Bu üst kaynak sunucularını kullanarak yerel olarak sunulan adresler için ters DNS aramaları yapın. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts, vb. tarafından bilinen istemciler dışında bu tür tüm PTR isteklerine NXDOMAIN ile yanıt verir.",
|
||||
"use_private_ptr_resolvers_desc": "Özel üst kaynak sunucuları, DHCP, /etc/hosts, vb. aracılığıyla özel IP adresleri içeren ARPA alan adları için PTR, SOA ve NS isteklerini çözümleyin. Devre dışı bırakılırsa, AdGuard Home bu tür tüm isteklere NXDOMAIN ile yanıt verir.",
|
||||
"check_dhcp_servers": "DHCP sunucularını denetle",
|
||||
"save_config": "Yapılandırmayı kaydet",
|
||||
"enabled_dhcp": "DHCP sunucusu etkinleştirildi",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "Назва вузла",
|
||||
"dhcp_table_expires": "Закінчується",
|
||||
"dhcp_warning": "Якщо ви однаково хочете увімкнути DHCP-сервер, переконайтеся, що у вашій мережі немає інших активних DHCP-серверів. Інакше, це може порушити роботу інтернету на під'єднаних пристроях!",
|
||||
"dhcp_warning": "Якщо ви однаково хочете увімкнути DHCP-сервер, переконайтеся, що у вашій мережі немає інших активних DHCP-серверів. Інакше, це може порушити роботу інтернету на підʼєднаних пристроях!",
|
||||
"dhcp_error": "AdGuard Home не зміг визначити, чи є в мережі інший DHCP-сервер",
|
||||
"dhcp_static_ip_error": "Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Нам не вдалося визначити, чи цей мережевий інтерфейс налаштовано для використання статичної IP-адреси. Встановіть статичну IP-адресу вручну.",
|
||||
"dhcp_dynamic_ip_found": "Ваша система використовує конфігурацію з динамічною IP-адресою для інтерфейсу <0>{{interfaceName}}</0>. Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Ваша поточна IP-адреса <0>{{ipAddress}}</0>. Ми автоматично встановимо цю IP-адресу як статичну, якщо ви натиснете кнопку «Увімкнути DHCP-сервер».",
|
||||
@@ -364,7 +364,7 @@
|
||||
"install_submit_title": "Вітаємо!",
|
||||
"install_submit_desc": "Процедура налаштування завершена і тепер все готово, аби почати користуватися AdGuard Home.",
|
||||
"install_devices_router": "Роутер",
|
||||
"install_devices_router_desc": "Це налаштування буде автоматично охоплювати всі пристрої, що під'єднано до домашнього маршрутизатора. Вам не потрібно буде налаштовувати кожен з них вручну.",
|
||||
"install_devices_router_desc": "Це налаштування буде автоматично охоплювати всі пристрої, що підʼєднано до домашнього маршрутизатора. Вам не потрібно буде налаштовувати кожен з них вручну.",
|
||||
"install_devices_address": "DNS-сервер AdGuard Home прослуховує наступні адреси",
|
||||
"install_devices_router_list_1": "Відкрийте налаштування маршрутизатора. Зазвичай ви можете отримати до нього доступ із браузера за допомогою URL-адреси, наприклад, http://192.168.0.1/ або http://192.168.1.1/. Можливо, треба буде ввести пароль. Якщо ви його не знаєте, часто можна скинути пароль, натиснувши кнопку на самому маршрутизаторі. Для деяких маршрутизаторів потрібна спеціальна програма, яка в такому випадку повинна бути вже встановлена на вашому комп’ютері чи телефоні.",
|
||||
"install_devices_router_list_2": "Знайдіть налаштування DHCP/DNS. Шукайте літери DNS поруч із полем, в яке можна ввести два або три набори чисел, кожен з яких розбитий на чотири групи від однієї до трьох цифр.",
|
||||
@@ -448,7 +448,7 @@
|
||||
"manual_update": "Щоб оновити самостійно, <a>виконайте ці кроки</a>.",
|
||||
"processing_update": "Зачекайте будь ласка, AdGuard Home оновлюється",
|
||||
"clients_title": "Постійні клієнти",
|
||||
"clients_desc": "Налаштуйте пристрої, під'єднані до AdGuard Home",
|
||||
"clients_desc": "Налаштуйте пристрої, які підʼєднано до AdGuard Home",
|
||||
"settings_global": "Загальні",
|
||||
"settings_custom": "Власні",
|
||||
"table_client": "Клієнт",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "当上游 DNS 服务器没有响应时使用的后备 DNS 服务器列表。语法与上面的「主要上游」字段相同。",
|
||||
"fallback_dns_placeholder": "每行输入一个后备 DNS 服务器",
|
||||
"local_ptr_title": "私人反向 DNS 服务器",
|
||||
"local_ptr_desc": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器。这些服务器将使用反向 DNS 解析具有私人 IP 地址的客户机的主机名,比如 \"192.168.12.34\"。如果没有设置,除非是 AdGuard Home 里设置的地址,AdGuard Home 都将自动使用您的操作系统的默认 DNS 解析器。",
|
||||
"local_ptr_desc": "AdGuard Home 用于私人 PTR、SOA 和 NS 请求的 DNS 服务器。如果请求的 ARPA 域名包含私有 IP 范围内的子网(如 \"192.168.12.34\"),且请求来自具有私有 IP 地址的客户端,该请求被视为私有请求。如果未设置,将使用操作系统的默认 DNS 解析器,AdGuard Home IP 地址除外。",
|
||||
"local_ptr_default_resolver": "AdGuard Home 默认使用下列反向 DNS 解析器: {{ip}}",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home 无法为这个系统确定合适的私人反向 DNS 解析器。",
|
||||
"local_ptr_placeholder": "每行输入一个 IP 地址",
|
||||
"resolve_clients_title": "启用客户端的 IP 地址的反向解析",
|
||||
"resolve_clients_desc": "通过发送 PTR 查询到对应的解析器 (本地客户端的私人 DNS 服务器,公共 IP 客户端的上游服务器) 将 IP 地址反向解析成其客户端主机名。",
|
||||
"use_private_ptr_resolvers_title": "使用私人反向 DNS 解析器",
|
||||
"use_private_ptr_resolvers_desc": "使用这些上游服务器对本地服务的地址执行反向 DNS 查找。 如果禁用,则 AdGuard Home 会以 NXDOMAIN 响应所有此类 PTR 请求,从 DHCP、/etc/hosts 等获知的客户端除外。",
|
||||
"use_private_ptr_resolvers_desc": "使用私有上游服务器、DHCP、/etc/hosts 等解决包含私有 IP 地址的 ARPA 域名的 PTR、SOA 和 NS 请求。如果禁用,AdGuard Home 将以 NXDOMAIN 回应所有此类请求。",
|
||||
"check_dhcp_servers": "检查 DHCP 服务器",
|
||||
"save_config": "保存配置",
|
||||
"enabled_dhcp": "DHCP 服务器已启用",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "當上游 DNS 伺服器未回覆時被使用的應變 DNS 伺服器之清單。此語法與在上面主要上游欄位中的相同。",
|
||||
"fallback_dns_placeholder": "每行輸入一個應變 DNS 伺服器",
|
||||
"local_ptr_title": "私人反向的 DNS 伺服器",
|
||||
"local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析有關在私人 IP 範圍的位址之區域指標查詢,例如,\"192.168.12.34\",使用反向的 DNS。如果未被設定,AdGuard Home 使用您的作業系統之預設 DNS 解析器的位址。",
|
||||
"local_ptr_desc": "AdGuard Home 使用的 DNS 伺服器用於私人 PTR、SOA 和 NS 請求。如果請求要求包含私人 IP 範圍內的子網域的 ARPA 網域(例如 \"192.168.12.34\"),並來自具有私人 IP 位址的用戶端,該請求被視為私人。如果未設定,將使用您的作業系統的預設 DNS 解析器,但不包括 AdGuard Home 的 IP 位址。",
|
||||
"local_ptr_default_resolver": "預設下,AdGuard Home 使用以下反向的 DNS 解析器:{{ip}}。",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home 無法為此系統決定合適的私人反向的 DNS 解析器。",
|
||||
"local_ptr_placeholder": "每行輸入一個 IP 位址",
|
||||
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
|
||||
"resolve_clients_desc": "透過傳送指標(PTR)查詢到對應的解析器(私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。",
|
||||
"use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器",
|
||||
"use_private_ptr_resolvers_desc": "對於正使用這些上游伺服器之區域服務的位址執行反向的 DNS 查找。如果被禁用,除已知來自 DHCP、/etc/hosts 等等的用戶端之外,AdGuard Home 對於所有此類的區域指標(PTR)請求以 NXDOMAIN 回覆。",
|
||||
"use_private_ptr_resolvers_desc": "使用私人上游伺服器、DHCP、/etc/hosts 等方式解析包含私人 IP 位址的 ARPA 網域的 PTR、SOA 和 NS 請求。如果禁用,AdGuard Home 將對所有此類請求以 NXDOMAIN 回應。",
|
||||
"check_dhcp_servers": "檢查動態主機設定協定(DHCP)伺服器",
|
||||
"save_config": "儲存配置",
|
||||
"enabled_dhcp": "動態主機設定協定(DHCP)伺服器被啟用",
|
||||
|
||||
@@ -142,6 +142,12 @@ export default {
|
||||
"homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt"
|
||||
},
|
||||
"adguard_popup_filter": {
|
||||
"name": "AdGuard DNS Popup Hosts filter",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_59.txt"
|
||||
},
|
||||
"awavenue_ads_rule": {
|
||||
"name": "AWAvenue Ads Rule",
|
||||
"categoryId": "general",
|
||||
@@ -298,6 +304,12 @@ export default {
|
||||
"homepage": "https://github.com/durablenapkin/scamblocklist",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_10.txt"
|
||||
},
|
||||
"shadowwhisperers_dating_list": {
|
||||
"name": "ShadowWhisperer's Dating List",
|
||||
"categoryId": "other",
|
||||
"homepage": "https://github.com/ShadowWhisperer/BlockLists",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_57.txt"
|
||||
},
|
||||
"shadowwhisperers_malware_list": {
|
||||
"name": "ShadowWhisperer's Malware List",
|
||||
"categoryId": "security",
|
||||
|
||||
23
go.mod
23
go.mod
@@ -1,10 +1,10 @@
|
||||
module github.com/AdguardTeam/AdGuardHome
|
||||
|
||||
go 1.22.2
|
||||
go 1.22.3
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.67.1-0.20240405111306-032a0534ccd2
|
||||
github.com/AdguardTeam/golibs v0.21.0
|
||||
github.com/AdguardTeam/dnsproxy v0.71.1
|
||||
github.com/AdguardTeam/golibs v0.23.2
|
||||
github.com/AdguardTeam/urlfilter v0.18.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.7
|
||||
@@ -28,14 +28,15 @@ require (
|
||||
// own code for that. Perhaps, use gopacket.
|
||||
github.com/mdlayher/raw v0.1.0
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/quic-go/quic-go v0.41.0
|
||||
// TODO(a.garipov): Use release version.
|
||||
github.com/quic-go/quic-go v0.42.1-0.20240424141022-12aa63824c7f
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/ti-mo/netfilter v0.5.1
|
||||
go.etcd.io/bbolt v1.3.9
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.1
|
||||
@@ -58,9 +59,9 @@ require (
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
gonum.org/v1/gonum v0.14.0 // indirect
|
||||
)
|
||||
|
||||
42
go.sum
42
go.sum
@@ -1,7 +1,7 @@
|
||||
github.com/AdguardTeam/dnsproxy v0.67.1-0.20240405111306-032a0534ccd2 h1:XDhWNn1OfmbtLgj3bR52WWIa0/cf0ijanOvuaT75f1I=
|
||||
github.com/AdguardTeam/dnsproxy v0.67.1-0.20240405111306-032a0534ccd2/go.mod h1:7hAE3du5XPrBkdsqAPJIEGWklsE0ahHZONRlLASPeNI=
|
||||
github.com/AdguardTeam/golibs v0.21.0 h1:0swWyNaHTmT7aMwffKd9d54g4wBd8Oaj0fl+5l/PRdE=
|
||||
github.com/AdguardTeam/golibs v0.21.0/go.mod h1:/votX6WK1PdcZ3T2kBOPjPCGmfhlKixhI6ljYrFRPvI=
|
||||
github.com/AdguardTeam/dnsproxy v0.71.1 h1:R8jKmoE9HwqdTt7bm8irpvrQEOSmD+iGdNXbOg/uM8Y=
|
||||
github.com/AdguardTeam/dnsproxy v0.71.1/go.mod h1:rCaCL4m4n63sgwTOyUVdc7MC42PlUYBt11Fz/UjD+kM=
|
||||
github.com/AdguardTeam/golibs v0.23.2 h1:rMjYantwtQ39e8G4zBQ6ZLlm4s3XH30Bc9VxhoOHwao=
|
||||
github.com/AdguardTeam/golibs v0.23.2/go.mod h1:o9i55Sx6v7qogRQeqaBfmLbC/pZqeMBWi015U5PTDY0=
|
||||
github.com/AdguardTeam/urlfilter v0.18.0 h1:ZZzwODC/ADpjJSODxySrrUnt/fvOCfGFaCW6j+wsGfQ=
|
||||
github.com/AdguardTeam/urlfilter v0.18.0/go.mod h1:IXxBwedLiZA2viyHkaFxY/8mjub0li2PXRg8a3d9Z1s=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
@@ -101,8 +101,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
|
||||
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
|
||||
github.com/quic-go/quic-go v0.42.1-0.20240424141022-12aa63824c7f h1:L7x60Z6AW2giF/SvbDpMglGHJxtmFJV03khPwXLDScU=
|
||||
github.com/quic-go/quic-go v0.42.1-0.20240424141022-12aa63824c7f/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
@@ -131,26 +131,26 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -161,17 +161,19 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=
|
||||
|
||||
@@ -10,29 +10,8 @@ import (
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// Coalesce returns the first non-zero value. It is named after function
|
||||
// COALESCE in SQL. If values or all its elements are empty, it returns a zero
|
||||
// value.
|
||||
//
|
||||
// T is comparable, because Go currently doesn't have a comparableWithZeroValue
|
||||
// constraint.
|
||||
//
|
||||
// TODO(a.garipov): Think of ways to merge with [CoalesceSlice].
|
||||
func Coalesce[T comparable](values ...T) (res T) {
|
||||
var zero T
|
||||
for _, v := range values {
|
||||
if v != zero {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return zero
|
||||
}
|
||||
|
||||
// CoalesceSlice returns the first non-zero value. It is named after function
|
||||
// COALESCE in SQL. If values or all its elements are empty, it returns nil.
|
||||
//
|
||||
// TODO(a.garipov): Think of ways to merge with [Coalesce].
|
||||
func CoalesceSlice[E any, S []E](values ...S) (res S) {
|
||||
for _, v := range values {
|
||||
if v != nil {
|
||||
|
||||
@@ -33,7 +33,7 @@ func elements(b *aghalg.RingBuffer[int], n uint, reverse bool) (es []int) {
|
||||
func TestNewRingBuffer(t *testing.T) {
|
||||
t.Run("success_and_clear", func(t *testing.T) {
|
||||
b := aghalg.NewRingBuffer[int](5)
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
b.Append(i)
|
||||
}
|
||||
assert.Equal(t, []int{5, 6, 7, 8, 9}, elements(b, b.Len(), false))
|
||||
@@ -44,7 +44,7 @@ func TestNewRingBuffer(t *testing.T) {
|
||||
|
||||
t.Run("zero", func(t *testing.T) {
|
||||
b := aghalg.NewRingBuffer[int](0)
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
b.Append(i)
|
||||
bufLen := b.Len()
|
||||
assert.EqualValues(t, 0, bufLen)
|
||||
@@ -55,7 +55,7 @@ func TestNewRingBuffer(t *testing.T) {
|
||||
|
||||
t.Run("single", func(t *testing.T) {
|
||||
b := aghalg.NewRingBuffer[int](1)
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
b.Append(i)
|
||||
bufLen := b.Len()
|
||||
assert.EqualValues(t, 1, bufLen)
|
||||
@@ -94,7 +94,7 @@ func TestRingBuffer_Range(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
for i := 0; i < tc.count; i++ {
|
||||
for i := range tc.count {
|
||||
b.Append(i)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ func TestNewSortedMap(t *testing.T) {
|
||||
var m SortedMap[string, int]
|
||||
|
||||
letters := []string{}
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
r := string('a' + rune(i))
|
||||
letters = append(letters, r)
|
||||
}
|
||||
|
||||
@@ -97,6 +97,8 @@ func (fw FileWalker) Walk(fsys fs.FS, initial ...string) (ok bool, err error) {
|
||||
var filename string
|
||||
defer func() { err = errors.Annotate(err, "checking %q: %w", filename) }()
|
||||
|
||||
// TODO(e.burkov): Redo this loop, as it modifies the very same slice it
|
||||
// iterates over.
|
||||
for i := 0; i < len(src); i++ {
|
||||
var patterns []string
|
||||
var cont bool
|
||||
|
||||
@@ -159,21 +159,11 @@ func NotifyReconfigureSignal(c chan<- os.Signal) {
|
||||
notifyReconfigureSignal(c)
|
||||
}
|
||||
|
||||
// NotifyShutdownSignal notifies c on receiving shutdown signals.
|
||||
func NotifyShutdownSignal(c chan<- os.Signal) {
|
||||
notifyShutdownSignal(c)
|
||||
}
|
||||
|
||||
// IsReconfigureSignal returns true if sig is a reconfigure signal.
|
||||
func IsReconfigureSignal(sig os.Signal) (ok bool) {
|
||||
return isReconfigureSignal(sig)
|
||||
}
|
||||
|
||||
// IsShutdownSignal returns true if sig is a shutdown signal.
|
||||
func IsShutdownSignal(sig os.Signal) (ok bool) {
|
||||
return isShutdownSignal(sig)
|
||||
}
|
||||
|
||||
// SendShutdownSignal sends the shutdown signal to the channel.
|
||||
func SendShutdownSignal(c chan<- os.Signal) {
|
||||
sendShutdownSignal(c)
|
||||
|
||||
@@ -13,26 +13,10 @@ func notifyReconfigureSignal(c chan<- os.Signal) {
|
||||
signal.Notify(c, unix.SIGHUP)
|
||||
}
|
||||
|
||||
func notifyShutdownSignal(c chan<- os.Signal) {
|
||||
signal.Notify(c, unix.SIGINT, unix.SIGQUIT, unix.SIGTERM)
|
||||
}
|
||||
|
||||
func isReconfigureSignal(sig os.Signal) (ok bool) {
|
||||
return sig == unix.SIGHUP
|
||||
}
|
||||
|
||||
func isShutdownSignal(sig os.Signal) (ok bool) {
|
||||
switch sig {
|
||||
case
|
||||
unix.SIGINT,
|
||||
unix.SIGQUIT,
|
||||
unix.SIGTERM:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func sendShutdownSignal(_ chan<- os.Signal) {
|
||||
// On Unix we are already notified by the system.
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package aghos
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
@@ -43,25 +42,10 @@ func notifyReconfigureSignal(c chan<- os.Signal) {
|
||||
signal.Notify(c, windows.SIGHUP)
|
||||
}
|
||||
|
||||
func notifyShutdownSignal(c chan<- os.Signal) {
|
||||
// syscall.SIGTERM is processed automatically. See go doc os/signal,
|
||||
// section Windows.
|
||||
signal.Notify(c, os.Interrupt)
|
||||
}
|
||||
|
||||
func isReconfigureSignal(sig os.Signal) (ok bool) {
|
||||
return sig == windows.SIGHUP
|
||||
}
|
||||
|
||||
func isShutdownSignal(sig os.Signal) (ok bool) {
|
||||
switch sig {
|
||||
case os.Interrupt, syscall.SIGTERM:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func sendShutdownSignal(c chan<- os.Signal) {
|
||||
c <- os.Interrupt
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ func TestWithDeferredCleanup(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -91,8 +91,6 @@ func TestDefaultAddrProc_Process_rDNS(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -186,8 +184,6 @@ func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ package client
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||
)
|
||||
@@ -56,6 +57,9 @@ func (cs Source) MarshalText() (text []byte, err error) {
|
||||
|
||||
// Runtime is a client information from different sources.
|
||||
type Runtime struct {
|
||||
// ip is an IP address of a client.
|
||||
ip netip.Addr
|
||||
|
||||
// whois is the filtered WHOIS information of a client.
|
||||
whois *whois.Info
|
||||
|
||||
@@ -80,6 +84,15 @@ type Runtime struct {
|
||||
hostsFile []string
|
||||
}
|
||||
|
||||
// NewRuntime constructs a new runtime client. ip must be valid IP address.
|
||||
//
|
||||
// TODO(s.chzhen): Validate IP address.
|
||||
func NewRuntime(ip netip.Addr) (r *Runtime) {
|
||||
return &Runtime{
|
||||
ip: ip,
|
||||
}
|
||||
}
|
||||
|
||||
// Info returns a client information from the highest-priority source.
|
||||
func (r *Runtime) Info() (cs Source, host string) {
|
||||
info := []string{}
|
||||
@@ -133,8 +146,8 @@ func (r *Runtime) SetWHOIS(info *whois.Info) {
|
||||
r.whois = info
|
||||
}
|
||||
|
||||
// Unset clears a cs information.
|
||||
func (r *Runtime) Unset(cs Source) {
|
||||
// unset clears a cs information.
|
||||
func (r *Runtime) unset(cs Source) {
|
||||
switch cs {
|
||||
case SourceWHOIS:
|
||||
r.whois = nil
|
||||
@@ -149,11 +162,16 @@ func (r *Runtime) Unset(cs Source) {
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmpty returns true if there is no information from any source.
|
||||
func (r *Runtime) IsEmpty() (ok bool) {
|
||||
// isEmpty returns true if there is no information from any source.
|
||||
func (r *Runtime) isEmpty() (ok bool) {
|
||||
return r.whois == nil &&
|
||||
r.arp == nil &&
|
||||
r.rdns == nil &&
|
||||
r.dhcp == nil &&
|
||||
r.hostsFile == nil
|
||||
}
|
||||
|
||||
// Addr returns an IP address of the client.
|
||||
func (r *Runtime) Addr() (ip netip.Addr) {
|
||||
return r.ip
|
||||
}
|
||||
|
||||
@@ -4,8 +4,12 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// macKey contains MAC as byte array of 6, 8, or 20 bytes.
|
||||
@@ -28,6 +32,9 @@ func macToKey(mac net.HardwareAddr) (key macKey) {
|
||||
|
||||
// Index stores all information about persistent clients.
|
||||
type Index struct {
|
||||
// nameToUID maps client name to UID.
|
||||
nameToUID map[string]UID
|
||||
|
||||
// clientIDToUID maps client ID to UID.
|
||||
clientIDToUID map[string]UID
|
||||
|
||||
@@ -47,6 +54,7 @@ type Index struct {
|
||||
// NewIndex initializes the new instance of client index.
|
||||
func NewIndex() (ci *Index) {
|
||||
return &Index{
|
||||
nameToUID: map[string]UID{},
|
||||
clientIDToUID: map[string]UID{},
|
||||
ipToUID: map[netip.Addr]UID{},
|
||||
subnetToUID: aghalg.NewSortedMap[netip.Prefix, UID](subnetCompare),
|
||||
@@ -62,6 +70,8 @@ func (ci *Index) Add(c *Persistent) {
|
||||
panic("client must contain uid")
|
||||
}
|
||||
|
||||
ci.nameToUID[c.Name] = c.UID
|
||||
|
||||
for _, id := range c.ClientIDs {
|
||||
ci.clientIDToUID[id] = c.UID
|
||||
}
|
||||
@@ -82,15 +92,30 @@ func (ci *Index) Add(c *Persistent) {
|
||||
ci.uidToClient[c.UID] = c
|
||||
}
|
||||
|
||||
// ClashesUID returns existing persistent client with the same UID as c. Note
|
||||
// that this is only possible when configuration contains duplicate fields.
|
||||
func (ci *Index) ClashesUID(c *Persistent) (err error) {
|
||||
p, ok := ci.uidToClient[c.UID]
|
||||
if ok {
|
||||
return fmt.Errorf("another client %q uses the same uid", p.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clashes returns an error if the index contains a different persistent client
|
||||
// with at least a single identifier contained by c. c must be non-nil.
|
||||
func (ci *Index) Clashes(c *Persistent) (err error) {
|
||||
if p := ci.clashesName(c); p != nil {
|
||||
return fmt.Errorf("another client uses the same name %q", p.Name)
|
||||
}
|
||||
|
||||
for _, id := range c.ClientIDs {
|
||||
existing, ok := ci.clientIDToUID[id]
|
||||
if ok && existing != c.UID {
|
||||
p := ci.uidToClient[existing]
|
||||
|
||||
return fmt.Errorf("another client %q uses the same ID %q", p.Name, id)
|
||||
return fmt.Errorf("another client %q uses the same ClientID %q", p.Name, id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +137,21 @@ func (ci *Index) Clashes(c *Persistent) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// clashesName returns existing persistent client with the same name as c or
|
||||
// nil. c must be non-nil.
|
||||
func (ci *Index) clashesName(c *Persistent) (existing *Persistent) {
|
||||
existing, ok := ci.FindByName(c.Name)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if existing.UID != c.UID {
|
||||
return existing
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clashesIP returns a previous client with the same IP address as c. c must be
|
||||
// non-nil.
|
||||
func (ci *Index) clashesIP(c *Persistent) (p *Persistent, ip netip.Addr) {
|
||||
@@ -184,21 +224,33 @@ func (ci *Index) Find(id string) (c *Persistent, ok bool) {
|
||||
|
||||
mac, err := net.ParseMAC(id)
|
||||
if err == nil {
|
||||
return ci.findByMAC(mac)
|
||||
return ci.FindByMAC(mac)
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// find finds persistent client by IP address.
|
||||
// FindByName finds persistent client by name.
|
||||
func (ci *Index) FindByName(name string) (c *Persistent, found bool) {
|
||||
uid, found := ci.nameToUID[name]
|
||||
if found {
|
||||
return ci.uidToClient[uid], true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// findByIP finds persistent client by IP address.
|
||||
func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
|
||||
uid, found := ci.ipToUID[ip]
|
||||
if found {
|
||||
return ci.uidToClient[uid], true
|
||||
}
|
||||
|
||||
ipWithoutZone := ip.WithZone("")
|
||||
ci.subnetToUID.Range(func(pref netip.Prefix, id UID) (cont bool) {
|
||||
if pref.Contains(ip) {
|
||||
// Remove zone before checking because prefixes strip zones.
|
||||
if pref.Contains(ipWithoutZone) {
|
||||
uid, found = id, true
|
||||
|
||||
return false
|
||||
@@ -214,8 +266,8 @@ func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// find finds persistent client by MAC.
|
||||
func (ci *Index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
|
||||
// FindByMAC finds persistent client by MAC.
|
||||
func (ci *Index) FindByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
|
||||
k := macToKey(mac)
|
||||
uid, found := ci.macToUID[k]
|
||||
if found {
|
||||
@@ -225,9 +277,31 @@ func (ci *Index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// FindByIPWithoutZone finds a persistent client by IP address without zone. It
|
||||
// strips the IPv6 zone index from the stored IP addresses before comparing,
|
||||
// because querylog entries don't have it. See TODO on [querylog.logEntry.IP].
|
||||
//
|
||||
// Note that multiple clients can have the same IP address with different zones.
|
||||
// Therefore, the result of this method is indeterminate.
|
||||
func (ci *Index) FindByIPWithoutZone(ip netip.Addr) (c *Persistent) {
|
||||
if (ip == netip.Addr{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
for addr, uid := range ci.ipToUID {
|
||||
if addr.WithZone("") == ip {
|
||||
return ci.uidToClient[uid]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes information about persistent client from the index. c must be
|
||||
// non-nil.
|
||||
func (ci *Index) Delete(c *Persistent) {
|
||||
delete(ci.nameToUID, c.Name)
|
||||
|
||||
for _, id := range c.ClientIDs {
|
||||
delete(ci.clientIDToUID, id)
|
||||
}
|
||||
@@ -247,3 +321,48 @@ func (ci *Index) Delete(c *Persistent) {
|
||||
|
||||
delete(ci.uidToClient, c.UID)
|
||||
}
|
||||
|
||||
// Size returns the number of persistent clients.
|
||||
func (ci *Index) Size() (n int) {
|
||||
return len(ci.uidToClient)
|
||||
}
|
||||
|
||||
// Range calls f for each persistent client, unless cont is false. The order is
|
||||
// undefined.
|
||||
func (ci *Index) Range(f func(c *Persistent) (cont bool)) {
|
||||
for _, c := range ci.uidToClient {
|
||||
if !f(c) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RangeByName is like [Index.Range] but sorts the persistent clients by name
|
||||
// before iterating ensuring a predictable order.
|
||||
func (ci *Index) RangeByName(f func(c *Persistent) (cont bool)) {
|
||||
cs := maps.Values(ci.uidToClient)
|
||||
slices.SortFunc(cs, func(a, b *Persistent) (n int) {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
for _, c := range cs {
|
||||
if !f(c) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CloseUpstreams closes upstream configurations of persistent clients.
|
||||
func (ci *Index) CloseUpstreams() (err error) {
|
||||
var errs []error
|
||||
ci.RangeByName(func(c *Persistent) (cont bool) {
|
||||
err = c.CloseUpstreams()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func newIDIndex(m []*Persistent) (ci *Index) {
|
||||
return ci
|
||||
}
|
||||
|
||||
func TestClientIndex(t *testing.T) {
|
||||
func TestClientIndex_Find(t *testing.T) {
|
||||
const (
|
||||
cliIPNone = "1.2.3.4"
|
||||
cliIP1 = "1.1.1.1"
|
||||
@@ -35,26 +35,49 @@ func TestClientIndex(t *testing.T) {
|
||||
|
||||
cliID = "client-id"
|
||||
cliMAC = "11:11:11:11:11:11"
|
||||
|
||||
linkLocalIP = "fe80::abcd:abcd:abcd:ab%eth0"
|
||||
linkLocalSubnet = "fe80::/16"
|
||||
)
|
||||
|
||||
clients := []*Persistent{{
|
||||
Name: "client1",
|
||||
IPs: []netip.Addr{
|
||||
netip.MustParseAddr(cliIP1),
|
||||
netip.MustParseAddr(cliIPv6),
|
||||
},
|
||||
}, {
|
||||
Name: "client2",
|
||||
IPs: []netip.Addr{netip.MustParseAddr(cliIP2)},
|
||||
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
|
||||
}, {
|
||||
Name: "client_with_mac",
|
||||
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
|
||||
}, {
|
||||
Name: "client_with_id",
|
||||
ClientIDs: []string{cliID},
|
||||
}}
|
||||
var (
|
||||
clientWithBothFams = &Persistent{
|
||||
Name: "client1",
|
||||
IPs: []netip.Addr{
|
||||
netip.MustParseAddr(cliIP1),
|
||||
netip.MustParseAddr(cliIPv6),
|
||||
},
|
||||
}
|
||||
|
||||
clientWithSubnet = &Persistent{
|
||||
Name: "client2",
|
||||
IPs: []netip.Addr{netip.MustParseAddr(cliIP2)},
|
||||
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
|
||||
}
|
||||
|
||||
clientWithMAC = &Persistent{
|
||||
Name: "client_with_mac",
|
||||
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
|
||||
}
|
||||
|
||||
clientWithID = &Persistent{
|
||||
Name: "client_with_id",
|
||||
ClientIDs: []string{cliID},
|
||||
}
|
||||
|
||||
clientLinkLocal = &Persistent{
|
||||
Name: "client_link_local",
|
||||
Subnets: []netip.Prefix{netip.MustParsePrefix(linkLocalSubnet)},
|
||||
}
|
||||
)
|
||||
|
||||
clients := []*Persistent{
|
||||
clientWithBothFams,
|
||||
clientWithSubnet,
|
||||
clientWithMAC,
|
||||
clientWithID,
|
||||
clientLinkLocal,
|
||||
}
|
||||
ci := newIDIndex(clients)
|
||||
|
||||
testCases := []struct {
|
||||
@@ -64,19 +87,23 @@ func TestClientIndex(t *testing.T) {
|
||||
}{{
|
||||
name: "ipv4_ipv6",
|
||||
ids: []string{cliIP1, cliIPv6},
|
||||
want: clients[0],
|
||||
want: clientWithBothFams,
|
||||
}, {
|
||||
name: "ipv4_subnet",
|
||||
ids: []string{cliIP2, cliSubnetIP},
|
||||
want: clients[1],
|
||||
want: clientWithSubnet,
|
||||
}, {
|
||||
name: "mac",
|
||||
ids: []string{cliMAC},
|
||||
want: clients[2],
|
||||
want: clientWithMAC,
|
||||
}, {
|
||||
name: "client_id",
|
||||
ids: []string{cliID},
|
||||
want: clients[3],
|
||||
want: clientWithID,
|
||||
}, {
|
||||
name: "client_link_local_subnet",
|
||||
ids: []string{linkLocalIP},
|
||||
want: clientLinkLocal,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -221,3 +248,103 @@ func TestMACToKey(t *testing.T) {
|
||||
_ = macToKey(mac)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIndex_FindByIPWithoutZone(t *testing.T) {
|
||||
var (
|
||||
ip = netip.MustParseAddr("fe80::a098:7654:32ef:ff1")
|
||||
ipWithZone = netip.MustParseAddr("fe80::1ff:fe23:4567:890a%eth2")
|
||||
)
|
||||
|
||||
var (
|
||||
clientNoZone = &Persistent{
|
||||
Name: "client",
|
||||
IPs: []netip.Addr{ip},
|
||||
}
|
||||
|
||||
clientWithZone = &Persistent{
|
||||
Name: "client_with_zone",
|
||||
IPs: []netip.Addr{ipWithZone},
|
||||
}
|
||||
)
|
||||
|
||||
ci := newIDIndex([]*Persistent{
|
||||
clientNoZone,
|
||||
clientWithZone,
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
ip netip.Addr
|
||||
want *Persistent
|
||||
name string
|
||||
}{{
|
||||
name: "without_zone",
|
||||
ip: ip,
|
||||
want: clientNoZone,
|
||||
}, {
|
||||
name: "with_zone",
|
||||
ip: ipWithZone,
|
||||
want: clientWithZone,
|
||||
}, {
|
||||
name: "zero_address",
|
||||
ip: netip.Addr{},
|
||||
want: nil,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := ci.FindByIPWithoutZone(tc.ip.WithZone(""))
|
||||
require.Equal(t, tc.want, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientIndex_RangeByName(t *testing.T) {
|
||||
sortedClients := []*Persistent{{
|
||||
Name: "clientA",
|
||||
ClientIDs: []string{"A"},
|
||||
}, {
|
||||
Name: "clientB",
|
||||
ClientIDs: []string{"B"},
|
||||
}, {
|
||||
Name: "clientC",
|
||||
ClientIDs: []string{"C"},
|
||||
}, {
|
||||
Name: "clientD",
|
||||
ClientIDs: []string{"D"},
|
||||
}, {
|
||||
Name: "clientE",
|
||||
ClientIDs: []string{"E"},
|
||||
}}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
want []*Persistent
|
||||
}{{
|
||||
name: "basic",
|
||||
want: sortedClients,
|
||||
}, {
|
||||
name: "nil",
|
||||
want: nil,
|
||||
}, {
|
||||
name: "one_element",
|
||||
want: sortedClients[:1],
|
||||
}, {
|
||||
name: "two_elements",
|
||||
want: sortedClients[:2],
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ci := newIDIndex(tc.want)
|
||||
|
||||
var got []*Persistent
|
||||
ci.RangeByName(func(c *Persistent) (cont bool) {
|
||||
got = append(got, c)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +64,7 @@ type Persistent struct {
|
||||
// upstream must be used.
|
||||
UpstreamConfig *proxy.CustomUpstreamConfig
|
||||
|
||||
// TODO(d.kolyshev): Make SafeSearchConf a pointer.
|
||||
SafeSearchConf filtering.SafeSearchConfig
|
||||
SafeSearch filtering.SafeSearch
|
||||
SafeSearch filtering.SafeSearch
|
||||
|
||||
// BlockedServices is the configuration of blocked services of a client.
|
||||
BlockedServices *filtering.BlockedServices
|
||||
@@ -95,6 +93,9 @@ type Persistent struct {
|
||||
UseOwnBlockedServices bool
|
||||
IgnoreQueryLog bool
|
||||
IgnoreStatistics bool
|
||||
|
||||
// TODO(d.kolyshev): Make SafeSearchConf a pointer.
|
||||
SafeSearchConf filtering.SafeSearchConfig
|
||||
}
|
||||
|
||||
// SetTags sets the tags if they are known, otherwise logs an unknown tag.
|
||||
|
||||
63
internal/client/runtimeindex.go
Normal file
63
internal/client/runtimeindex.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package client
|
||||
|
||||
import "net/netip"
|
||||
|
||||
// RuntimeIndex stores information about runtime clients.
|
||||
type RuntimeIndex struct {
|
||||
// index maps IP address to runtime client.
|
||||
index map[netip.Addr]*Runtime
|
||||
}
|
||||
|
||||
// NewRuntimeIndex returns initialized runtime index.
|
||||
func NewRuntimeIndex() (ri *RuntimeIndex) {
|
||||
return &RuntimeIndex{
|
||||
index: map[netip.Addr]*Runtime{},
|
||||
}
|
||||
}
|
||||
|
||||
// Client returns the saved runtime client by ip. If no such client exists,
|
||||
// returns nil.
|
||||
func (ri *RuntimeIndex) Client(ip netip.Addr) (rc *Runtime) {
|
||||
return ri.index[ip]
|
||||
}
|
||||
|
||||
// Add saves the runtime client in the index. IP address of a client must be
|
||||
// unique. See [Runtime.Client]. rc must not be nil.
|
||||
func (ri *RuntimeIndex) Add(rc *Runtime) {
|
||||
ip := rc.Addr()
|
||||
ri.index[ip] = rc
|
||||
}
|
||||
|
||||
// Size returns the number of the runtime clients.
|
||||
func (ri *RuntimeIndex) Size() (n int) {
|
||||
return len(ri.index)
|
||||
}
|
||||
|
||||
// Range calls f for each runtime client in an undefined order.
|
||||
func (ri *RuntimeIndex) Range(f func(rc *Runtime) (cont bool)) {
|
||||
for _, rc := range ri.index {
|
||||
if !f(rc) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete removes the runtime client by ip.
|
||||
func (ri *RuntimeIndex) Delete(ip netip.Addr) {
|
||||
delete(ri.index, ip)
|
||||
}
|
||||
|
||||
// DeleteBySource removes all runtime clients that have information only from
|
||||
// the specified source and returns the number of removed clients.
|
||||
func (ri *RuntimeIndex) DeleteBySource(src Source) (n int) {
|
||||
for ip, rc := range ri.index {
|
||||
rc.unset(src)
|
||||
|
||||
if rc.isEmpty() {
|
||||
delete(ri.index, ip)
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
85
internal/client/runtimeindex_test.go
Normal file
85
internal/client/runtimeindex_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package client_test
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRuntimeIndex(t *testing.T) {
|
||||
const cliSrc = client.SourceARP
|
||||
|
||||
var (
|
||||
ip1 = netip.MustParseAddr("1.1.1.1")
|
||||
ip2 = netip.MustParseAddr("2.2.2.2")
|
||||
ip3 = netip.MustParseAddr("3.3.3.3")
|
||||
)
|
||||
|
||||
ri := client.NewRuntimeIndex()
|
||||
currentSize := 0
|
||||
|
||||
testCases := []struct {
|
||||
ip netip.Addr
|
||||
name string
|
||||
hosts []string
|
||||
src client.Source
|
||||
}{{
|
||||
src: cliSrc,
|
||||
ip: ip1,
|
||||
name: "1",
|
||||
hosts: []string{"host1"},
|
||||
}, {
|
||||
src: cliSrc,
|
||||
ip: ip2,
|
||||
name: "2",
|
||||
hosts: []string{"host2"},
|
||||
}, {
|
||||
src: cliSrc,
|
||||
ip: ip3,
|
||||
name: "3",
|
||||
hosts: []string{"host3"},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rc := client.NewRuntime(tc.ip)
|
||||
rc.SetInfo(tc.src, tc.hosts)
|
||||
|
||||
ri.Add(rc)
|
||||
currentSize++
|
||||
|
||||
got := ri.Client(tc.ip)
|
||||
assert.Equal(t, rc, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("size", func(t *testing.T) {
|
||||
assert.Equal(t, currentSize, ri.Size())
|
||||
})
|
||||
|
||||
t.Run("range", func(t *testing.T) {
|
||||
s := 0
|
||||
|
||||
ri.Range(func(rc *client.Runtime) (cont bool) {
|
||||
s++
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(t, currentSize, s)
|
||||
})
|
||||
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
ri.Delete(ip1)
|
||||
currentSize--
|
||||
|
||||
assert.Equal(t, currentSize, ri.Size())
|
||||
})
|
||||
|
||||
t.Run("delete_by_src", func(t *testing.T) {
|
||||
assert.Equal(t, currentSize, ri.DeleteBySource(cliSrc))
|
||||
assert.Equal(t, 0, ri.Size())
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package configmigrate
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
|
||||
// migrateTo15 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
@@ -43,7 +45,7 @@ func migrateTo15(diskConf yobj) (err error) {
|
||||
}
|
||||
diskConf["querylog"] = qlog
|
||||
|
||||
return coalesceError(
|
||||
return errors.Join(
|
||||
moveVal[bool](dns, qlog, "querylog_enabled", "enabled"),
|
||||
moveVal[bool](dns, qlog, "querylog_file_enabled", "file_enabled"),
|
||||
moveVal[any](dns, qlog, "querylog_interval", "interval"),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package configmigrate
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
|
||||
// migrateTo24 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
@@ -28,7 +30,7 @@ func migrateTo24(diskConf yobj) (err error) {
|
||||
diskConf["schema_version"] = 24
|
||||
|
||||
logObj := yobj{}
|
||||
err = coalesceError(
|
||||
err = errors.Join(
|
||||
moveVal[string](diskConf, logObj, "log_file", "file"),
|
||||
moveVal[int](diskConf, logObj, "log_max_backups", "max_backups"),
|
||||
moveVal[int](diskConf, logObj, "log_max_size", "max_size"),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package configmigrate
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
|
||||
// migrateTo26 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
@@ -78,7 +80,7 @@ func migrateTo26(diskConf yobj) (err error) {
|
||||
}
|
||||
|
||||
filteringObj := yobj{}
|
||||
err = coalesceError(
|
||||
err = errors.Join(
|
||||
moveSameVal[bool](dns, filteringObj, "filtering_enabled"),
|
||||
moveSameVal[int](dns, filteringObj, "filters_update_interval"),
|
||||
moveSameVal[bool](dns, filteringObj, "parental_enabled"),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package configmigrate
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
|
||||
// migrateTo7 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
@@ -37,7 +39,7 @@ func migrateTo7(diskConf yobj) (err error) {
|
||||
}
|
||||
|
||||
dhcpv4 := yobj{}
|
||||
err = coalesceError(
|
||||
err = errors.Join(
|
||||
moveSameVal[string](dhcp, dhcpv4, "gateway_ip"),
|
||||
moveSameVal[string](dhcp, dhcpv4, "subnet_mask"),
|
||||
moveSameVal[string](dhcp, dhcpv4, "range_start"),
|
||||
|
||||
@@ -50,19 +50,3 @@ func moveVal[T any](src, dst yobj, srcKey, dstKey string) (err error) {
|
||||
func moveSameVal[T any](src, dst yobj, key string) (err error) {
|
||||
return moveVal[T](src, dst, key, key)
|
||||
}
|
||||
|
||||
// coalesceError returns the first non-nil error. It is named after function
|
||||
// COALESCE in SQL. If all errors are nil, it returns nil.
|
||||
//
|
||||
// TODO(e.burkov): Replace with [errors.Join].
|
||||
//
|
||||
// TODO(a.garipov): Think of ways to merge with [aghalg.Coalesce].
|
||||
func coalesceError(errors ...error) (res error) {
|
||||
for _, err := range errors {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,7 +156,10 @@ func (a *accessManager) isBlockedIP(ip netip.Addr) (blocked bool, rule string) {
|
||||
}
|
||||
|
||||
for _, ipnet := range ipnets {
|
||||
if ipnet.Contains(ip) {
|
||||
// Remove zone before checking because prefixes stip zones.
|
||||
//
|
||||
// TODO(d.kolyshev): Cover with tests.
|
||||
if ipnet.Contains(ip.WithZone("")) {
|
||||
return blocked, ipnet.String()
|
||||
}
|
||||
}
|
||||
|
||||
116
internal/dnsforward/beforerequest.go
Normal file
116
internal/dnsforward/beforerequest.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// type check
|
||||
var _ proxy.BeforeRequestHandler = (*Server)(nil)
|
||||
|
||||
// HandleBefore is the handler that is called before any other processing,
|
||||
// including logs. It performs access checks and puts the client ID, if there
|
||||
// is one, into the server's cache.
|
||||
//
|
||||
// TODO(d.kolyshev): Extract to separate package.
|
||||
func (s *Server) HandleBefore(
|
||||
_ *proxy.Proxy,
|
||||
pctx *proxy.DNSContext,
|
||||
) (err error) {
|
||||
clientID, err := s.clientIDFromDNSContext(pctx)
|
||||
if err != nil {
|
||||
return &proxy.BeforeRequestError{
|
||||
Err: fmt.Errorf("getting clientid: %w", err),
|
||||
Response: s.NewMsgSERVFAIL(pctx.Req),
|
||||
}
|
||||
}
|
||||
|
||||
blocked, _ := s.IsBlockedClient(pctx.Addr.Addr(), clientID)
|
||||
if blocked {
|
||||
return s.preBlockedResponse(pctx)
|
||||
}
|
||||
|
||||
if len(pctx.Req.Question) == 1 {
|
||||
q := pctx.Req.Question[0]
|
||||
qt := q.Qtype
|
||||
host := aghnet.NormalizeDomain(q.Name)
|
||||
if s.access.isBlockedHost(host, qt) {
|
||||
log.Debug("access: request %s %s is in access blocklist", dns.Type(qt), host)
|
||||
|
||||
return s.preBlockedResponse(pctx)
|
||||
}
|
||||
}
|
||||
|
||||
if clientID != "" {
|
||||
key := [8]byte{}
|
||||
binary.BigEndian.PutUint64(key[:], pctx.RequestID)
|
||||
s.clientIDCache.Set(key[:], []byte(clientID))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clientIDFromDNSContext extracts the client's ID from the server name of the
|
||||
// client's DoT or DoQ request or the path of the client's DoH. If the protocol
|
||||
// is not one of these, clientID is an empty string and err is nil.
|
||||
func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string, err error) {
|
||||
proto := pctx.Proto
|
||||
if proto == proxy.ProtoHTTPS {
|
||||
clientID, err = clientIDFromDNSContextHTTPS(pctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("checking url: %w", err)
|
||||
} else if clientID != "" {
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// Go on and check the domain name as well.
|
||||
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
hostSrvName := s.conf.ServerName
|
||||
if hostSrvName == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
cliSrvName, err := clientServerName(pctx, proto)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
clientID, err = clientIDFromClientServerName(
|
||||
hostSrvName,
|
||||
cliSrvName,
|
||||
s.conf.StrictSNICheck,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("clientid check: %w", err)
|
||||
}
|
||||
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// errAccessBlocked is a sentinel error returned when a request is blocked by
|
||||
// access settings.
|
||||
var errAccessBlocked errors.Error = "blocked by access settings"
|
||||
|
||||
// preBlockedResponse returns a protocol-appropriate response for a request that
|
||||
// was blocked by access settings.
|
||||
func (s *Server) preBlockedResponse(pctx *proxy.DNSContext) (err error) {
|
||||
if pctx.Proto == proxy.ProtoUDP || pctx.Proto == proxy.ProtoDNSCrypt {
|
||||
// Return nil so that dnsproxy drops the connection and thus
|
||||
// prevent DNS amplification attacks.
|
||||
return errAccessBlocked
|
||||
}
|
||||
|
||||
return &proxy.BeforeRequestError{
|
||||
Err: errAccessBlocked,
|
||||
Response: s.makeResponseREFUSED(pctx.Req),
|
||||
}
|
||||
}
|
||||
299
internal/dnsforward/beforerequest_internal_test.go
Normal file
299
internal/dnsforward/beforerequest_internal_test.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
blockedHost = "blockedhost.org"
|
||||
testFQDN = "example.org."
|
||||
dnsClientTimeout = 200 * time.Millisecond
|
||||
)
|
||||
|
||||
func TestServer_HandleBefore_tls(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const clientID = "client-1"
|
||||
|
||||
testCases := []struct {
|
||||
clientSrvName string
|
||||
name string
|
||||
host string
|
||||
allowedClients []string
|
||||
disallowedClients []string
|
||||
blockedHosts []string
|
||||
wantRCode int
|
||||
}{{
|
||||
clientSrvName: tlsServerName,
|
||||
name: "allow_all",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
}, {
|
||||
clientSrvName: "%" + "." + tlsServerName,
|
||||
name: "invalid_client_id",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeServerFailure,
|
||||
}, {
|
||||
clientSrvName: clientID + "." + tlsServerName,
|
||||
name: "allowed_client_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{clientID},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
}, {
|
||||
clientSrvName: "client-2." + tlsServerName,
|
||||
name: "allowed_client_rejected",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{clientID},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeRefused,
|
||||
}, {
|
||||
clientSrvName: tlsServerName,
|
||||
name: "disallowed_client_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{clientID},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
}, {
|
||||
clientSrvName: clientID + "." + tlsServerName,
|
||||
name: "disallowed_client_rejected",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{clientID},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeRefused,
|
||||
}, {
|
||||
clientSrvName: tlsServerName,
|
||||
name: "blocked_hosts_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{blockedHost},
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
}, {
|
||||
clientSrvName: tlsServerName,
|
||||
name: "blocked_hosts_rejected",
|
||||
host: dns.Fqdn(blockedHost),
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{blockedHost},
|
||||
wantRCode: dns.RcodeRefused,
|
||||
}}
|
||||
|
||||
localAns := []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: testFQDN,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 3600,
|
||||
Rdlength: 4,
|
||||
},
|
||||
A: net.IP{1, 2, 3, 4},
|
||||
}}
|
||||
localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := (&dns.Msg{}).SetReply(req)
|
||||
resp.Answer = localAns
|
||||
|
||||
require.NoError(t, w.WriteMsg(resp))
|
||||
})
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, localUpsHdlr).String()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, _ := createTestTLS(t, TLSConfig{
|
||||
TLSListenAddrs: []*net.TCPAddr{{}},
|
||||
ServerName: tlsServerName,
|
||||
})
|
||||
|
||||
s.conf.UpstreamDNS = []string{localUpsAddr}
|
||||
|
||||
s.conf.AllowedClients = tc.allowedClients
|
||||
s.conf.DisallowedClients = tc.disallowedClients
|
||||
s.conf.BlockedHosts = tc.blockedHosts
|
||||
|
||||
err := s.Prepare(&s.conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
startDeferStop(t, s)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: tc.clientSrvName,
|
||||
}
|
||||
|
||||
client := &dns.Client{
|
||||
Net: "tcp-tls",
|
||||
TLSConfig: tlsConfig,
|
||||
Timeout: dnsClientTimeout,
|
||||
}
|
||||
|
||||
req := createTestMessage(tc.host)
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoTLS).String()
|
||||
|
||||
reply, _, err := client.Exchange(req, addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.wantRCode, reply.Rcode)
|
||||
if tc.wantRCode == dns.RcodeSuccess {
|
||||
assert.Equal(t, localAns, reply.Answer)
|
||||
} else {
|
||||
assert.Empty(t, reply.Answer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_HandleBefore_udp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
clientIPv4 = "127.0.0.1"
|
||||
clientIPv6 = "::1"
|
||||
)
|
||||
|
||||
clientIPs := []string{clientIPv4, clientIPv6}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
allowedClients []string
|
||||
disallowedClients []string
|
||||
blockedHosts []string
|
||||
wantTimeout bool
|
||||
}{{
|
||||
name: "allow_all",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: false,
|
||||
}, {
|
||||
name: "allowed_client_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: clientIPs,
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: false,
|
||||
}, {
|
||||
name: "allowed_client_rejected",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{"1:2:3::4"},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: true,
|
||||
}, {
|
||||
name: "disallowed_client_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{"1:2:3::4"},
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: false,
|
||||
}, {
|
||||
name: "disallowed_client_rejected",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: clientIPs,
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: true,
|
||||
}, {
|
||||
name: "blocked_hosts_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{blockedHost},
|
||||
wantTimeout: false,
|
||||
}, {
|
||||
name: "blocked_hosts_rejected",
|
||||
host: dns.Fqdn(blockedHost),
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{blockedHost},
|
||||
wantTimeout: true,
|
||||
}}
|
||||
|
||||
localAns := []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: testFQDN,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 3600,
|
||||
Rdlength: 4,
|
||||
},
|
||||
A: net.IP{1, 2, 3, 4},
|
||||
}}
|
||||
localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := (&dns.Msg{}).SetReply(req)
|
||||
resp.Answer = localAns
|
||||
|
||||
require.NoError(t, w.WriteMsg(resp))
|
||||
})
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, localUpsHdlr).String()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
Config: Config{
|
||||
AllowedClients: tc.allowedClients,
|
||||
DisallowedClients: tc.disallowedClients,
|
||||
BlockedHosts: tc.blockedHosts,
|
||||
UpstreamDNS: []string{localUpsAddr},
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
})
|
||||
|
||||
startDeferStop(t, s)
|
||||
|
||||
client := &dns.Client{
|
||||
Net: "udp",
|
||||
Timeout: dnsClientTimeout,
|
||||
}
|
||||
|
||||
req := createTestMessage(tc.host)
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP).String()
|
||||
|
||||
reply, _, err := client.Exchange(req, addr)
|
||||
if tc.wantTimeout {
|
||||
wantErr := &net.OpError{}
|
||||
require.ErrorAs(t, err, &wantErr)
|
||||
assert.True(t, wantErr.Timeout())
|
||||
|
||||
assert.Nil(t, reply)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, reply)
|
||||
|
||||
assert.Equal(t, dns.RcodeSuccess, reply.Rcode)
|
||||
assert.Equal(t, localAns, reply.Answer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -110,46 +110,6 @@ type quicConnection interface {
|
||||
ConnectionState() (cs quic.ConnectionState)
|
||||
}
|
||||
|
||||
// clientIDFromDNSContext extracts the client's ID from the server name of the
|
||||
// client's DoT or DoQ request or the path of the client's DoH. If the protocol
|
||||
// is not one of these, clientID is an empty string and err is nil.
|
||||
func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string, err error) {
|
||||
proto := pctx.Proto
|
||||
if proto == proxy.ProtoHTTPS {
|
||||
clientID, err = clientIDFromDNSContextHTTPS(pctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("checking url: %w", err)
|
||||
} else if clientID != "" {
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// Go on and check the domain name as well.
|
||||
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
hostSrvName := s.conf.ServerName
|
||||
if hostSrvName == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
cliSrvName, err := clientServerName(pctx, proto)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
clientID, err = clientIDFromClientServerName(
|
||||
hostSrvName,
|
||||
cliSrvName,
|
||||
s.conf.StrictSNICheck,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("clientid check: %w", err)
|
||||
}
|
||||
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// clientServerName returns the TLS server name based on the protocol. For
|
||||
// DNS-over-HTTPS requests, it will return the hostname part of the Host header
|
||||
// if there is one.
|
||||
|
||||
@@ -235,9 +235,18 @@ type DNSCryptConfig struct {
|
||||
// ServerConfig represents server configuration.
|
||||
// The zero ServerConfig is empty and ready for use.
|
||||
type ServerConfig struct {
|
||||
UDPListenAddrs []*net.UDPAddr // UDP listen address
|
||||
TCPListenAddrs []*net.TCPAddr // TCP listen address
|
||||
UpstreamConfig *proxy.UpstreamConfig // Upstream DNS servers config
|
||||
// UDPListenAddrs is the list of addresses to listen for DNS-over-UDP.
|
||||
UDPListenAddrs []*net.UDPAddr
|
||||
|
||||
// TCPListenAddrs is the list of addresses to listen for DNS-over-TCP.
|
||||
TCPListenAddrs []*net.TCPAddr
|
||||
|
||||
// UpstreamConfig is the general configuration of upstream DNS servers.
|
||||
UpstreamConfig *proxy.UpstreamConfig
|
||||
|
||||
// PrivateRDNSUpstreamConfig is the configuration of upstream DNS servers
|
||||
// for private reverse DNS.
|
||||
PrivateRDNSUpstreamConfig *proxy.UpstreamConfig
|
||||
|
||||
// AddrProcConf defines the configuration for the client IP processor.
|
||||
// If nil, [client.EmptyAddrProc] is used.
|
||||
@@ -306,24 +315,28 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
|
||||
trustedPrefixes := netutil.UnembedPrefixes(srvConf.TrustedProxies)
|
||||
|
||||
conf = &proxy.Config{
|
||||
HTTP3: srvConf.ServeHTTP3,
|
||||
Ratelimit: int(srvConf.Ratelimit),
|
||||
RatelimitSubnetLenIPv4: srvConf.RatelimitSubnetLenIPv4,
|
||||
RatelimitSubnetLenIPv6: srvConf.RatelimitSubnetLenIPv6,
|
||||
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
||||
RefuseAny: srvConf.RefuseAny,
|
||||
TrustedProxies: netutil.SliceSubnetSet(trustedPrefixes),
|
||||
CacheMinTTL: srvConf.CacheMinTTL,
|
||||
CacheMaxTTL: srvConf.CacheMaxTTL,
|
||||
CacheOptimistic: srvConf.CacheOptimistic,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
BeforeRequestHandler: s.beforeRequestHandler,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
HTTPSServerName: aghhttp.UserAgent(),
|
||||
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
|
||||
MaxGoroutines: srvConf.MaxGoroutines,
|
||||
UseDNS64: srvConf.UseDNS64,
|
||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||
HTTP3: srvConf.ServeHTTP3,
|
||||
Ratelimit: int(srvConf.Ratelimit),
|
||||
RatelimitSubnetLenIPv4: srvConf.RatelimitSubnetLenIPv4,
|
||||
RatelimitSubnetLenIPv6: srvConf.RatelimitSubnetLenIPv6,
|
||||
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
||||
RefuseAny: srvConf.RefuseAny,
|
||||
TrustedProxies: netutil.SliceSubnetSet(trustedPrefixes),
|
||||
CacheMinTTL: srvConf.CacheMinTTL,
|
||||
CacheMaxTTL: srvConf.CacheMaxTTL,
|
||||
CacheOptimistic: srvConf.CacheOptimistic,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
PrivateRDNSUpstreamConfig: srvConf.PrivateRDNSUpstreamConfig,
|
||||
BeforeRequestHandler: s,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
HTTPSServerName: aghhttp.UserAgent(),
|
||||
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
|
||||
MaxGoroutines: srvConf.MaxGoroutines,
|
||||
UseDNS64: srvConf.UseDNS64,
|
||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||
UsePrivateRDNS: srvConf.UsePrivateRDNS,
|
||||
PrivateSubnets: s.privateNets,
|
||||
MessageConstructor: s,
|
||||
}
|
||||
|
||||
if srvConf.EDNSClientSubnet.UseCustom {
|
||||
@@ -452,12 +465,33 @@ func (s *Server) prepareIpsetListSettings() (err error) {
|
||||
}
|
||||
|
||||
ipsets := stringutil.SplitTrimmed(string(data), "\n")
|
||||
ipsets = stringutil.FilterOut(ipsets, IsCommentOrEmpty)
|
||||
|
||||
log.Debug("dns: using %d ipset rules from file %q", len(ipsets), fn)
|
||||
|
||||
return s.ipset.init(ipsets)
|
||||
}
|
||||
|
||||
// loadUpstreams parses upstream DNS servers from the configured file or from
|
||||
// the configuration itself.
|
||||
func (conf *ServerConfig) loadUpstreams() (upstreams []string, err error) {
|
||||
if conf.UpstreamDNSFileName == "" {
|
||||
return stringutil.FilterOut(conf.UpstreamDNS, IsCommentOrEmpty), nil
|
||||
}
|
||||
|
||||
var data []byte
|
||||
data, err = os.ReadFile(conf.UpstreamDNSFileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading upstream from file: %w", err)
|
||||
}
|
||||
|
||||
upstreams = stringutil.SplitTrimmed(string(data), "\n")
|
||||
|
||||
log.Debug("dnsforward: got %d upstreams in %q", len(upstreams), conf.UpstreamDNSFileName)
|
||||
|
||||
return stringutil.FilterOut(upstreams, IsCommentOrEmpty), nil
|
||||
}
|
||||
|
||||
// collectListenAddr adds addrPort to addrs. It also adds its port to
|
||||
// unspecPorts if its address is unspecified.
|
||||
func collectListenAddr(
|
||||
@@ -529,8 +563,8 @@ func (m *combinedAddrPortSet) Has(addrPort netip.AddrPort) (ok bool) {
|
||||
return m.ports.Has(addrPort.Port()) && m.addrs.Has(addrPort.Addr())
|
||||
}
|
||||
|
||||
// filterOut filters out all the upstreams that match um. It returns all the
|
||||
// closing errors joined.
|
||||
// filterOutAddrs filters out all the upstreams that match um. It returns all
|
||||
// the closing errors joined.
|
||||
func filterOutAddrs(upsConf *proxy.UpstreamConfig, set addrPortSet) (err error) {
|
||||
var errs []error
|
||||
delFunc := func(u upstream.Upstream) (ok bool) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package dnsforward
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -64,6 +64,8 @@ func newRR(t *testing.T, name string, qtype uint16, ttl uint32, val any) (rr dns
|
||||
}
|
||||
|
||||
func TestServer_HandleDNSRequest_dns64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
ipv4Domain = "ipv4.only."
|
||||
ipv6Domain = "ipv6.only."
|
||||
@@ -252,33 +254,33 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) {
|
||||
localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
require.Len(pt, m.Question, 1)
|
||||
require.Equal(pt, m.Question[0].Name, ptr64Domain)
|
||||
resp := (&dns.Msg{
|
||||
Answer: []dns.RR{localRR},
|
||||
}).SetReply(m)
|
||||
|
||||
resp := (&dns.Msg{}).SetReply(m)
|
||||
resp.Answer = []dns.RR{localRR}
|
||||
|
||||
require.NoError(t, w.WriteMsg(resp))
|
||||
})
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, localUpsHdlr).String()
|
||||
|
||||
client := &dns.Client{
|
||||
Net: "tcp",
|
||||
Timeout: 1 * time.Second,
|
||||
Net: string(proxy.ProtoTCP),
|
||||
Timeout: testTimeout,
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
upsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
q := req.Question[0]
|
||||
require.Contains(pt, tc.upsAns, q.Qtype)
|
||||
|
||||
require.Contains(pt, tc.upsAns, q.Qtype)
|
||||
answer := tc.upsAns[q.Qtype]
|
||||
|
||||
resp := (&dns.Msg{
|
||||
Answer: answer[sectionAnswer],
|
||||
Ns: answer[sectionAuthority],
|
||||
Extra: answer[sectionAdditional],
|
||||
}).SetReply(req)
|
||||
resp := (&dns.Msg{}).SetReply(req)
|
||||
resp.Answer = answer[sectionAnswer]
|
||||
resp.Ns = answer[sectionAuthority]
|
||||
resp.Extra = answer[sectionAdditional]
|
||||
|
||||
require.NoError(pt, w.WriteMsg(resp))
|
||||
})
|
||||
@@ -308,10 +310,54 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) {
|
||||
|
||||
req := (&dns.Msg{}).SetQuestion(tc.qname, tc.qtype)
|
||||
|
||||
resp, _, excErr := client.Exchange(req, s.dnsProxy.Addr(proxy.ProtoTCP).String())
|
||||
resp, _, excErr := client.Exchange(req, s.proxy().Addr(proxy.ProtoTCP).String())
|
||||
require.NoError(t, excErr)
|
||||
|
||||
require.Equal(t, tc.wantAns, resp.Answer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_dns64WithDisabledRDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Shouldn't go to upstream at all.
|
||||
panicHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
panic("not implemented")
|
||||
})
|
||||
upsAddr := aghtest.StartLocalhostUpstream(t, panicHdlr).String()
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, panicHdlr).String()
|
||||
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
UseDNS64: true,
|
||||
Config: Config{
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
UpstreamDNS: []string{upsAddr},
|
||||
},
|
||||
UsePrivateRDNS: false,
|
||||
LocalPTRResolvers: []string{localUpsAddr},
|
||||
ServePlainDNS: true,
|
||||
})
|
||||
startDeferStop(t, s)
|
||||
|
||||
mappedIPv6 := net.ParseIP("64:ff9b::102:304")
|
||||
arpa, err := netutil.IPToReversedAddr(mappedIPv6)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := (&dns.Msg{}).SetQuestion(dns.Fqdn(arpa), dns.TypePTR)
|
||||
|
||||
cli := &dns.Client{
|
||||
Net: string(proxy.ProtoTCP),
|
||||
Timeout: testTimeout,
|
||||
}
|
||||
|
||||
resp, _, err := cli.Exchange(req, s.proxy().Addr(proxy.ProtoTCP).String())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, dns.RcodeNameError, resp.Rcode)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
@@ -135,12 +135,6 @@ type Server struct {
|
||||
// WHOIS, etc.
|
||||
addrProc client.AddressProcessor
|
||||
|
||||
// localResolvers is a DNS proxy instance used to resolve PTR records for
|
||||
// addresses considered private as per the [privateNets].
|
||||
//
|
||||
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
|
||||
localResolvers *proxy.Proxy
|
||||
|
||||
// sysResolvers used to fetch system resolvers to use by default for private
|
||||
// PTR resolving.
|
||||
sysResolvers SystemResolvers
|
||||
@@ -158,12 +152,6 @@ type Server struct {
|
||||
// [upstream.Resolver] interface.
|
||||
bootResolvers []*upstream.UpstreamResolver
|
||||
|
||||
// recDetector is a cache for recursive requests. It is used to detect and
|
||||
// prevent recursive requests only for private upstreams.
|
||||
//
|
||||
// See https://github.com/adguardTeam/adGuardHome/issues/3185#issuecomment-851048135.
|
||||
recDetector *recursionDetector
|
||||
|
||||
// dns64Pref is the NAT64 prefix used for DNS64 response mapping. The major
|
||||
// part of DNS64 happens inside the [proxy] package, but there still are
|
||||
// some places where response mapping is needed (e.g. DHCP).
|
||||
@@ -212,14 +200,6 @@ type DNSCreateParams struct {
|
||||
LocalDomain string
|
||||
}
|
||||
|
||||
const (
|
||||
// recursionTTL is the time recursive request is cached for.
|
||||
recursionTTL = 1 * time.Second
|
||||
// cachedRecurrentReqNum is the maximum number of cached recurrent
|
||||
// requests.
|
||||
cachedRecurrentReqNum = 1000
|
||||
)
|
||||
|
||||
// NewServer creates a new instance of the dnsforward.Server
|
||||
// Note: this function must be called only once
|
||||
//
|
||||
@@ -256,7 +236,6 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
||||
// TODO(e.burkov): Use some case-insensitive string comparison.
|
||||
localDomainSuffix: strings.ToLower(localDomainSuffix),
|
||||
etcHosts: etcHosts,
|
||||
recDetector: newRecursionDetector(recursionTTL, cachedRecurrentReqNum),
|
||||
clientIDCache: cache.New(cache.Config{
|
||||
EnableLRU: true,
|
||||
MaxCount: defaultClientIDCacheCount,
|
||||
@@ -366,6 +345,7 @@ func (s *Server) Exchange(ip netip.Addr) (host string, ttl time.Duration, err er
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
// TODO(e.burkov): Migrate to [netip.Addr] already.
|
||||
arpa, err := netutil.IPToReversedAddr(ip.AsSlice())
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reversing ip: %w", err)
|
||||
@@ -386,25 +366,23 @@ func (s *Server) Exchange(ip netip.Addr) (host string, ttl time.Duration, err er
|
||||
}
|
||||
|
||||
dctx := &proxy.DNSContext{
|
||||
Proto: "udp",
|
||||
Req: req,
|
||||
Proto: proxy.ProtoUDP,
|
||||
Req: req,
|
||||
IsPrivateClient: true,
|
||||
}
|
||||
|
||||
var resolver *proxy.Proxy
|
||||
var errMsg string
|
||||
if s.privateNets.Contains(ip) {
|
||||
if !s.conf.UsePrivateRDNS {
|
||||
return "", 0, nil
|
||||
}
|
||||
|
||||
resolver = s.localResolvers
|
||||
errMsg = "resolving a private address: %w"
|
||||
s.recDetector.add(*req)
|
||||
dctx.RequestedPrivateRDNS = netip.PrefixFrom(ip, ip.BitLen())
|
||||
} else {
|
||||
resolver = s.internalProxy
|
||||
errMsg = "resolving an address: %w"
|
||||
}
|
||||
if err = resolver.Resolve(dctx); err != nil {
|
||||
if err = s.internalProxy.Resolve(dctx); err != nil {
|
||||
return "", 0, fmt.Errorf(errMsg, err)
|
||||
}
|
||||
|
||||
@@ -473,103 +451,6 @@ func (s *Server) startLocked() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// prepareLocalResolvers initializes the local upstreams configuration using
|
||||
// boot as bootstrap. It assumes that s.serverLock is locked or s not running.
|
||||
func (s *Server) prepareLocalResolvers(
|
||||
boot upstream.Resolver,
|
||||
) (uc *proxy.UpstreamConfig, err error) {
|
||||
set, err := s.conf.ourAddrsSet()
|
||||
if err != nil {
|
||||
// Don't wrap the error because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := s.conf.LocalPTRResolvers
|
||||
confNeedsFiltering := len(resolvers) > 0
|
||||
if confNeedsFiltering {
|
||||
resolvers = stringutil.FilterOut(resolvers, IsCommentOrEmpty)
|
||||
} else {
|
||||
sysResolvers := slices.DeleteFunc(slices.Clone(s.sysResolvers.Addrs()), set.Has)
|
||||
resolvers = make([]string, 0, len(sysResolvers))
|
||||
for _, r := range sysResolvers {
|
||||
resolvers = append(resolvers, r.String())
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", resolvers)
|
||||
|
||||
uc, err = s.prepareUpstreamConfig(resolvers, nil, &upstream.Options{
|
||||
Bootstrap: boot,
|
||||
Timeout: defaultLocalTimeout,
|
||||
// TODO(e.burkov): Should we verify server's certificates?
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preparing private upstreams: %w", err)
|
||||
}
|
||||
|
||||
if confNeedsFiltering {
|
||||
err = filterOutAddrs(uc, set)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("filtering private upstreams: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// LocalResolversError is an error type for errors during local resolvers setup.
|
||||
// This is only needed to distinguish these errors from errors returned by
|
||||
// creating the proxy.
|
||||
type LocalResolversError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ error = (*LocalResolversError)(nil)
|
||||
|
||||
// Error implements the error interface for *LocalResolversError.
|
||||
func (err *LocalResolversError) Error() (s string) {
|
||||
return fmt.Sprintf("creating local resolvers: %s", err.Err)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ errors.Wrapper = (*LocalResolversError)(nil)
|
||||
|
||||
// Unwrap implements the [errors.Wrapper] interface for *LocalResolversError.
|
||||
func (err *LocalResolversError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
// setupLocalResolvers initializes and sets the resolvers for local addresses.
|
||||
// It assumes s.serverLock is locked or s not running. It returns the upstream
|
||||
// configuration used for private PTR resolving, or nil if it's disabled. Note,
|
||||
// that it's safe to put nil into [proxy.Config.PrivateRDNSUpstreamConfig].
|
||||
func (s *Server) setupLocalResolvers(boot upstream.Resolver) (uc *proxy.UpstreamConfig, err error) {
|
||||
if !s.conf.UsePrivateRDNS {
|
||||
// It's safe to put nil into [proxy.Config.PrivateRDNSUpstreamConfig].
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
uc, err = s.prepareLocalResolvers(boot)
|
||||
if err != nil {
|
||||
// Don't wrap the error because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localResolvers, err := proxy.New(&proxy.Config{
|
||||
UpstreamConfig: uc,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, &LocalResolversError{Err: err}
|
||||
}
|
||||
|
||||
s.localResolvers = localResolvers
|
||||
|
||||
// TODO(e.burkov): Should we also consider the DNS64 usage?
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// Prepare initializes parameters of s using data from conf. conf must not be
|
||||
// nil.
|
||||
func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
@@ -586,7 +467,7 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
|
||||
s.initDefaultSettings()
|
||||
|
||||
boot, err := s.prepareInternalDNS()
|
||||
err = s.prepareInternalDNS()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
@@ -608,12 +489,6 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
return fmt.Errorf("preparing access: %w", err)
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
|
||||
proxyConfig.PrivateRDNSUpstreamConfig, err = s.setupLocalResolvers(boot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up resolvers: %w", err)
|
||||
}
|
||||
|
||||
proxyConfig.Fallbacks, err = s.setupFallbackDNS()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up fallback dns servers: %w", err)
|
||||
@@ -626,8 +501,6 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
|
||||
s.dnsProxy = dnsProxy
|
||||
|
||||
s.recDetector.clear()
|
||||
|
||||
s.setupAddrProc()
|
||||
|
||||
s.registerHandlers()
|
||||
@@ -635,36 +508,127 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareInternalDNS initializes the internal state of s before initializing
|
||||
// the primary DNS proxy instance. It assumes s.serverLock is locked or the
|
||||
// Server not running.
|
||||
func (s *Server) prepareInternalDNS() (boot upstream.Resolver, err error) {
|
||||
err = s.prepareIpsetListSettings()
|
||||
// prepareUpstreamSettings sets upstream DNS server settings.
|
||||
func (s *Server) prepareUpstreamSettings(boot upstream.Resolver) (err error) {
|
||||
// Load upstreams either from the file, or from the settings
|
||||
var upstreams []string
|
||||
upstreams, err = s.conf.loadUpstreams()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preparing ipset settings: %w", err)
|
||||
return fmt.Errorf("loading upstreams: %w", err)
|
||||
}
|
||||
|
||||
s.bootstrap, s.bootResolvers, err = s.createBootstrap(s.conf.BootstrapDNS, &upstream.Options{
|
||||
Timeout: DefaultTimeout,
|
||||
uc, err := newUpstreamConfig(upstreams, defaultDNS, &upstream.Options{
|
||||
Bootstrap: boot,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
// Use a customized set of RootCAs, because Go's default mechanism of
|
||||
// loading TLS roots does not always work properly on some routers so we're
|
||||
// loading roots manually and pass it here.
|
||||
//
|
||||
// See [aghtls.SystemRootCAs].
|
||||
//
|
||||
// TODO(a.garipov): Investigate if that's true.
|
||||
RootCAs: s.conf.TLSv12Roots,
|
||||
CipherSuites: s.conf.TLSCiphers,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing upstream config: %w", err)
|
||||
}
|
||||
|
||||
s.conf.UpstreamConfig = uc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrivateRDNSError is returned when the private rDNS upstreams are
|
||||
// invalid but enabled.
|
||||
//
|
||||
// TODO(e.burkov): Consider allowing to use incomplete private rDNS upstreams
|
||||
// configuration in proxy when the private rDNS function is enabled. In theory,
|
||||
// proxy supports the case when no upstreams provided to resolve the private
|
||||
// request, since it already supports this for DNS64-prefixed PTR requests.
|
||||
type PrivateRDNSError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// Error implements the [errors.Error] interface.
|
||||
func (e *PrivateRDNSError) Error() (s string) {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func (e *PrivateRDNSError) Unwrap() (err error) {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// prepareLocalResolvers initializes the private RDNS upstream configuration
|
||||
// according to the server's settings. It assumes s.serverLock is locked or the
|
||||
// Server not running.
|
||||
func (s *Server) prepareLocalResolvers() (uc *proxy.UpstreamConfig, err error) {
|
||||
if !s.conf.UsePrivateRDNS {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var ownAddrs addrPortSet
|
||||
ownAddrs, err = s.conf.ourAddrsSet()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := &upstream.Options{
|
||||
Bootstrap: s.bootstrap,
|
||||
Timeout: defaultLocalTimeout,
|
||||
// TODO(e.burkov): Should we verify server's certificates?
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
}
|
||||
|
||||
addrs := s.conf.LocalPTRResolvers
|
||||
uc, err = newPrivateConfig(addrs, ownAddrs, s.sysResolvers, s.privateNets, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preparing resolvers: %w", err)
|
||||
}
|
||||
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// prepareInternalDNS initializes the internal state of s before initializing
|
||||
// the primary DNS proxy instance. It assumes s.serverLock is locked or the
|
||||
// Server not running.
|
||||
func (s *Server) prepareInternalDNS() (err error) {
|
||||
err = s.prepareIpsetListSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing ipset settings: %w", err)
|
||||
}
|
||||
|
||||
bootOpts := &upstream.Options{
|
||||
Timeout: DefaultTimeout,
|
||||
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
|
||||
}
|
||||
|
||||
s.bootstrap, s.bootResolvers, err = newBootstrap(s.conf.BootstrapDNS, s.etcHosts, bootOpts)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.prepareUpstreamSettings(s.bootstrap)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return s.bootstrap, err
|
||||
return err
|
||||
}
|
||||
|
||||
s.conf.PrivateRDNSUpstreamConfig, err = s.prepareLocalResolvers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.prepareInternalProxy()
|
||||
if err != nil {
|
||||
return s.bootstrap, fmt.Errorf("preparing internal proxy: %w", err)
|
||||
return fmt.Errorf("preparing internal proxy: %w", err)
|
||||
}
|
||||
|
||||
return s.bootstrap, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupFallbackDNS initializes the fallback DNS servers.
|
||||
@@ -743,10 +707,16 @@ func validateBlockingMode(
|
||||
func (s *Server) prepareInternalProxy() (err error) {
|
||||
srvConf := s.conf
|
||||
conf := &proxy.Config{
|
||||
CacheEnabled: true,
|
||||
CacheSizeBytes: 4096,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
MaxGoroutines: s.conf.MaxGoroutines,
|
||||
CacheEnabled: true,
|
||||
CacheSizeBytes: 4096,
|
||||
PrivateRDNSUpstreamConfig: srvConf.PrivateRDNSUpstreamConfig,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
MaxGoroutines: srvConf.MaxGoroutines,
|
||||
UseDNS64: srvConf.UseDNS64,
|
||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||
UsePrivateRDNS: srvConf.UsePrivateRDNS,
|
||||
PrivateSubnets: s.privateNets,
|
||||
MessageConstructor: s,
|
||||
}
|
||||
|
||||
err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, srvConf.FastestTimeout.Duration)
|
||||
@@ -782,11 +752,6 @@ func (s *Server) stopLocked() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
logCloserErr(s.internalProxy.UpstreamConfig, "dnsforward: closing internal resolvers: %s")
|
||||
if s.localResolvers != nil {
|
||||
logCloserErr(s.localResolvers.UpstreamConfig, "dnsforward: closing local resolvers: %s")
|
||||
}
|
||||
|
||||
for _, b := range s.bootResolvers {
|
||||
logCloserErr(b, "dnsforward: closing bootstrap %s: %s", b.Address())
|
||||
}
|
||||
@@ -908,5 +873,5 @@ func (s *Server) IsBlockedClient(ip netip.Addr, clientID string) (blocked bool,
|
||||
blocked = true
|
||||
}
|
||||
|
||||
return blocked, aghalg.Coalesce(rule, clientID)
|
||||
return blocked, cmp.Or(rule, clientID)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"context"
|
||||
"cmp"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
@@ -190,7 +189,7 @@ func newGoogleUpstream() (u upstream.Upstream) {
|
||||
return &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "google.upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
return cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, googleDomainName, "8.8.8.8"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
@@ -253,7 +252,7 @@ func sendTestMessagesAsync(t *testing.T, conn *dns.Conn) {
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
for i := 0; i < testMessagesCount; i++ {
|
||||
for range testMessagesCount {
|
||||
msg := createGoogleATestMessage()
|
||||
wg.Add(1)
|
||||
|
||||
@@ -276,7 +275,7 @@ func sendTestMessagesAsync(t *testing.T, conn *dns.Conn) {
|
||||
func sendTestMessages(t *testing.T, conn *dns.Conn) {
|
||||
t.Helper()
|
||||
|
||||
for i := 0; i < testMessagesCount; i++ {
|
||||
for i := range testMessagesCount {
|
||||
req := createGoogleATestMessage()
|
||||
err := conn.WriteMsg(req)
|
||||
assert.NoErrorf(t, err, "cannot write message #%d: %s", i, err)
|
||||
@@ -491,19 +490,10 @@ func TestServerRace(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSafeSearch(t *testing.T) {
|
||||
resolver := &aghtest.Resolver{
|
||||
OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) {
|
||||
ip4, ip6 := aghtest.HostToIPs(host)
|
||||
|
||||
return []net.IP{ip4.AsSlice(), ip6.AsSlice()}, nil
|
||||
},
|
||||
}
|
||||
|
||||
safeSearchConf := filtering.SafeSearchConfig{
|
||||
Enabled: true,
|
||||
Google: true,
|
||||
Yandex: true,
|
||||
CustomResolver: resolver,
|
||||
Enabled: true,
|
||||
Google: true,
|
||||
Yandex: true,
|
||||
}
|
||||
|
||||
filterConf := &filtering.Config{
|
||||
@@ -540,7 +530,6 @@ func TestSafeSearch(t *testing.T) {
|
||||
client := &dns.Client{}
|
||||
|
||||
yandexIP := netip.AddrFrom4([4]byte{213, 180, 193, 56})
|
||||
googleIP, _ := aghtest.HostToIPs("forcesafesearch.google.com")
|
||||
|
||||
testCases := []struct {
|
||||
host string
|
||||
@@ -564,19 +553,19 @@ func TestSafeSearch(t *testing.T) {
|
||||
wantCNAME: "",
|
||||
}, {
|
||||
host: "www.google.com.",
|
||||
want: googleIP,
|
||||
want: netip.Addr{},
|
||||
wantCNAME: "forcesafesearch.google.com.",
|
||||
}, {
|
||||
host: "www.google.com.af.",
|
||||
want: googleIP,
|
||||
want: netip.Addr{},
|
||||
wantCNAME: "forcesafesearch.google.com.",
|
||||
}, {
|
||||
host: "www.google.be.",
|
||||
want: googleIP,
|
||||
want: netip.Addr{},
|
||||
wantCNAME: "forcesafesearch.google.com.",
|
||||
}, {
|
||||
host: "www.google.by.",
|
||||
want: googleIP,
|
||||
want: netip.Addr{},
|
||||
wantCNAME: "forcesafesearch.google.com.",
|
||||
}}
|
||||
|
||||
@@ -593,12 +582,15 @@ func TestSafeSearch(t *testing.T) {
|
||||
|
||||
cname := testutil.RequireTypeAssert[*dns.CNAME](t, reply.Answer[0])
|
||||
assert.Equal(t, tc.wantCNAME, cname.Target)
|
||||
|
||||
a := testutil.RequireTypeAssert[*dns.A](t, reply.Answer[1])
|
||||
assert.NotEmpty(t, a.A)
|
||||
} else {
|
||||
require.Len(t, reply.Answer, 1)
|
||||
}
|
||||
|
||||
a := testutil.RequireTypeAssert[*dns.A](t, reply.Answer[len(reply.Answer)-1])
|
||||
assert.Equal(t, net.IP(tc.want.AsSlice()), a.A)
|
||||
a := testutil.RequireTypeAssert[*dns.A](t, reply.Answer[0])
|
||||
assert.Equal(t, net.IP(tc.want.AsSlice()), a.A)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -691,7 +683,7 @@ func TestServerCustomClientUpstream(t *testing.T) {
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
atomic.AddUint32(&upsCalledCounter, 1)
|
||||
|
||||
return aghalg.Coalesce(
|
||||
return cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, "host", "192.168.0.1"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
@@ -1152,7 +1144,7 @@ func TestRewrite(t *testing.T) {
|
||||
}))
|
||||
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
return cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, "example.org", "4.3.2.1"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
@@ -1481,7 +1473,7 @@ func TestServer_Exchange(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
extUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := aghalg.Coalesce(
|
||||
resp := cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, onesRevExtIPv4, dns.Fqdn(onesHost)),
|
||||
doubleTTL(aghtest.MatchedResponse(req, dns.TypePTR, twosRevExtIPv4, dns.Fqdn(twosHost))),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
@@ -1495,7 +1487,7 @@ func TestServer_Exchange(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
locUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := aghalg.Coalesce(
|
||||
resp := cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, revLocIPv4, dns.Fqdn(localDomainHost)),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
@@ -143,7 +143,7 @@ func (s *Server) filterDNSRewrite(
|
||||
res *filtering.Result,
|
||||
pctx *proxy.DNSContext,
|
||||
) (err error) {
|
||||
resp := s.makeResponse(req)
|
||||
resp := s.replyCompressed(req)
|
||||
dnsrr := res.DNSRewriteResult
|
||||
if dnsrr == nil {
|
||||
return errors.Error("no dns rewrite rule content")
|
||||
|
||||
@@ -1,57 +1,17 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// beforeRequestHandler is the handler that is called before any other
|
||||
// processing, including logs. It performs access checks and puts the client
|
||||
// ID, if there is one, into the server's cache.
|
||||
func (s *Server) beforeRequestHandler(
|
||||
_ *proxy.Proxy,
|
||||
pctx *proxy.DNSContext,
|
||||
) (reply bool, err error) {
|
||||
clientID, err := s.clientIDFromDNSContext(pctx)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getting clientid: %w", err)
|
||||
}
|
||||
|
||||
blocked, _ := s.IsBlockedClient(pctx.Addr.Addr(), clientID)
|
||||
if blocked {
|
||||
return s.preBlockedResponse(pctx)
|
||||
}
|
||||
|
||||
if len(pctx.Req.Question) == 1 {
|
||||
q := pctx.Req.Question[0]
|
||||
qt := q.Qtype
|
||||
host := aghnet.NormalizeDomain(q.Name)
|
||||
if s.access.isBlockedHost(host, qt) {
|
||||
log.Debug("access: request %s %s is in access blocklist", dns.Type(qt), host)
|
||||
|
||||
return s.preBlockedResponse(pctx)
|
||||
}
|
||||
}
|
||||
|
||||
if clientID != "" {
|
||||
key := [8]byte{}
|
||||
binary.BigEndian.PutUint64(key[:], pctx.RequestID)
|
||||
s.clientIDCache.Set(key[:], []byte(clientID))
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// clientRequestFilteringSettings looks up client filtering settings using the
|
||||
// client's IP address and ID, if any, from dctx.
|
||||
func (s *Server) clientRequestFilteringSettings(dctx *dnsContext) (setts *filtering.Settings) {
|
||||
@@ -71,6 +31,7 @@ func (s *Server) filterDNSRequest(dctx *dnsContext) (res *filtering.Result, err
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
host := strings.TrimSuffix(q.Name, ".")
|
||||
|
||||
resVal, err := s.dnsFilter.CheckHost(host, q.Qtype, dctx.setts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("checking host %q: %w", host, err)
|
||||
@@ -79,22 +40,15 @@ func (s *Server) filterDNSRequest(dctx *dnsContext) (res *filtering.Result, err
|
||||
// TODO(a.garipov): Make CheckHost return a pointer.
|
||||
res = &resVal
|
||||
switch {
|
||||
case res.IsFiltered:
|
||||
log.Debug(
|
||||
"dnsforward: host %q is filtered, reason: %q; rule: %q",
|
||||
host,
|
||||
res.Reason,
|
||||
res.Rules[0].Text,
|
||||
)
|
||||
pctx.Res = s.genDNSFilterMessage(pctx, res)
|
||||
case res.Reason.In(filtering.Rewritten, filtering.RewrittenRule) &&
|
||||
res.CanonName != "" &&
|
||||
len(res.IPList) == 0:
|
||||
case isRewrittenCNAME(res):
|
||||
// Resolve the new canonical name, not the original host name. The
|
||||
// original question is readded in processFilteringAfterResponse.
|
||||
dctx.origQuestion = q
|
||||
req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||
case res.Reason == filtering.Rewritten:
|
||||
case res.IsFiltered:
|
||||
log.Debug("dnsforward: host %q is filtered, reason: %q", host, res.Reason)
|
||||
pctx.Res = s.genDNSFilterMessage(pctx, res)
|
||||
case res.Reason.In(filtering.Rewritten, filtering.FilteredSafeSearch):
|
||||
pctx.Res = s.getCNAMEWithIPs(req, res.IPList, res.CanonName)
|
||||
case res.Reason.In(filtering.RewrittenRule, filtering.RewrittenAutoHosts):
|
||||
if err = s.filterDNSRewrite(req, res, pctx); err != nil {
|
||||
@@ -105,6 +59,17 @@ func (s *Server) filterDNSRequest(dctx *dnsContext) (res *filtering.Result, err
|
||||
return res, err
|
||||
}
|
||||
|
||||
// isRewrittenCNAME returns true if the request considered to be rewritten with
|
||||
// CNAME and has no resolved IPs.
|
||||
func isRewrittenCNAME(res *filtering.Result) (ok bool) {
|
||||
return res.Reason.In(
|
||||
filtering.Rewritten,
|
||||
filtering.RewrittenRule,
|
||||
filtering.FilteredSafeSearch) &&
|
||||
res.CanonName != "" &&
|
||||
len(res.IPList) == 0
|
||||
}
|
||||
|
||||
// checkHostRules checks the host against filters. It is safe for concurrent
|
||||
// use.
|
||||
func (s *Server) checkHostRules(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -261,55 +262,17 @@ func (req *jsonDNSConfig) checkUpstreamMode() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// checkBootstrap returns an error if any bootstrap address is invalid.
|
||||
func (req *jsonDNSConfig) checkBootstrap() (err error) {
|
||||
if req.Bootstraps == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var b string
|
||||
defer func() { err = errors.Annotate(err, "checking bootstrap %s: %w", b) }()
|
||||
|
||||
for _, b = range *req.Bootstraps {
|
||||
if b == "" {
|
||||
return errors.Error("empty")
|
||||
}
|
||||
|
||||
var resolver *upstream.UpstreamResolver
|
||||
if resolver, err = upstream.NewUpstreamResolver(b, nil); err != nil {
|
||||
// Don't wrap the error because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
if err = resolver.Close(); err != nil {
|
||||
return fmt.Errorf("closing %s: %w", b, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkFallbacks returns an error if any fallback address is invalid.
|
||||
func (req *jsonDNSConfig) checkFallbacks() (err error) {
|
||||
if req.Fallbacks == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = proxy.ParseUpstreamsConfig(*req.Fallbacks, &upstream.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fallback servers: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validate returns an error if any field of req is invalid.
|
||||
//
|
||||
// TODO(s.chzhen): Parse, don't validate.
|
||||
func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) {
|
||||
func (req *jsonDNSConfig) validate(
|
||||
ownAddrs addrPortSet,
|
||||
sysResolvers SystemResolvers,
|
||||
privateNets netutil.SubnetSet,
|
||||
) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "validating dns config: %w") }()
|
||||
|
||||
err = req.validateUpstreamDNSServers(privateNets)
|
||||
err = req.validateUpstreamDNSServers(ownAddrs, sysResolvers, privateNets)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
@@ -342,20 +305,77 @@ func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkBootstrap returns an error if any bootstrap address is invalid.
|
||||
func (req *jsonDNSConfig) checkBootstrap() (err error) {
|
||||
if req.Bootstraps == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var b string
|
||||
defer func() { err = errors.Annotate(err, "checking bootstrap %s: %w", b) }()
|
||||
|
||||
for _, b = range *req.Bootstraps {
|
||||
if b == "" {
|
||||
return errors.Error("empty")
|
||||
}
|
||||
|
||||
var resolver *upstream.UpstreamResolver
|
||||
if resolver, err = upstream.NewUpstreamResolver(b, nil); err != nil {
|
||||
// Don't wrap the error because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
if err = resolver.Close(); err != nil {
|
||||
return fmt.Errorf("closing %s: %w", b, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkPrivateRDNS returns an error if the configuration of the private RDNS is
|
||||
// not valid.
|
||||
func (req *jsonDNSConfig) checkPrivateRDNS(
|
||||
ownAddrs addrPortSet,
|
||||
sysResolvers SystemResolvers,
|
||||
privateNets netutil.SubnetSet,
|
||||
) (err error) {
|
||||
if (req.UsePrivateRDNS == nil || !*req.UsePrivateRDNS) && req.LocalPTRUpstreams == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
addrs := cmp.Or(req.LocalPTRUpstreams, &[]string{})
|
||||
|
||||
uc, err := newPrivateConfig(*addrs, ownAddrs, sysResolvers, privateNets, &upstream.Options{})
|
||||
err = errors.WithDeferred(err, uc.Close())
|
||||
if err != nil {
|
||||
return fmt.Errorf("private upstream servers: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateUpstreamDNSServers returns an error if any field of req is invalid.
|
||||
func (req *jsonDNSConfig) validateUpstreamDNSServers(privateNets netutil.SubnetSet) (err error) {
|
||||
func (req *jsonDNSConfig) validateUpstreamDNSServers(
|
||||
ownAddrs addrPortSet,
|
||||
sysResolvers SystemResolvers,
|
||||
privateNets netutil.SubnetSet,
|
||||
) (err error) {
|
||||
var uc *proxy.UpstreamConfig
|
||||
opts := &upstream.Options{}
|
||||
|
||||
if req.Upstreams != nil {
|
||||
_, err = proxy.ParseUpstreamsConfig(*req.Upstreams, &upstream.Options{})
|
||||
uc, err = proxy.ParseUpstreamsConfig(*req.Upstreams, opts)
|
||||
err = errors.WithDeferred(err, uc.Close())
|
||||
if err != nil {
|
||||
return fmt.Errorf("upstream servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if req.LocalPTRUpstreams != nil {
|
||||
err = ValidateUpstreamsPrivate(*req.LocalPTRUpstreams, privateNets)
|
||||
if err != nil {
|
||||
return fmt.Errorf("private upstream servers: %w", err)
|
||||
}
|
||||
err = req.checkPrivateRDNS(ownAddrs, sysResolvers, privateNets)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = req.checkBootstrap()
|
||||
@@ -364,10 +384,12 @@ func (req *jsonDNSConfig) validateUpstreamDNSServers(privateNets netutil.SubnetS
|
||||
return err
|
||||
}
|
||||
|
||||
err = req.checkFallbacks()
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
if req.Fallbacks != nil {
|
||||
uc, err = proxy.ParseUpstreamsConfig(*req.Fallbacks, opts)
|
||||
err = errors.WithDeferred(err, uc.Close())
|
||||
if err != nil {
|
||||
return fmt.Errorf("fallback servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -436,7 +458,16 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = req.validate(s.privateNets)
|
||||
// TODO(e.burkov): Consider prebuilding this set on startup.
|
||||
ourAddrs, err := s.conf.ourAddrsSet()
|
||||
if err != nil {
|
||||
// TODO(e.burkov): Put into openapi.
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "getting our addresses: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = req.validate(ourAddrs, s.sysResolvers, s.privateNets)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
|
||||
@@ -587,7 +618,7 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var boots []*upstream.UpstreamResolver
|
||||
opts.Bootstrap, boots, err = s.createBootstrap(req.BootstrapDNS, opts)
|
||||
opts.Bootstrap, boots, err = newBootstrap(req.BootstrapDNS, s.etcHosts, opts)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to parse bootstrap servers: %s", err)
|
||||
|
||||
|
||||
@@ -245,9 +245,8 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "local_ptr_upstreams_bad",
|
||||
wantSet: `validating dns config: ` +
|
||||
`private upstream servers: checking domain-specific upstreams: ` +
|
||||
`bad arpa domain name "non.arpa.": not a reversed ip network`,
|
||||
wantSet: `validating dns config: private upstream servers: ` +
|
||||
`bad arpa domain name "non.arpa": not a reversed ip network`,
|
||||
}, {
|
||||
name: "local_ptr_upstreams_null",
|
||||
wantSet: "",
|
||||
@@ -318,58 +317,6 @@ func TestIsCommentOrEmpty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUpstreamsPrivate(t *testing.T) {
|
||||
ss := netutil.SubnetSetFunc(netutil.IsLocallyServed)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErr string
|
||||
u string
|
||||
}{{
|
||||
name: "success_address",
|
||||
wantErr: ``,
|
||||
u: "[/1.0.0.127.in-addr.arpa/]#",
|
||||
}, {
|
||||
name: "success_subnet",
|
||||
wantErr: ``,
|
||||
u: "[/127.in-addr.arpa/]#",
|
||||
}, {
|
||||
name: "not_arpa_subnet",
|
||||
wantErr: `checking domain-specific upstreams: ` +
|
||||
`bad arpa domain name "hello.world.": not a reversed ip network`,
|
||||
u: "[/hello.world/]#",
|
||||
}, {
|
||||
name: "non-private_arpa_address",
|
||||
wantErr: `checking domain-specific upstreams: ` +
|
||||
`arpa domain "1.2.3.4.in-addr.arpa." should point to a locally-served network`,
|
||||
u: "[/1.2.3.4.in-addr.arpa/]#",
|
||||
}, {
|
||||
name: "non-private_arpa_subnet",
|
||||
wantErr: `checking domain-specific upstreams: ` +
|
||||
`arpa domain "128.in-addr.arpa." should point to a locally-served network`,
|
||||
u: "[/128.in-addr.arpa/]#",
|
||||
}, {
|
||||
name: "several_bad",
|
||||
wantErr: `checking domain-specific upstreams: ` +
|
||||
`arpa domain "1.2.3.4.in-addr.arpa." should point to a locally-served network` + "\n" +
|
||||
`bad arpa domain name "non.arpa.": not a reversed ip network`,
|
||||
u: "[/non.arpa/1.2.3.4.in-addr.arpa/127.in-addr.arpa/]#",
|
||||
}, {
|
||||
name: "partial_good",
|
||||
wantErr: "",
|
||||
u: "[/a.1.2.3.10.in-addr.arpa/a.10.in-addr.arpa/]#",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
set := []string{"192.168.0.1", tc.u}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := ValidateUpstreamsPrivate(set, ss)
|
||||
testutil.AssertErrorMsg(t, tc.wantErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newLocalUpstreamListener(t *testing.T, port uint16, handler dns.Handler) (real netip.AddrPort) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -11,17 +11,21 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// makeResponse creates a DNS response by req and sets necessary flags. It also
|
||||
// guarantees that req.Question will be not empty.
|
||||
func (s *Server) makeResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||
resp = &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
RecursionAvailable: true,
|
||||
},
|
||||
Compress: true,
|
||||
}
|
||||
// TODO(e.burkov): Name all the methods by a [proxy.MessageConstructor]
|
||||
// template. Also extract all the methods to a separate entity.
|
||||
|
||||
resp.SetReply(req)
|
||||
// reply creates a DNS response for req.
|
||||
func (*Server) reply(req *dns.Msg, code int) (resp *dns.Msg) {
|
||||
resp = (&dns.Msg{}).SetRcode(req, code)
|
||||
resp.RecursionAvailable = true
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// replyCompressed creates a DNS response for req and sets the compress flag.
|
||||
func (s *Server) replyCompressed(req *dns.Msg) (resp *dns.Msg) {
|
||||
resp = s.reply(req, dns.RcodeSuccess)
|
||||
resp.Compress = true
|
||||
|
||||
return resp
|
||||
}
|
||||
@@ -48,10 +52,10 @@ func (s *Server) genDNSFilterMessage(
|
||||
) (resp *dns.Msg) {
|
||||
req := dctx.Req
|
||||
qt := req.Question[0].Qtype
|
||||
if qt != dns.TypeA && qt != dns.TypeAAAA {
|
||||
if qt != dns.TypeA && qt != dns.TypeAAAA && qt != dns.TypeHTTPS {
|
||||
m, _, _ := s.dnsFilter.BlockingMode()
|
||||
if m == filtering.BlockingModeNullIP {
|
||||
return s.makeResponse(req)
|
||||
return s.replyCompressed(req)
|
||||
}
|
||||
|
||||
return s.newMsgNODATA(req)
|
||||
@@ -75,7 +79,7 @@ func (s *Server) genDNSFilterMessage(
|
||||
// getCNAMEWithIPs generates a filtered response to req for with CNAME record
|
||||
// and provided ips.
|
||||
func (s *Server) getCNAMEWithIPs(req *dns.Msg, ips []netip.Addr, cname string) (resp *dns.Msg) {
|
||||
resp = s.makeResponse(req)
|
||||
resp = s.replyCompressed(req)
|
||||
|
||||
originalName := req.Question[0].Name
|
||||
|
||||
@@ -121,13 +125,13 @@ func (s *Server) genForBlockingMode(req *dns.Msg, ips []netip.Addr) (resp *dns.M
|
||||
case filtering.BlockingModeNullIP:
|
||||
return s.makeResponseNullIP(req)
|
||||
case filtering.BlockingModeNXDOMAIN:
|
||||
return s.genNXDomain(req)
|
||||
return s.NewMsgNXDOMAIN(req)
|
||||
case filtering.BlockingModeREFUSED:
|
||||
return s.makeResponseREFUSED(req)
|
||||
default:
|
||||
log.Error("dnsforward: invalid blocking mode %q", mode)
|
||||
|
||||
return s.makeResponse(req)
|
||||
return s.replyCompressed(req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,25 +152,18 @@ func (s *Server) makeResponseCustomIP(
|
||||
// genDNSFilterMessage.
|
||||
log.Error("dnsforward: invalid msg type %s for custom IP blocking mode", dns.Type(qt))
|
||||
|
||||
return s.makeResponse(req)
|
||||
return s.replyCompressed(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) genServerFailure(request *dns.Msg) *dns.Msg {
|
||||
resp := dns.Msg{}
|
||||
resp.SetRcode(request, dns.RcodeServerFailure)
|
||||
resp.RecursionAvailable = true
|
||||
return &resp
|
||||
}
|
||||
|
||||
func (s *Server) genARecord(request *dns.Msg, ip netip.Addr) *dns.Msg {
|
||||
resp := s.makeResponse(request)
|
||||
resp := s.replyCompressed(request)
|
||||
resp.Answer = append(resp.Answer, s.genAnswerA(request, ip))
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Server) genAAAARecord(request *dns.Msg, ip netip.Addr) *dns.Msg {
|
||||
resp := s.makeResponse(request)
|
||||
resp := s.replyCompressed(request)
|
||||
resp.Answer = append(resp.Answer, s.genAnswerAAAA(request, ip))
|
||||
return resp
|
||||
}
|
||||
@@ -252,7 +249,7 @@ func (s *Server) genResponseWithIPs(req *dns.Msg, ips []netip.Addr) (resp *dns.M
|
||||
// Go on and return an empty response.
|
||||
}
|
||||
|
||||
resp = s.makeResponse(req)
|
||||
resp = s.replyCompressed(req)
|
||||
resp.Answer = ans
|
||||
|
||||
return resp
|
||||
@@ -288,7 +285,7 @@ func (s *Server) makeResponseNullIP(req *dns.Msg) (resp *dns.Msg) {
|
||||
case dns.TypeAAAA:
|
||||
resp = s.genResponseWithIPs(req, []netip.Addr{netip.IPv6Unspecified()})
|
||||
default:
|
||||
resp = s.makeResponse(req)
|
||||
resp = s.replyCompressed(req)
|
||||
}
|
||||
|
||||
return resp
|
||||
@@ -298,7 +295,7 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
|
||||
if newAddr == "" {
|
||||
log.Info("dnsforward: block host is not specified")
|
||||
|
||||
return s.genServerFailure(request)
|
||||
return s.NewMsgSERVFAIL(request)
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(newAddr)
|
||||
@@ -321,17 +318,17 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
|
||||
if prx == nil {
|
||||
log.Debug("dnsforward: %s", srvClosedErr)
|
||||
|
||||
return s.genServerFailure(request)
|
||||
return s.NewMsgSERVFAIL(request)
|
||||
}
|
||||
|
||||
err = prx.Resolve(newContext)
|
||||
if err != nil {
|
||||
log.Info("dnsforward: looking up replacement host %q: %s", newAddr, err)
|
||||
|
||||
return s.genServerFailure(request)
|
||||
return s.NewMsgSERVFAIL(request)
|
||||
}
|
||||
|
||||
resp := s.makeResponse(request)
|
||||
resp := s.replyCompressed(request)
|
||||
if newContext.Res != nil {
|
||||
for _, answer := range newContext.Res.Answer {
|
||||
answer.Header().Name = request.Question[0].Name
|
||||
@@ -342,48 +339,21 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
|
||||
return resp
|
||||
}
|
||||
|
||||
// preBlockedResponse returns a protocol-appropriate response for a request that
|
||||
// was blocked by access settings.
|
||||
func (s *Server) preBlockedResponse(pctx *proxy.DNSContext) (reply bool, err error) {
|
||||
if pctx.Proto == proxy.ProtoUDP || pctx.Proto == proxy.ProtoDNSCrypt {
|
||||
// Return nil so that dnsproxy drops the connection and thus
|
||||
// prevent DNS amplification attacks.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
pctx.Res = s.makeResponseREFUSED(pctx.Req)
|
||||
|
||||
// Return true so that dnsproxy responds with the REFUSED message.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Create REFUSED DNS response
|
||||
func (s *Server) makeResponseREFUSED(request *dns.Msg) *dns.Msg {
|
||||
resp := dns.Msg{}
|
||||
resp.SetRcode(request, dns.RcodeRefused)
|
||||
resp.RecursionAvailable = true
|
||||
return &resp
|
||||
func (s *Server) makeResponseREFUSED(req *dns.Msg) *dns.Msg {
|
||||
return s.reply(req, dns.RcodeRefused)
|
||||
}
|
||||
|
||||
// newMsgNODATA returns a properly initialized NODATA response.
|
||||
//
|
||||
// See https://www.rfc-editor.org/rfc/rfc2308#section-2.2.
|
||||
func (s *Server) newMsgNODATA(req *dns.Msg) (resp *dns.Msg) {
|
||||
resp = (&dns.Msg{}).SetRcode(req, dns.RcodeSuccess)
|
||||
resp.RecursionAvailable = true
|
||||
resp = s.reply(req, dns.RcodeSuccess)
|
||||
resp.Ns = s.genSOA(req)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Server) genNXDomain(request *dns.Msg) *dns.Msg {
|
||||
resp := dns.Msg{}
|
||||
resp.SetRcode(request, dns.RcodeNameError)
|
||||
resp.RecursionAvailable = true
|
||||
resp.Ns = s.genSOA(request)
|
||||
return &resp
|
||||
}
|
||||
|
||||
func (s *Server) genSOA(request *dns.Msg) []dns.RR {
|
||||
zone := ""
|
||||
if len(request.Question) > 0 {
|
||||
@@ -415,5 +385,43 @@ func (s *Server) genSOA(request *dns.Msg) []dns.RR {
|
||||
if len(zone) > 0 && zone[0] != '.' {
|
||||
soa.Mbox += zone
|
||||
}
|
||||
|
||||
return []dns.RR{&soa}
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ proxy.MessageConstructor = (*Server)(nil)
|
||||
|
||||
// NewMsgNXDOMAIN implements the [proxy.MessageConstructor] interface for
|
||||
// *Server.
|
||||
func (s *Server) NewMsgNXDOMAIN(req *dns.Msg) (resp *dns.Msg) {
|
||||
resp = s.reply(req, dns.RcodeNameError)
|
||||
resp.Ns = s.genSOA(req)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// NewMsgSERVFAIL implements the [proxy.MessageConstructor] interface for
|
||||
// *Server.
|
||||
func (s *Server) NewMsgSERVFAIL(req *dns.Msg) (resp *dns.Msg) {
|
||||
return s.reply(req, dns.RcodeServerFailure)
|
||||
}
|
||||
|
||||
// NewMsgNOTIMPLEMENTED implements the [proxy.MessageConstructor] interface for
|
||||
// *Server.
|
||||
func (s *Server) NewMsgNOTIMPLEMENTED(req *dns.Msg) (resp *dns.Msg) {
|
||||
resp = s.reply(req, dns.RcodeNotImplemented)
|
||||
|
||||
// Most of the Internet and especially the inner core has an MTU of at least
|
||||
// 1500 octets. Maximum DNS/UDP payload size for IPv6 on MTU 1500 ethernet
|
||||
// is 1452 (1500 minus 40 (IPv6 header size) minus 8 (UDP header size)).
|
||||
//
|
||||
// See appendix A of https://datatracker.ietf.org/doc/draft-ietf-dnsop-avoid-fragmentation/17.
|
||||
const maxUDPPayload = 1452
|
||||
|
||||
// NOTIMPLEMENTED without EDNS is treated as 'we don't support EDNS', so
|
||||
// explicitly set it.
|
||||
resp.SetEdns0(maxUDPPayload, false)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -34,11 +31,6 @@ type dnsContext struct {
|
||||
// response is modified by filters.
|
||||
origResp *dns.Msg
|
||||
|
||||
// unreversedReqIP stores an IP address obtained from a PTR request if it
|
||||
// was parsed successfully and belongs to one of the locally served IP
|
||||
// ranges.
|
||||
unreversedReqIP netip.Addr
|
||||
|
||||
// err is the error returned from a processing function.
|
||||
err error
|
||||
|
||||
@@ -63,10 +55,6 @@ type dnsContext struct {
|
||||
// responseAD shows if the response had the AD bit set.
|
||||
responseAD bool
|
||||
|
||||
// isLocalClient shows if client's IP address is from locally served
|
||||
// network.
|
||||
isLocalClient bool
|
||||
|
||||
// isDHCPHost is true if the request for a local domain name and the DHCP is
|
||||
// available for this request.
|
||||
isDHCPHost bool
|
||||
@@ -109,15 +97,11 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, pctx *proxy.DNSContext) error
|
||||
// (*proxy.Proxy).handleDNSRequest method performs it before calling the
|
||||
// appropriate handler.
|
||||
mods := []modProcessFunc{
|
||||
s.processRecursion,
|
||||
s.processInitial,
|
||||
s.processDDRQuery,
|
||||
s.processDetermineLocal,
|
||||
s.processDHCPHosts,
|
||||
s.processRestrictLocal,
|
||||
s.processDHCPAddrs,
|
||||
s.processFilteringBeforeRequest,
|
||||
s.processLocalPTR,
|
||||
s.processUpstream,
|
||||
s.processFilteringAfterResponse,
|
||||
s.ipset.process,
|
||||
@@ -145,24 +129,6 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, pctx *proxy.DNSContext) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// processRecursion checks the incoming request and halts its handling by
|
||||
// answering NXDOMAIN if s has tried to resolve it recently.
|
||||
func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing recursion")
|
||||
defer log.Debug("dnsforward: finished processing recursion")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
|
||||
if msg := pctx.Req; msg != nil && s.recDetector.check(*msg) {
|
||||
log.Debug("dnsforward: recursion detected resolving %q", msg.Question[0].Name)
|
||||
pctx.Res = s.genNXDomain(pctx.Req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// mozillaFQDN is the domain used to signal the Firefox browser to not use its
|
||||
// own DoH server.
|
||||
//
|
||||
@@ -199,14 +165,14 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
|
||||
}
|
||||
|
||||
if (qt == dns.TypeA || qt == dns.TypeAAAA) && q.Name == mozillaFQDN {
|
||||
pctx.Res = s.genNXDomain(pctx.Req)
|
||||
pctx.Res = s.NewMsgNXDOMAIN(pctx.Req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
if q.Name == healthcheckFQDN {
|
||||
// Generate a NODATA negative response to make nslookup exit with 0.
|
||||
pctx.Res = s.makeResponse(pctx.Req)
|
||||
pctx.Res = s.replyCompressed(pctx.Req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
@@ -272,7 +238,7 @@ func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
|
||||
//
|
||||
// [draft standard]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
|
||||
func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||
resp = s.makeResponse(req)
|
||||
resp = s.replyCompressed(req)
|
||||
if req.Question[0].Qtype != dns.TypeSVCB {
|
||||
return resp
|
||||
}
|
||||
@@ -339,19 +305,6 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||
return resp
|
||||
}
|
||||
|
||||
// processDetermineLocal determines if the client's IP address is from locally
|
||||
// served network and saves the result into the context.
|
||||
func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing local detection")
|
||||
defer log.Debug("dnsforward: finished processing local detection")
|
||||
|
||||
rc = resultCodeSuccess
|
||||
|
||||
dctx.isLocalClient = s.privateNets.Contains(dctx.proxyCtx.Addr.Addr())
|
||||
|
||||
return rc
|
||||
}
|
||||
|
||||
// processDHCPHosts respond to A requests if the target hostname is known to
|
||||
// the server. It responds with a mapped IP address if the DNS64 is enabled and
|
||||
// the request is for AAAA.
|
||||
@@ -370,9 +323,9 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
if !dctx.isLocalClient {
|
||||
if !pctx.IsPrivateClient {
|
||||
log.Debug("dnsforward: %q requests for dhcp host %q", pctx.Addr, dhcpHost)
|
||||
pctx.Res = s.genNXDomain(req)
|
||||
pctx.Res = s.NewMsgNXDOMAIN(req)
|
||||
|
||||
// Do not even put into query log.
|
||||
return resultCodeFinish
|
||||
@@ -389,7 +342,7 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
|
||||
|
||||
log.Debug("dnsforward: dhcp record for %q is %s", dhcpHost, ip)
|
||||
|
||||
resp := s.makeResponse(req)
|
||||
resp := s.replyCompressed(req)
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
a := &dns.A{
|
||||
@@ -416,141 +369,6 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// indexFirstV4Label returns the index at which the reversed IPv4 address
|
||||
// starts, assuming the domain is pre-validated ARPA domain having in-addr and
|
||||
// arpa labels removed.
|
||||
func indexFirstV4Label(domain string) (idx int) {
|
||||
idx = len(domain)
|
||||
for labelsNum := 0; labelsNum < net.IPv4len && idx > 0; labelsNum++ {
|
||||
curIdx := strings.LastIndexByte(domain[:idx-1], '.') + 1
|
||||
_, parseErr := strconv.ParseUint(domain[curIdx:idx-1], 10, 8)
|
||||
if parseErr != nil {
|
||||
return idx
|
||||
}
|
||||
|
||||
idx = curIdx
|
||||
}
|
||||
|
||||
return idx
|
||||
}
|
||||
|
||||
// indexFirstV6Label returns the index at which the reversed IPv6 address
|
||||
// starts, assuming the domain is pre-validated ARPA domain having ip6 and arpa
|
||||
// labels removed.
|
||||
func indexFirstV6Label(domain string) (idx int) {
|
||||
idx = len(domain)
|
||||
for labelsNum := 0; labelsNum < net.IPv6len*2 && idx > 0; labelsNum++ {
|
||||
curIdx := idx - len("a.")
|
||||
if curIdx > 1 && domain[curIdx-1] != '.' {
|
||||
return idx
|
||||
}
|
||||
|
||||
nibble := domain[curIdx]
|
||||
if (nibble < '0' || nibble > '9') && (nibble < 'a' || nibble > 'f') {
|
||||
return idx
|
||||
}
|
||||
|
||||
idx = curIdx
|
||||
}
|
||||
|
||||
return idx
|
||||
}
|
||||
|
||||
// extractARPASubnet tries to convert a reversed ARPA address being a part of
|
||||
// domain to an IP network. domain must be an FQDN.
|
||||
//
|
||||
// TODO(e.burkov): Move to golibs.
|
||||
func extractARPASubnet(domain string) (pref netip.Prefix, err error) {
|
||||
err = netutil.ValidateDomainName(strings.TrimSuffix(domain, "."))
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return netip.Prefix{}, err
|
||||
}
|
||||
|
||||
const (
|
||||
v4Suffix = "in-addr.arpa."
|
||||
v6Suffix = "ip6.arpa."
|
||||
)
|
||||
|
||||
domain = strings.ToLower(domain)
|
||||
|
||||
var idx int
|
||||
switch {
|
||||
case strings.HasSuffix(domain, v4Suffix):
|
||||
idx = indexFirstV4Label(domain[:len(domain)-len(v4Suffix)])
|
||||
case strings.HasSuffix(domain, v6Suffix):
|
||||
idx = indexFirstV6Label(domain[:len(domain)-len(v6Suffix)])
|
||||
default:
|
||||
return netip.Prefix{}, &netutil.AddrError{
|
||||
Err: netutil.ErrNotAReversedSubnet,
|
||||
Kind: netutil.AddrKindARPA,
|
||||
Addr: domain,
|
||||
}
|
||||
}
|
||||
|
||||
return netutil.PrefixFromReversedAddr(domain[idx:])
|
||||
}
|
||||
|
||||
// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
|
||||
// in locally served network from external clients.
|
||||
func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing local restriction")
|
||||
defer log.Debug("dnsforward: finished processing local restriction")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
if q.Qtype != dns.TypePTR {
|
||||
// No need for restriction.
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
subnet, err := extractARPASubnet(q.Name)
|
||||
if err != nil {
|
||||
if errors.Is(err, netutil.ErrNotAReversedSubnet) {
|
||||
log.Debug("dnsforward: request is not for arpa domain")
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: parsing reversed addr: %s", err)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
// Restrict an access to local addresses for external clients. We also
|
||||
// assume that all the DHCP leases we give are locally served or at least
|
||||
// shouldn't be accessible externally.
|
||||
subnetAddr := subnet.Addr()
|
||||
if !s.privateNets.Contains(subnetAddr) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: addr %s is from locally served network", subnetAddr)
|
||||
|
||||
if !dctx.isLocalClient {
|
||||
log.Debug("dnsforward: %q requests an internal ip", pctx.Addr)
|
||||
pctx.Res = s.genNXDomain(req)
|
||||
|
||||
// Do not even put into query log.
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
// Do not perform unreversing ever again.
|
||||
dctx.unreversedReqIP = subnetAddr
|
||||
|
||||
// There is no need to filter request from external addresses since this
|
||||
// code is only executed when the request is for locally served ARPA
|
||||
// hostname so disable redundant filters.
|
||||
dctx.setts.ParentalEnabled = false
|
||||
dctx.setts.SafeBrowsingEnabled = false
|
||||
dctx.setts.SafeSearchEnabled = false
|
||||
dctx.setts.ServicesRules = nil
|
||||
|
||||
// Nothing to restrict.
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// processDHCPAddrs responds to PTR requests if the target IP is leased by the
|
||||
// DHCP server.
|
||||
func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
|
||||
@@ -562,23 +380,27 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
ipAddr := dctx.unreversedReqIP
|
||||
if ipAddr == (netip.Addr{}) {
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
pref := pctx.RequestedPrivateRDNS
|
||||
// TODO(e.burkov): Consider answering authoritatively for SOA and NS
|
||||
// queries.
|
||||
if pref == (netip.Prefix{}) || q.Qtype != dns.TypePTR {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
host := s.dhcpServer.HostByIP(ipAddr)
|
||||
addr := pref.Addr()
|
||||
host := s.dhcpServer.HostByIP(addr)
|
||||
if host == "" {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: dhcp client %s is %q", ipAddr, host)
|
||||
log.Debug("dnsforward: dhcp client %s is %q", addr, host)
|
||||
|
||||
req := pctx.Req
|
||||
resp := s.makeResponse(req)
|
||||
resp := s.replyCompressed(req)
|
||||
ptr := &dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Name: q.Name,
|
||||
Rrtype: dns.TypePTR,
|
||||
// TODO(e.burkov): Use [dhcpsvc.Lease.Expiry]. See
|
||||
// https://github.com/AdguardTeam/AdGuardHome/issues/3932.
|
||||
@@ -593,62 +415,20 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// processLocalPTR responds to PTR requests if the target IP is detected to be
|
||||
// inside the local network and the query was not answered from DHCP.
|
||||
func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing local ptr")
|
||||
defer log.Debug("dnsforward: finished processing local ptr")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
if pctx.Res != nil {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
ip := dctx.unreversedReqIP
|
||||
if ip == (netip.Addr{}) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
if s.conf.UsePrivateRDNS {
|
||||
s.recDetector.add(*pctx.Req)
|
||||
if err := s.localResolvers.Resolve(pctx); err != nil {
|
||||
log.Debug("dnsforward: resolving private address: %s", err)
|
||||
|
||||
// Generate the server failure if the private upstream configuration
|
||||
// is empty.
|
||||
//
|
||||
// This is a crutch, see TODO at [Server.localResolvers].
|
||||
if errors.Is(err, upstream.ErrNoUpstreams) {
|
||||
pctx.Res = s.genServerFailure(pctx.Req)
|
||||
|
||||
// Do not even put into query log.
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
dctx.err = err
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
}
|
||||
|
||||
if pctx.Res == nil {
|
||||
pctx.Res = s.genNXDomain(pctx.Req)
|
||||
|
||||
// Do not even put into query log.
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// Apply filtering logic
|
||||
func (s *Server) processFilteringBeforeRequest(dctx *dnsContext) (rc resultCode) {
|
||||
log.Debug("dnsforward: started processing filtering before req")
|
||||
defer log.Debug("dnsforward: finished processing filtering before req")
|
||||
|
||||
if dctx.proxyCtx.RequestedPrivateRDNS != (netip.Prefix{}) {
|
||||
// There is no need to filter request for locally served ARPA hostname
|
||||
// so disable redundant filters.
|
||||
dctx.setts.ParentalEnabled = false
|
||||
dctx.setts.SafeBrowsingEnabled = false
|
||||
dctx.setts.SafeSearchEnabled = false
|
||||
dctx.setts.ServicesRules = nil
|
||||
}
|
||||
|
||||
if dctx.proxyCtx.Res != nil {
|
||||
// Go on since the response is already set.
|
||||
return resultCodeSuccess
|
||||
@@ -695,7 +475,7 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
|
||||
// local domain name if there is one.
|
||||
name := req.Question[0].Name
|
||||
log.Debug("dnsforward: dhcp client hostname %q was not filtered", name[:len(name)-1])
|
||||
pctx.Res = s.genNXDomain(req)
|
||||
pctx.Res = s.NewMsgNXDOMAIN(req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
@@ -712,21 +492,7 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
if err := prx.Resolve(pctx); err != nil {
|
||||
if errors.Is(err, upstream.ErrNoUpstreams) {
|
||||
// Do not even put into querylog. Currently this happens either
|
||||
// when the private resolvers enabled and the request is DNS64 PTR,
|
||||
// or when the client isn't considered local by prx.
|
||||
//
|
||||
// TODO(e.burkov): Make proxy detect local client the same way as
|
||||
// AGH does.
|
||||
pctx.Res = s.genNXDomain(req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
dctx.err = err
|
||||
|
||||
if dctx.err = prx.Resolve(pctx); dctx.err != nil {
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
@@ -810,7 +576,7 @@ func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
|
||||
}
|
||||
|
||||
// Use the ClientID first, since it has a higher priority.
|
||||
id := stringutil.Coalesce(clientID, pctx.Addr.Addr().String())
|
||||
id := cmp.Or(clientID, pctx.Addr.Addr().String())
|
||||
upsConf, err := s.conf.ClientsContainer.UpstreamConfigByID(id, s.bootstrap)
|
||||
if err != nil {
|
||||
log.Error("dnsforward: getting custom upstreams for client %s: %s", id, err)
|
||||
@@ -835,7 +601,8 @@ func (s *Server) processFilteringAfterResponse(dctx *dnsContext) (rc resultCode)
|
||||
return resultCodeSuccess
|
||||
case
|
||||
filtering.Rewritten,
|
||||
filtering.RewrittenRule:
|
||||
filtering.RewrittenRule,
|
||||
filtering.FilteredSafeSearch:
|
||||
|
||||
if dctx.origQuestion.Name == "" {
|
||||
// origQuestion is set in case we get only CNAME without IP from
|
||||
@@ -845,11 +612,10 @@ func (s *Server) processFilteringAfterResponse(dctx *dnsContext) (rc resultCode)
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
pctx.Req.Question[0], pctx.Res.Question[0] = dctx.origQuestion, dctx.origQuestion
|
||||
if len(pctx.Res.Answer) > 0 {
|
||||
rr := s.genAnswerCNAME(pctx.Req, res.CanonName)
|
||||
answer := append([]dns.RR{rr}, pctx.Res.Answer...)
|
||||
pctx.Res.Answer = answer
|
||||
}
|
||||
|
||||
rr := s.genAnswerCNAME(pctx.Req, res.CanonName)
|
||||
answer := append([]dns.RR{rr}, pctx.Res.Answer...)
|
||||
pctx.Res.Answer = answer
|
||||
|
||||
return resultCodeSuccess
|
||||
default:
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
@@ -70,8 +71,6 @@ func TestServer_ProcessInitial(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -171,8 +170,6 @@ func TestServer_ProcessFilteringAfterResponse(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -379,44 +376,6 @@ func createTestDNSFilter(t *testing.T) (f *filtering.DNSFilter) {
|
||||
return f
|
||||
}
|
||||
|
||||
func TestServer_ProcessDetermineLocal(t *testing.T) {
|
||||
s := &Server{
|
||||
privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
want assert.BoolAssertionFunc
|
||||
name string
|
||||
cliAddr netip.AddrPort
|
||||
}{{
|
||||
want: assert.True,
|
||||
name: "local",
|
||||
cliAddr: netip.MustParseAddrPort("192.168.0.1:1"),
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "external",
|
||||
cliAddr: netip.MustParseAddrPort("250.249.0.1:1"),
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "invalid",
|
||||
cliAddr: netip.AddrPort{},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
proxyCtx := &proxy.DNSContext{
|
||||
Addr: tc.cliAddr,
|
||||
}
|
||||
dctx := &dnsContext{
|
||||
proxyCtx: proxyCtx,
|
||||
}
|
||||
s.processDetermineLocal(dctx)
|
||||
|
||||
tc.want(t, dctx.isLocalClient)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
|
||||
const (
|
||||
localDomainSuffix = "lan"
|
||||
@@ -486,9 +445,9 @@ func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
|
||||
|
||||
dctx := &dnsContext{
|
||||
proxyCtx: &proxy.DNSContext{
|
||||
Req: req,
|
||||
Req: req,
|
||||
IsPrivateClient: tc.isLocalCli,
|
||||
},
|
||||
isLocalClient: tc.isLocalCli,
|
||||
}
|
||||
|
||||
res := s.processDHCPHosts(dctx)
|
||||
@@ -621,9 +580,9 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
|
||||
|
||||
dctx := &dnsContext{
|
||||
proxyCtx: &proxy.DNSContext{
|
||||
Req: req,
|
||||
Req: req,
|
||||
IsPrivateClient: true,
|
||||
},
|
||||
isLocalClient: true,
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -658,19 +617,28 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ProcessRestrictLocal(t *testing.T) {
|
||||
// TODO(e.burkov): Rewrite this test to use the whole server instead of just
|
||||
// testing the [handleDNSRequest] method. See comment on
|
||||
// "from_external_for_local" test case.
|
||||
func TestServer_HandleDNSRequest_restrictLocal(t *testing.T) {
|
||||
intAddr := netip.MustParseAddr("192.168.1.1")
|
||||
intPTRQuestion, err := netutil.IPToReversedAddr(intAddr.AsSlice())
|
||||
require.NoError(t, err)
|
||||
|
||||
extAddr := netip.MustParseAddr("254.253.252.1")
|
||||
extPTRQuestion, err := netutil.IPToReversedAddr(extAddr.AsSlice())
|
||||
require.NoError(t, err)
|
||||
|
||||
const (
|
||||
extPTRQuestion = "251.252.253.254.in-addr.arpa."
|
||||
extPTRAnswer = "host1.example.net."
|
||||
intPTRQuestion = "1.1.168.192.in-addr.arpa."
|
||||
intPTRAnswer = "some.local-client."
|
||||
extPTRAnswer = "host1.example.net."
|
||||
intPTRAnswer = "some.local-client."
|
||||
)
|
||||
|
||||
localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := aghalg.Coalesce(
|
||||
resp := cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, extPTRQuestion, extPTRAnswer),
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, intPTRQuestion, intPTRAnswer),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
(&dns.Msg{}).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
require.NoError(testutil.PanicT{}, w.WriteMsg(resp))
|
||||
@@ -696,123 +664,165 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
|
||||
startDeferStop(t, s)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
want string
|
||||
question net.IP
|
||||
cliAddr netip.AddrPort
|
||||
wantLen int
|
||||
name string
|
||||
question string
|
||||
wantErr error
|
||||
wantAns []dns.RR
|
||||
isPrivate bool
|
||||
}{{
|
||||
name: "from_local_to_external",
|
||||
want: "host1.example.net.",
|
||||
question: net.IP{254, 253, 252, 251},
|
||||
cliAddr: netip.MustParseAddrPort("192.168.10.10:1"),
|
||||
wantLen: 1,
|
||||
name: "from_local_for_external",
|
||||
question: extPTRQuestion,
|
||||
wantErr: nil,
|
||||
wantAns: []dns.RR{&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: dns.Fqdn(extPTRQuestion),
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 60,
|
||||
Rdlength: uint16(len(extPTRAnswer) + 1),
|
||||
},
|
||||
Ptr: dns.Fqdn(extPTRAnswer),
|
||||
}},
|
||||
isPrivate: true,
|
||||
}, {
|
||||
name: "from_external_for_local",
|
||||
want: "",
|
||||
question: net.IP{192, 168, 1, 1},
|
||||
cliAddr: netip.MustParseAddrPort("254.253.252.251:1"),
|
||||
wantLen: 0,
|
||||
// In theory this case is not reproducible because [proxy.Proxy] should
|
||||
// respond to such queries with NXDOMAIN before they reach
|
||||
// [Server.handleDNSRequest].
|
||||
name: "from_external_for_local",
|
||||
question: intPTRQuestion,
|
||||
wantErr: upstream.ErrNoUpstreams,
|
||||
wantAns: nil,
|
||||
isPrivate: false,
|
||||
}, {
|
||||
name: "from_local_for_local",
|
||||
want: "some.local-client.",
|
||||
question: net.IP{192, 168, 1, 1},
|
||||
cliAddr: netip.MustParseAddrPort("192.168.1.2:1"),
|
||||
wantLen: 1,
|
||||
question: intPTRQuestion,
|
||||
wantErr: nil,
|
||||
wantAns: []dns.RR{&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: dns.Fqdn(intPTRQuestion),
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 60,
|
||||
Rdlength: uint16(len(intPTRAnswer) + 1),
|
||||
},
|
||||
Ptr: dns.Fqdn(intPTRAnswer),
|
||||
}},
|
||||
isPrivate: true,
|
||||
}, {
|
||||
name: "from_external_for_external",
|
||||
want: "host1.example.net.",
|
||||
question: net.IP{254, 253, 252, 251},
|
||||
cliAddr: netip.MustParseAddrPort("254.253.252.255:1"),
|
||||
wantLen: 1,
|
||||
question: extPTRQuestion,
|
||||
wantErr: nil,
|
||||
wantAns: []dns.RR{&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: dns.Fqdn(extPTRQuestion),
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 60,
|
||||
Rdlength: uint16(len(extPTRAnswer) + 1),
|
||||
},
|
||||
Ptr: dns.Fqdn(extPTRAnswer),
|
||||
}},
|
||||
isPrivate: false,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
reqAddr, err := dns.ReverseAddr(tc.question.String())
|
||||
require.NoError(t, err)
|
||||
req := createTestMessageWithType(reqAddr, dns.TypePTR)
|
||||
pref, extErr := netutil.ExtractReversedAddr(tc.question)
|
||||
require.NoError(t, extErr)
|
||||
|
||||
req := createTestMessageWithType(dns.Fqdn(tc.question), dns.TypePTR)
|
||||
pctx := &proxy.DNSContext{
|
||||
Proto: proxy.ProtoTCP,
|
||||
Req: req,
|
||||
Addr: tc.cliAddr,
|
||||
Req: req,
|
||||
IsPrivateClient: tc.isPrivate,
|
||||
}
|
||||
// TODO(e.burkov): Configure the subnet set properly.
|
||||
if netutil.IsLocallyServed(pref.Addr()) {
|
||||
pctx.RequestedPrivateRDNS = pref
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err = s.handleDNSRequest(nil, pctx)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pctx.Res)
|
||||
require.Len(t, pctx.Res.Answer, tc.wantLen)
|
||||
err = s.handleDNSRequest(s.dnsProxy, pctx)
|
||||
require.ErrorIs(t, err, tc.wantErr)
|
||||
|
||||
if tc.wantLen > 0 {
|
||||
assert.Equal(t, tc.want, pctx.Res.Answer[0].(*dns.PTR).Ptr)
|
||||
}
|
||||
require.NotNil(t, pctx.Res)
|
||||
assert.Equal(t, tc.wantAns, pctx.Res.Answer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
|
||||
func TestServer_ProcessUpstream_localPTR(t *testing.T) {
|
||||
const locDomain = "some.local."
|
||||
const reqAddr = "1.1.168.192.in-addr.arpa."
|
||||
|
||||
localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := aghalg.Coalesce(
|
||||
resp := cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, reqAddr, locDomain),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
(&dns.Msg{}).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
require.NoError(testutil.PanicT{}, w.WriteMsg(resp))
|
||||
})
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, localUpsHdlr).String()
|
||||
|
||||
s := createTestServer(
|
||||
t,
|
||||
&filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
},
|
||||
ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
Config: Config{
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
UsePrivateRDNS: true,
|
||||
LocalPTRResolvers: []string{localUpsAddr},
|
||||
ServePlainDNS: true,
|
||||
},
|
||||
)
|
||||
|
||||
var proxyCtx *proxy.DNSContext
|
||||
var dnsCtx *dnsContext
|
||||
setup := func(use bool) {
|
||||
proxyCtx = &proxy.DNSContext{
|
||||
Addr: testClientAddrPort,
|
||||
Req: createTestMessageWithType(reqAddr, dns.TypePTR),
|
||||
newPrxCtx := func() (prxCtx *proxy.DNSContext) {
|
||||
return &proxy.DNSContext{
|
||||
Addr: testClientAddrPort,
|
||||
Req: createTestMessageWithType(reqAddr, dns.TypePTR),
|
||||
IsPrivateClient: true,
|
||||
RequestedPrivateRDNS: netip.MustParsePrefix("192.168.1.1/32"),
|
||||
}
|
||||
dnsCtx = &dnsContext{
|
||||
proxyCtx: proxyCtx,
|
||||
unreversedReqIP: netip.MustParseAddr("192.168.1.1"),
|
||||
}
|
||||
s.conf.UsePrivateRDNS = use
|
||||
}
|
||||
|
||||
t.Run("enabled", func(t *testing.T) {
|
||||
setup(true)
|
||||
s := createTestServer(
|
||||
t,
|
||||
&filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
},
|
||||
ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
Config: Config{
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
UsePrivateRDNS: true,
|
||||
LocalPTRResolvers: []string{localUpsAddr},
|
||||
ServePlainDNS: true,
|
||||
},
|
||||
)
|
||||
pctx := newPrxCtx()
|
||||
|
||||
rc := s.processLocalPTR(dnsCtx)
|
||||
rc := s.processUpstream(&dnsContext{proxyCtx: pctx})
|
||||
require.Equal(t, resultCodeSuccess, rc)
|
||||
require.NotEmpty(t, proxyCtx.Res.Answer)
|
||||
require.NotEmpty(t, pctx.Res.Answer)
|
||||
ptr := testutil.RequireTypeAssert[*dns.PTR](t, pctx.Res.Answer[0])
|
||||
|
||||
assert.Equal(t, locDomain, proxyCtx.Res.Answer[0].(*dns.PTR).Ptr)
|
||||
assert.Equal(t, locDomain, ptr.Ptr)
|
||||
})
|
||||
|
||||
t.Run("disabled", func(t *testing.T) {
|
||||
setup(false)
|
||||
s := createTestServer(
|
||||
t,
|
||||
&filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
},
|
||||
ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
Config: Config{
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
UsePrivateRDNS: false,
|
||||
LocalPTRResolvers: []string{localUpsAddr},
|
||||
ServePlainDNS: true,
|
||||
},
|
||||
)
|
||||
pctx := newPrxCtx()
|
||||
|
||||
rc := s.processLocalPTR(dnsCtx)
|
||||
require.Equal(t, resultCodeFinish, rc)
|
||||
require.Empty(t, proxyCtx.Res.Answer)
|
||||
rc := s.processUpstream(&dnsContext{proxyCtx: pctx})
|
||||
require.Equal(t, resultCodeError, rc)
|
||||
require.Empty(t, pctx.Res.Answer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -830,129 +840,3 @@ func TestIPStringFromAddr(t *testing.T) {
|
||||
assert.Empty(t, ipStringFromAddr(nil))
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Add fuzzing when moving to golibs.
|
||||
func TestExtractARPASubnet(t *testing.T) {
|
||||
const (
|
||||
v4Suf = `in-addr.arpa.`
|
||||
v4Part = `2.1.` + v4Suf
|
||||
v4Whole = `4.3.` + v4Part
|
||||
|
||||
v6Suf = `ip6.arpa.`
|
||||
v6Part = `4.3.2.1.0.0.0.0.0.0.0.0.0.0.0.0.` + v6Suf
|
||||
v6Whole = `f.e.d.c.0.0.0.0.0.0.0.0.0.0.0.0.` + v6Part
|
||||
)
|
||||
|
||||
v4Pref := netip.MustParsePrefix("1.2.3.4/32")
|
||||
v4PrefPart := netip.MustParsePrefix("1.2.0.0/16")
|
||||
v6Pref := netip.MustParsePrefix("::1234:0:0:0:cdef/128")
|
||||
v6PrefPart := netip.MustParsePrefix("0:0:0:1234::/64")
|
||||
|
||||
testCases := []struct {
|
||||
want netip.Prefix
|
||||
name string
|
||||
domain string
|
||||
wantErr string
|
||||
}{{
|
||||
want: netip.Prefix{},
|
||||
name: "not_an_arpa",
|
||||
domain: "some.domain.name.",
|
||||
wantErr: `bad arpa domain name "some.domain.name.": ` +
|
||||
`not a reversed ip network`,
|
||||
}, {
|
||||
want: netip.Prefix{},
|
||||
name: "bad_domain_name",
|
||||
domain: "abc.123.",
|
||||
wantErr: `bad domain name "abc.123": ` +
|
||||
`bad top-level domain name label "123": all octets are numeric`,
|
||||
}, {
|
||||
want: v4Pref,
|
||||
name: "whole_v4",
|
||||
domain: v4Whole,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: v4PrefPart,
|
||||
name: "partial_v4",
|
||||
domain: v4Part,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: v4Pref,
|
||||
name: "whole_v4_within_domain",
|
||||
domain: "a." + v4Whole,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: v4Pref,
|
||||
name: "whole_v4_additional_label",
|
||||
domain: "5." + v4Whole,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: v4PrefPart,
|
||||
name: "partial_v4_within_domain",
|
||||
domain: "a." + v4Part,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: v4PrefPart,
|
||||
name: "overflow_v4",
|
||||
domain: "256." + v4Part,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: v4PrefPart,
|
||||
name: "overflow_v4_within_domain",
|
||||
domain: "a.256." + v4Part,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: netip.Prefix{},
|
||||
name: "empty_v4",
|
||||
domain: v4Suf,
|
||||
wantErr: `bad arpa domain name "in-addr.arpa": ` +
|
||||
`not a reversed ip network`,
|
||||
}, {
|
||||
want: netip.Prefix{},
|
||||
name: "empty_v4_within_domain",
|
||||
domain: "a." + v4Suf,
|
||||
wantErr: `bad arpa domain name "in-addr.arpa": ` +
|
||||
`not a reversed ip network`,
|
||||
}, {
|
||||
want: v6Pref,
|
||||
name: "whole_v6",
|
||||
domain: v6Whole,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: v6PrefPart,
|
||||
name: "partial_v6",
|
||||
domain: v6Part,
|
||||
}, {
|
||||
want: v6Pref,
|
||||
name: "whole_v6_within_domain",
|
||||
domain: "g." + v6Whole,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: v6Pref,
|
||||
name: "whole_v6_additional_label",
|
||||
domain: "1." + v6Whole,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: v6PrefPart,
|
||||
name: "partial_v6_within_domain",
|
||||
domain: "label." + v6Part,
|
||||
wantErr: "",
|
||||
}, {
|
||||
want: netip.Prefix{},
|
||||
name: "empty_v6",
|
||||
domain: v6Suf,
|
||||
wantErr: `bad arpa domain name "ip6.arpa": not a reversed ip network`,
|
||||
}, {
|
||||
want: netip.Prefix{},
|
||||
name: "empty_v6_within_domain",
|
||||
domain: "g." + v6Suf,
|
||||
wantErr: `bad arpa domain name "ip6.arpa": not a reversed ip network`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
subnet, err := extractARPASubnet(tc.domain)
|
||||
testutil.AssertErrorMsg(t, tc.wantErr, err)
|
||||
assert.Equal(t, tc.want, subnet)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,13 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
|
||||
|
||||
log.Debug("dnsforward: client ip for stats and querylog: %s", ipStr)
|
||||
|
||||
ids := []string{ipStr, dctx.clientID}
|
||||
ids := []string{ipStr}
|
||||
if dctx.clientID != "" {
|
||||
// Use the ClientID first because it has a higher priority. Filters
|
||||
// have the same priority, see applyAdditionalFiltering.
|
||||
ids = []string{dctx.clientID, ipStr}
|
||||
}
|
||||
|
||||
qt, cl := q.Qtype, q.Qclass
|
||||
|
||||
// Synchronize access to s.queryLog and s.stats so they won't be suddenly
|
||||
@@ -124,7 +130,7 @@ func (s *Server) logQuery(dctx *dnsContext, ip net.IP, processingTime time.Durat
|
||||
s.queryLog.Add(p)
|
||||
}
|
||||
|
||||
// updatesStats writes the request into statistics.
|
||||
// updateStats writes the request data into statistics.
|
||||
func (s *Server) updateStats(dctx *dnsContext, clientIP string, processingTime time.Duration) {
|
||||
pctx := dctx.proxyCtx
|
||||
|
||||
|
||||
@@ -2,90 +2,77 @@ package dnsforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// loadUpstreams parses upstream DNS servers from the configured file or from
|
||||
// the configuration itself.
|
||||
func (s *Server) loadUpstreams() (upstreams []string, err error) {
|
||||
if s.conf.UpstreamDNSFileName == "" {
|
||||
return stringutil.FilterOut(s.conf.UpstreamDNS, IsCommentOrEmpty), nil
|
||||
// newBootstrap returns a bootstrap resolver based on the configuration of s.
|
||||
// boots are the upstream resolvers that should be closed after use. r is the
|
||||
// actual bootstrap resolver, which may include the system hosts.
|
||||
//
|
||||
// TODO(e.burkov): This function currently returns a resolver and a slice of
|
||||
// the upstream resolvers, which are essentially the same. boots are returned
|
||||
// for being able to close them afterwards, but it introduces an implicit
|
||||
// contract that r could only be used before that. Anyway, this code should
|
||||
// improve when the [proxy.UpstreamConfig] will become an [upstream.Resolver]
|
||||
// and be used here.
|
||||
func newBootstrap(
|
||||
addrs []string,
|
||||
etcHosts upstream.Resolver,
|
||||
opts *upstream.Options,
|
||||
) (r upstream.Resolver, boots []*upstream.UpstreamResolver, err error) {
|
||||
if len(addrs) == 0 {
|
||||
addrs = defaultBootstrap
|
||||
}
|
||||
|
||||
var data []byte
|
||||
data, err = os.ReadFile(s.conf.UpstreamDNSFileName)
|
||||
boots, err = aghnet.ParseBootstraps(addrs, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading upstream from file: %w", err)
|
||||
// Don't wrap the error, since it's informative enough as is.
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
upstreams = stringutil.SplitTrimmed(string(data), "\n")
|
||||
var parallel upstream.ParallelResolver
|
||||
for _, b := range boots {
|
||||
parallel = append(parallel, upstream.NewCachingResolver(b))
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: got %d upstreams in %q", len(upstreams), s.conf.UpstreamDNSFileName)
|
||||
if etcHosts != nil {
|
||||
r = upstream.ConsequentResolver{etcHosts, parallel}
|
||||
} else {
|
||||
r = parallel
|
||||
}
|
||||
|
||||
return stringutil.FilterOut(upstreams, IsCommentOrEmpty), nil
|
||||
return r, boots, nil
|
||||
}
|
||||
|
||||
// prepareUpstreamSettings sets upstream DNS server settings.
|
||||
func (s *Server) prepareUpstreamSettings(boot upstream.Resolver) (err error) {
|
||||
// Load upstreams either from the file, or from the settings
|
||||
var upstreams []string
|
||||
upstreams, err = s.loadUpstreams()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading upstreams: %w", err)
|
||||
}
|
||||
|
||||
s.conf.UpstreamConfig, err = s.prepareUpstreamConfig(upstreams, defaultDNS, &upstream.Options{
|
||||
Bootstrap: boot,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
// Use a customized set of RootCAs, because Go's default mechanism of
|
||||
// loading TLS roots does not always work properly on some routers so we're
|
||||
// loading roots manually and pass it here.
|
||||
//
|
||||
// See [aghtls.SystemRootCAs].
|
||||
//
|
||||
// TODO(a.garipov): Investigate if that's true.
|
||||
RootCAs: s.conf.TLSv12Roots,
|
||||
CipherSuites: s.conf.TLSCiphers,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing upstream config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareUpstreamConfig returns the upstream configuration based on upstreams
|
||||
// and configuration of s.
|
||||
func (s *Server) prepareUpstreamConfig(
|
||||
// newUpstreamConfig returns the upstream configuration based on upstreams. If
|
||||
// upstreams slice specifies no default upstreams, defaultUpstreams are used to
|
||||
// create upstreams with no domain specifications. opts are used when creating
|
||||
// upstream configuration.
|
||||
func newUpstreamConfig(
|
||||
upstreams []string,
|
||||
defaultUpstreams []string,
|
||||
opts *upstream.Options,
|
||||
) (uc *proxy.UpstreamConfig, err error) {
|
||||
uc, err = proxy.ParseUpstreamsConfig(upstreams, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing upstream config: %w", err)
|
||||
return uc, fmt.Errorf("parsing upstreams: %w", err)
|
||||
}
|
||||
|
||||
if len(uc.Upstreams) == 0 && defaultUpstreams != nil {
|
||||
if len(uc.Upstreams) == 0 && len(defaultUpstreams) > 0 {
|
||||
log.Info("dnsforward: warning: no default upstreams specified, using %v", defaultUpstreams)
|
||||
|
||||
var defaultUpstreamConfig *proxy.UpstreamConfig
|
||||
defaultUpstreamConfig, err = proxy.ParseUpstreamsConfig(defaultUpstreams, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing default upstreams: %w", err)
|
||||
return uc, fmt.Errorf("parsing default upstreams: %w", err)
|
||||
}
|
||||
|
||||
uc.Upstreams = defaultUpstreamConfig.Upstreams
|
||||
@@ -94,6 +81,54 @@ func (s *Server) prepareUpstreamConfig(
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// newPrivateConfig creates an upstream configuration for resolving PTR records
|
||||
// for local addresses. The configuration is built either from the provided
|
||||
// addresses or from the system resolvers. unwanted filters the resulting
|
||||
// upstream configuration.
|
||||
func newPrivateConfig(
|
||||
addrs []string,
|
||||
unwanted addrPortSet,
|
||||
sysResolvers SystemResolvers,
|
||||
privateNets netutil.SubnetSet,
|
||||
opts *upstream.Options,
|
||||
) (uc *proxy.UpstreamConfig, err error) {
|
||||
confNeedsFiltering := len(addrs) > 0
|
||||
if confNeedsFiltering {
|
||||
addrs = stringutil.FilterOut(addrs, IsCommentOrEmpty)
|
||||
} else {
|
||||
sysResolvers := slices.DeleteFunc(slices.Clone(sysResolvers.Addrs()), unwanted.Has)
|
||||
addrs = make([]string, 0, len(sysResolvers))
|
||||
for _, r := range sysResolvers {
|
||||
addrs = append(addrs, r.String())
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", addrs)
|
||||
|
||||
uc, err = proxy.ParseUpstreamsConfig(addrs, opts)
|
||||
if err != nil {
|
||||
return uc, fmt.Errorf("preparing private upstreams: %w", err)
|
||||
}
|
||||
|
||||
if !confNeedsFiltering {
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
err = filterOutAddrs(uc, unwanted)
|
||||
if err != nil {
|
||||
return uc, fmt.Errorf("filtering private upstreams: %w", err)
|
||||
}
|
||||
|
||||
// Prevalidate the config to catch the exact error before creating proxy.
|
||||
// See TODO on [PrivateRDNSError].
|
||||
err = proxy.ValidatePrivateConfig(uc, privateNets)
|
||||
if err != nil {
|
||||
return uc, &PrivateRDNSError{err: err}
|
||||
}
|
||||
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
|
||||
// depending on configuration.
|
||||
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
|
||||
@@ -130,85 +165,9 @@ func setProxyUpstreamMode(
|
||||
return nil
|
||||
}
|
||||
|
||||
// createBootstrap returns a bootstrap resolver based on the configuration of s.
|
||||
// boots are the upstream resolvers that should be closed after use. r is the
|
||||
// actual bootstrap resolver, which may include the system hosts.
|
||||
//
|
||||
// TODO(e.burkov): This function currently returns a resolver and a slice of
|
||||
// the upstream resolvers, which are essentially the same. boots are returned
|
||||
// for being able to close them afterwards, but it introduces an implicit
|
||||
// contract that r could only be used before that. Anyway, this code should
|
||||
// improve when the [proxy.UpstreamConfig] will become an [upstream.Resolver]
|
||||
// and be used here.
|
||||
func (s *Server) createBootstrap(
|
||||
addrs []string,
|
||||
opts *upstream.Options,
|
||||
) (r upstream.Resolver, boots []*upstream.UpstreamResolver, err error) {
|
||||
if len(addrs) == 0 {
|
||||
addrs = defaultBootstrap
|
||||
}
|
||||
|
||||
boots, err = aghnet.ParseBootstraps(addrs, opts)
|
||||
if err != nil {
|
||||
// Don't wrap the error, since it's informative enough as is.
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var parallel upstream.ParallelResolver
|
||||
for _, b := range boots {
|
||||
parallel = append(parallel, upstream.NewCachingResolver(b))
|
||||
}
|
||||
|
||||
if s.etcHosts != nil {
|
||||
r = upstream.ConsequentResolver{s.etcHosts, parallel}
|
||||
} else {
|
||||
r = parallel
|
||||
}
|
||||
|
||||
return r, boots, nil
|
||||
}
|
||||
|
||||
// IsCommentOrEmpty returns true if s starts with a "#" character or is empty.
|
||||
// This function is useful for filtering out non-upstream lines from upstream
|
||||
// configs.
|
||||
func IsCommentOrEmpty(s string) (ok bool) {
|
||||
return len(s) == 0 || s[0] == '#'
|
||||
}
|
||||
|
||||
// ValidateUpstreamsPrivate validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified. It also
|
||||
// checks each domain of domain-specific upstreams for being ARPA pointing to
|
||||
// a locally-served network. privateNets must not be nil.
|
||||
func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) (err error) {
|
||||
conf, err := proxy.ParseUpstreamsConfig(upstreams, &upstream.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating config: %w", err)
|
||||
}
|
||||
|
||||
if conf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := maps.Keys(conf.DomainReservedUpstreams)
|
||||
slices.Sort(keys)
|
||||
|
||||
var errs []error
|
||||
for _, domain := range keys {
|
||||
var subnet netip.Prefix
|
||||
subnet, err = extractARPASubnet(domain)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !privateNets.Contains(subnet.Addr()) {
|
||||
errs = append(
|
||||
errs,
|
||||
fmt.Errorf("arpa domain %q should point to a locally-served network", domain),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Annotate(errors.Join(errs...), "checking domain-specific upstreams: %w")
|
||||
}
|
||||
|
||||
@@ -559,6 +559,8 @@ type Result struct {
|
||||
Reason Reason `json:",omitempty"`
|
||||
|
||||
// IsFiltered is true if the request is filtered.
|
||||
//
|
||||
// TODO(d.kolyshev): Get rid of this flag.
|
||||
IsFiltered bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ func TestParallelSB(t *testing.T) {
|
||||
t.Cleanup(d.Close)
|
||||
|
||||
t.Run("group", func(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
t.Run(fmt.Sprintf("aaa%d", i), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
d.checkMatch(t, sbBlocked, setts)
|
||||
@@ -670,7 +670,7 @@ func BenchmarkSafeBrowsing(b *testing.B) {
|
||||
}, nil)
|
||||
b.Cleanup(d.Close)
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
for range b.N {
|
||||
res, err := d.CheckHost(sbBlocked, dns.TypeA, setts)
|
||||
require.NoError(b, err)
|
||||
|
||||
|
||||
@@ -63,8 +63,6 @@ func TestIDGenerator_Fix(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
g := newIDGenerator(1)
|
||||
g.fix(tc.in)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package rulelist_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@@ -28,14 +27,12 @@ func TestEngine_Refresh(t *testing.T) {
|
||||
require.NotNil(t, eng)
|
||||
testutil.CleanupAndRequireSuccess(t, eng.Close)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
buf := make([]byte, rulelist.DefaultRuleBufSize)
|
||||
cli := &http.Client{
|
||||
Timeout: testTimeout,
|
||||
}
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
err := eng.Refresh(ctx, buf, cli, cacheDir, rulelist.DefaultMaxRuleListSize)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package rulelist_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -67,14 +66,12 @@ func TestFilter_Refresh(t *testing.T) {
|
||||
|
||||
require.NotNil(t, f)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
buf := make([]byte, rulelist.DefaultRuleBufSize)
|
||||
cli := &http.Client{
|
||||
Timeout: testTimeout,
|
||||
}
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
res, err := f.Refresh(ctx, buf, cli, cacheDir, rulelist.DefaultMaxRuleListSize)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -132,7 +132,6 @@ func TestParser_Parse(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -216,7 +215,7 @@ func BenchmarkParser_Parse(b *testing.B) {
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
resSink, errSink = p.Parse(dst, src, buf)
|
||||
dst.Reset()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package filtering
|
||||
|
||||
import "github.com/miekg/dns"
|
||||
|
||||
// SafeSearch interface describes a service for search engines hosts rewrites.
|
||||
type SafeSearch interface {
|
||||
// CheckHost checks host with safe search filter. CheckHost must be safe
|
||||
@@ -16,9 +14,6 @@ type SafeSearch interface {
|
||||
|
||||
// SafeSearchConfig is a struct with safe search related settings.
|
||||
type SafeSearchConfig struct {
|
||||
// CustomResolver is the resolver used by safe search.
|
||||
CustomResolver Resolver `yaml:"-" json:"-"`
|
||||
|
||||
// Enabled indicates if safe search is enabled entirely.
|
||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||
|
||||
@@ -40,13 +35,7 @@ func (d *DNSFilter) checkSafeSearch(
|
||||
qtype uint16,
|
||||
setts *Settings,
|
||||
) (res Result, err error) {
|
||||
if !setts.ProtectionEnabled ||
|
||||
!setts.SafeSearchEnabled ||
|
||||
(qtype != dns.TypeA && qtype != dns.TypeAAAA) {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
if d.safeSearch == nil {
|
||||
if d.safeSearch == nil || !setts.ProtectionEnabled || !setts.SafeSearchEnabled {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@ package safesearch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -67,7 +65,6 @@ type Default struct {
|
||||
engine *urlfilter.DNSEngine
|
||||
|
||||
cache cache.Cache
|
||||
resolver filtering.Resolver
|
||||
logPrefix string
|
||||
cacheTTL time.Duration
|
||||
}
|
||||
@@ -80,11 +77,6 @@ func NewDefault(
|
||||
cacheSize uint,
|
||||
cacheTTL time.Duration,
|
||||
) (ss *Default, err error) {
|
||||
var resolver filtering.Resolver = net.DefaultResolver
|
||||
if conf.CustomResolver != nil {
|
||||
resolver = conf.CustomResolver
|
||||
}
|
||||
|
||||
ss = &Default{
|
||||
mu: &sync.RWMutex{},
|
||||
|
||||
@@ -92,7 +84,6 @@ func NewDefault(
|
||||
EnableLRU: true,
|
||||
MaxSize: cacheSize,
|
||||
}),
|
||||
resolver: resolver,
|
||||
// Use %s, because the client safe-search names already contain double
|
||||
// quotes.
|
||||
logPrefix: fmt.Sprintf("safesearch %s: ", name),
|
||||
@@ -170,8 +161,11 @@ func (ss *Default) CheckHost(host string, qtype rules.RRType) (res filtering.Res
|
||||
ss.log(log.DEBUG, "lookup for %q finished in %s", host, time.Since(start))
|
||||
}()
|
||||
|
||||
if qtype != dns.TypeA && qtype != dns.TypeAAAA {
|
||||
return filtering.Result{}, fmt.Errorf("unsupported question type %s", dns.Type(qtype))
|
||||
switch qtype {
|
||||
case dns.TypeA, dns.TypeAAAA, dns.TypeHTTPS:
|
||||
// Go on.
|
||||
default:
|
||||
return filtering.Result{}, nil
|
||||
}
|
||||
|
||||
// Check cache. Return cached result if it was found
|
||||
@@ -195,6 +189,9 @@ func (ss *Default) CheckHost(host string, qtype rules.RRType) (res filtering.Res
|
||||
}
|
||||
|
||||
res = *fltRes
|
||||
|
||||
// TODO(a.garipov): Consider switch back to resolving CNAME records IPs and
|
||||
// saving results to cache.
|
||||
ss.setCacheResult(host, qtype, res)
|
||||
|
||||
return res, nil
|
||||
@@ -223,20 +220,13 @@ func (ss *Default) searchHost(host string, qtype rules.RRType) (res *rules.DNSRe
|
||||
}
|
||||
|
||||
// newResult creates Result object from rewrite rule. qtype must be either
|
||||
// [dns.TypeA] or [dns.TypeAAAA]. If err is nil, res is never nil, so that the
|
||||
// empty result is converted into a NODATA response.
|
||||
//
|
||||
// TODO(a.garipov): Use the main rewrite result mechanism used in
|
||||
// [dnsforward.Server.filterDNSRequest]. Now we resolve IPs for CNAME to save
|
||||
// them in the safe search cache.
|
||||
// [dns.TypeA] or [dns.TypeAAAA], or [dns.TypeHTTPS]. If err is nil, res is
|
||||
// never nil, so that the empty result is converted into a NODATA response.
|
||||
func (ss *Default) newResult(
|
||||
rewrite *rules.DNSRewrite,
|
||||
qtype rules.RRType,
|
||||
) (res *filtering.Result, err error) {
|
||||
res = &filtering.Result{
|
||||
Rules: []*filtering.ResultRule{{
|
||||
FilterListID: rulelist.URLFilterIDSafeSearch,
|
||||
}},
|
||||
Reason: filtering.FilteredSafeSearch,
|
||||
IsFiltered: true,
|
||||
}
|
||||
@@ -247,69 +237,19 @@ func (ss *Default) newResult(
|
||||
return nil, fmt.Errorf("expected ip rewrite value, got %T(%[1]v)", rewrite.Value)
|
||||
}
|
||||
|
||||
res.Rules[0].IP = ip
|
||||
res.Rules = []*filtering.ResultRule{{
|
||||
FilterListID: rulelist.URLFilterIDSafeSearch,
|
||||
IP: ip,
|
||||
}}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
host := rewrite.NewCNAME
|
||||
if host == "" {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.CanonName = host
|
||||
|
||||
ss.log(log.DEBUG, "resolving %q", host)
|
||||
|
||||
ips, err := ss.resolver.LookupIP(context.Background(), qtypeToProto(qtype), host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving cname: %w", err)
|
||||
}
|
||||
|
||||
ss.log(log.DEBUG, "resolved %s", ips)
|
||||
|
||||
for _, ip := range ips {
|
||||
// TODO(a.garipov): Remove this filtering once the resolver we use
|
||||
// actually learns about network.
|
||||
addr := fitToProto(ip, qtype)
|
||||
if addr == (netip.Addr{}) {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Rules[0]?
|
||||
res.Rules[0].IP = addr
|
||||
}
|
||||
res.CanonName = rewrite.NewCNAME
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// qtypeToProto returns "ip4" for [dns.TypeA] and "ip6" for [dns.TypeAAAA].
|
||||
// It panics for other types.
|
||||
func qtypeToProto(qtype rules.RRType) (proto string) {
|
||||
switch qtype {
|
||||
case dns.TypeA:
|
||||
return "ip4"
|
||||
case dns.TypeAAAA:
|
||||
return "ip6"
|
||||
default:
|
||||
panic(fmt.Errorf("safesearch: unsupported question type %s", dns.Type(qtype)))
|
||||
}
|
||||
}
|
||||
|
||||
// fitToProto returns a non-nil IP address if ip is the correct protocol version
|
||||
// for qtype. qtype is expected to be either [dns.TypeA] or [dns.TypeAAAA].
|
||||
func fitToProto(ip net.IP, qtype rules.RRType) (res netip.Addr) {
|
||||
if ip4 := ip.To4(); qtype == dns.TypeA {
|
||||
if ip4 != nil {
|
||||
return netip.AddrFrom4([4]byte(ip4))
|
||||
}
|
||||
} else if ip = ip.To16(); ip != nil && qtype == dns.TypeAAAA {
|
||||
return netip.AddrFrom16([16]byte(ip))
|
||||
}
|
||||
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
// setCacheResult stores data in cache for host. qtype is expected to be either
|
||||
// [dns.TypeA] or [dns.TypeAAAA].
|
||||
func (ss *Default) setCacheResult(host string, qtype rules.RRType, res filtering.Result) {
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package safesearch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
@@ -79,47 +76,6 @@ func TestSafeSearchCacheYandex(t *testing.T) {
|
||||
assert.Equal(t, cachedValue.Rules[0].IP, yandexIP)
|
||||
}
|
||||
|
||||
func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||
const domain = "www.google.ru"
|
||||
|
||||
ss := newForTest(t, filtering.SafeSearchConfig{Enabled: false})
|
||||
|
||||
res, err := ss.CheckHost(domain, testQType)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, res.IsFiltered)
|
||||
assert.Empty(t, res.Rules)
|
||||
|
||||
resolver := &aghtest.Resolver{
|
||||
OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) {
|
||||
ip4, ip6 := aghtest.HostToIPs(host)
|
||||
|
||||
return []net.IP{ip4.AsSlice(), ip6.AsSlice()}, nil
|
||||
},
|
||||
}
|
||||
|
||||
ss = newForTest(t, defaultSafeSearchConf)
|
||||
ss.resolver = resolver
|
||||
|
||||
// Lookup for safesearch domain.
|
||||
rewrite := ss.searchHost(domain, testQType)
|
||||
|
||||
wantIP, _ := aghtest.HostToIPs(rewrite.NewCNAME)
|
||||
|
||||
res, err = ss.CheckHost(domain, testQType)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res.Rules, 1)
|
||||
|
||||
assert.Equal(t, wantIP, res.Rules[0].IP)
|
||||
|
||||
// Check cache.
|
||||
cachedValue, isFound := ss.getCachedResult(domain, testQType)
|
||||
require.True(t, isFound)
|
||||
require.Len(t, cachedValue.Rules, 1)
|
||||
|
||||
assert.Equal(t, wantIP, cachedValue.Rules[0].IP)
|
||||
}
|
||||
|
||||
const googleHost = "www.google.com"
|
||||
|
||||
var dnsRewriteSink *rules.DNSRewrite
|
||||
@@ -127,7 +83,7 @@ var dnsRewriteSink *rules.DNSRewrite
|
||||
func BenchmarkSafeSearch(b *testing.B) {
|
||||
ss := newForTest(b, defaultSafeSearchConf)
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
for range b.N {
|
||||
dnsRewriteSink = ss.searchHost(googleHost, testQType)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
||||
@@ -31,8 +30,6 @@ const (
|
||||
|
||||
// testConf is the default safe search configuration for tests.
|
||||
var testConf = filtering.SafeSearchConfig{
|
||||
CustomResolver: nil,
|
||||
|
||||
Enabled: true,
|
||||
|
||||
Bing: true,
|
||||
@@ -52,61 +49,60 @@ func TestDefault_CheckHost_yandex(t *testing.T) {
|
||||
ss, err := safesearch.NewDefault(conf, "", testCacheSize, testCacheTTL)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check host for each domain.
|
||||
for _, host := range []string{
|
||||
hosts := []string{
|
||||
"yandex.ru",
|
||||
"yAndeX.ru",
|
||||
"YANdex.COM",
|
||||
"yandex.by",
|
||||
"yandex.kz",
|
||||
"www.yandex.com",
|
||||
} {
|
||||
var res filtering.Result
|
||||
res, err = ss.CheckHost(host, testQType)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, res.IsFiltered)
|
||||
|
||||
require.Len(t, res.Rules, 1)
|
||||
|
||||
assert.Equal(t, yandexIP, res.Rules[0].IP)
|
||||
assert.Equal(t, rulelist.URLFilterIDSafeSearch, res.Rules[0].FilterListID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefault_CheckHost_yandexAAAA(t *testing.T) {
|
||||
conf := testConf
|
||||
ss, err := safesearch.NewDefault(conf, "", testCacheSize, testCacheTTL)
|
||||
require.NoError(t, err)
|
||||
testCases := []struct {
|
||||
want netip.Addr
|
||||
name string
|
||||
qt uint16
|
||||
}{{
|
||||
want: yandexIP,
|
||||
name: "a",
|
||||
qt: dns.TypeA,
|
||||
}, {
|
||||
want: netip.Addr{},
|
||||
name: "aaaa",
|
||||
qt: dns.TypeAAAA,
|
||||
}, {
|
||||
want: netip.Addr{},
|
||||
name: "https",
|
||||
qt: dns.TypeHTTPS,
|
||||
}}
|
||||
|
||||
res, err := ss.CheckHost("www.yandex.ru", dns.TypeAAAA)
|
||||
require.NoError(t, err)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
for _, host := range hosts {
|
||||
// Check host for each domain.
|
||||
var res filtering.Result
|
||||
res, err = ss.CheckHost(host, tc.qt)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, res.IsFiltered)
|
||||
assert.True(t, res.IsFiltered)
|
||||
assert.Equal(t, filtering.FilteredSafeSearch, res.Reason)
|
||||
|
||||
// TODO(a.garipov): Currently, the safe-search filter returns a single rule
|
||||
// with a nil IP address. This isn't really necessary and should be changed
|
||||
// once the TODO in [safesearch.Default.newResult] is resolved.
|
||||
require.Len(t, res.Rules, 1)
|
||||
if tc.want == (netip.Addr{}) {
|
||||
assert.Empty(t, res.Rules)
|
||||
} else {
|
||||
require.Len(t, res.Rules, 1)
|
||||
|
||||
assert.Empty(t, res.Rules[0].IP)
|
||||
assert.Equal(t, rulelist.URLFilterIDSafeSearch, res.Rules[0].FilterListID)
|
||||
rule := res.Rules[0]
|
||||
assert.Equal(t, tc.want, rule.IP)
|
||||
assert.Equal(t, rulelist.URLFilterIDSafeSearch, rule.FilterListID)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefault_CheckHost_google(t *testing.T) {
|
||||
resolver := &aghtest.Resolver{
|
||||
OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) {
|
||||
ip4, ip6 := aghtest.HostToIPs(host)
|
||||
|
||||
return []net.IP{ip4.AsSlice(), ip6.AsSlice()}, nil
|
||||
},
|
||||
}
|
||||
|
||||
wantIP, _ := aghtest.HostToIPs("forcesafesearch.google.com")
|
||||
|
||||
conf := testConf
|
||||
conf.CustomResolver = resolver
|
||||
ss, err := safesearch.NewDefault(conf, "", testCacheSize, testCacheTTL)
|
||||
ss, err := safesearch.NewDefault(testConf, "", testCacheSize, testCacheTTL)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check host for each domain.
|
||||
@@ -125,11 +121,9 @@ func TestDefault_CheckHost_google(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, res.IsFiltered)
|
||||
|
||||
require.Len(t, res.Rules, 1)
|
||||
|
||||
assert.Equal(t, wantIP, res.Rules[0].IP)
|
||||
assert.Equal(t, rulelist.URLFilterIDSafeSearch, res.Rules[0].FilterListID)
|
||||
assert.Equal(t, filtering.FilteredSafeSearch, res.Reason)
|
||||
assert.Equal(t, "forcesafesearch.google.com", res.CanonName)
|
||||
assert.Empty(t, res.Rules)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -154,17 +148,7 @@ func (r *testResolver) LookupIP(
|
||||
}
|
||||
|
||||
func TestDefault_CheckHost_duckduckgoAAAA(t *testing.T) {
|
||||
conf := testConf
|
||||
conf.CustomResolver = &testResolver{
|
||||
OnLookupIP: func(_ context.Context, network, host string) (ips []net.IP, err error) {
|
||||
assert.Equal(t, "ip6", network)
|
||||
assert.Equal(t, "safe.duckduckgo.com", host)
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
ss, err := safesearch.NewDefault(conf, "", testCacheSize, testCacheTTL)
|
||||
ss, err := safesearch.NewDefault(testConf, "", testCacheSize, testCacheTTL)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The DuckDuckGo safe-search addresses are resolved through CNAMEs, but
|
||||
@@ -174,14 +158,9 @@ func TestDefault_CheckHost_duckduckgoAAAA(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, res.IsFiltered)
|
||||
|
||||
// TODO(a.garipov): Currently, the safe-search filter returns a single rule
|
||||
// with a nil IP address. This isn't really necessary and should be changed
|
||||
// once the TODO in [safesearch.Default.newResult] is resolved.
|
||||
require.Len(t, res.Rules, 1)
|
||||
|
||||
assert.Empty(t, res.Rules[0].IP)
|
||||
assert.Equal(t, rulelist.URLFilterIDSafeSearch, res.Rules[0].FilterListID)
|
||||
assert.Equal(t, filtering.FilteredSafeSearch, res.Reason)
|
||||
assert.Equal(t, "safe.duckduckgo.com", res.CanonName)
|
||||
assert.Empty(t, res.Rules)
|
||||
}
|
||||
|
||||
func TestDefault_Update(t *testing.T) {
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/hostsfile"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// DHCP is an interface for accessing DHCP lease data the [clientsContainer]
|
||||
@@ -46,22 +45,20 @@ type DHCP interface {
|
||||
|
||||
// clientsContainer is the storage of all runtime and persistent clients.
|
||||
type clientsContainer struct {
|
||||
// TODO(a.garipov): Perhaps use a number of separate indices for different
|
||||
// types (string, netip.Addr, and so on).
|
||||
list map[string]*client.Persistent // name -> client
|
||||
|
||||
// clientIndex stores information about persistent clients.
|
||||
clientIndex *client.Index
|
||||
|
||||
// ipToRC maps IP addresses to runtime client information.
|
||||
ipToRC map[netip.Addr]*client.Runtime
|
||||
// runtimeIndex stores information about runtime clients.
|
||||
runtimeIndex *client.RuntimeIndex
|
||||
|
||||
allTags *container.MapSet[string]
|
||||
|
||||
// dhcp is the DHCP service implementation.
|
||||
dhcp DHCP
|
||||
|
||||
// dnsServer is used for checking clients IP status access list status
|
||||
dnsServer *dnsforward.Server
|
||||
// clientChecker checks if a client is blocked by the current access
|
||||
// settings.
|
||||
clientChecker BlockedClientChecker
|
||||
|
||||
// etcHosts contains list of rewrite rules taken from the operating system's
|
||||
// hosts database.
|
||||
@@ -90,6 +87,12 @@ type clientsContainer struct {
|
||||
testing bool
|
||||
}
|
||||
|
||||
// BlockedClientChecker checks if a client is blocked by the current access
|
||||
// settings.
|
||||
type BlockedClientChecker interface {
|
||||
IsBlockedClient(ip netip.Addr, clientID string) (blocked bool, rule string)
|
||||
}
|
||||
|
||||
// Init initializes clients container
|
||||
// dhcpServer: optional
|
||||
// Note: this function must be called only once
|
||||
@@ -100,12 +103,12 @@ func (clients *clientsContainer) Init(
|
||||
arpDB arpdb.Interface,
|
||||
filteringConf *filtering.Config,
|
||||
) (err error) {
|
||||
if clients.list != nil {
|
||||
log.Fatal("clients.list != nil")
|
||||
// TODO(s.chzhen): Refactor it.
|
||||
if clients.clientIndex != nil {
|
||||
return errors.Error("clients container already initialized")
|
||||
}
|
||||
|
||||
clients.list = map[string]*client.Persistent{}
|
||||
clients.ipToRC = map[netip.Addr]*client.Runtime{}
|
||||
clients.runtimeIndex = client.NewRuntimeIndex()
|
||||
|
||||
clients.clientIndex = client.NewIndex()
|
||||
|
||||
@@ -248,8 +251,6 @@ func (o *clientObject) toPersistent(
|
||||
}
|
||||
|
||||
if o.SafeSearchConf.Enabled {
|
||||
o.SafeSearchConf.CustomResolver = safeSearchResolver{}
|
||||
|
||||
err = cli.SetSafeSearch(
|
||||
o.SafeSearchConf,
|
||||
filteringConf.SafeSearchCacheSize,
|
||||
@@ -285,9 +286,17 @@ func (clients *clientsContainer) addFromConfig(
|
||||
return fmt.Errorf("clients: init persistent client at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
_, err = clients.add(cli)
|
||||
// TODO(s.chzhen): Consider moving to the client index constructor.
|
||||
err = clients.clientIndex.ClashesUID(cli)
|
||||
if err != nil {
|
||||
log.Error("clients: adding client at index %d %s: %s", i, cli.Name, err)
|
||||
return fmt.Errorf("adding client %s at index %d: %w", cli.Name, i, err)
|
||||
}
|
||||
|
||||
err = clients.add(cli)
|
||||
if err != nil {
|
||||
// TODO(s.chzhen): Return an error instead of logging if more
|
||||
// stringent requirements are implemented.
|
||||
log.Error("clients: adding client %s at index %d: %s", cli.Name, i, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,9 +309,9 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
objs = make([]*clientObject, 0, len(clients.list))
|
||||
for _, cli := range clients.list {
|
||||
o := &clientObject{
|
||||
objs = make([]*clientObject, 0, clients.clientIndex.Size())
|
||||
clients.clientIndex.Range(func(cli *client.Persistent) (cont bool) {
|
||||
objs = append(objs, &clientObject{
|
||||
Name: cli.Name,
|
||||
|
||||
BlockedServices: cli.BlockedServices.Clone(),
|
||||
@@ -323,10 +332,10 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
|
||||
IgnoreStatistics: cli.IgnoreStatistics,
|
||||
UpstreamsCacheEnabled: cli.UpstreamsCacheEnabled,
|
||||
UpstreamsCacheSize: cli.UpstreamsCacheSize,
|
||||
}
|
||||
})
|
||||
|
||||
objs = append(objs, o)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Maps aren't guaranteed to iterate in the same order each time, so the
|
||||
// above loop can generate different orderings when writing to the config
|
||||
@@ -363,8 +372,8 @@ func (clients *clientsContainer) clientSource(ip netip.Addr) (src client.Source)
|
||||
return client.SourcePersistent
|
||||
}
|
||||
|
||||
rc, ok := clients.ipToRC[ip]
|
||||
if ok {
|
||||
rc := clients.runtimeIndex.Client(ip)
|
||||
if rc != nil {
|
||||
src, _ = rc.Info()
|
||||
}
|
||||
|
||||
@@ -406,23 +415,26 @@ func (clients *clientsContainer) clientOrArtificial(
|
||||
id string,
|
||||
) (c *querylog.Client, art bool) {
|
||||
defer func() {
|
||||
c.Disallowed, c.DisallowedRule = clients.dnsServer.IsBlockedClient(ip, id)
|
||||
c.Disallowed, c.DisallowedRule = clients.clientChecker.IsBlockedClient(ip, id)
|
||||
if c.WHOIS == nil {
|
||||
c.WHOIS = &whois.Info{}
|
||||
}
|
||||
}()
|
||||
|
||||
cli, ok := clients.find(id)
|
||||
if ok {
|
||||
if !ok {
|
||||
cli = clients.clientIndex.FindByIPWithoutZone(ip)
|
||||
}
|
||||
|
||||
if cli != nil {
|
||||
return &querylog.Client{
|
||||
Name: cli.Name,
|
||||
IgnoreQueryLog: cli.IgnoreQueryLog,
|
||||
}, false
|
||||
}
|
||||
|
||||
var rc *client.Runtime
|
||||
rc, ok = clients.findRuntimeClient(ip)
|
||||
if ok {
|
||||
rc := clients.findRuntimeClient(ip)
|
||||
if rc != nil {
|
||||
_, host := rc.Info()
|
||||
|
||||
return &querylog.Client{
|
||||
@@ -542,47 +554,38 @@ func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *client.Persistent,
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for _, c = range clients.list {
|
||||
_, found := slices.BinarySearchFunc(c.MACs, foundMAC, slices.Compare[net.HardwareAddr])
|
||||
if found {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
return clients.clientIndex.FindByMAC(foundMAC)
|
||||
}
|
||||
|
||||
// runtimeClient returns a runtime client from internal index. Note that it
|
||||
// doesn't include DHCP clients.
|
||||
func (clients *clientsContainer) runtimeClient(ip netip.Addr) (rc *client.Runtime, ok bool) {
|
||||
func (clients *clientsContainer) runtimeClient(ip netip.Addr) (rc *client.Runtime) {
|
||||
if ip == (netip.Addr{}) {
|
||||
return nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
rc, ok = clients.ipToRC[ip]
|
||||
|
||||
return rc, ok
|
||||
return clients.runtimeIndex.Client(ip)
|
||||
}
|
||||
|
||||
// findRuntimeClient finds a runtime client by their IP.
|
||||
func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *client.Runtime, ok bool) {
|
||||
rc, ok = clients.runtimeClient(ip)
|
||||
func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *client.Runtime) {
|
||||
rc = clients.runtimeClient(ip)
|
||||
host := clients.dhcp.HostByIP(ip)
|
||||
|
||||
if host != "" {
|
||||
if !ok {
|
||||
rc = &client.Runtime{}
|
||||
if rc == nil {
|
||||
rc = client.NewRuntime(ip)
|
||||
}
|
||||
|
||||
rc.SetInfo(client.SourceDHCP, []string{host})
|
||||
|
||||
return rc, true
|
||||
return rc
|
||||
}
|
||||
|
||||
return rc, ok
|
||||
return rc
|
||||
}
|
||||
|
||||
// check validates the client. It also sorts the client tags.
|
||||
@@ -615,43 +618,32 @@ func (clients *clientsContainer) check(c *client.Persistent) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// add adds a new client object. ok is false if such client already exists or
|
||||
// if an error occurred.
|
||||
func (clients *clientsContainer) add(c *client.Persistent) (ok bool, err error) {
|
||||
// add adds a persistent client or returns an error.
|
||||
func (clients *clientsContainer) add(c *client.Persistent) (err error) {
|
||||
err = clients.check(c)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
// check Name index
|
||||
_, ok = clients.list[c.Name]
|
||||
if ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// check ID index
|
||||
err = clients.clientIndex.Clashes(c)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
clients.addLocked(c)
|
||||
|
||||
log.Debug("clients: added %q: ID:%q [%d]", c.Name, c.IDs(), len(clients.list))
|
||||
log.Debug("clients: added %q: ID:%q [%d]", c.Name, c.IDs(), clients.clientIndex.Size())
|
||||
|
||||
return true, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// addLocked c to the indexes. clients.lock is expected to be locked.
|
||||
func (clients *clientsContainer) addLocked(c *client.Persistent) {
|
||||
// update Name index
|
||||
clients.list[c.Name] = c
|
||||
|
||||
// update ID index
|
||||
clients.clientIndex.Add(c)
|
||||
}
|
||||
|
||||
@@ -660,8 +652,7 @@ func (clients *clientsContainer) remove(name string) (ok bool) {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
var c *client.Persistent
|
||||
c, ok = clients.list[name]
|
||||
c, ok := clients.clientIndex.FindByName(name)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -678,9 +669,6 @@ func (clients *clientsContainer) removeLocked(c *client.Persistent) {
|
||||
log.Error("client container: removing client %s: %s", c.Name, err)
|
||||
}
|
||||
|
||||
// Update the name index.
|
||||
delete(clients.list, c.Name)
|
||||
|
||||
// Update the ID index.
|
||||
clients.clientIndex.Delete(c)
|
||||
}
|
||||
@@ -696,22 +684,6 @@ func (clients *clientsContainer) update(prev, c *client.Persistent) (err error)
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
// Check the name index.
|
||||
if prev.Name != c.Name {
|
||||
_, ok := clients.list[c.Name]
|
||||
if ok {
|
||||
return errors.Error("client already exists")
|
||||
}
|
||||
}
|
||||
|
||||
if c.EqualIDs(prev) {
|
||||
clients.removeLocked(prev)
|
||||
clients.addLocked(c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check the ID index.
|
||||
err = clients.clientIndex.Clashes(c)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
@@ -734,12 +706,12 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
|
||||
return
|
||||
}
|
||||
|
||||
rc, ok := clients.ipToRC[ip]
|
||||
if !ok {
|
||||
rc := clients.runtimeIndex.Client(ip)
|
||||
if rc == nil {
|
||||
// Create a RuntimeClient implicitly so that we don't do this check
|
||||
// again.
|
||||
rc = &client.Runtime{}
|
||||
clients.ipToRC[ip] = rc
|
||||
rc = client.NewRuntime(ip)
|
||||
clients.runtimeIndex.Add(rc)
|
||||
|
||||
log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi)
|
||||
} else {
|
||||
@@ -798,61 +770,54 @@ func (clients *clientsContainer) addHostLocked(
|
||||
host string,
|
||||
src client.Source,
|
||||
) (ok bool) {
|
||||
rc, ok := clients.ipToRC[ip]
|
||||
if !ok {
|
||||
rc := clients.runtimeIndex.Client(ip)
|
||||
if rc == nil {
|
||||
if src < client.SourceDHCP {
|
||||
if clients.dhcp.HostByIP(ip) != "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
rc = &client.Runtime{}
|
||||
clients.ipToRC[ip] = rc
|
||||
rc = client.NewRuntime(ip)
|
||||
clients.runtimeIndex.Add(rc)
|
||||
}
|
||||
|
||||
rc.SetInfo(src, []string{host})
|
||||
|
||||
log.Debug("clients: adding client info %s -> %q %q [%d]", ip, src, host, len(clients.ipToRC))
|
||||
log.Debug(
|
||||
"clients: adding client info %s -> %q %q [%d]",
|
||||
ip,
|
||||
src,
|
||||
host,
|
||||
clients.runtimeIndex.Size(),
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// rmHostsBySrc removes all entries that match the specified source.
|
||||
func (clients *clientsContainer) rmHostsBySrc(src client.Source) {
|
||||
n := 0
|
||||
for ip, rc := range clients.ipToRC {
|
||||
rc.Unset(src)
|
||||
if rc.IsEmpty() {
|
||||
delete(clients.ipToRC, ip)
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("clients: removed %d client aliases", n)
|
||||
}
|
||||
|
||||
// addFromHostsFile fills the client-hostname pairing index from the system's
|
||||
// hosts files.
|
||||
func (clients *clientsContainer) addFromHostsFile(hosts *hostsfile.DefaultStorage) {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
clients.rmHostsBySrc(client.SourceHostsFile)
|
||||
deleted := clients.runtimeIndex.DeleteBySource(client.SourceHostsFile)
|
||||
log.Debug("clients: removed %d client aliases from system hosts file", deleted)
|
||||
|
||||
n := 0
|
||||
added := 0
|
||||
hosts.RangeNames(func(addr netip.Addr, names []string) (cont bool) {
|
||||
// Only the first name of the first record is considered a canonical
|
||||
// hostname for the IP address.
|
||||
//
|
||||
// TODO(e.burkov): Consider using all the names from all the records.
|
||||
if clients.addHostLocked(addr, names[0], client.SourceHostsFile) {
|
||||
n++
|
||||
added++
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
log.Debug("clients: added %d client aliases from system hosts file", n)
|
||||
log.Debug("clients: added %d client aliases from system hosts file", added)
|
||||
}
|
||||
|
||||
// addFromSystemARP adds the IP-hostname pairings from the output of the arp -a
|
||||
@@ -876,7 +841,8 @@ func (clients *clientsContainer) addFromSystemARP() {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
clients.rmHostsBySrc(client.SourceARP)
|
||||
deleted := clients.runtimeIndex.DeleteBySource(client.SourceARP)
|
||||
log.Debug("clients: removed %d client aliases from arp neighborhood", deleted)
|
||||
|
||||
added := 0
|
||||
for _, n := range ns {
|
||||
@@ -891,18 +857,5 @@ func (clients *clientsContainer) addFromSystemARP() {
|
||||
// close gracefully closes all the client-specific upstream configurations of
|
||||
// the persistent clients.
|
||||
func (clients *clientsContainer) close() (err error) {
|
||||
persistent := maps.Values(clients.list)
|
||||
slices.SortFunc(persistent, func(a, b *client.Persistent) (res int) {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, cli := range persistent {
|
||||
if err = cli.CloseUpstreams(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
return clients.clientIndex.CloseUpstreams()
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func newClientsContainer(t *testing.T) (c *clientsContainer) {
|
||||
}
|
||||
|
||||
dhcp := &testDHCP{
|
||||
OnLeases: func() (leases []*dhcpsvc.Lease) { panic("not implemented") },
|
||||
OnLeases: func() (leases []*dhcpsvc.Lease) { return nil },
|
||||
OnHostBy: func(ip netip.Addr) (host string) { return "" },
|
||||
OnMACBy: func(ip netip.Addr) (mac net.HardwareAddr) { return nil },
|
||||
}
|
||||
@@ -72,23 +72,19 @@ func TestClients(t *testing.T) {
|
||||
IPs: []netip.Addr{cli1IP, cliIPv6},
|
||||
}
|
||||
|
||||
ok, err := clients.add(c)
|
||||
err := clients.add(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, ok)
|
||||
|
||||
c = &client.Persistent{
|
||||
Name: "client2",
|
||||
UID: client.MustNewUID(),
|
||||
IPs: []netip.Addr{cli2IP},
|
||||
}
|
||||
|
||||
ok, err = clients.add(c)
|
||||
err = clients.add(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, ok)
|
||||
|
||||
c, ok = clients.find(cli1)
|
||||
c, ok := clients.find(cli1)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "client1", c.Name)
|
||||
@@ -111,22 +107,20 @@ func TestClients(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("add_fail_name", func(t *testing.T) {
|
||||
ok, err := clients.add(&client.Persistent{
|
||||
err := clients.add(&client.Persistent{
|
||||
Name: "client1",
|
||||
UID: client.MustNewUID(),
|
||||
IPs: []netip.Addr{netip.MustParseAddr("1.2.3.5")},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.False(t, ok)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("add_fail_ip", func(t *testing.T) {
|
||||
ok, err := clients.add(&client.Persistent{
|
||||
err := clients.add(&client.Persistent{
|
||||
Name: "client3",
|
||||
UID: client.MustNewUID(),
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("update_fail_ip", func(t *testing.T) {
|
||||
@@ -145,12 +139,13 @@ func TestClients(t *testing.T) {
|
||||
cliNewIP = netip.MustParseAddr(cliNew)
|
||||
)
|
||||
|
||||
prev, ok := clients.list["client1"]
|
||||
prev, ok := clients.clientIndex.FindByName("client1")
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, prev)
|
||||
|
||||
err := clients.update(prev, &client.Persistent{
|
||||
Name: "client1",
|
||||
UID: client.MustNewUID(),
|
||||
UID: prev.UID,
|
||||
IPs: []netip.Addr{cliNewIP},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -160,12 +155,13 @@ func TestClients(t *testing.T) {
|
||||
|
||||
assert.Equal(t, clients.clientSource(cliNewIP), client.SourcePersistent)
|
||||
|
||||
prev, ok = clients.list["client1"]
|
||||
prev, ok = clients.clientIndex.FindByName("client1")
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, prev)
|
||||
|
||||
err = clients.update(prev, &client.Persistent{
|
||||
Name: "client1-renamed",
|
||||
UID: client.MustNewUID(),
|
||||
UID: prev.UID,
|
||||
IPs: []netip.Addr{cliNewIP},
|
||||
UseOwnSettings: true,
|
||||
})
|
||||
@@ -177,7 +173,7 @@ func TestClients(t *testing.T) {
|
||||
assert.Equal(t, "client1-renamed", c.Name)
|
||||
assert.True(t, c.UseOwnSettings)
|
||||
|
||||
nilCli, ok := clients.list["client1"]
|
||||
nilCli, ok := clients.clientIndex.FindByName("client1")
|
||||
require.False(t, ok)
|
||||
|
||||
assert.Nil(t, nilCli)
|
||||
@@ -244,7 +240,7 @@ func TestClientsWHOIS(t *testing.T) {
|
||||
t.Run("new_client", func(t *testing.T) {
|
||||
ip := netip.MustParseAddr("1.1.1.255")
|
||||
clients.setWHOISInfo(ip, whois)
|
||||
rc := clients.ipToRC[ip]
|
||||
rc := clients.runtimeIndex.Client(ip)
|
||||
require.NotNil(t, rc)
|
||||
|
||||
assert.Equal(t, whois, rc.WHOIS())
|
||||
@@ -256,7 +252,7 @@ func TestClientsWHOIS(t *testing.T) {
|
||||
assert.True(t, ok)
|
||||
|
||||
clients.setWHOISInfo(ip, whois)
|
||||
rc := clients.ipToRC[ip]
|
||||
rc := clients.runtimeIndex.Client(ip)
|
||||
require.NotNil(t, rc)
|
||||
|
||||
assert.Equal(t, whois, rc.WHOIS())
|
||||
@@ -265,16 +261,15 @@ func TestClientsWHOIS(t *testing.T) {
|
||||
t.Run("can't_set_manually-added", func(t *testing.T) {
|
||||
ip := netip.MustParseAddr("1.1.1.2")
|
||||
|
||||
ok, err := clients.add(&client.Persistent{
|
||||
err := clients.add(&client.Persistent{
|
||||
Name: "client1",
|
||||
UID: client.MustNewUID(),
|
||||
IPs: []netip.Addr{netip.MustParseAddr("1.1.1.2")},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ok)
|
||||
|
||||
clients.setWHOISInfo(ip, whois)
|
||||
rc := clients.ipToRC[ip]
|
||||
rc := clients.runtimeIndex.Client(ip)
|
||||
require.Nil(t, rc)
|
||||
|
||||
assert.True(t, clients.remove("client1"))
|
||||
@@ -288,7 +283,7 @@ func TestClientsAddExisting(t *testing.T) {
|
||||
ip := netip.MustParseAddr("1.1.1.1")
|
||||
|
||||
// Add a client.
|
||||
ok, err := clients.add(&client.Persistent{
|
||||
err := clients.add(&client.Persistent{
|
||||
Name: "client1",
|
||||
UID: client.MustNewUID(),
|
||||
IPs: []netip.Addr{ip, netip.MustParseAddr("1:2:3::4")},
|
||||
@@ -296,10 +291,9 @@ func TestClientsAddExisting(t *testing.T) {
|
||||
MACs: []net.HardwareAddr{{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ok)
|
||||
|
||||
// Now add an auto-client with the same IP.
|
||||
ok = clients.addHost(ip, "test", client.SourceRDNS)
|
||||
ok := clients.addHost(ip, "test", client.SourceRDNS)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
@@ -339,22 +333,20 @@ func TestClientsAddExisting(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add a new client with the same IP as for a client with MAC.
|
||||
ok, err := clients.add(&client.Persistent{
|
||||
err = clients.add(&client.Persistent{
|
||||
Name: "client2",
|
||||
UID: client.MustNewUID(),
|
||||
IPs: []netip.Addr{ip},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ok)
|
||||
|
||||
// Add a new client with the IP from the first client's IP range.
|
||||
ok, err = clients.add(&client.Persistent{
|
||||
err = clients.add(&client.Persistent{
|
||||
Name: "client3",
|
||||
UID: client.MustNewUID(),
|
||||
IPs: []netip.Addr{netip.MustParseAddr("2.2.2.2")},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -362,7 +354,7 @@ func TestClientsCustomUpstream(t *testing.T) {
|
||||
clients := newClientsContainer(t)
|
||||
|
||||
// Add client with upstreams.
|
||||
ok, err := clients.add(&client.Persistent{
|
||||
err := clients.add(&client.Persistent{
|
||||
Name: "client1",
|
||||
UID: client.MustNewUID(),
|
||||
IPs: []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1:2:3::4")},
|
||||
@@ -372,7 +364,6 @@ func TestClientsCustomUpstream(t *testing.T) {
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ok)
|
||||
|
||||
upsConf, err := clients.UpstreamConfigByID("1.2.3.4", net.DefaultResolver)
|
||||
assert.Nil(t, upsConf)
|
||||
|
||||
@@ -96,22 +96,26 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
for _, c := range clients.list {
|
||||
clients.clientIndex.Range(func(c *client.Persistent) (cont bool) {
|
||||
cj := clientToJSON(c)
|
||||
data.Clients = append(data.Clients, cj)
|
||||
}
|
||||
|
||||
for ip, rc := range clients.ipToRC {
|
||||
return true
|
||||
})
|
||||
|
||||
clients.runtimeIndex.Range(func(rc *client.Runtime) (cont bool) {
|
||||
src, host := rc.Info()
|
||||
cj := runtimeClientJSON{
|
||||
WHOIS: whoisOrEmpty(rc),
|
||||
Name: host,
|
||||
Source: src,
|
||||
IP: ip,
|
||||
IP: rc.Addr(),
|
||||
}
|
||||
|
||||
data.RuntimeClients = append(data.RuntimeClients, cj)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
for _, l := range clients.dhcp.Leases() {
|
||||
cj := runtimeClientJSON{
|
||||
@@ -332,20 +336,16 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
ok, err := clients.add(c)
|
||||
err = clients.add(c)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !ok {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "Client already exists")
|
||||
|
||||
return
|
||||
if !clients.testing {
|
||||
onConfigModified()
|
||||
}
|
||||
|
||||
onConfigModified()
|
||||
}
|
||||
|
||||
// handleDelClient is the handler for POST /control/clients/delete HTTP API.
|
||||
@@ -370,7 +370,9 @@ func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
onConfigModified()
|
||||
if !clients.testing {
|
||||
onConfigModified()
|
||||
}
|
||||
}
|
||||
|
||||
// updateJSON contains the name and data of the updated persistent client.
|
||||
@@ -404,7 +406,7 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
prev, ok = clients.list[dj.Name]
|
||||
prev, ok = clients.clientIndex.FindByName(dj.Name)
|
||||
}()
|
||||
|
||||
if !ok {
|
||||
@@ -427,14 +429,16 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
onConfigModified()
|
||||
if !clients.testing {
|
||||
onConfigModified()
|
||||
}
|
||||
}
|
||||
|
||||
// handleFindClient is the handler for GET /control/clients/find HTTP API.
|
||||
func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
data := []map[string]*clientJSON{}
|
||||
for i := 0; i < len(q); i++ {
|
||||
for i := range len(q) {
|
||||
idStr := q.Get(fmt.Sprintf("ip%d", i))
|
||||
if idStr == "" {
|
||||
break
|
||||
@@ -447,7 +451,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
|
||||
cj = clients.findRuntime(ip, idStr)
|
||||
} else {
|
||||
cj = clientToJSON(c)
|
||||
disallowed, rule := clients.dnsServer.IsBlockedClient(ip, idStr)
|
||||
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, idStr)
|
||||
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
||||
}
|
||||
|
||||
@@ -463,14 +467,14 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
|
||||
// /etc/hosts tables, DHCP leases, or blocklists. cj is guaranteed to be
|
||||
// non-nil.
|
||||
func (clients *clientsContainer) findRuntime(ip netip.Addr, idStr string) (cj *clientJSON) {
|
||||
rc, ok := clients.findRuntimeClient(ip)
|
||||
if !ok {
|
||||
rc := clients.findRuntimeClient(ip)
|
||||
if rc == nil {
|
||||
// It is still possible that the IP used to be in the runtime clients
|
||||
// list, but then the server was reloaded. So, check the DNS server's
|
||||
// blocked IP list.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2428.
|
||||
disallowed, rule := clients.dnsServer.IsBlockedClient(ip, idStr)
|
||||
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, idStr)
|
||||
cj = &clientJSON{
|
||||
IDs: []string{idStr},
|
||||
Disallowed: &disallowed,
|
||||
@@ -488,7 +492,7 @@ func (clients *clientsContainer) findRuntime(ip netip.Addr, idStr string) (cj *c
|
||||
WHOIS: whoisOrEmpty(rc),
|
||||
}
|
||||
|
||||
disallowed, rule := clients.dnsServer.IsBlockedClient(ip, idStr)
|
||||
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, idStr)
|
||||
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
||||
|
||||
return cj
|
||||
|
||||
399
internal/home/clientshttp_internal_test.go
Normal file
399
internal/home/clientshttp_internal_test.go
Normal file
@@ -0,0 +1,399 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testClientIP1 = "1.1.1.1"
|
||||
testClientIP2 = "2.2.2.2"
|
||||
)
|
||||
|
||||
// testBlockedClientChecker is a mock implementation of the
|
||||
// [BlockedClientChecker] interface.
|
||||
type testBlockedClientChecker struct {
|
||||
onIsBlockedClient func(ip netip.Addr, clientiD string) (blocked bool, rule string)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ BlockedClientChecker = (*testBlockedClientChecker)(nil)
|
||||
|
||||
// IsBlockedClient implements the [BlockedClientChecker] interface for
|
||||
// *testBlockedClientChecker.
|
||||
func (c *testBlockedClientChecker) IsBlockedClient(
|
||||
ip netip.Addr,
|
||||
clientID string,
|
||||
) (blocked bool, rule string) {
|
||||
return c.onIsBlockedClient(ip, clientID)
|
||||
}
|
||||
|
||||
// newPersistentClient is a helper function that returns a persistent client
|
||||
// with the specified name and newly generated UID.
|
||||
func newPersistentClient(name string) (c *client.Persistent) {
|
||||
return &client.Persistent{
|
||||
Name: name,
|
||||
UID: client.MustNewUID(),
|
||||
BlockedServices: &filtering.BlockedServices{
|
||||
Schedule: &schedule.Weekly{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// newPersistentClientWithIDs is a helper function that returns a persistent
|
||||
// client with the specified name and ids.
|
||||
func newPersistentClientWithIDs(tb testing.TB, name string, ids []string) (c *client.Persistent) {
|
||||
tb.Helper()
|
||||
|
||||
c = newPersistentClient(name)
|
||||
err := c.SetIDs(ids)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// assertClients is a helper function that compares lists of persistent clients.
|
||||
func assertClients(tb testing.TB, want, got []*client.Persistent) {
|
||||
tb.Helper()
|
||||
|
||||
require.Len(tb, got, len(want))
|
||||
|
||||
sortFunc := func(a, b *client.Persistent) (n int) {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
}
|
||||
|
||||
slices.SortFunc(want, sortFunc)
|
||||
slices.SortFunc(got, sortFunc)
|
||||
|
||||
slices.CompareFunc(want, got, func(a, b *client.Persistent) (n int) {
|
||||
assert.True(tb, a.EqualIDs(b), "%q doesn't have the same ids as %q", a.Name, b.Name)
|
||||
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
// assertPersistentClients is a helper function that uses HTTP API to check
|
||||
// whether want persistent clients are the same as the persistent clients stored
|
||||
// in the clients container.
|
||||
func assertPersistentClients(tb testing.TB, clients *clientsContainer, want []*client.Persistent) {
|
||||
tb.Helper()
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
clients.handleGetClients(rw, &http.Request{})
|
||||
|
||||
body, err := io.ReadAll(rw.Body)
|
||||
require.NoError(tb, err)
|
||||
|
||||
clientList := &clientListJSON{}
|
||||
err = json.Unmarshal(body, clientList)
|
||||
require.NoError(tb, err)
|
||||
|
||||
var got []*client.Persistent
|
||||
for _, cj := range clientList.Clients {
|
||||
var c *client.Persistent
|
||||
c, err = clients.jsonToClient(*cj, nil)
|
||||
require.NoError(tb, err)
|
||||
|
||||
got = append(got, c)
|
||||
}
|
||||
|
||||
assertClients(tb, want, got)
|
||||
}
|
||||
|
||||
// assertPersistentClientsData is a helper function that checks whether want
|
||||
// persistent clients are the same as the persistent clients stored in data.
|
||||
func assertPersistentClientsData(
|
||||
tb testing.TB,
|
||||
clients *clientsContainer,
|
||||
data []map[string]*clientJSON,
|
||||
want []*client.Persistent,
|
||||
) {
|
||||
tb.Helper()
|
||||
|
||||
var got []*client.Persistent
|
||||
for _, cm := range data {
|
||||
for _, cj := range cm {
|
||||
var c *client.Persistent
|
||||
c, err := clients.jsonToClient(*cj, nil)
|
||||
require.NoError(tb, err)
|
||||
|
||||
got = append(got, c)
|
||||
}
|
||||
}
|
||||
|
||||
assertClients(tb, want, got)
|
||||
}
|
||||
|
||||
func TestClientsContainer_HandleAddClient(t *testing.T) {
|
||||
clients := newClientsContainer(t)
|
||||
|
||||
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
||||
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||
|
||||
clientEmptyID := newPersistentClient("empty_client_id")
|
||||
clientEmptyID.ClientIDs = []string{""}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
client *client.Persistent
|
||||
wantCode int
|
||||
wantClient []*client.Persistent
|
||||
}{{
|
||||
name: "add_one",
|
||||
client: clientOne,
|
||||
wantCode: http.StatusOK,
|
||||
wantClient: []*client.Persistent{clientOne},
|
||||
}, {
|
||||
name: "add_two",
|
||||
client: clientTwo,
|
||||
wantCode: http.StatusOK,
|
||||
wantClient: []*client.Persistent{clientOne, clientTwo},
|
||||
}, {
|
||||
name: "duplicate_client",
|
||||
client: clientTwo,
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantClient: []*client.Persistent{clientOne, clientTwo},
|
||||
}, {
|
||||
name: "empty_client_id",
|
||||
client: clientEmptyID,
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantClient: []*client.Persistent{clientOne, clientTwo},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cj := clientToJSON(tc.client)
|
||||
|
||||
body, err := json.Marshal(cj)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
clients.handleAddClient(rw, r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.wantCode, rw.Code)
|
||||
|
||||
assertPersistentClients(t, clients, tc.wantClient)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsContainer_HandleDelClient(t *testing.T) {
|
||||
clients := newClientsContainer(t)
|
||||
|
||||
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
||||
err := clients.add(clientOne)
|
||||
require.NoError(t, err)
|
||||
|
||||
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||
err = clients.add(clientTwo)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
client *client.Persistent
|
||||
wantCode int
|
||||
wantClient []*client.Persistent
|
||||
}{{
|
||||
name: "remove_one",
|
||||
client: clientOne,
|
||||
wantCode: http.StatusOK,
|
||||
wantClient: []*client.Persistent{clientTwo},
|
||||
}, {
|
||||
name: "duplicate_client",
|
||||
client: clientOne,
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantClient: []*client.Persistent{clientTwo},
|
||||
}, {
|
||||
name: "empty_client_name",
|
||||
client: newPersistentClient(""),
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantClient: []*client.Persistent{clientTwo},
|
||||
}, {
|
||||
name: "remove_two",
|
||||
client: clientTwo,
|
||||
wantCode: http.StatusOK,
|
||||
wantClient: []*client.Persistent{},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cj := clientToJSON(tc.client)
|
||||
|
||||
var body []byte
|
||||
body, err = json.Marshal(cj)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
clients.handleDelClient(rw, r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.wantCode, rw.Code)
|
||||
|
||||
assertPersistentClients(t, clients, tc.wantClient)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsContainer_HandleUpdateClient(t *testing.T) {
|
||||
clients := newClientsContainer(t)
|
||||
|
||||
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
||||
err := clients.add(clientOne)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertPersistentClients(t, clients, []*client.Persistent{clientOne})
|
||||
|
||||
clientModified := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||
|
||||
clientEmptyID := newPersistentClient("empty_client_id")
|
||||
clientEmptyID.ClientIDs = []string{""}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
clientName string
|
||||
modified *client.Persistent
|
||||
wantCode int
|
||||
wantClient []*client.Persistent
|
||||
}{{
|
||||
name: "update_one",
|
||||
clientName: clientOne.Name,
|
||||
modified: clientModified,
|
||||
wantCode: http.StatusOK,
|
||||
wantClient: []*client.Persistent{clientModified},
|
||||
}, {
|
||||
name: "empty_name",
|
||||
clientName: "",
|
||||
modified: clientOne,
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantClient: []*client.Persistent{clientModified},
|
||||
}, {
|
||||
name: "client_not_found",
|
||||
clientName: "client_not_found",
|
||||
modified: clientOne,
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantClient: []*client.Persistent{clientModified},
|
||||
}, {
|
||||
name: "empty_client_id",
|
||||
clientName: clientModified.Name,
|
||||
modified: clientEmptyID,
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantClient: []*client.Persistent{clientModified},
|
||||
}, {
|
||||
name: "no_ids",
|
||||
clientName: clientModified.Name,
|
||||
modified: newPersistentClient("no_ids"),
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantClient: []*client.Persistent{clientModified},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
uj := updateJSON{
|
||||
Name: tc.clientName,
|
||||
Data: *clientToJSON(tc.modified),
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body, err = json.Marshal(uj)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
clients.handleUpdateClient(rw, r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.wantCode, rw.Code)
|
||||
|
||||
assertPersistentClients(t, clients, tc.wantClient)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientsContainer_HandleFindClient(t *testing.T) {
|
||||
clients := newClientsContainer(t)
|
||||
clients.clientChecker = &testBlockedClientChecker{
|
||||
onIsBlockedClient: func(ip netip.Addr, clientID string) (ok bool, rule string) {
|
||||
return false, ""
|
||||
},
|
||||
}
|
||||
|
||||
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
||||
err := clients.add(clientOne)
|
||||
require.NoError(t, err)
|
||||
|
||||
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||
err = clients.add(clientTwo)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
query url.Values
|
||||
wantCode int
|
||||
wantClient []*client.Persistent
|
||||
}{{
|
||||
name: "single",
|
||||
query: url.Values{
|
||||
"ip0": []string{testClientIP1},
|
||||
},
|
||||
wantCode: http.StatusOK,
|
||||
wantClient: []*client.Persistent{clientOne},
|
||||
}, {
|
||||
name: "multiple",
|
||||
query: url.Values{
|
||||
"ip0": []string{testClientIP1},
|
||||
"ip1": []string{testClientIP2},
|
||||
},
|
||||
wantCode: http.StatusOK,
|
||||
wantClient: []*client.Persistent{clientOne, clientTwo},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodGet, "", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
r.URL.RawQuery = tc.query.Encode()
|
||||
rw := httptest.NewRecorder()
|
||||
clients.handleFindClient(rw, r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.wantCode, rw.Code)
|
||||
|
||||
var body []byte
|
||||
body, err = io.ReadAll(rw.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
clientData := []map[string]*clientJSON{}
|
||||
err = json.Unmarshal(body, &clientData)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertPersistentClientsData(t, clients, clientData, tc.wantClient)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -203,15 +203,24 @@ type dnsConfig struct {
|
||||
// resolver should be used.
|
||||
PrivateNets []netutil.Prefix `yaml:"private_networks"`
|
||||
|
||||
// UsePrivateRDNS defines if the PTR requests for unknown addresses from
|
||||
// locally-served networks should be resolved via private PTR resolvers.
|
||||
// UsePrivateRDNS enables resolving requests containing a private IP address
|
||||
// using private reverse DNS resolvers. See PrivateRDNSResolvers.
|
||||
//
|
||||
// TODO(e.burkov): Rename in YAML.
|
||||
UsePrivateRDNS bool `yaml:"use_private_ptr_resolvers"`
|
||||
|
||||
// LocalPTRResolvers is the slice of addresses to be used as upstreams
|
||||
// for PTR queries for locally-served networks.
|
||||
LocalPTRResolvers []string `yaml:"local_ptr_upstreams"`
|
||||
// PrivateRDNSResolvers is the slice of addresses to be used as upstreams
|
||||
// for private requests. It's only used for PTR, SOA, and NS queries,
|
||||
// containing an ARPA subdomain, came from the the client with private
|
||||
// address. The address considered private according to PrivateNets.
|
||||
//
|
||||
// If empty, the OS-provided resolvers are used for private requests.
|
||||
PrivateRDNSResolvers []string `yaml:"local_ptr_upstreams"`
|
||||
|
||||
// UseDNS64 defines if DNS64 should be used for incoming requests.
|
||||
// UseDNS64 defines if DNS64 should be used for incoming requests. Requests
|
||||
// of type PTR for addresses within the configured prefixes will be resolved
|
||||
// via [PrivateRDNSResolvers], so those should be valid and UsePrivateRDNS
|
||||
// be set to true.
|
||||
UseDNS64 bool `yaml:"use_dns64"`
|
||||
|
||||
// DNS64Prefixes is the list of NAT64 prefixes to be used for DNS64.
|
||||
@@ -658,7 +667,7 @@ func (c *configuration) write() (err error) {
|
||||
dns := &config.DNS
|
||||
dns.Config = c
|
||||
|
||||
dns.LocalPTRResolvers = s.LocalPTRResolvers()
|
||||
dns.PrivateRDNSResolvers = s.LocalPTRResolvers()
|
||||
|
||||
addrProcConf := s.AddrProcConfig()
|
||||
config.Clients.Sources.RDNS = addrProcConf.UseRDNS
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -18,7 +17,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
@@ -150,21 +148,19 @@ func initDNSServer(
|
||||
return fmt.Errorf("dnsforward.NewServer: %w", err)
|
||||
}
|
||||
|
||||
Context.clients.dnsServer = Context.dnsServer
|
||||
Context.clients.clientChecker = Context.dnsServer
|
||||
|
||||
dnsConf, err := newServerConfig(&config.DNS, config.Clients.Sources, tlsConf, httpReg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("newServerConfig: %w", err)
|
||||
}
|
||||
|
||||
// Try to prepare the server with disabled private RDNS resolution if it
|
||||
// failed to prepare as is. See TODO on [ErrBadPrivateRDNSUpstreams].
|
||||
err = Context.dnsServer.Prepare(dnsConf)
|
||||
if privRDNSErr := (&dnsforward.PrivateRDNSError{}); errors.As(err, &privRDNSErr) {
|
||||
log.Info("WARNING: %s; trying to disable private RDNS resolution", err)
|
||||
|
||||
// TODO(e.burkov): Recreate the server with private RDNS disabled. This
|
||||
// should go away once the private RDNS resolution is moved to the proxy.
|
||||
var locResErr *dnsforward.LocalResolversError
|
||||
if errors.As(err, &locResErr) && errors.Is(locResErr.Err, upstream.ErrNoUpstreams) {
|
||||
log.Info("WARNING: no local resolvers configured while private RDNS " +
|
||||
"resolution enabled, trying to disable")
|
||||
dnsConf.UsePrivateRDNS = false
|
||||
err = Context.dnsServer.Prepare(dnsConf)
|
||||
}
|
||||
@@ -245,7 +241,7 @@ func newServerConfig(
|
||||
TLSv12Roots: Context.tlsRoots,
|
||||
ConfigModified: onConfigModified,
|
||||
HTTPRegister: httpReg,
|
||||
LocalPTRResolvers: dnsConf.LocalPTRResolvers,
|
||||
LocalPTRResolvers: dnsConf.PrivateRDNSResolvers,
|
||||
UseDNS64: dnsConf.UseDNS64,
|
||||
DNS64Prefixes: dnsConf.DNS64Prefixes,
|
||||
UsePrivateRDNS: dnsConf.UsePrivateRDNS,
|
||||
@@ -531,36 +527,6 @@ func closeDNSServer() {
|
||||
log.Debug("all dns modules are closed")
|
||||
}
|
||||
|
||||
// safeSearchResolver is a [filtering.Resolver] implementation used for safe
|
||||
// search.
|
||||
type safeSearchResolver struct{}
|
||||
|
||||
// type check
|
||||
var _ filtering.Resolver = safeSearchResolver{}
|
||||
|
||||
// LookupIP implements [filtering.Resolver] interface for safeSearchResolver.
|
||||
// It returns the slice of net.Addr with IPv4 and IPv6 instances.
|
||||
func (r safeSearchResolver) LookupIP(
|
||||
ctx context.Context,
|
||||
network string,
|
||||
host string,
|
||||
) (ips []net.IP, err error) {
|
||||
addrs, err := Context.dnsServer.Resolve(ctx, network, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(addrs) == 0 {
|
||||
return nil, fmt.Errorf("couldn't lookup host: %s", host)
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
ips = append(ips, a.AsSlice())
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// checkStatsAndQuerylogDirs checks and returns directory paths to store
|
||||
// statistics and query log.
|
||||
func checkStatsAndQuerylogDirs(
|
||||
|
||||
@@ -439,7 +439,6 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) {
|
||||
conf.ParentalBlockHost = host
|
||||
}
|
||||
|
||||
conf.SafeSearchConf.CustomResolver = safeSearchResolver{}
|
||||
conf.SafeSearch, err = safesearch.NewDefault(
|
||||
conf.SafeSearchConf,
|
||||
"default",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -76,8 +76,7 @@ func getLogSettings(opts options) (ls *logSettings) {
|
||||
ls.Verbose = true
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Use cmp.Or in Go 1.22.
|
||||
ls.File = stringutil.Coalesce(opts.logFile, ls.File)
|
||||
ls.File = cmp.Or(opts.logFile, ls.File)
|
||||
|
||||
if opts.runningAsService && ls.File == "" && runtime.GOOS == "windows" {
|
||||
// When running as a Windows service, use eventlog by default if
|
||||
|
||||
@@ -306,7 +306,7 @@ func handleServiceStatusCommand(s service.Service) {
|
||||
}
|
||||
}
|
||||
|
||||
// handleServiceStatusCommand handles service "install" command
|
||||
// handleServiceInstallCommand handles service "install" command.
|
||||
func handleServiceInstallCommand(s service.Service) {
|
||||
err := svcAction(s, "install")
|
||||
if err != nil {
|
||||
@@ -340,7 +340,7 @@ AdGuard Home is now available at the following addresses:`)
|
||||
}
|
||||
}
|
||||
|
||||
// handleServiceStatusCommand handles service "uninstall" command
|
||||
// handleServiceUninstallCommand handles service "uninstall" command.
|
||||
func handleServiceUninstallCommand(s service.Service) {
|
||||
if aghos.IsOpenWrt() {
|
||||
// On OpenWrt it is important to run disable command first
|
||||
@@ -649,11 +649,6 @@ status() {
|
||||
|
||||
// freeBSDScript is the source of the daemon script for FreeBSD. Keep as close
|
||||
// as possible to the https://github.com/kardianos/service/blob/18c957a3dc1120a2efe77beb401d476bade9e577/service_freebsd.go#L204.
|
||||
//
|
||||
// TODO(a.garipov): Don't use .WorkingDirectory here. There are currently no
|
||||
// guarantees that it will actually be the required directory.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2614.
|
||||
const freeBSDScript = `#!/bin/sh
|
||||
# PROVIDE: {{.Name}}
|
||||
# REQUIRE: networking
|
||||
@@ -667,7 +662,9 @@ name="{{.Name}}"
|
||||
pidfile_child="/var/run/${name}.pid"
|
||||
pidfile="/var/run/${name}_daemon.pid"
|
||||
command="/usr/sbin/daemon"
|
||||
command_args="-P ${pidfile} -p ${pidfile_child} -T ${name} -r {{.WorkingDirectory}}/{{.Name}}"
|
||||
daemon_args="-P ${pidfile} -p ${pidfile_child} -r -t ${name}"
|
||||
command_args="${daemon_args} {{.Path}}{{range .Arguments}} {{.}}{{end}}"
|
||||
|
||||
run_rc_command "$1"
|
||||
`
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
@@ -76,7 +76,7 @@ func (*openbsdRunComService) Platform() (p string) {
|
||||
|
||||
// String implements service.Service interface for *openbsdRunComService.
|
||||
func (s *openbsdRunComService) String() string {
|
||||
return stringutil.Coalesce(s.cfg.DisplayName, s.cfg.Name)
|
||||
return cmp.Or(s.cfg.DisplayName, s.cfg.Name)
|
||||
}
|
||||
|
||||
// getBool returns the value of the given name from kv, assuming the value is a
|
||||
|
||||
@@ -147,7 +147,7 @@ func BenchmarkManager_LookupHost(b *testing.B) {
|
||||
|
||||
b.Run("long", func(b *testing.B) {
|
||||
const name = "a.very.long.domain.name.inside.the.domain.example.com"
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
ipsetPropsSink = m.lookupHost(name)
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ func BenchmarkManager_LookupHost(b *testing.B) {
|
||||
|
||||
b.Run("short", func(b *testing.B) {
|
||||
const name = "example.net"
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
ipsetPropsSink = m.lookupHost(name)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
"github.com/google/renameio/v2/maybe"
|
||||
)
|
||||
|
||||
@@ -38,7 +39,7 @@ func (h *signalHandler) handle() {
|
||||
|
||||
if aghos.IsReconfigureSignal(sig) {
|
||||
h.reconfigure()
|
||||
} else if aghos.IsShutdownSignal(sig) {
|
||||
} else if osutil.IsShutdownSignal(sig) {
|
||||
status := h.shutdown()
|
||||
h.removePID()
|
||||
|
||||
@@ -122,7 +123,8 @@ func newSignalHandler(
|
||||
services: svcs,
|
||||
}
|
||||
|
||||
aghos.NotifyShutdownSignal(h.signal)
|
||||
notifier := osutil.DefaultSignalNotifier{}
|
||||
osutil.NotifyShutdownSignal(notifier, h.signal)
|
||||
aghos.NotifyReconfigureSignal(h.signal)
|
||||
|
||||
return h
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package dnssvc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -94,10 +93,8 @@ func TestService(t *testing.T) {
|
||||
}},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
defer cancel()
|
||||
|
||||
cli := &dns.Client{}
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
|
||||
var resp *dns.Msg
|
||||
require.Eventually(t, func() (ok bool) {
|
||||
@@ -110,10 +107,8 @@ func TestService(t *testing.T) {
|
||||
assert.NotNil(t, resp)
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
defer cancel()
|
||||
err = svc.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
|
||||
|
||||
err = svc.Shutdown(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = upstreamSrv.Shutdown()
|
||||
|
||||
@@ -109,12 +109,8 @@ func newTestServer(
|
||||
|
||||
err = svc.Start()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
err = svc.Shutdown(ctx)
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, func() (err error) {
|
||||
return svc.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
|
||||
})
|
||||
|
||||
c = svc.Config()
|
||||
|
||||
@@ -303,7 +303,7 @@ func BenchmarkAnonymizeIP(b *testing.B) {
|
||||
b.Run(bc.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
AnonymizeIP(bc.ip)
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ func BenchmarkAnonymizeIP(b *testing.B) {
|
||||
b.Run(bc.name+"_slow", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
anonymizeIPSlow(bc.ip)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ type logEntry struct {
|
||||
Answer []byte `json:",omitempty"`
|
||||
OrigAnswer []byte `json:",omitempty"`
|
||||
|
||||
// TODO(s.chzhen): Use netip.Addr.
|
||||
IP net.IP `json:"IP"`
|
||||
|
||||
Result filtering.Result
|
||||
|
||||
@@ -143,13 +143,13 @@ func TestQueryLogOffsetLimit(t *testing.T) {
|
||||
secondPageDomain = "second.example.org"
|
||||
)
|
||||
// Add entries to the log.
|
||||
for i := 0; i < entNum; i++ {
|
||||
for range entNum {
|
||||
addEntry(l, secondPageDomain, net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||
}
|
||||
// Write them to the first file.
|
||||
require.NoError(t, l.flushLogBuffer())
|
||||
// Add more to the in-memory part of log.
|
||||
for i := 0; i < entNum; i++ {
|
||||
for range entNum {
|
||||
addEntry(l, firstPageDomain, net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ func TestQueryLogMaxFileScanEntries(t *testing.T) {
|
||||
|
||||
const entNum = 10
|
||||
// Add entries to the log.
|
||||
for i := 0; i < entNum; i++ {
|
||||
for range entNum {
|
||||
addEntry(l, "example.org", net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
|
||||
}
|
||||
// Write them to disk.
|
||||
|
||||
@@ -37,7 +37,7 @@ func prepareTestFile(t *testing.T, dir string, linesNum int) (name string) {
|
||||
|
||||
var lineIP uint32
|
||||
lineTime := time.Date(2020, 2, 18, 19, 36, 35, 920973000, time.UTC)
|
||||
for i := 0; i < linesNum; i++ {
|
||||
for range linesNum {
|
||||
lineIP++
|
||||
lineTime = lineTime.Add(time.Second)
|
||||
|
||||
|
||||
@@ -68,13 +68,13 @@ func TestStats_races(t *testing.T) {
|
||||
startWG, finWG := &sync.WaitGroup{}, &sync.WaitGroup{}
|
||||
waitCh := make(chan unit)
|
||||
|
||||
for i := 0; i < writersNum; i++ {
|
||||
for i := range writersNum {
|
||||
startWG.Add(1)
|
||||
finWG.Add(1)
|
||||
go writeFunc(startWG, finWG, waitCh, i)
|
||||
}
|
||||
|
||||
for i := 0; i < readersNum; i++ {
|
||||
for range readersNum {
|
||||
startWG.Add(1)
|
||||
finWG.Add(1)
|
||||
go readFunc(startWG, finWG, waitCh)
|
||||
@@ -111,7 +111,7 @@ func TestStatsCtx_FillCollectedStats_daily(t *testing.T) {
|
||||
|
||||
dailyData := []*unitDB{}
|
||||
|
||||
for i := 0; i < daysCount*24; i++ {
|
||||
for i := range daysCount * 24 {
|
||||
n := uint64(i)
|
||||
nResult := make([]uint64, resultLast)
|
||||
nResult[RFiltered] = n
|
||||
|
||||
@@ -195,7 +195,7 @@ func TestLargeNumbers(t *testing.T) {
|
||||
for h := 0; h < hoursNum; h++ {
|
||||
atomic.AddUint32(&curHour, 1)
|
||||
|
||||
for i := 0; i < cliNumPerHour; i++ {
|
||||
for i := range cliNumPerHour {
|
||||
ip := net.IP{127, 0, byte((i & 0xff00) >> 8), byte(i & 0xff)}
|
||||
e := &stats.Entry{
|
||||
Domain: fmt.Sprintf("domain%d.hour%d", i, h),
|
||||
|
||||
@@ -525,9 +525,8 @@ func (s *StatsCtx) fillCollectedStatsDaily(
|
||||
hours := countHours(curHour, days)
|
||||
units = units[len(units)-hours:]
|
||||
|
||||
for i := 0; i < len(units); i++ {
|
||||
for i, u := range units {
|
||||
day := i / 24
|
||||
u := units[i]
|
||||
|
||||
data.DNSQueries[day] += u.NTotal
|
||||
data.BlockedFiltering[day] += u.NResult[RFiltered]
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
module github.com/AdguardTeam/AdGuardHome/internal/tools
|
||||
|
||||
go 1.22.2
|
||||
go 1.22.3
|
||||
|
||||
require (
|
||||
github.com/fzipp/gocyclo v0.6.0
|
||||
github.com/golangci/misspell v0.4.1
|
||||
github.com/golangci/misspell v0.5.1
|
||||
github.com/gordonklaus/ineffassign v0.1.0
|
||||
github.com/kisielk/errcheck v1.7.0
|
||||
github.com/kyoh86/looppointer v0.2.1
|
||||
github.com/securego/gosec/v2 v2.19.0
|
||||
github.com/securego/gosec/v2 v2.20.0
|
||||
github.com/uudashr/gocognit v1.1.2
|
||||
golang.org/x/tools v0.19.0
|
||||
golang.org/x/vuln v1.0.4
|
||||
golang.org/x/tools v0.21.0
|
||||
golang.org/x/vuln v1.1.0
|
||||
honnef.co/go/tools v0.4.7
|
||||
mvdan.cc/gofumpt v0.6.0
|
||||
mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14
|
||||
mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -26,9 +26,9 @@ require (
|
||||
github.com/kyoh86/nolint v0.0.1 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -8,18 +8,18 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g=
|
||||
github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM=
|
||||
github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
|
||||
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
|
||||
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -38,16 +38,16 @@ github.com/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fx
|
||||
github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw=
|
||||
github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU=
|
||||
github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI=
|
||||
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
|
||||
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
|
||||
github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
|
||||
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
|
||||
github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
|
||||
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk=
|
||||
github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM=
|
||||
github.com/securego/gosec/v2 v2.20.0 h1:z/d5qp1niWa2avgFyUIglYTYYuGq2LrJwNj1HRVXsqc=
|
||||
github.com/securego/gosec/v2 v2.20.0/go.mod h1:hkiArbBZLwK1cehBcg3oFWUlYPWTBffPwwJVWChu83o=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI=
|
||||
@@ -63,26 +63,26 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240325151524-a685a6edb6d8 h1:ShhqwXlNzuDeQzaa6htzo1S333ACXZzJZgZLpKAza8E=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240506185415-9bf2ced13842 h1:S62OJe0/hUkTgveY1HXZMHWBOy21DVrobMYz2cMCO64=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240506185415-9bf2ced13842/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -93,24 +93,24 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/vuln v1.0.4 h1:SP0mPeg2PmGCu03V+61EcQiOjmpri2XijexKdzv8Z1I=
|
||||
golang.org/x/vuln v1.0.4/go.mod h1:NbJdUQhX8jY++FtuhrXs2Eyx0yePo9pF7nPlIjo9aaQ=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/vuln v1.1.0 h1:ECEdI+aEtjpF90eqEcDL5Q11DWSZAw5PJQWlp0+gWqc=
|
||||
golang.org/x/vuln v1.1.0/go.mod h1:HT/Ar8fE34tbxWG2s7PYjVl+iIE4Er36/940Z+K540Y=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -122,5 +122,5 @@ honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs=
|
||||
honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
|
||||
mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
|
||||
mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA=
|
||||
mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w=
|
||||
mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI=
|
||||
mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1 h1:Nykk7fggxChwLK4rUPYESzeIwqsuxXXlFEAh5YhaMRo=
|
||||
mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI=
|
||||
|
||||
@@ -3,6 +3,7 @@ package whois
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -17,7 +18,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/ioutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/bluele/gcache"
|
||||
)
|
||||
|
||||
@@ -174,7 +174,7 @@ func whoisParse(data []byte, maxLen int) (info map[string]string) {
|
||||
val = trimValue(val, maxLen)
|
||||
case "descr", "netname":
|
||||
key = "orgname"
|
||||
val = stringutil.Coalesce(orgname, val)
|
||||
val = cmp.Or(orgname, val)
|
||||
orgname = val
|
||||
case "whois":
|
||||
key = "whois"
|
||||
@@ -232,7 +232,7 @@ func (w *Default) queryAll(ctx context.Context, target string) (info map[string]
|
||||
server := net.JoinHostPort(w.serverAddr, w.portStr)
|
||||
var data []byte
|
||||
|
||||
for i := 0; i < w.maxRedirects; i++ {
|
||||
for range w.maxRedirects {
|
||||
data, err = w.query(ctx, target, server)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user