Skip to content

نظرة عامة على نظام 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  |   ... وسائل نقل أخرى
        +---------+   +---------+   +---------+
  1. موجه DNS (dns/router.go): يطابق استعلامات DNS مع القواعد، ويختار وسيلة النقل المناسبة، ويتعامل مع استراتيجية النطاق والتعيين العكسي
  2. عميل DNS (dns/client.go): ينفذ تبادل DNS الفعلي مع التخزين المؤقت (freelru)، وحقن شبكة عميل EDNS0، وذاكرة التخزين المؤقت لرفض استجابة النطاق (RDRC)، وتعديل TTL
  3. وسائل نقل 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 الخام)

  1. يستقبل Router.Exchange رسالة *dns.Msg
  2. استخراج البيانات الوصفية: نوع الاستعلام، النطاق، إصدار IP
  3. إذا لم يتم تحديد وسيلة نقل صراحة، تتم المطابقة مع قواعد DNS:
    • RuleActionDNSRoute -- اختيار وسيلة نقل مع خيارات (الاستراتيجية، التخزين المؤقت، TTL، شبكة العميل)
    • RuleActionDNSRouteOptions -- تعديل الخيارات دون اختيار وسيلة نقل
    • RuleActionReject -- إرجاع REFUSED أو إسقاط
    • RuleActionPredefined -- إرجاع استجابة مُعدة مسبقاً
  4. ينفذ Client.Exchange الاستعلام الفعلي:
    • التحقق من ذاكرة التخزين المؤقت (مع إزالة التكرار عبر قفل قائم على القنوات)
    • التحقق من RDRC للاستجابات المرفوضة سابقاً
    • تطبيق شبكة عميل EDNS0
    • تنفيذ transport.Exchange مع مهلة زمنية
    • التحقق من صحة الاستجابة (فحص حد العناوين)
    • توحيد قيم TTL
    • التخزين في ذاكرة التخزين المؤقت
  5. تخزين التعيين العكسي (IP -> نطاق) إذا كان مفعلاً

Lookup (تحويل النطاق إلى عناوين)

  1. يستقبل Router.Lookup سلسلة نصية للنطاق
  2. يحدد الاستراتيجية (IPv4Only، IPv6Only، PreferIPv4، PreferIPv6، AsIS)
  3. يرسل Client.Lookup الاستعلامات:
    • IPv4Only: استعلام A واحد
    • IPv6Only: استعلام AAAA واحد
    • غير ذلك: استعلامات A + AAAA متوازية عبر task.Group
  4. يتم ترتيب النتائج بناءً على تفضيل الاستراتيجية

حلقة إعادة محاولة القواعد

عندما يكون للقاعدة حدود عناوين (مثل قيود 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)
}