移除search domain功能,不再支持自动添加域名前缀进行查询;移除DNSConfig结构体中的PrefixDomain字段
This commit is contained in:
@@ -2,6 +2,13 @@
|
||||
|
||||
所有对本项目的显著更改都将记录在此文件中。
|
||||
|
||||
## [1.1.3] - 2025-12-19
|
||||
|
||||
### 移除
|
||||
- 移除search domain功能,不再支持自动添加域名前缀进行查询
|
||||
- 移除DNSConfig结构体中的PrefixDomain字段
|
||||
- 移除配置文件中的prefixDomain配置项
|
||||
|
||||
## [1.1.2] - 2025-12-19
|
||||
|
||||
### 添加
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
BIN
dns-server
BIN
dns-server
Binary file not shown.
605
dns/server.go
605
dns/server.go
@@ -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
|
||||
}
|
||||
|
||||
// 随机选择一个权重
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"blockedDomainsCount": {},
|
||||
"resolvedDomainsCount": {},
|
||||
"lastSaved": "2025-11-29T02:08:50.6341349+08:00"
|
||||
}
|
||||
Reference in New Issue
Block a user