Skip to content

وسائل نقل Hosts و Local لـ DNS ​

المصدر: dns/transport/hosts/hosts.go، dns/transport/hosts/hosts_file.go، dns/transport/local/local.go، dns/transport/dhcp/dhcp.go

وسيلة نقل Hosts ​

تحل وسيلة نقل Hosts النطاقات مقابل إدخالات ملف hosts والتعيينات المُعرفة مسبقاً.

البنية ​

go
type Transport struct {
    dns.TransportAdapter
    files      []*File
    predefined map[string][]netip.Addr
}

أولوية البحث ​

  1. الإدخالات المُعرفة مسبقاً يتم فحصها أولاً (التعيينات داخل التكوين)
  2. ملفات Hosts يتم فحصها بالترتيب
go
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
    question := message.Question[0]
    domain := mDNS.CanonicalName(question.Name)
    if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
        if addresses, ok := t.predefined[domain]; ok {
            return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
        }
        for _, file := range t.files {
            addresses := file.Lookup(domain)
            if len(addresses) > 0 {
                return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
            }
        }
    }
    return &mDNS.Msg{
        MsgHdr: mDNS.MsgHdr{Id: message.Id, Rcode: mDNS.RcodeNameError, Response: true},
        Question: []mDNS.Question{question},
    }, nil
}

يتم التعامل فقط مع استعلامات A و AAAA. النطاقات غير القابلة للحل تُرجع NXDOMAIN. الاستعلامات غير المتعلقة بالعناوين تُرجع أيضاً NXDOMAIN.

الإنشاء ​

go
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string,
    options option.HostsDNSServerOptions) (adapter.DNSTransport, error) {
    if len(options.Path) == 0 {
        files = append(files, NewFile(DefaultPath))  // /etc/hosts
    } else {
        for _, path := range options.Path {
            files = append(files, NewFile(filemanager.BasePath(ctx, os.ExpandEnv(path))))
        }
    }
    if options.Predefined != nil {
        for _, entry := range options.Predefined.Entries() {
            predefined[mDNS.CanonicalName(entry.Key)] = entry.Value
        }
    }
}

يتم تحويل أسماء النطاقات إلى الشكل القانوني (أحرف صغيرة، FQDN مع نقطة لاحقة) عبر mDNS.CanonicalName.

تحليل ملف Hosts ​

توفر بنية File تحليلاً كسولاً مع التخزين المؤقت:

go
type File struct {
    path    string
    access  sync.Mutex
    modTime time.Time
    modSize int64
    entries map[string][]netip.Addr
    lastCheck time.Time
}

إبطال ذاكرة التخزين المؤقت: يُعاد تحليل الملف فقط عندما:

  • مرت أكثر من 5 ثوانٍ منذ آخر فحص، و
  • تغير وقت تعديل الملف أو حجمه
go
func (f *File) Lookup(domain string) []netip.Addr {
    f.access.Lock()
    defer f.access.Unlock()
    if time.Since(f.lastCheck) > 5*time.Second {
        stat, err := os.Stat(f.path)
        if stat.ModTime() != f.modTime || stat.Size() != f.modSize {
            f.entries = parseHostsFile(f.path)
            f.modTime = stat.ModTime()
            f.modSize = stat.Size()
        }
        f.lastCheck = time.Now()
    }
    return f.entries[domain]
}

قواعد التحليل:

  • الأسطر التي تبدأ بـ # هي تعليقات
  • كل سطر: <IP> <اسم المضيف1> [اسم المضيف2] ...
  • يتم تحويل أسماء المضيفات إلى الشكل القانوني (أحرف صغيرة + نقطة لاحقة)
  • يتم دعم عناوين IPv4 و IPv6
  • يتم تجميع الإدخالات المتعددة لنفس اسم المضيف

المسار الافتراضي ​

go
// Linux/macOS
var DefaultPath = "/etc/hosts"

// Windows
var DefaultPath = `C:\Windows\System32\drivers\etc\hosts`

وسيلة نقل DNS المحلية ​

تحل وسيلة النقل المحلية استعلامات DNS باستخدام محلل النظام.

البنية (غير Darwin) ​

go
type Transport struct {
    dns.TransportAdapter
    ctx      context.Context
    logger   logger.ContextLogger
    hosts    *hosts.File
    dialer   N.Dialer
    preferGo bool
    resolved ResolvedResolver
}

أولوية الحل ​

  1. systemd-resolved (Linux فقط): إذا كان النظام يستخدم resolved، تُرسل الاستعلامات عبر D-Bus
  2. ملف hosts المحلي: يتم فحصه قبل حل الشبكة
  3. محلل النظام: يرجع إلى net.Resolver الخاص بـ Go
go
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
    // 1. Try systemd-resolved
    if t.resolved != nil {
        resolverObject := t.resolved.Object()
        if resolverObject != nil {
            return t.resolved.Exchange(resolverObject, ctx, message)
        }
    }
    // 2. Try local hosts file
    if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
        addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
        if len(addresses) > 0 {
            return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
        }
    }
    // 3. System resolver
    return t.exchange(ctx, message, question.Name)
}

كشف systemd-resolved ​

go
func (t *Transport) Start(stage adapter.StartStage) error {
    switch stage {
    case adapter.StartStateInitialize:
        if !t.preferGo {
            if isSystemdResolvedManaged() {
                resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
                if err == nil {
                    err = resolvedResolver.Start()
                    if err == nil {
                        t.resolved = resolvedResolver
                    }
                }
            }
        }
    }
}

إذا كان preferGo مضبوطاً على true، يتم استخدام محلل Go مباشرة، متجاوزاً systemd-resolved.

متغير Darwin (macOS) ​

على macOS، تستخدم وسيلة النقل المحلية خوادم DNS المكتشفة عبر DHCP أو محلل النظام مع معالجة خاصة لنطاقات .local (mDNS).

وسيلة نقل DHCP ​

تكتشف وسيلة نقل DHCP خوادم DNS ديناميكياً عبر DHCPv4:

الاكتشاف ​

ترسل وسيلة النقل رسائل DHCPv4 Discover/Request على واجهة الشبكة المحددة وتستخرج عناوين خوادم DNS من رسائل DHCP Offer/Ack.

مراقبة الواجهة ​

يتم تخزين خوادم DNS مؤقتاً لكل واجهة ويتم تحديثها عند:

  • تغير حالة الواجهة (تشغيل/إيقاف الرابط)
  • تغير عنوان الواجهة
  • انتهاء صلاحية ذاكرة التخزين المؤقت

تخزين الخوادم مؤقتاً ​

go
type Transport struct {
    dns.TransportAdapter
    ctx           context.Context
    logger        logger.ContextLogger
    interfaceName string
    autoInterface bool
    // ...
    transportAccess sync.Mutex
    transports      []adapter.DNSTransport
    lastUpdate      time.Time
}

تنشئ وسيلة نقل DHCP وسائل نقل فرعية (عادةً UDP) لكل خادم DNS مكتشف وتفوض الاستعلامات إليها.

التكوين ​

Hosts ​

json
{
  "dns": {
    "servers": [
      {
        "tag": "hosts",
        "type": "hosts",
        "path": ["/etc/hosts", "/custom/hosts"],
        "predefined": {
          "myserver.local": ["192.168.1.100"]
        }
      }
    ]
  }
}

Local ​

json
{
  "dns": {
    "servers": [
      {
        "tag": "local",
        "type": "local",
        "prefer_go": false
      }
    ]
  }
}

DHCP ​

json
{
  "dns": {
    "servers": [
      {
        "tag": "dhcp",
        "type": "dhcp",
        "interface": "eth0"
      }
    ]
  }
}