移除search domain功能,不再支持自动添加域名前缀进行查询;移除DNSConfig结构体中的PrefixDomain字段

This commit is contained in:
Alex Yang
2025-12-19 22:18:11 +08:00
parent c12f6c8584
commit b2ec6c88a7
7 changed files with 300 additions and 356 deletions

View File

@@ -2,6 +2,13 @@
所有对本项目的显著更改都将记录在此文件中。
## [1.1.3] - 2025-12-19
### 移除
- 移除search domain功能不再支持自动添加域名前缀进行查询
- 移除DNSConfig结构体中的PrefixDomain字段
- 移除配置文件中的prefixDomain配置项
## [1.1.2] - 2025-12-19
### 添加

View File

@@ -16,7 +16,7 @@
"saveInterval": 300,
"cacheTTL": 10,
"enableDNSSEC": true,
"queryMode": "parallel",
"queryMode": "loadbalance",
"domainSpecificDNS": {
"amazehome.cn": [
"10.35.10.200:53"
@@ -38,9 +38,6 @@
]
},
"prefixDomain": [
""
],
"noDNSSECDomains": [
"amazehome.cn",
"addr.arpa",

View File

@@ -14,18 +14,17 @@ type DomainSpecificDNS map[string][]string
// DNSConfig DNS配置
type DNSConfig struct {
Port int `json:"port"`
UpstreamDNS []string `json:"upstreamDNS"`
DNSSECUpstreamDNS []string `json:"dnssecUpstreamDNS"` // 用于DNSSEC查询的专用服务器
Timeout int `json:"timeout"`
StatsFile string `json:"statsFile"` // 统计数据持久化文件
SaveInterval int `json:"saveInterval"` // 数据保存间隔(秒)
CacheTTL int `json:"cacheTTL"` // DNS缓存过期时间分钟
EnableDNSSEC bool `json:"enableDNSSEC"` // 是否启用DNSSEC支持
QueryMode string `json:"queryMode"` // 查询模式:"loadbalance"(负载均衡)、"parallel"(并行请求)、"fastest-ip"最快的IP地址
Port int `json:"port"`
UpstreamDNS []string `json:"upstreamDNS"`
DNSSECUpstreamDNS []string `json:"dnssecUpstreamDNS"` // 用于DNSSEC查询的专用服务器
Timeout int `json:"timeout"`
StatsFile string `json:"statsFile"` // 统计数据持久化文件
SaveInterval int `json:"saveInterval"` // 数据保存间隔(秒)
CacheTTL int `json:"cacheTTL"` // DNS缓存过期时间分钟
EnableDNSSEC bool `json:"enableDNSSEC"` // 是否启用DNSSEC支持
QueryMode string `json:"queryMode"` // 查询模式:"loadbalance"(负载均衡)、"parallel"(并行请求)、"fastest-ip"最快的IP地址
DomainSpecificDNS DomainSpecificDNS `json:"domainSpecificDNS"` // 域名特定DNS服务器配置
NoDNSSECDomains []string `json:"noDNSSECDomains"` // 不验证DNSSEC的域名模式列表
PrefixDomain []string `json:"prefixDomain"` // 搜索域名前缀列表,对应/etc/resolv.conf中的search domain
NoDNSSECDomains []string `json:"noDNSSECDomains"` // 不验证DNSSEC的域名模式列表
}
// HTTPConfig HTTP控制台配置
@@ -93,6 +92,9 @@ func LoadConfig(path string) (*Config, error) {
if config.DNS.Port == 0 {
config.DNS.Port = 53
}
if config.DNS.Timeout == 0 {
config.DNS.Timeout = 5000 // 默认超时时间为5000毫秒
}
if len(config.DNS.UpstreamDNS) == 0 {
config.DNS.UpstreamDNS = []string{"223.5.5.5:53", "223.6.6.6:53"}
}
@@ -120,10 +122,7 @@ func LoadConfig(path string) (*Config, error) {
if config.DNS.DomainSpecificDNS == nil {
config.DNS.DomainSpecificDNS = make(DomainSpecificDNS) // 默认为空映射
}
// PrefixDomain默认值处理
if config.DNS.PrefixDomain == nil {
config.DNS.PrefixDomain = []string{} // 默认为空切片
}
if config.HTTP.Port == 0 {
config.HTTP.Port = 8080
}

View File

@@ -1,5 +0,0 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 1765974335 session_id 1765887935065810022_0

Binary file not shown.

View File

@@ -494,42 +494,10 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
var dnsServer string
var dnssecServer string
// 1. 首先尝试直接查询原始域名
// 直接查询原始域名
queryAttempts = append(queryAttempts, domain)
response, rtt, dnsServer, dnssecServer = s.forwardDNSRequestWithCache(r, domain)
// 2. 如果直接查询失败且配置了prefixDomain尝试添加前缀
if (response == nil || response.Rcode != dns.RcodeSuccess) && len(s.config.PrefixDomain) > 0 {
logger.Debug("直接查询失败尝试使用prefixDomain", "domain", domain, "prefixDomain", s.config.PrefixDomain)
// 保存原始请求
originalQuestion := r.Question[0]
// 遍历所有prefixDomain尝试添加前缀
for _, prefix := range s.config.PrefixDomain {
// 构建完整域名
fullDomain := domain + "." + prefix
queryAttempts = append(queryAttempts, fullDomain)
logger.Debug("尝试查询完整域名", "fullDomain", fullDomain)
// 创建新的请求消息
newReq := r.Copy()
// 更新查询域名
newReq.Question[0] = dns.Question{
Name: fullDomain + ".", // 域名需要以点结尾
Qtype: originalQuestion.Qtype,
Qclass: originalQuestion.Qclass,
}
// 查询带有前缀的域名
response, rtt, dnsServer, dnssecServer = s.forwardDNSRequestWithCache(newReq, fullDomain)
if response != nil && response.Rcode == dns.RcodeSuccess {
logger.Debug("使用prefixDomain查询成功", "fullDomain", fullDomain, "originalDomain", domain)
break // 找到成功的响应,退出循环
}
}
}
if response != nil {
// 如果客户端请求包含EDNS记录确保响应也包含EDNS
if opt := r.IsEdns0(); opt != nil {
@@ -763,8 +731,8 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 2. 如果没有匹配的域名特定配置
if !domainMatched {
// 如果启用了DNSSEC且有配置DNSSEC专用服务器则使用DNSSEC专用服务器
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 {
// 如果启用了DNSSEC且有配置DNSSEC专用服务器并且域名不匹配NoDNSSECDomains则使用DNSSEC专用服务器
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 && !noDNSSEC {
selectedUpstreamDNS = s.config.DNSSECUpstreamDNS
logger.Debug("使用DNSSEC专用服务器", "servers", selectedUpstreamDNS)
} else {
@@ -917,7 +885,37 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 1. 选择一个加权随机服务器
selectedServer := s.selectWeightedRandomServer(selectedUpstreamDNS)
if selectedServer != "" {
response, rtt, err := s.resolver.Exchange(r, selectedServer)
// 设置超时上下文
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
defer cancel()
// 使用带超时的方式执行Exchange
resultChan := make(chan struct {
response *dns.Msg
rtt time.Duration
err error
}, 1)
go func() {
response, rtt, err := s.resolver.Exchange(r, selectedServer)
resultChan <- struct {
response *dns.Msg
rtt time.Duration
err error
}{response, rtt, err}
}()
var response *dns.Msg
var rtt time.Duration
var err error
select {
case result := <-resultChan:
response, rtt, err = result.response, result.rtt, result.err
case <-timeoutCtx.Done():
err = timeoutCtx.Err()
}
if err == nil && response != nil {
// 更新服务器统计信息
s.updateServerStats(selectedServer, true, rtt)
@@ -1011,7 +1009,36 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 1. 选择最快的服务器
fastestServer := s.selectFastestServer(selectedUpstreamDNS)
if fastestServer != "" {
response, rtt, err := s.resolver.Exchange(r, fastestServer)
// 设置超时上下文
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
defer cancel()
// 使用带超时的方式执行Exchange
resultChan := make(chan struct {
response *dns.Msg
rtt time.Duration
err error
}, 1)
go func() {
resp, r, e := s.resolver.Exchange(r, fastestServer)
resultChan <- struct {
response *dns.Msg
rtt time.Duration
err error
}{resp, r, e}
}()
var response *dns.Msg
var rtt time.Duration
var err error
select {
case result := <-resultChan:
response, rtt, err = result.response, result.rtt, result.err
case <-timeoutCtx.Done():
err = timeoutCtx.Err()
}
if err == nil && response != nil {
// 更新服务器统计信息
s.updateServerStats(fastestServer, true, rtt)
@@ -1101,26 +1128,45 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
}
default:
// 默认使用并行请求模式
// 默认使用并行请求模式 - 添加超时处理和快速响应返回
responses := make(chan serverResponse, len(selectedUpstreamDNS))
var wg sync.WaitGroup
// 超时上下文
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
defer cancel()
// 向所有上游服务器并行发送请求
for _, upstream := range selectedUpstreamDNS {
wg.Add(1)
go func(server string) {
defer wg.Done()
// 发送请求并获取响应
response, rtt, err := s.resolver.Exchange(r, server)
responses <- serverResponse{response, rtt, server, err}
select {
case responses <- serverResponse{response, rtt, server, err}:
// 成功发送响应
case <-timeoutCtx.Done():
// 超时,忽略此响应
logger.Debug("并行请求超时", "server", server, "domain", domain)
return
}
}(upstream)
}
// 等待所有请求完成
// 等待所有请求完成或超时
go func() {
wg.Wait()
close(responses)
}()
// 等待上下文超时,防止泄漏
go func() {
<-timeoutCtx.Done()
}()
// 处理所有响应
for resp := range responses {
if resp.error == nil && resp.response != nil {
@@ -1214,8 +1260,8 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
}
// 2. 当启用DNSSEC且没有找到带DNSSEC的响应时向DNSSEC专用服务器发送请求
// 但如果域名匹配了domainSpecificDNS配置则不使用DNSSEC专用服务器只使用指定的DNS服务器
if s.config.EnableDNSSEC && !hasDNSSECResponse && !domainMatched {
// 但如果域名匹配了domainSpecificDNS配置或NoDNSSECDomains则不使用DNSSEC专用服务器只使用指定的DNS服务器
if s.config.EnableDNSSEC && !hasDNSSECResponse && !domainMatched && !noDNSSEC {
logger.Debug("向DNSSEC专用服务器发送请求", "domain", domain)
// 增加DNSSEC查询计数
@@ -1223,276 +1269,91 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
stats.DNSSECQueries++
})
// 根据查询模式处理DNSSEC服务器请求
switch s.config.QueryMode {
case "parallel":
// 并行请求模式 - 优化版:添加超时处理和服务器统计
responses := make(chan serverResponse, len(dnssecServers))
var wg sync.WaitGroup
// 超时上下文
// 无论查询模式是什么,DNSSEC验证都只使用加权随机选择一个服务器
selectedDnssecServer := s.selectWeightedRandomServer(dnssecServers)
if selectedDnssecServer != "" {
// 设置超时上下文
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
defer cancel()
// 向所有DNSSEC服务器并行发送请求
for _, dnssecServer := range dnssecServers {
wg.Add(1)
go func(server string) {
defer wg.Done()
// 使用带超时的方式执行Exchange
resultChan := make(chan struct {
response *dns.Msg
rtt time.Duration
err error
}, 1)
// 发送请求并获取响应
response, rtt, err := s.resolver.Exchange(r, server)
select {
case responses <- serverResponse{response, rtt, server, err}:
// 成功发送响应
case <-timeoutCtx.Done():
// 超时,忽略此响应
logger.Debug("DNSSEC并行请求超时", "server", server, "domain", domain)
return
}
}(dnssecServer)
}
// 等待所有请求完成或超时
go func() {
wg.Wait()
close(responses)
response, rtt, err := s.resolver.Exchange(r, selectedDnssecServer)
resultChan <- struct {
response *dns.Msg
rtt time.Duration
err error
}{response, rtt, err}
}()
// 处理所有响应
for resp := range responses {
if resp.error == nil && resp.response != nil {
// 更新服务器统计信息
s.updateServerStats(resp.server, true, resp.rtt)
var response *dns.Msg
var rtt time.Duration
var err error
// 检查是否包含DNSSEC记录
containsDNSSEC := s.hasDNSSECRecords(resp.response)
if resp.response.Rcode == dns.RcodeSuccess {
// 无论响应是否包含DNSSEC记录只要使用了DNSSEC专用服务器就设置usedDNSSECServer
usedDNSSECServer = resp.server
// 验证DNSSEC记录
signatureValid := s.verifyDNSSEC(resp.response)
// 设置AD标志Authenticated Data
resp.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 {
bestResponse = resp.response
bestRtt = resp.rtt
hasBestResponse = true
hasDNSSECResponse = true
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应优先使用", "domain", domain, "server", resp.server, "rtt", resp.rtt)
}
// 注意如果DNSSEC专用服务器返回的响应不包含DNSSEC记录
// 我们不会覆盖之前从upstreamDNS获取的响应
// 这符合"本地解析指的是直接使用上游服务器upstreamDNS进行解析, 而不是dnssecUpstreamDNS"的要求
// 更新备选响应
if !hasBackup {
backupResponse = resp.response
backupRtt = resp.rtt
hasBackup = true
}
}
} else {
// 更新服务器统计信息(失败)
s.updateServerStats(resp.server, false, 0)
}
select {
case result := <-resultChan:
response, rtt, err = result.response, result.rtt, result.err
case <-timeoutCtx.Done():
err = timeoutCtx.Err()
}
case "loadbalance":
// 负载均衡模式 - 使用加权随机选择算法
// 1. 选择一个加权随机DNSSEC服务器
selectedServer := s.selectWeightedRandomServer(dnssecServers)
if selectedServer != "" {
response, rtt, err := s.resolver.Exchange(r, selectedServer)
if err == nil && response != nil {
// 更新服务器统计信息
s.updateServerStats(selectedServer, true, rtt)
if err == nil && response != nil {
// 更新服务器统计信息
s.updateServerStats(selectedDnssecServer, true, rtt)
// 检查是否包含DNSSEC记录
containsDNSSEC := s.hasDNSSECRecords(response)
// 检查是否包含DNSSEC记录
containsDNSSEC := s.hasDNSSECRecords(response)
if response.Rcode == dns.RcodeSuccess {
// 无论响应是否包含DNSSEC记录只要使用了DNSSEC专用服务器就设置usedDNSSECServer
usedDNSSECServer = selectedServer
if response.Rcode == dns.RcodeSuccess {
// 无论响应是否包含DNSSEC记录只要使用了DNSSEC专用服务器就设置usedDNSSECServer
usedDNSSECServer = selectedDnssecServer
// 验证DNSSEC记录
signatureValid := s.verifyDNSSEC(response)
// 验证DNSSEC记录
signatureValid := s.verifyDNSSEC(response)
// 设置AD标志Authenticated Data
response.AuthenticatedData = signatureValid
// 设置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 {
bestResponse = response
bestRtt = rtt
hasBestResponse = true
hasDNSSECResponse = true
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应优先使用", "domain", domain, "server", selectedServer, "rtt", rtt)
}
// 注意如果DNSSEC专用服务器返回的响应不包含DNSSEC记录
// 我们不会覆盖之前从upstreamDNS获取的响应
// 这符合"本地解析指的是直接使用上游服务器upstreamDNS进行解析, 而不是dnssecUpstreamDNS"的要求
// 更新备选响应
if !hasBackup {
backupResponse = response
backupRtt = rtt
hasBackup = true
}
if signatureValid {
// 更新DNSSEC验证成功计数
s.updateStats(func(stats *Stats) {
stats.DNSSECSuccess++
})
} else {
// 更新DNSSEC验证失败计数
s.updateStats(func(stats *Stats) {
stats.DNSSECFailed++
})
}
} else {
// 更新服务器统计信息(失败)
s.updateServerStats(selectedServer, false, 0)
}
}
case "fastest-ip":
// 最快的IP地址模式 - 使用TCP连接速度测量选择最快DNSSEC服务器
// 1. 选择最快的DNSSEC服务器
fastestServer := s.selectFastestServer(dnssecServers)
if fastestServer != "" {
response, rtt, err := s.resolver.Exchange(r, fastestServer)
if err == nil && response != nil {
// 更新服务器统计信息
s.updateServerStats(fastestServer, true, rtt)
// 检查是否包含DNSSEC记录
containsDNSSEC := s.hasDNSSECRecords(response)
if response.Rcode == dns.RcodeSuccess {
// 无论响应是否包含DNSSEC记录只要使用了DNSSEC专用服务器就设置usedDNSSECServer
usedDNSSECServer = fastestServer
// 验证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 {
bestResponse = response
bestRtt = rtt
hasBestResponse = true
hasDNSSECResponse = true
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应优先使用", "domain", domain, "server", fastestServer, "rtt", rtt)
}
// 注意如果DNSSEC专用服务器返回的响应不包含DNSSEC记录
// 我们不会覆盖之前从upstreamDNS获取的响应
// 这符合"本地解析指的是直接使用上游服务器upstreamDNS进行解析, 而不是dnssecUpstreamDNS"的要求
// 更新备选响应
if !hasBackup {
backupResponse = response
backupRtt = rtt
hasBackup = true
}
// 优先使用DNSSEC专用服务器的响应尤其是带有DNSSEC记录的
if containsDNSSEC {
bestResponse = response
bestRtt = rtt
hasBestResponse = true
hasDNSSECResponse = true
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应优先使用", "domain", domain, "server", selectedDnssecServer, "rtt", rtt)
}
} else {
// 更新服务器统计信息(失败)
s.updateServerStats(fastestServer, false, 0)
}
}
// 注意如果DNSSEC专用服务器返回的响应不包含DNSSEC记录
// 我们不会覆盖之前从upstreamDNS获取的响应
// 这符合"本地解析指的是直接使用上游服务器upstreamDNS进行解析, 而不是dnssecUpstreamDNS"的要求
default:
// 默认使用顺序请求模式
for _, dnssecServer := range dnssecServers {
response, rtt, err := s.resolver.Exchange(r, dnssecServer)
if err == nil && response != nil {
// 更新服务器统计信息
s.updateServerStats(dnssecServer, true, rtt)
// 检查是否包含DNSSEC记录
containsDNSSEC := s.hasDNSSECRecords(response)
if response.Rcode == dns.RcodeSuccess {
// 无论响应是否包含DNSSEC记录只要使用了DNSSEC专用服务器就设置usedDNSSECServer
usedDNSSECServer = dnssecServer
// 验证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 {
bestResponse = response
bestRtt = rtt
hasBestResponse = true
hasDNSSECResponse = true
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应优先使用", "domain", domain, "server", dnssecServer, "rtt", rtt)
break // 找到带DNSSEC的响应立即返回
}
// 注意如果DNSSEC专用服务器返回的响应不包含DNSSEC记录
// 我们不会覆盖之前从upstreamDNS获取的响应
// 这符合"本地解析指的是直接使用上游服务器upstreamDNS进行解析, 而不是dnssecUpstreamDNS"的要求
// 更新备选响应
if !hasBackup {
backupResponse = response
backupRtt = rtt
hasBackup = true
}
// 更新备选响应
if !hasBackup {
backupResponse = response
backupRtt = rtt
hasBackup = true
}
} else {
// 更新服务器统计信息(失败)
s.updateServerStats(dnssecServer, false, 0)
}
} else {
// 更新服务器统计信息(失败)
s.updateServerStats(selectedDnssecServer, false, 0)
}
}
}
@@ -1502,42 +1363,84 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 检查最佳响应是否包含DNSSEC记录
bestHasDNSSEC := s.hasDNSSECRecords(bestResponse)
// 如果启用了DNSSEC且最佳响应不包含DNSSEC记录尝试使用本地解析
if s.config.EnableDNSSEC && !bestHasDNSSEC {
logger.Debug("最佳响应不包含DNSSEC记录尝试使用本地解析", "domain", domain)
if ip, exists := s.shieldManager.GetHostsIP(domain); exists {
// 本地解析成功,构建响应
localResponse := new(dns.Msg)
localResponse.SetReply(r)
// 如果启用了DNSSEC且最佳响应不包含DNSSEC记录尝试使用本地解析使用upstreamDNS服务器
// 但如果域名匹配了domainSpecificDNS配置则不执行此逻辑只使用指定的DNS服务器
if s.config.EnableDNSSEC && !bestHasDNSSEC && !domainMatched {
logger.Debug("最佳响应不包含DNSSEC记录尝试使用本地解析upstreamDNS", "domain", domain)
// 选择一个upstreamDNS服务器进行解析使用加权随机算法
localServer := s.selectWeightedRandomServer(s.config.UpstreamDNS)
if localServer != "" {
// 设置超时上下文
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
defer cancel()
localResponse.AuthenticatedData = false
localResponse.Rcode = dns.RcodeSuccess
// 使用带超时的方式执行Exchange
resultChan := make(chan struct {
response *dns.Msg
rtt time.Duration
err error
}, 1)
if len(r.Question) > 0 {
q := r.Question[0]
answer := new(dns.A)
answer.Hdr = dns.RR_Header{
Name: q.Name,
Rrtype: q.Qtype,
Class: q.Qclass,
Ttl: 300,
}
answer.A = net.ParseIP(ip)
localResponse.Answer = append(localResponse.Answer, answer)
go func() {
resp, r, e := s.resolver.Exchange(r, localServer)
resultChan <- struct {
response *dns.Msg
rtt time.Duration
err error
}{resp, r, e}
}()
var localResponse *dns.Msg
var rtt time.Duration
var err error
select {
case result := <-resultChan:
localResponse, rtt, err = result.response, result.rtt, result.err
case <-timeoutCtx.Done():
err = timeoutCtx.Err()
}
if err == nil && localResponse != nil {
// 更新服务器统计信息
s.updateServerStats(localServer, true, rtt)
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
// 检查是否包含DNSSEC记录
localHasDNSSEC := s.hasDNSSECRecords(localResponse)
// 更新域名的DNSSEC状态为false
s.updateDomainDNSSECStatus(domain, false)
// 验证DNSSEC记录(如果存在),但不影响最终响应
if localHasDNSSEC {
signatureValid := s.verifyDNSSEC(localResponse)
localResponse.AuthenticatedData = signatureValid
s.updateStats(func(stats *Stats) {
stats.Allowed++
})
if signatureValid {
s.updateStats(func(stats *Stats) {
stats.DNSSECQueries++
stats.DNSSECSuccess++
})
} else {
s.updateStats(func(stats *Stats) {
stats.DNSSECQueries++
stats.DNSSECFailed++
})
}
}
logger.Debug("使用本地解析结果", "domain", domain, "ip", ip)
return localResponse, 0, "", ""
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
// 更新域名的DNSSEC状态
s.updateDomainDNSSECStatus(domain, localHasDNSSEC)
s.updateStats(func(stats *Stats) {
stats.Allowed++
})
logger.Debug("使用本地解析结果upstreamDNS", "domain", domain, "server", localServer, "rtt", rtt)
return localResponse, rtt, localServer, ""
} else {
// 更新服务器统计信息(失败)
s.updateServerStats(localServer, false, 0)
}
}
}
@@ -1740,11 +1643,17 @@ func (s *Server) verifyDNSSEC(response *dns.Msg) bool {
// 验证所有RRSIG记录
signatureValid := true
// 用于记录已经警告过的DNSKEY tag避免重复警告
warnedKeyTags := make(map[uint16]bool)
for _, rrsig := range rrsigs {
// 查找对应的DNSKEY
dnskey, exists := dnskeys[rrsig.KeyTag]
if !exists {
logger.Warn("DNSSEC验证失败找不到对应的DNSKEY", "key_tag", rrsig.KeyTag)
// 仅当该key_tag尚未警告过时才记录警告
if !warnedKeyTags[rrsig.KeyTag] {
logger.Warn("DNSSEC验证失败找不到对应的DNSKEY", "key_tag", rrsig.KeyTag)
warnedKeyTags[rrsig.KeyTag] = true
}
signatureValid = false
continue
}
@@ -1892,18 +1801,60 @@ func (s *Server) selectWeightedRandomServer(servers []string) string {
var totalWeight int64
weights := make([]serverWeight, 0, len(servers))
// 获取所有服务器的平均响应时间,用于归一化
var totalResponseTime time.Duration
validServers := 0
for _, server := range servers {
stats := s.getServerStats(server)
if stats.ResponseTime > 0 {
totalResponseTime += stats.ResponseTime
validServers++
}
}
// 计算平均响应时间基准值
var avgResponseTime time.Duration
if validServers > 0 {
avgResponseTime = totalResponseTime / time.Duration(validServers)
} else {
avgResponseTime = 1 * time.Second // 默认基准值
}
for _, server := range servers {
stats := s.getServerStats(server)
// 计算权重:成功次数 - 失败次数 * 2失败权重更高
// 计算基础权重:成功次数 - 失败次数 * 2失败权重更高
// 确保权重至少为1
weight := stats.SuccessCount - stats.FailureCount*2
if weight < 1 {
weight = 1
baseWeight := stats.SuccessCount - stats.FailureCount*2
if baseWeight < 1 {
baseWeight = 1
}
weights = append(weights, serverWeight{server, weight})
totalWeight += weight
// 计算响应时间调整因子:响应时间越短,因子越高
// 如果没有响应时间数据使用默认值1
var responseFactor float64 = 1.0
if stats.ResponseTime > 0 {
// 使用平均响应时间作为基准,计算调整因子
// 响应时间越短因子越高最高为2.0最低为0.5
responseFactor = float64(avgResponseTime) / float64(stats.ResponseTime)
// 限制调整因子的范围,避免权重波动过大
if responseFactor > 2.0 {
responseFactor = 2.0
} else if responseFactor < 0.5 {
responseFactor = 0.5
}
}
// 综合计算最终权重,四舍五入到整数
finalWeight := int64(float64(baseWeight) * responseFactor)
// 确保最终权重至少为1
if finalWeight < 1 {
finalWeight = 1
}
weights = append(weights, serverWeight{server, finalWeight})
totalWeight += finalWeight
}
// 随机选择一个权重

View File

@@ -1,5 +0,0 @@
{
"blockedDomainsCount": {},
"resolvedDomainsCount": {},
"lastSaved": "2025-11-29T02:08:50.6341349+08:00"
}