dnssec优化

This commit is contained in:
Alex Yang
2025-12-16 12:23:22 +08:00
parent fb3b28c22f
commit 50d2b5fbdb
40 changed files with 36602 additions and 457878 deletions

View File

@@ -11,6 +11,7 @@ import (
type DNSCacheItem struct {
Response *dns.Msg // DNS响应消息
Expiry time.Time // 过期时间
HasDNSSEC bool // 是否包含DNSSEC记录
}
// DNSCache DNS缓存结构
@@ -38,6 +39,36 @@ func cacheKey(qName string, qType uint16) string {
return qName + "|" + dns.TypeToString[qType]
}
// hasDNSSECRecords 检查响应是否包含DNSSEC记录
func hasDNSSECRecords(response *dns.Msg) bool {
// 检查响应中是否包含DNSKEY或RRSIG记录
for _, rr := range response.Answer {
if _, ok := rr.(*dns.DNSKEY); ok {
return true
}
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
}
for _, rr := range response.Ns {
if _, ok := rr.(*dns.DNSKEY); ok {
return true
}
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
}
for _, rr := range response.Extra {
if _, ok := rr.(*dns.DNSKEY); ok {
return true
}
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
}
return false
}
// Set 设置缓存项
func (c *DNSCache) Set(qName string, qType uint16, response *dns.Msg, ttl time.Duration) {
if ttl <= 0 {
@@ -48,6 +79,7 @@ func (c *DNSCache) Set(qName string, qType uint16, response *dns.Msg, ttl time.D
item := &DNSCacheItem{
Response: response.Copy(), // 复制响应以避免外部修改
Expiry: time.Now().Add(ttl),
HasDNSSEC: hasDNSSECRecords(response), // 检查并设置DNSSEC标志
}
c.mutex.Lock()

View File

@@ -129,6 +129,10 @@ type Stats struct {
QueryTypes map[string]int64 // 查询类型统计
SourceIPs map[string]bool // 活跃来源IP
CpuUsage float64 // CPU使用率(%)
DNSSECQueries int64 // DNSSEC查询总数
DNSSECSuccess int64 // DNSSEC验证成功数
DNSSECFailed int64 // DNSSEC验证失败数
DNSSECEnabled bool // 是否启用了DNSSEC
}
// NewServer 创建DNS服务器实例
@@ -160,6 +164,10 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
QueryTypes: make(map[string]int64),
SourceIPs: make(map[string]bool),
CpuUsage: 0,
DNSSECQueries: 0,
DNSSECSuccess: 0,
DNSSECFailed: 0,
DNSSECEnabled: config.EnableDNSSEC,
},
blockedDomains: make(map[string]*BlockedDomain),
resolvedDomains: make(map[string]*BlockedDomain),
@@ -387,8 +395,27 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
return
}
// 检查缓存中是否有响应(增强版缓存查询
if cachedResponse, found := s.dnsCache.Get(r.Question[0].Name, qType); found {
// 检查缓存中是否有响应(优先查找带DNSSEC的缓存项
var cachedResponse *dns.Msg
var found bool
var cachedDNSSEC bool
// 1. 首先检查是否有普通缓存项
if tempResponse, tempFound := s.dnsCache.Get(r.Question[0].Name, qType); tempFound {
cachedResponse = tempResponse
found = tempFound
cachedDNSSEC = s.hasDNSSECRecords(tempResponse)
}
// 2. 如果启用了DNSSEC且没有找到带DNSSEC的缓存项
// 尝试从所有缓存中查找是否有其他响应包含DNSSEC记录
// 这里可以进一步优化比如在缓存中标记DNSSEC状态快速查找
if s.config.EnableDNSSEC && !cachedDNSSEC {
// 目前的缓存实现不支持按DNSSEC状态查找所以这里暂时跳过
// 后续可以考虑改进缓存实现添加DNSSEC状态标记
}
if found {
// 缓存命中,直接返回缓存的响应
cachedResponseCopy := cachedResponse.Copy() // 创建响应副本避免并发修改问题
cachedResponseCopy.Id = r.Id // 更新ID以匹配请求
@@ -404,30 +431,18 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
}
})
// 检查缓存响应是否包含DNSSEC记录
cachedDNSSEC := false
for _, rr := range cachedResponse.Answer {
if _, ok := rr.(*dns.RRSIG); ok {
cachedDNSSEC = true
break
}
}
for _, rr := range cachedResponse.Ns {
if _, ok := rr.(*dns.RRSIG); ok {
cachedDNSSEC = true
break
}
}
for _, rr := range cachedResponse.Extra {
if _, ok := rr.(*dns.RRSIG); ok {
cachedDNSSEC = true
break
}
// 如果缓存响应包含DNSSEC记录更新DNSSEC查询计数
if cachedDNSSEC {
s.updateStats(func(stats *Stats) {
stats.DNSSECQueries++
// 缓存响应视为DNSSEC成功
stats.DNSSECSuccess++
})
}
// 添加查询日志 - 标记为缓存
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC)
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType)
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC)
return
}
@@ -452,26 +467,15 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
}
})
// 检查响应是否包含DNSSEC记录
// 检查响应是否包含DNSSEC记录并验证结果
responseDNSSEC := false
if response != nil {
for _, rr := range response.Answer {
if _, ok := rr.(*dns.RRSIG); ok {
responseDNSSEC = true
break
}
}
for _, rr := range response.Ns {
if _, ok := rr.(*dns.RRSIG); ok {
responseDNSSEC = true
break
}
}
for _, rr := range response.Extra {
if _, ok := rr.(*dns.RRSIG); ok {
responseDNSSEC = true
break
}
// 使用hasDNSSECRecords函数检查是否包含DNSSEC记录
responseDNSSEC = s.hasDNSSECRecords(response)
// 检查AD标志确认DNSSEC验证是否成功
if response.AuthenticatedData {
responseDNSSEC = true
}
// 更新域名的DNSSEC状态
@@ -487,7 +491,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
// 设置合理的TTL不超过默认的30分钟
defaultCacheTTL := 30 * time.Minute
s.dnsCache.Set(r.Question[0].Name, qType, responseCopy, defaultCacheTTL)
logger.Debug("DNS响应已缓存", "domain", domain, "type", queryType, "ttl", defaultCacheTTL)
logger.Debug("DNS响应已缓存", "domain", domain, "type", queryType, "ttl", defaultCacheTTL, "dnssec", responseDNSSEC)
}
// 添加查询日志 - 标记为实时
@@ -598,142 +602,139 @@ func (s *Server) handleBlockedResponse(w dns.ResponseWriter, r *dns.Msg, domain
// forwardDNSRequestWithCache 转发DNS请求到上游服务器并返回响应
func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg, time.Duration) {
// 尝试所有上游DNS服务器
var bestResponse *dns.Msg
var bestRtt time.Duration
var hasBestResponse bool
var hasDNSSECResponse bool
var backupResponse *dns.Msg
var backupRtt time.Duration
var hasBackup bool
// DNSSEC专用服务器列表从配置中获取
dnssecServers := s.config.DNSSECUpstreamDNS
// 1. 首先尝试所有配置的上游DNS服务器
for _, upstream := range s.config.UpstreamDNS {
response, rtt, err := s.resolver.Exchange(r, upstream)
if err == nil && response != nil {
// 设置递归可用标志
response.RecursionAvailable = true
// 完整DNSSEC支持验证DNSSEC签名
if s.config.EnableDNSSEC {
// 提取DNSKEY和RRSIG记录
dnskeys := make(map[uint16]*dns.DNSKEY) // KeyTag -> DNSKEY
rrsigs := make([]*dns.RRSIG, 0)
// 检查是否包含DNSSEC记录
containsDNSSEC := s.hasDNSSECRecords(response)
// 从响应中提取所有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)
}
// 如果响应成功根据DNSSEC状态选择最佳响应
if response.Rcode == dns.RcodeSuccess {
// 优先选择带有DNSSEC记录的响应
if containsDNSSEC {
bestResponse = response
bestRtt = rtt
hasBestResponse = true
hasDNSSECResponse = true
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", upstream, "rtt", rtt)
} else if !hasBestResponse {
// 没有带DNSSEC的响应时保存第一个成功响应
bestResponse = response
bestRtt = rtt
hasBestResponse = true
logger.Debug("找到最佳响应", "domain", domain, "server", upstream, "rtt", rtt)
}
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)
}
// 保存为备选响应
if !hasBackup {
backupResponse = response
backupRtt = rtt
hasBackup = true
}
}
}
}
// 如果有RRSIG记录尝试验证签名
if len(rrsigs) > 0 {
logger.Debug("DNS响应包含DNSSEC记录", "domain", domain, "server", upstream, "rrsig_count", len(rrsigs), "dnskey_count", len(dnskeys))
// 2. 当启用DNSSEC且没有找到带DNSSEC的响应时向DNSSEC专用服务器发送请求
if s.config.EnableDNSSEC && !hasDNSSECResponse {
logger.Debug("向DNSSEC专用服务器发送请求", "domain", domain)
// 验证签名
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
}
// 增加DNSSEC查询计数
s.updateStats(func(stats *Stats) {
stats.DNSSECQueries++
})
// 收集需要验证的记录集
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)
}
}
for _, dnssecServer := range dnssecServers {
response, rtt, err := s.resolver.Exchange(r, dnssecServer)
if err == nil && response != nil {
// 设置递归可用标志
response.RecursionAvailable = true
// 验证签名
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)
}
}
}
// 检查是否包含DNSSEC记录
containsDNSSEC := s.hasDNSSECRecords(response)
if response.Rcode == dns.RcodeSuccess {
// 验证DNSSEC记录
signatureValid := s.verifyDNSSEC(response)
// 设置AD标志Authenticated Data
response.AuthenticatedData = signatureValid
if !signatureValid {
logger.Warn("DNSSEC验证失败至少一个签名无效", "domain", domain, "server", upstream)
}
// 如果响应成功且包含DNSSEC记录优先返回
if response.Rcode == dns.RcodeSuccess {
logger.Debug("DNS查询成功优先返回DNSSEC结果", "domain", domain, "server", upstream, "rtt", rtt)
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
if signatureValid {
// 更新DNSSEC验证成功计数
s.updateStats(func(stats *Stats) {
stats.Allowed++
stats.DNSSECSuccess++
})
} else {
// 更新DNSSEC验证失败计数
s.updateStats(func(stats *Stats) {
stats.DNSSECFailed++
})
return response, rtt
}
} else {
logger.Debug("DNS响应不包含DNSSEC记录", "domain", domain, "server", upstream)
// 如果响应成功,保存为备选响应
if response.Rcode == dns.RcodeSuccess && !hasBackup {
// 优先使用DNSSEC专用服务器的响应尤其是带有DNSSEC记录的
if containsDNSSEC {
// 即使之前有最佳响应也优先使用DNSSEC专用服务器的DNSSEC响应
bestResponse = response
bestRtt = rtt
hasBestResponse = true
hasDNSSECResponse = true
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应优先使用", "domain", domain, "server", dnssecServer, "rtt", rtt)
} else if !hasBestResponse || hasBestResponse && !s.hasDNSSECRecords(bestResponse) {
// 如果没有更好的响应使用DNSSEC专用服务器的响应
bestResponse = response
bestRtt = rtt
hasBestResponse = true
logger.Debug("使用DNSSEC专用服务器的响应", "domain", domain, "server", dnssecServer, "rtt", rtt)
}
// 更新备选响应
if !hasBackup {
backupResponse = response
backupRtt = rtt
hasBackup = true
logger.Debug("保存为备选响应", "domain", domain, "server", upstream, "rtt", rtt)
}
}
} else {
// DNSSEC已禁用使用原有逻辑
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
}
}
} else if !hasBackup && response != nil && response.Rcode == dns.RcodeSuccess {
// 保存第一个成功但有错误的响应作为备选
backupResponse = response
backupRtt = rtt
hasBackup = true
logger.Debug("保存为备选响应", "domain", domain, "server", upstream, "rtt", rtt)
}
}
// 3. 返回最佳响应
if hasBestResponse {
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
// 检查最佳响应是否包含DNSSEC记录
bestHasDNSSEC := s.hasDNSSECRecords(bestResponse)
// 更新域名的DNSSEC状态
if bestHasDNSSEC {
s.updateDomainDNSSECStatus(domain, true)
}
s.updateStats(func(stats *Stats) {
stats.Allowed++
})
return bestResponse, bestRtt
}
// 如果有备选响应,返回该响应
if hasBackup {
logger.Debug("使用备选响应,没有找到包含DNSSEC的结果", "domain", domain)
logger.Debug("使用备选响应,没有找到更好的结果", "domain", domain)
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
@@ -818,6 +819,112 @@ func (s *Server) updateClientStats(ip string) {
}
}
// hasDNSSECRecords 检查响应是否包含DNSSEC记录
func (s *Server) hasDNSSECRecords(response *dns.Msg) bool {
// 检查响应中是否包含DNSKEY或RRSIG记录
for _, rr := range response.Answer {
if _, ok := rr.(*dns.DNSKEY); ok {
return true
}
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
}
for _, rr := range response.Ns {
if _, ok := rr.(*dns.DNSKEY); ok {
return true
}
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
}
for _, rr := range response.Extra {
if _, ok := rr.(*dns.DNSKEY); ok {
return true
}
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
}
return false
}
// verifyDNSSEC 验证DNSSEC签名
func (s *Server) verifyDNSSEC(response *dns.Msg) bool {
// 提取DNSKEY和RRSIG记录
dnskeys := make(map[uint16]*dns.DNSKEY) // KeyTag -> DNSKEY
rrsigs := make([]*dns.RRSIG, 0)
// 从响应中提取所有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)
}
}
// 如果没有RRSIG记录验证失败
if len(rrsigs) == 0 {
return false
}
// 验证所有RRSIG记录
signatureValid := true
for _, rrsig := range rrsigs {
// 查找对应的DNSKEY
dnskey, exists := dnskeys[rrsig.KeyTag]
if !exists {
logger.Warn("DNSSEC验证失败找不到对应的DNSKEY", "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签名验证失败", "error", err, "key_tag", rrsig.KeyTag)
signatureValid = false
} else {
logger.Debug("DNSSEC签名验证成功", "key_tag", rrsig.KeyTag)
}
}
}
return signatureValid
}
// updateDomainDNSSECStatus 更新域名的DNSSEC状态
func (s *Server) updateDomainDNSSECStatus(domain string, dnssec bool) {
// 确保域名是小写
@@ -935,6 +1042,10 @@ func (s *Server) GetStats() *Stats {
QueryTypes: queryTypesCopy,
SourceIPs: sourceIPsCopy,
CpuUsage: s.stats.CpuUsage,
DNSSECQueries: s.stats.DNSSECQueries,
DNSSECSuccess: s.stats.DNSSECSuccess,
DNSSECFailed: s.stats.DNSSECFailed,
DNSSECEnabled: s.stats.DNSSECEnabled,
}
}
@@ -1360,6 +1471,8 @@ func (s *Server) loadStatsData() {
s.statsMutex.Lock()
if statsData.Stats != nil {
s.stats = statsData.Stats
// 确保使用当前配置中的EnableDNSSEC值覆盖从文件加载的值
s.stats.DNSSECEnabled = s.config.EnableDNSSEC
}
s.statsMutex.Unlock()