وسائل نقل 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 والتعيينات المُعرفة مسبقاً.
البنية
type Transport struct {
dns.TransportAdapter
files []*File
predefined map[string][]netip.Addr
}أولوية البحث
- الإدخالات المُعرفة مسبقاً يتم فحصها أولاً (التعيينات داخل التكوين)
- ملفات Hosts يتم فحصها بالترتيب
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.
الإنشاء
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 تحليلاً كسولاً مع التخزين المؤقت:
type File struct {
path string
access sync.Mutex
modTime time.Time
modSize int64
entries map[string][]netip.Addr
lastCheck time.Time
}إبطال ذاكرة التخزين المؤقت: يُعاد تحليل الملف فقط عندما:
- مرت أكثر من 5 ثوانٍ منذ آخر فحص، و
- تغير وقت تعديل الملف أو حجمه
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
- يتم تجميع الإدخالات المتعددة لنفس اسم المضيف
المسار الافتراضي
// Linux/macOS
var DefaultPath = "/etc/hosts"
// Windows
var DefaultPath = `C:\Windows\System32\drivers\etc\hosts`وسيلة نقل DNS المحلية
تحل وسيلة النقل المحلية استعلامات DNS باستخدام محلل النظام.
البنية (غير Darwin)
type Transport struct {
dns.TransportAdapter
ctx context.Context
logger logger.ContextLogger
hosts *hosts.File
dialer N.Dialer
preferGo bool
resolved ResolvedResolver
}أولوية الحل
- systemd-resolved (Linux فقط): إذا كان النظام يستخدم resolved، تُرسل الاستعلامات عبر D-Bus
- ملف hosts المحلي: يتم فحصه قبل حل الشبكة
- محلل النظام: يرجع إلى
net.Resolverالخاص بـ 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
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 مؤقتاً لكل واجهة ويتم تحديثها عند:
- تغير حالة الواجهة (تشغيل/إيقاف الرابط)
- تغير عنوان الواجهة
- انتهاء صلاحية ذاكرة التخزين المؤقت
تخزين الخوادم مؤقتاً
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
{
"dns": {
"servers": [
{
"tag": "hosts",
"type": "hosts",
"path": ["/etc/hosts", "/custom/hosts"],
"predefined": {
"myserver.local": ["192.168.1.100"]
}
}
]
}
}Local
{
"dns": {
"servers": [
{
"tag": "local",
"type": "local",
"prefer_go": false
}
]
}
}DHCP
{
"dns": {
"servers": [
{
"tag": "dhcp",
"type": "dhcp",
"interface": "eth0"
}
]
}
}