Golang中DNS解析:TTL和GetServers如何选择

Golang中DNS解析:TTL和GetServers如何选择 ![挥手] 你好,我一直在寻找一种方法来获取 DNS TXT 记录的 ttl 值。看起来 golang 标准库不支持记录的 ttl,所以我一直在研究 miekg/dns。它工作得很好,但为了让它开箱即用,我需要获取系统的 DNS 服务器。由于系统设置是针对每个网络接口的,我原以为 net.Interfaces() 会有一个 Interface.DNS 属性,或者有一个可用的 net.DNSServers() 函数。

你知道有什么其他库可以帮助解决这个问题吗? 你认为这应该是一个 golang 标准库的问题吗? 你认为支持 DNS 记录的 TTL 应该是 golang 标准库的一个功能吗? 这应该是像 miekd/dns 这样的第三方库的功能吗?

注意: 从在 Node.js 中使用的情况来看,似乎应该可以以跨平台的方式实现它。可能是因为 Node 在后台使用了 c-ares

$ node -p "(new dns.Resolver).getServers()"

更多关于Golang中DNS解析:TTL和GetServers如何选择的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

确实,Windows可能会给跨平台解决方案增加复杂性……

更多关于Golang中DNS解析:TTL和GetServers如何选择的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好 @martinheidegger,欢迎来到论坛。

关于获取系统的 DNS 服务器列表,这个 Stack Overflow 回答建议像 Ruby 那样解析 /etc/resolv.conf 文件。

我无法评论获取 TTL 数据是否应该成为标准库或第三方包的功能。这取决于 Go 团队或任何已经编写或计划编写 DNS 包的人的决定。但我发现 retryabledns 这个包似乎包含了 TTL 数据,作为 TXT 响应的一部分,这可能符合你的需求。

你好 @christophberger

retryabledns 是一个死胡同,因为它在底层使用了 miekg/dns 😓。

Stack Overflow 上的回答指出了正确的实现方向,即使它包含一个失效的链接。以下是我找到的实现片段:

          when /\Andots:(\d+)\z/
            ndots = $1.to_i
          end
        }
      end
    }
  }
  return { :nameserver => nameserver, :search => search, :ndots => ndots }
end

def Config.default_config_hash(filename="/etc/resolv.conf")
  if File.exist? filename
    config_hash = Config.parse_resolv_conf(filename)
  else
    if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
      require 'win32/resolv'
      search, nameserver = Win32::Resolv.get_resolv_info
      config_hash = {}
      config_hash[:nameserver] = nameserver if nameserver
      config_hash[:search] = [search].flatten if search
    end
  end

实际上,它会使用 win32/resolv 单独处理 Windows 的情况——所以相当复杂。尝试支持它看起来远不止是一个“5分钟搞定”的快速项目 😅,我在想是否有人(可能比我更感兴趣)愿意实现它。

在Golang中获取DNS记录的TTL值确实需要依赖第三方库,标准库的net.LookupTXT等函数不返回TTL信息。以下是具体实现方案:

使用miekg/dns获取TTL和系统DNS服务器

1. 获取系统DNS服务器(跨平台方案)

package main

import (
    "fmt"
    "github.com/miekg/dns"
    "net"
)

// 获取系统DNS服务器列表
func getSystemDNSServers() ([]string, error) {
    config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
    if err != nil {
        // Windows系统回退
        return []string{"8.8.8.8:53", "8.8.4.4:53"}, nil
    }
    
    servers := make([]string, 0, len(config.Servers))
    for _, server := range config.Servers {
        servers = append(servers, net.JoinHostPort(server, config.Port))
    }
    return servers, nil
}

2. 获取TXT记录及其TTL

func getTXTWithTTL(domain string) ([]string, []uint32, error) {
    servers, err := getSystemDNSServers()
    if err != nil {
        return nil, nil, err
    }
    
    c := new(dns.Client)
    m := new(dns.Msg)
    m.SetQuestion(dns.Fqdn(domain), dns.TypeTXT)
    
    var txtRecords []string
    var ttls []uint32
    
    for _, server := range servers {
        r, _, err := c.Exchange(m, server)
        if err != nil {
            continue // 尝试下一个DNS服务器
        }
        
        if r.Rcode != dns.RcodeSuccess {
            continue
        }
        
        for _, ans := range r.Answer {
            if txt, ok := ans.(*dns.TXT); ok {
                txtRecords = append(txtRecords, txt.Txt...)
                ttls = append(ttls, txt.Hdr.Ttl)
            }
        }
        break // 成功获取后退出
    }
    
    return txtRecords, ttls, nil
}

func main() {
    txts, ttls, err := getTXTWithTTL("example.com")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    
    for i, txt := range txts {
        fmt.Printf("TXT: %s, TTL: %d seconds\n", txt, ttls[i])
    }
}

3. 针对不同操作系统的优化处理

// 增强版系统DNS获取
func getSystemDNSServersEnhanced() []string {
    // Linux/macOS
    if servers, err := dns.ClientConfigFromFile("/etc/resolv.conf"); err == nil {
        result := make([]string, len(servers.Servers))
        for i, s := range servers.Servers {
            result[i] = net.JoinHostPort(s, servers.Port)
        }
        return result
    }
    
    // Windows备用方案
    return getWindowsDNSServers()
}

func getWindowsDNSServers() []string {
    // Windows系统可以通过注册表或WMI获取DNS服务器
    // 这里提供回退方案
    return []string{
        "8.8.8.8:53",     // Google DNS
        "1.1.1.1:53",     // Cloudflare DNS
        "208.67.222.222:53", // OpenDNS
    }
}

关于标准库支持的问题

  1. TTL支持必要性:DNS记录的TTL对于缓存策略至关重要,标准库应该提供此信息。当前net.LookupTXT等函数的设计确实存在不足。

  2. 系统DNS服务器获取:标准库缺少net.GetDNSServers()这样的函数是个明显的缺口。不同平台的实现差异应该由标准库封装。

  3. 第三方库现状miekg/dns是目前最成熟的解决方案,它提供了完整的DNS协议实现。对于需要TTL或高级DNS功能的场景,必须使用此类第三方库。

  4. Node.js对比:Node.js的dns模块确实更完善,它底层使用c-ares或系统解析器,提供了TTL和服务器配置访问。Go标准库在这方面需要跟进。

生产环境建议

// 带缓存的DNS解析器
type DNSCache struct {
    cache map[string]cacheEntry
    mu    sync.RWMutex
}

type cacheEntry struct {
    records []string
    ttls    []uint32
    expires time.Time
}

func (c *DNSCache) GetTXT(domain string) ([]string, error) {
    c.mu.RLock()
    if entry, found := c.cache[domain]; found && time.Now().Before(entry.expires) {
        c.mu.RUnlock()
        return entry.records, nil
    }
    c.mu.RUnlock()
    
    // 缓存未命中,重新解析
    txts, ttls, err := getTXTWithTTL(domain)
    if err != nil {
        return nil, err
    }
    
    // 使用最小TTL设置缓存过期
    minTTL := findMinTTL(ttls)
    c.mu.Lock()
    c.cache[domain] = cacheEntry{
        records: txts,
        ttls:    ttls,
        expires: time.Now().Add(time.Duration(minTTL) * time.Second),
    }
    c.mu.Unlock()
    
    return txts, nil
}

func findMinTTL(ttls []uint32) uint32 {
    if len(ttls) == 0 {
        return 300 // 默认5分钟
    }
    min := ttls[0]
    for _, ttl := range ttls[1:] {
        if ttl < min {
            min = ttl
        }
    }
    return min
}

当前情况下,使用miekg/dns是获取DNS记录TTL的唯一可靠方法。标准库需要增加对TTL和系统DNS配置的访问支持,这属于合理的功能需求。

回到顶部