增加DNSSEC验证支持

This commit is contained in:
Alex Yang
2025-12-16 00:43:34 +08:00
parent 87a05bfb6d
commit 0b365e6dfe
20 changed files with 218525 additions and 61 deletions

View File

@@ -141,6 +141,7 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
resolver: &dns.Client{
Net: "udp",
Timeout: time.Duration(config.Timeout) * time.Millisecond,
UDPSize: 4096, // 增加UDP缓冲区大小支持更大的DNSSEC响应
},
ctx: ctx,
cancel: cancel,
@@ -544,19 +545,103 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 尝试所有上游DNS服务器
for _, upstream := range s.config.UpstreamDNS {
response, rtt, err := s.resolver.Exchange(r, upstream)
if err == nil && response != nil && response.Rcode == dns.RcodeSuccess {
if err == nil && response != nil {
// 设置递归可用标志
response.RecursionAvailable = true
logger.Debug("DNS查询成功", "domain", domain, "rtt", rtt, "server", upstream)
// 完整DNSSEC支持验证DNSSEC签名
if s.config.EnableDNSSEC {
// 提取DNSKEY和RRSIG记录
dnskeys := make(map[uint16]*dns.DNSKEY) // KeyTag -> DNSKEY
rrsigs := make([]*dns.RRSIG, 0)
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
// 从响应中提取所有DNSKEY和RRSIG记录
for _, rr := range response.Answer {
if dnskey, ok := rr.(*dns.DNSKEY); ok {
tag := dnskey.KeyTag()
dnskeys[tag] = dnskey
} else if rrsig, ok := rr.(*dns.RRSIG); ok {
rrsigs = append(rrsigs, rrsig)
}
}
for _, rr := range response.Ns {
if dnskey, ok := rr.(*dns.DNSKEY); ok {
tag := dnskey.KeyTag()
dnskeys[tag] = dnskey
} else if rrsig, ok := rr.(*dns.RRSIG); ok {
rrsigs = append(rrsigs, rrsig)
}
}
for _, rr := range response.Extra {
if dnskey, ok := rr.(*dns.DNSKEY); ok {
tag := dnskey.KeyTag()
dnskeys[tag] = dnskey
} else if rrsig, ok := rr.(*dns.RRSIG); ok {
rrsigs = append(rrsigs, rrsig)
}
}
s.updateStats(func(stats *Stats) {
stats.Allowed++
})
return response, rtt
// 如果有RRSIG记录尝试验证签名
if len(rrsigs) > 0 {
logger.Debug("DNS响应包含DNSSEC记录", "domain", domain, "server", upstream, "rrsig_count", len(rrsigs), "dnskey_count", len(dnskeys))
// 验证签名
signatureValid := true
for _, rrsig := range rrsigs {
// 查找对应的DNSKEY
dnskey, exists := dnskeys[rrsig.KeyTag]
if !exists {
logger.Warn("DNSSEC验证失败找不到对应的DNSKEY", "domain", domain, "server", upstream, "key_tag", rrsig.KeyTag)
signatureValid = false
continue
}
// 收集需要验证的记录集
rrset := make([]dns.RR, 0)
for _, rr := range response.Answer {
if rr.Header().Name == rrsig.Header().Name && rr.Header().Rrtype == rrsig.TypeCovered {
rrset = append(rrset, rr)
}
}
for _, rr := range response.Ns {
if rr.Header().Name == rrsig.Header().Name && rr.Header().Rrtype == rrsig.TypeCovered {
rrset = append(rrset, rr)
}
}
// 验证签名
if len(rrset) > 0 {
err := rrsig.Verify(dnskey, rrset)
if err != nil {
logger.Warn("DNSSEC签名验证失败", "domain", domain, "server", upstream, "error", err, "key_tag", rrsig.KeyTag)
signatureValid = false
} else {
logger.Debug("DNSSEC签名验证成功", "domain", domain, "server", upstream, "key_tag", rrsig.KeyTag)
}
}
}
// 设置AD标志Authenticated Data
response.AuthenticatedData = signatureValid
if !signatureValid {
logger.Warn("DNSSEC验证失败至少一个签名无效", "domain", domain, "server", upstream)
}
} else {
logger.Debug("DNS响应不包含DNSSEC记录", "domain", domain, "server", upstream)
}
}
if response.Rcode == dns.RcodeSuccess {
logger.Debug("DNS查询成功", "domain", domain, "rtt", rtt, "server", upstream)
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
s.updateStats(func(stats *Stats) {
stats.Allowed++
})
return response, rtt
}
}
}