نظرة عامة على نظام DNS الفرعي
المصدر: dns/، dns/transport/، dns/transport/fakeip/، dns/transport/hosts/، dns/transport/local/، dns/transport/dhcp/
البنية المعمارية
يتكون نظام DNS الفرعي في sing-box من ثلاثة مكونات أساسية:
+------------------+
| DNS Router | مطابقة القواعد، اختيار وسيلة النقل
+------------------+
|
+------------------+
| DNS Client | التخزين المؤقت، EDNS0، RDRC، إدارة TTL
+------------------+
|
+-------------+-------------+
| | |
+---------+ +---------+ +---------+
| UDP | | HTTPS | | FakeIP | ... وسائل نقل أخرى
+---------+ +---------+ +---------+- موجه DNS (
dns/router.go): يطابق استعلامات DNS مع القواعد، ويختار وسيلة النقل المناسبة، ويتعامل مع استراتيجية النطاق والتعيين العكسي - عميل DNS (
dns/client.go): ينفذ تبادل DNS الفعلي مع التخزين المؤقت (freelru)، وحقن شبكة عميل EDNS0، وذاكرة التخزين المؤقت لرفض استجابة النطاق (RDRC)، وتعديل TTL - وسائل نقل DNS (
dns/transport/): تنفيذ الاستعلامات الخاصة بالبروتوكول (UDP، TCP، TLS، HTTPS، QUIC/HTTP3، FakeIP، Hosts، Local، DHCP)
المكونات المساعدة
- سجل وسائل النقل (
dns/transport_registry.go): تسجيل آمن النوع وعام لأنواع وسائل النقل - محول وسيلة النقل (
dns/transport_adapter.go): بنية أساسية تحتوي على النوع/الوسم/التبعيات/الاستراتيجية/شبكة العميل - وسيلة النقل الأساسية (
dns/transport/base.go): آلة حالة (New/Started/Closing/Closed) مع تتبع الاستعلامات قيد التنفيذ - الموصل (
dns/transport/connector.go): إدارة اتصال عامة من نوع singleflight
تدفق الاستعلام
Exchange (رسالة DNS الخام)
- يستقبل Router.Exchange رسالة
*dns.Msg - استخراج البيانات الوصفية: نوع الاستعلام، النطاق، إصدار IP
- إذا لم يتم تحديد وسيلة نقل صراحة، تتم المطابقة مع قواعد DNS:
RuleActionDNSRoute-- اختيار وسيلة نقل مع خيارات (الاستراتيجية، التخزين المؤقت، TTL، شبكة العميل)RuleActionDNSRouteOptions-- تعديل الخيارات دون اختيار وسيلة نقلRuleActionReject-- إرجاع REFUSED أو إسقاطRuleActionPredefined-- إرجاع استجابة مُعدة مسبقاً
- ينفذ Client.Exchange الاستعلام الفعلي:
- التحقق من ذاكرة التخزين المؤقت (مع إزالة التكرار عبر قفل قائم على القنوات)
- التحقق من RDRC للاستجابات المرفوضة سابقاً
- تطبيق شبكة عميل EDNS0
- تنفيذ transport.Exchange مع مهلة زمنية
- التحقق من صحة الاستجابة (فحص حد العناوين)
- توحيد قيم TTL
- التخزين في ذاكرة التخزين المؤقت
- تخزين التعيين العكسي (IP -> نطاق) إذا كان مفعلاً
Lookup (تحويل النطاق إلى عناوين)
- يستقبل Router.Lookup سلسلة نصية للنطاق
- يحدد الاستراتيجية (IPv4Only، IPv6Only، PreferIPv4، PreferIPv6، AsIS)
- يرسل Client.Lookup الاستعلامات:
- IPv4Only: استعلام A واحد
- IPv6Only: استعلام AAAA واحد
- غير ذلك: استعلامات A + AAAA متوازية عبر
task.Group
- يتم ترتيب النتائج بناءً على تفضيل الاستراتيجية
حلقة إعادة محاولة القواعد
عندما يكون للقاعدة حدود عناوين (مثل قيود geoip على عناوين الاستجابة)، يعيد الموجه المحاولة مع القواعد المطابقة التالية إذا تم رفض الاستجابة:
go
for {
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery, &dnsOptions)
responseCheck := addressLimitResponseCheck(rule, metadata)
response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)
if responseCheck != nil && rejected {
continue // Try next matching rule
}
break
}قرارات التصميم الرئيسية
إزالة التكرار
تستخدم ذاكرة التخزين المؤقت إزالة تكرار قائمة على القنوات لمنع مشكلة القطيع المتدافع (thundering herd):
go
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
if loaded {
<-cond // Wait for the in-flight query to complete
} else {
defer func() {
c.cacheLock.Delete(question)
close(cond) // Signal waiters
}()
}كشف الحلقات
يتم كشف حلقات استعلامات DNS (مثل أن تحتاج وسيلة النقل A إلى حل عنوان خادمها عبر وسيلة النقل A نفسها) عبر السياق:
go
contextTransport, loaded := transportTagFromContext(ctx)
if loaded && transport.Tag() == contextTransport {
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
}
ctx = contextWithTransportTag(ctx, transport.Tag())RDRC (ذاكرة التخزين المؤقت لرفض استجابة النطاق)
عندما يتم رفض استجابة بواسطة فحص حد العناوين، يتم تخزين مجموعة النطاق/نوع الاستعلام/وسيلة النقل في RDRC لتخطي الاستعلامات المستقبلية ضد نفس وسيلة النقل:
go
if rejected {
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
}
// On subsequent queries:
if c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype) {
return nil, ErrResponseRejectedCached
}شبكة عميل EDNS0
يتم تطبيقها قبل التبادل عند التكوين:
go
clientSubnet := options.ClientSubnet
if !clientSubnet.IsValid() {
clientSubnet = c.clientSubnet
}
if clientSubnet.IsValid() {
message = SetClientSubnet(message, clientSubnet)
}