更新Swaggers API
This commit is contained in:
59
dns/cache.go
59
dns/cache.go
@@ -11,6 +11,7 @@ import (
|
||||
type DNSCacheItem struct {
|
||||
Response *dns.Msg // DNS响应消息
|
||||
Expiry time.Time // 过期时间
|
||||
HasDNSSEC bool // 是否包含DNSSEC记录
|
||||
}
|
||||
|
||||
// DNSCache DNS缓存结构
|
||||
@@ -38,6 +39,63 @@ func cacheKey(qName string, qType uint16) string {
|
||||
return qName + "|" + dns.TypeToString[qType]
|
||||
}
|
||||
|
||||
// hasDNSSECRecords 检查响应是否包含DNSSEC记录
|
||||
func hasDNSSECRecords(response *dns.Msg) bool {
|
||||
// 检查响应中是否包含DNSSEC相关记录(DNSKEY、RRSIG、DS、NSEC、NSEC3等)
|
||||
for _, rr := range response.Answer {
|
||||
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.RRSIG); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.DS); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC3); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, rr := range response.Ns {
|
||||
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.RRSIG); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.DS); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC3); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, rr := range response.Extra {
|
||||
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.RRSIG); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.DS); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC3); 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 +106,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()
|
||||
|
||||
499
dns/server.go
499
dns/server.go
@@ -23,11 +23,11 @@ import (
|
||||
)
|
||||
|
||||
// BlockedDomain 屏蔽域名统计
|
||||
|
||||
type BlockedDomain struct {
|
||||
Domain string
|
||||
Count int64
|
||||
LastSeen time.Time
|
||||
DNSSEC bool // 是否使用了DNSSEC
|
||||
}
|
||||
|
||||
// ClientStats 客户端统计
|
||||
@@ -57,6 +57,8 @@ type QueryLog struct {
|
||||
BlockRule string // 屏蔽规则(如果被屏蔽)
|
||||
BlockType string // 屏蔽类型(如果被屏蔽)
|
||||
FromCache bool // 是否来自缓存
|
||||
DNSSEC bool // 是否使用了DNSSEC
|
||||
EDNS bool // 是否使用了EDNS
|
||||
}
|
||||
|
||||
// StatsData 用于持久化的统计数据结构
|
||||
@@ -111,6 +113,9 @@ type Server struct {
|
||||
|
||||
// DNS查询缓存
|
||||
dnsCache *DNSCache // DNS响应缓存
|
||||
|
||||
// 域名DNSSEC状态映射表
|
||||
domainDNSSECStatus map[string]bool // 域名到DNSSEC状态的映射
|
||||
}
|
||||
|
||||
// Stats DNS服务器统计信息
|
||||
@@ -125,6 +130,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服务器实例
|
||||
@@ -141,6 +150,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,
|
||||
@@ -155,6 +165,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),
|
||||
@@ -171,6 +185,8 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
|
||||
ipGeolocationCacheTTL: 24 * time.Hour, // 缓存有效期24小时
|
||||
// DNS查询缓存初始化
|
||||
dnsCache: NewDNSCache(cacheTTL),
|
||||
// 初始化域名DNSSEC状态映射表
|
||||
domainDNSSECStatus: make(map[string]bool),
|
||||
}
|
||||
|
||||
// 加载已保存的统计数据
|
||||
@@ -327,8 +343,8 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
response.SetRcode(r, dns.RcodeRefused)
|
||||
w.WriteMsg(response)
|
||||
|
||||
// 计算响应时间
|
||||
responseTime := time.Since(startTime).Milliseconds()
|
||||
// 缓存命中,响应时间设为0ms
|
||||
responseTime := int64(0)
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.TotalResponseTime += responseTime
|
||||
if stats.Queries > 0 {
|
||||
@@ -337,15 +353,15 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
})
|
||||
|
||||
// 添加查询日志
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false)
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查hosts文件是否有匹配
|
||||
if ip, exists := s.shieldManager.GetHostsIP(domain); exists {
|
||||
s.handleHostsResponse(w, r, ip)
|
||||
// 计算响应时间
|
||||
responseTime := time.Since(startTime).Milliseconds()
|
||||
// 缓存命中,响应时间设为0ms
|
||||
responseTime := int64(0)
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.TotalResponseTime += responseTime
|
||||
if stats.Queries > 0 {
|
||||
@@ -354,7 +370,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
})
|
||||
|
||||
// 添加查询日志
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false)
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, false, true)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -376,16 +392,58 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
})
|
||||
|
||||
// 添加查询日志
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false)
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true)
|
||||
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以匹配请求
|
||||
cachedResponseCopy.Compress = true
|
||||
|
||||
// 如果客户端请求包含EDNS记录,确保响应也包含EDNS
|
||||
if opt := r.IsEdns0(); opt != nil {
|
||||
// 检查响应是否已经包含EDNS记录
|
||||
if respOpt := cachedResponseCopy.IsEdns0(); respOpt == nil {
|
||||
// 添加EDNS记录,使用客户端的UDP缓冲区大小
|
||||
cachedResponseCopy.SetEdns0(opt.UDPSize(), s.config.EnableDNSSEC)
|
||||
} else {
|
||||
// 确保响应的UDP缓冲区大小不超过客户端请求的大小
|
||||
if respOpt.UDPSize() > opt.UDPSize() {
|
||||
// 移除现有的EDNS记录
|
||||
for i := range cachedResponseCopy.Extra {
|
||||
if cachedResponseCopy.Extra[i] == respOpt {
|
||||
cachedResponseCopy.Extra = append(cachedResponseCopy.Extra[:i], cachedResponseCopy.Extra[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// 添加新的EDNS记录,使用客户端的UDP缓冲区大小
|
||||
cachedResponseCopy.SetEdns0(opt.UDPSize(), s.config.EnableDNSSEC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteMsg(cachedResponseCopy)
|
||||
|
||||
// 计算响应时间
|
||||
@@ -397,21 +455,57 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
}
|
||||
})
|
||||
|
||||
// 如果缓存响应包含DNSSEC记录,更新DNSSEC查询计数
|
||||
if cachedDNSSEC {
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.DNSSECQueries++
|
||||
// 缓存响应视为DNSSEC成功
|
||||
stats.DNSSECSuccess++
|
||||
})
|
||||
}
|
||||
|
||||
// 添加查询日志 - 标记为缓存
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true)
|
||||
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType)
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true)
|
||||
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC)
|
||||
return
|
||||
}
|
||||
|
||||
// 缓存未命中,转发到上游DNS服务器
|
||||
response, _ := s.forwardDNSRequestWithCache(r, domain)
|
||||
response, rtt := s.forwardDNSRequestWithCache(r, domain)
|
||||
if response != nil {
|
||||
// 如果客户端请求包含EDNS记录,确保响应也包含EDNS
|
||||
if opt := r.IsEdns0(); opt != nil {
|
||||
// 检查响应是否已经包含EDNS记录
|
||||
if respOpt := response.IsEdns0(); respOpt == nil {
|
||||
// 添加EDNS记录,使用客户端的UDP缓冲区大小
|
||||
response.SetEdns0(opt.UDPSize(), s.config.EnableDNSSEC)
|
||||
} else {
|
||||
// 确保响应的UDP缓冲区大小不超过客户端请求的大小
|
||||
if respOpt.UDPSize() > opt.UDPSize() {
|
||||
// 移除现有的EDNS记录
|
||||
for i := range response.Extra {
|
||||
if response.Extra[i] == respOpt {
|
||||
response.Extra = append(response.Extra[:i], response.Extra[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// 添加新的EDNS记录,使用客户端的UDP缓冲区大小
|
||||
response.SetEdns0(opt.UDPSize(), s.config.EnableDNSSEC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入响应给客户端
|
||||
w.WriteMsg(response)
|
||||
}
|
||||
|
||||
// 计算响应时间
|
||||
responseTime := time.Since(startTime).Milliseconds()
|
||||
// 使用上游服务器的实际响应时间(转换为毫秒)
|
||||
responseTime := int64(rtt.Milliseconds())
|
||||
// 如果rtt为0(查询失败),则使用本地计算的时间
|
||||
if responseTime == 0 {
|
||||
responseTime = time.Since(startTime).Milliseconds()
|
||||
}
|
||||
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.TotalResponseTime += responseTime
|
||||
if stats.Queries > 0 {
|
||||
@@ -419,6 +513,23 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
}
|
||||
})
|
||||
|
||||
// 检查响应是否包含DNSSEC记录并验证结果
|
||||
responseDNSSEC := false
|
||||
if response != nil {
|
||||
// 使用hasDNSSECRecords函数检查是否包含DNSSEC记录
|
||||
responseDNSSEC = s.hasDNSSECRecords(response)
|
||||
|
||||
// 检查AD标志,确认DNSSEC验证是否成功
|
||||
if response.AuthenticatedData {
|
||||
responseDNSSEC = true
|
||||
}
|
||||
|
||||
// 更新域名的DNSSEC状态
|
||||
if responseDNSSEC {
|
||||
s.updateDomainDNSSECStatus(domain, true)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果响应成功,缓存结果(增强版缓存存储)
|
||||
if response != nil && response.Rcode == dns.RcodeSuccess {
|
||||
// 创建响应副本以避免后续修改影响缓存
|
||||
@@ -426,11 +537,11 @@ 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)
|
||||
}
|
||||
|
||||
// 添加查询日志 - 标记为实时
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false)
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true)
|
||||
}
|
||||
|
||||
// handleHostsResponse 处理hosts文件匹配的响应
|
||||
@@ -537,24 +648,193 @@ 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
|
||||
|
||||
// 始终支持EDNS
|
||||
var udpSize uint16 = 4096
|
||||
var doFlag bool = s.config.EnableDNSSEC
|
||||
|
||||
// 检查客户端请求是否包含EDNS记录
|
||||
if opt := r.IsEdns0(); opt != nil {
|
||||
// 保留客户端的UDP缓冲区大小
|
||||
udpSize = opt.UDPSize()
|
||||
// 移除现有的EDNS记录,以便重新添加
|
||||
for i := range r.Extra {
|
||||
if r.Extra[i] == opt {
|
||||
r.Extra = append(r.Extra[:i], r.Extra[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加EDNS记录,设置适当的UDPSize和DO标志
|
||||
r.SetEdns0(udpSize, doFlag)
|
||||
|
||||
// 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.Rcode == dns.RcodeSuccess {
|
||||
if err == nil && response != nil {
|
||||
// 设置递归可用标志
|
||||
response.RecursionAvailable = true
|
||||
|
||||
logger.Debug("DNS查询成功", "domain", domain, "rtt", rtt, "server", upstream)
|
||||
// 检查是否包含DNSSEC记录
|
||||
containsDNSSEC := s.hasDNSSECRecords(response)
|
||||
|
||||
// 记录解析域名统计
|
||||
s.updateResolvedDomainStats(domain)
|
||||
// 如果启用了DNSSEC且响应包含DNSSEC记录,验证DNSSEC签名
|
||||
if s.config.EnableDNSSEC && containsDNSSEC {
|
||||
// 验证DNSSEC记录
|
||||
signatureValid := s.verifyDNSSEC(response)
|
||||
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.Allowed++
|
||||
})
|
||||
return response, rtt
|
||||
// 设置AD标志(Authenticated Data)
|
||||
response.AuthenticatedData = signatureValid
|
||||
|
||||
if signatureValid {
|
||||
// 更新DNSSEC验证成功计数
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.DNSSECQueries++
|
||||
stats.DNSSECSuccess++
|
||||
})
|
||||
} else {
|
||||
// 更新DNSSEC验证失败计数
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.DNSSECQueries++
|
||||
stats.DNSSECFailed++
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 如果响应成功,根据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)
|
||||
}
|
||||
// 保存为备选响应
|
||||
if !hasBackup {
|
||||
backupResponse = response
|
||||
backupRtt = rtt
|
||||
hasBackup = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 当启用DNSSEC且没有找到带DNSSEC的响应时,向DNSSEC专用服务器发送请求
|
||||
if s.config.EnableDNSSEC && !hasDNSSECResponse {
|
||||
logger.Debug("向DNSSEC专用服务器发送请求", "domain", domain)
|
||||
|
||||
// 增加DNSSEC查询计数
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.DNSSECQueries++
|
||||
})
|
||||
|
||||
for _, dnssecServer := range dnssecServers {
|
||||
response, rtt, err := s.resolver.Exchange(r, dnssecServer)
|
||||
if err == nil && response != nil {
|
||||
// 设置递归可用标志
|
||||
response.RecursionAvailable = true
|
||||
|
||||
// 检查是否包含DNSSEC记录
|
||||
containsDNSSEC := s.hasDNSSECRecords(response)
|
||||
|
||||
if response.Rcode == dns.RcodeSuccess {
|
||||
// 验证DNSSEC记录
|
||||
signatureValid := s.verifyDNSSEC(response)
|
||||
|
||||
// 设置AD标志(Authenticated Data)
|
||||
response.AuthenticatedData = signatureValid
|
||||
|
||||
if signatureValid {
|
||||
// 更新DNSSEC验证成功计数
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.DNSSECSuccess++
|
||||
})
|
||||
} else {
|
||||
// 更新DNSSEC验证失败计数
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.DNSSECFailed++
|
||||
})
|
||||
}
|
||||
|
||||
// 优先使用DNSSEC专用服务器的响应,尤其是带有DNSSEC记录的
|
||||
if containsDNSSEC {
|
||||
// 即使之前有最佳响应,也优先使用DNSSEC专用服务器的DNSSEC响应
|
||||
bestResponse = response
|
||||
bestRtt = rtt
|
||||
hasBestResponse = true
|
||||
hasDNSSECResponse = true
|
||||
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应,优先使用", "domain", domain, "server", dnssecServer, "rtt", rtt)
|
||||
}
|
||||
// 注意:如果DNSSEC专用服务器返回的响应不包含DNSSEC记录,
|
||||
// 我们不会覆盖之前从upstreamDNS获取的响应,
|
||||
// 这符合"本地解析指的是直接使用上游服务器upstreamDNS进行解析, 而不是dnssecUpstreamDNS"的要求
|
||||
|
||||
// 更新备选响应
|
||||
if !hasBackup {
|
||||
backupResponse = response
|
||||
backupRtt = rtt
|
||||
hasBackup = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 返回最佳响应
|
||||
if hasBestResponse {
|
||||
// 检查最佳响应是否包含DNSSEC记录
|
||||
bestHasDNSSEC := s.hasDNSSECRecords(bestResponse)
|
||||
|
||||
// 如果启用了DNSSEC且最佳响应不包含DNSSEC记录,使用upstreamDNS的解析结果
|
||||
if s.config.EnableDNSSEC && !bestHasDNSSEC {
|
||||
logger.Debug("最佳响应不包含DNSSEC记录,使用upstreamDNS的解析结果", "domain", domain)
|
||||
}
|
||||
|
||||
// 记录解析域名统计
|
||||
s.updateResolvedDomainStats(domain)
|
||||
|
||||
// 更新域名的DNSSEC状态
|
||||
if bestHasDNSSEC {
|
||||
s.updateDomainDNSSECStatus(domain, true)
|
||||
}
|
||||
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.Allowed++
|
||||
})
|
||||
return bestResponse, bestRtt
|
||||
}
|
||||
|
||||
// 如果有备选响应,返回该响应
|
||||
if hasBackup {
|
||||
logger.Debug("使用备选响应,没有找到更好的结果", "domain", domain)
|
||||
// 记录解析域名统计
|
||||
s.updateResolvedDomainStats(domain)
|
||||
|
||||
s.updateStats(func(stats *Stats) {
|
||||
stats.Allowed++
|
||||
})
|
||||
return backupResponse, backupRtt
|
||||
}
|
||||
|
||||
// 所有上游服务器都失败,返回服务器失败错误
|
||||
response := new(dns.Msg)
|
||||
response.SetReply(r)
|
||||
@@ -630,6 +910,164 @@ func (s *Server) updateClientStats(ip string) {
|
||||
}
|
||||
}
|
||||
|
||||
// hasDNSSECRecords 检查响应是否包含DNSSEC记录
|
||||
func (s *Server) hasDNSSECRecords(response *dns.Msg) bool {
|
||||
// 检查响应中是否包含DNSSEC相关记录(DNSKEY、RRSIG、DS、NSEC、NSEC3等)
|
||||
for _, rr := range response.Answer {
|
||||
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.RRSIG); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.DS); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC3); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, rr := range response.Ns {
|
||||
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.RRSIG); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.DS); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC3); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, rr := range response.Extra {
|
||||
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.RRSIG); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.DS); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rr.(*dns.NSEC3); 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) {
|
||||
// 确保域名是小写
|
||||
domain = strings.ToLower(domain)
|
||||
|
||||
// 更新域名的DNSSEC状态
|
||||
s.resolvedDomainsMutex.Lock()
|
||||
defer s.resolvedDomainsMutex.Unlock()
|
||||
|
||||
// 更新resolvedDomains中的DNSSEC状态
|
||||
if entry, exists := s.resolvedDomains[domain]; exists {
|
||||
entry.DNSSEC = dnssec
|
||||
} else {
|
||||
s.resolvedDomains[domain] = &BlockedDomain{
|
||||
Domain: domain,
|
||||
Count: 1,
|
||||
LastSeen: time.Now(),
|
||||
DNSSEC: dnssec,
|
||||
}
|
||||
}
|
||||
|
||||
// 更新domainDNSSECStatus映射
|
||||
s.domainDNSSECStatus[domain] = dnssec
|
||||
}
|
||||
|
||||
// updateResolvedDomainStats 更新解析域名统计
|
||||
func (s *Server) updateResolvedDomainStats(domain string) {
|
||||
s.resolvedDomainsMutex.Lock()
|
||||
@@ -643,6 +1081,7 @@ func (s *Server) updateResolvedDomainStats(domain string) {
|
||||
Domain: domain,
|
||||
Count: 1,
|
||||
LastSeen: time.Now(),
|
||||
DNSSEC: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -655,7 +1094,7 @@ func (s *Server) updateStats(update func(*Stats)) {
|
||||
}
|
||||
|
||||
// addQueryLog 添加查询日志
|
||||
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache bool) {
|
||||
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool) {
|
||||
// 获取IP地理位置
|
||||
location := s.getIpGeolocation(clientIP)
|
||||
|
||||
@@ -671,6 +1110,8 @@ func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime in
|
||||
BlockRule: blockRule,
|
||||
BlockType: blockType,
|
||||
FromCache: fromCache,
|
||||
DNSSEC: dnssec,
|
||||
EDNS: edns,
|
||||
}
|
||||
|
||||
// 添加到日志列表
|
||||
@@ -720,6 +1161,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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1145,6 +1590,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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user