移除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
|
## [1.1.2] - 2025-12-19
|
||||||
|
|
||||||
### 添加
|
### 添加
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"saveInterval": 300,
|
"saveInterval": 300,
|
||||||
"cacheTTL": 10,
|
"cacheTTL": 10,
|
||||||
"enableDNSSEC": true,
|
"enableDNSSEC": true,
|
||||||
"queryMode": "parallel",
|
"queryMode": "loadbalance",
|
||||||
"domainSpecificDNS": {
|
"domainSpecificDNS": {
|
||||||
"amazehome.cn": [
|
"amazehome.cn": [
|
||||||
"10.35.10.200:53"
|
"10.35.10.200:53"
|
||||||
@@ -38,9 +38,6 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
},
|
},
|
||||||
"prefixDomain": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"noDNSSECDomains": [
|
"noDNSSECDomains": [
|
||||||
"amazehome.cn",
|
"amazehome.cn",
|
||||||
"addr.arpa",
|
"addr.arpa",
|
||||||
|
|||||||
@@ -14,18 +14,17 @@ type DomainSpecificDNS map[string][]string
|
|||||||
|
|
||||||
// DNSConfig DNS配置
|
// DNSConfig DNS配置
|
||||||
type DNSConfig struct {
|
type DNSConfig struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
UpstreamDNS []string `json:"upstreamDNS"`
|
UpstreamDNS []string `json:"upstreamDNS"`
|
||||||
DNSSECUpstreamDNS []string `json:"dnssecUpstreamDNS"` // 用于DNSSEC查询的专用服务器
|
DNSSECUpstreamDNS []string `json:"dnssecUpstreamDNS"` // 用于DNSSEC查询的专用服务器
|
||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
StatsFile string `json:"statsFile"` // 统计数据持久化文件
|
StatsFile string `json:"statsFile"` // 统计数据持久化文件
|
||||||
SaveInterval int `json:"saveInterval"` // 数据保存间隔(秒)
|
SaveInterval int `json:"saveInterval"` // 数据保存间隔(秒)
|
||||||
CacheTTL int `json:"cacheTTL"` // DNS缓存过期时间(分钟)
|
CacheTTL int `json:"cacheTTL"` // DNS缓存过期时间(分钟)
|
||||||
EnableDNSSEC bool `json:"enableDNSSEC"` // 是否启用DNSSEC支持
|
EnableDNSSEC bool `json:"enableDNSSEC"` // 是否启用DNSSEC支持
|
||||||
QueryMode string `json:"queryMode"` // 查询模式:"loadbalance"(负载均衡)、"parallel"(并行请求)、"fastest-ip"(最快的IP地址)
|
QueryMode string `json:"queryMode"` // 查询模式:"loadbalance"(负载均衡)、"parallel"(并行请求)、"fastest-ip"(最快的IP地址)
|
||||||
DomainSpecificDNS DomainSpecificDNS `json:"domainSpecificDNS"` // 域名特定DNS服务器配置
|
DomainSpecificDNS DomainSpecificDNS `json:"domainSpecificDNS"` // 域名特定DNS服务器配置
|
||||||
NoDNSSECDomains []string `json:"noDNSSECDomains"` // 不验证DNSSEC的域名模式列表
|
NoDNSSECDomains []string `json:"noDNSSECDomains"` // 不验证DNSSEC的域名模式列表
|
||||||
PrefixDomain []string `json:"prefixDomain"` // 搜索域名前缀列表,对应/etc/resolv.conf中的search domain
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPConfig HTTP控制台配置
|
// HTTPConfig HTTP控制台配置
|
||||||
@@ -93,6 +92,9 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
if config.DNS.Port == 0 {
|
if config.DNS.Port == 0 {
|
||||||
config.DNS.Port = 53
|
config.DNS.Port = 53
|
||||||
}
|
}
|
||||||
|
if config.DNS.Timeout == 0 {
|
||||||
|
config.DNS.Timeout = 5000 // 默认超时时间为5000毫秒
|
||||||
|
}
|
||||||
if len(config.DNS.UpstreamDNS) == 0 {
|
if len(config.DNS.UpstreamDNS) == 0 {
|
||||||
config.DNS.UpstreamDNS = []string{"223.5.5.5:53", "223.6.6.6:53"}
|
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 {
|
if config.DNS.DomainSpecificDNS == nil {
|
||||||
config.DNS.DomainSpecificDNS = make(DomainSpecificDNS) // 默认为空映射
|
config.DNS.DomainSpecificDNS = make(DomainSpecificDNS) // 默认为空映射
|
||||||
}
|
}
|
||||||
// PrefixDomain默认值处理
|
|
||||||
if config.DNS.PrefixDomain == nil {
|
|
||||||
config.DNS.PrefixDomain = []string{} // 默认为空切片
|
|
||||||
}
|
|
||||||
if config.HTTP.Port == 0 {
|
if config.HTTP.Port == 0 {
|
||||||
config.HTTP.Port = 8080
|
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 dnsServer string
|
||||||
var dnssecServer string
|
var dnssecServer string
|
||||||
|
|
||||||
// 1. 首先尝试直接查询原始域名
|
// 直接查询原始域名
|
||||||
queryAttempts = append(queryAttempts, domain)
|
queryAttempts = append(queryAttempts, domain)
|
||||||
response, rtt, dnsServer, dnssecServer = s.forwardDNSRequestWithCache(r, 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 {
|
if response != nil {
|
||||||
// 如果客户端请求包含EDNS记录,确保响应也包含EDNS
|
// 如果客户端请求包含EDNS记录,确保响应也包含EDNS
|
||||||
if opt := r.IsEdns0(); opt != nil {
|
if opt := r.IsEdns0(); opt != nil {
|
||||||
@@ -763,8 +731,8 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
|
|
||||||
// 2. 如果没有匹配的域名特定配置
|
// 2. 如果没有匹配的域名特定配置
|
||||||
if !domainMatched {
|
if !domainMatched {
|
||||||
// 如果启用了DNSSEC且有配置DNSSEC专用服务器,则使用DNSSEC专用服务器
|
// 如果启用了DNSSEC且有配置DNSSEC专用服务器,并且域名不匹配NoDNSSECDomains,则使用DNSSEC专用服务器
|
||||||
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 {
|
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 && !noDNSSEC {
|
||||||
selectedUpstreamDNS = s.config.DNSSECUpstreamDNS
|
selectedUpstreamDNS = s.config.DNSSECUpstreamDNS
|
||||||
logger.Debug("使用DNSSEC专用服务器", "servers", selectedUpstreamDNS)
|
logger.Debug("使用DNSSEC专用服务器", "servers", selectedUpstreamDNS)
|
||||||
} else {
|
} else {
|
||||||
@@ -917,7 +885,37 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
// 1. 选择一个加权随机服务器
|
// 1. 选择一个加权随机服务器
|
||||||
selectedServer := s.selectWeightedRandomServer(selectedUpstreamDNS)
|
selectedServer := s.selectWeightedRandomServer(selectedUpstreamDNS)
|
||||||
if selectedServer != "" {
|
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 {
|
if err == nil && response != nil {
|
||||||
// 更新服务器统计信息
|
// 更新服务器统计信息
|
||||||
s.updateServerStats(selectedServer, true, rtt)
|
s.updateServerStats(selectedServer, true, rtt)
|
||||||
@@ -1011,7 +1009,36 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
// 1. 选择最快的服务器
|
// 1. 选择最快的服务器
|
||||||
fastestServer := s.selectFastestServer(selectedUpstreamDNS)
|
fastestServer := s.selectFastestServer(selectedUpstreamDNS)
|
||||||
if fastestServer != "" {
|
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 {
|
if err == nil && response != nil {
|
||||||
// 更新服务器统计信息
|
// 更新服务器统计信息
|
||||||
s.updateServerStats(fastestServer, true, rtt)
|
s.updateServerStats(fastestServer, true, rtt)
|
||||||
@@ -1101,26 +1128,45 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// 默认使用并行请求模式
|
// 默认使用并行请求模式 - 添加超时处理和快速响应返回
|
||||||
responses := make(chan serverResponse, len(selectedUpstreamDNS))
|
responses := make(chan serverResponse, len(selectedUpstreamDNS))
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// 超时上下文
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
// 向所有上游服务器并行发送请求
|
// 向所有上游服务器并行发送请求
|
||||||
for _, upstream := range selectedUpstreamDNS {
|
for _, upstream := range selectedUpstreamDNS {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(server string) {
|
go func(server string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
|
// 发送请求并获取响应
|
||||||
response, rtt, err := s.resolver.Exchange(r, server)
|
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)
|
}(upstream)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待所有请求完成
|
// 等待所有请求完成或超时
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(responses)
|
close(responses)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// 等待上下文超时,防止泄漏
|
||||||
|
go func() {
|
||||||
|
<-timeoutCtx.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
// 处理所有响应
|
// 处理所有响应
|
||||||
for resp := range responses {
|
for resp := range responses {
|
||||||
if resp.error == nil && resp.response != nil {
|
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专用服务器发送请求
|
// 2. 当启用DNSSEC且没有找到带DNSSEC的响应时,向DNSSEC专用服务器发送请求
|
||||||
// 但如果域名匹配了domainSpecificDNS配置,则不使用DNSSEC专用服务器,只使用指定的DNS服务器
|
// 但如果域名匹配了domainSpecificDNS配置或NoDNSSECDomains,则不使用DNSSEC专用服务器,只使用指定的DNS服务器
|
||||||
if s.config.EnableDNSSEC && !hasDNSSECResponse && !domainMatched {
|
if s.config.EnableDNSSEC && !hasDNSSECResponse && !domainMatched && !noDNSSEC {
|
||||||
logger.Debug("向DNSSEC专用服务器发送请求", "domain", domain)
|
logger.Debug("向DNSSEC专用服务器发送请求", "domain", domain)
|
||||||
|
|
||||||
// 增加DNSSEC查询计数
|
// 增加DNSSEC查询计数
|
||||||
@@ -1223,276 +1269,91 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
stats.DNSSECQueries++
|
stats.DNSSECQueries++
|
||||||
})
|
})
|
||||||
|
|
||||||
// 根据查询模式处理DNSSEC服务器请求
|
// 无论查询模式是什么,DNSSEC验证都只使用加权随机选择一个服务器
|
||||||
switch s.config.QueryMode {
|
selectedDnssecServer := s.selectWeightedRandomServer(dnssecServers)
|
||||||
case "parallel":
|
if selectedDnssecServer != "" {
|
||||||
// 并行请求模式 - 优化版:添加超时处理和服务器统计
|
// 设置超时上下文
|
||||||
responses := make(chan serverResponse, len(dnssecServers))
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
// 超时上下文
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
|
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// 向所有DNSSEC服务器并行发送请求
|
// 使用带超时的方式执行Exchange
|
||||||
for _, dnssecServer := range dnssecServers {
|
resultChan := make(chan struct {
|
||||||
wg.Add(1)
|
response *dns.Msg
|
||||||
go func(server string) {
|
rtt time.Duration
|
||||||
defer wg.Done()
|
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() {
|
go func() {
|
||||||
wg.Wait()
|
response, rtt, err := s.resolver.Exchange(r, selectedDnssecServer)
|
||||||
close(responses)
|
resultChan <- struct {
|
||||||
|
response *dns.Msg
|
||||||
|
rtt time.Duration
|
||||||
|
err error
|
||||||
|
}{response, rtt, err}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 处理所有响应
|
var response *dns.Msg
|
||||||
for resp := range responses {
|
var rtt time.Duration
|
||||||
if resp.error == nil && resp.response != nil {
|
var err error
|
||||||
// 更新服务器统计信息
|
|
||||||
s.updateServerStats(resp.server, true, resp.rtt)
|
|
||||||
|
|
||||||
// 检查是否包含DNSSEC记录
|
select {
|
||||||
containsDNSSEC := s.hasDNSSECRecords(resp.response)
|
case result := <-resultChan:
|
||||||
|
response, rtt, err = result.response, result.rtt, result.err
|
||||||
if resp.response.Rcode == dns.RcodeSuccess {
|
case <-timeoutCtx.Done():
|
||||||
// 无论响应是否包含DNSSEC记录,只要使用了DNSSEC专用服务器,就设置usedDNSSECServer
|
err = timeoutCtx.Err()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "loadbalance":
|
if err == nil && response != nil {
|
||||||
// 负载均衡模式 - 使用加权随机选择算法
|
// 更新服务器统计信息
|
||||||
// 1. 选择一个加权随机DNSSEC服务器
|
s.updateServerStats(selectedDnssecServer, true, rtt)
|
||||||
selectedServer := s.selectWeightedRandomServer(dnssecServers)
|
|
||||||
if selectedServer != "" {
|
|
||||||
response, rtt, err := s.resolver.Exchange(r, selectedServer)
|
|
||||||
if err == nil && response != nil {
|
|
||||||
// 更新服务器统计信息
|
|
||||||
s.updateServerStats(selectedServer, true, rtt)
|
|
||||||
|
|
||||||
// 检查是否包含DNSSEC记录
|
// 检查是否包含DNSSEC记录
|
||||||
containsDNSSEC := s.hasDNSSECRecords(response)
|
containsDNSSEC := s.hasDNSSECRecords(response)
|
||||||
|
|
||||||
if response.Rcode == dns.RcodeSuccess {
|
if response.Rcode == dns.RcodeSuccess {
|
||||||
// 无论响应是否包含DNSSEC记录,只要使用了DNSSEC专用服务器,就设置usedDNSSECServer
|
// 无论响应是否包含DNSSEC记录,只要使用了DNSSEC专用服务器,就设置usedDNSSECServer
|
||||||
usedDNSSECServer = selectedServer
|
usedDNSSECServer = selectedDnssecServer
|
||||||
|
|
||||||
// 验证DNSSEC记录
|
// 验证DNSSEC记录
|
||||||
signatureValid := s.verifyDNSSEC(response)
|
signatureValid := s.verifyDNSSEC(response)
|
||||||
|
|
||||||
// 设置AD标志(Authenticated Data)
|
// 设置AD标志(Authenticated Data)
|
||||||
response.AuthenticatedData = signatureValid
|
response.AuthenticatedData = signatureValid
|
||||||
|
|
||||||
if signatureValid {
|
if signatureValid {
|
||||||
// 更新DNSSEC验证成功计数
|
// 更新DNSSEC验证成功计数
|
||||||
s.updateStats(func(stats *Stats) {
|
s.updateStats(func(stats *Stats) {
|
||||||
stats.DNSSECSuccess++
|
stats.DNSSECSuccess++
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 更新DNSSEC验证失败计数
|
// 更新DNSSEC验证失败计数
|
||||||
s.updateStats(func(stats *Stats) {
|
s.updateStats(func(stats *Stats) {
|
||||||
stats.DNSSECFailed++
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 更新服务器统计信息(失败)
|
|
||||||
s.updateServerStats(selectedServer, false, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "fastest-ip":
|
// 优先使用DNSSEC专用服务器的响应,尤其是带有DNSSEC记录的
|
||||||
// 最快的IP地址模式 - 使用TCP连接速度测量选择最快DNSSEC服务器
|
if containsDNSSEC {
|
||||||
// 1. 选择最快的DNSSEC服务器
|
bestResponse = response
|
||||||
fastestServer := s.selectFastestServer(dnssecServers)
|
bestRtt = rtt
|
||||||
if fastestServer != "" {
|
hasBestResponse = true
|
||||||
response, rtt, err := s.resolver.Exchange(r, fastestServer)
|
hasDNSSECResponse = true
|
||||||
if err == nil && response != nil {
|
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应,优先使用", "domain", domain, "server", selectedDnssecServer, "rtt", rtt)
|
||||||
// 更新服务器统计信息
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
// 注意:如果DNSSEC专用服务器返回的响应不包含DNSSEC记录,
|
||||||
// 更新服务器统计信息(失败)
|
// 我们不会覆盖之前从upstreamDNS获取的响应,
|
||||||
s.updateServerStats(fastestServer, false, 0)
|
// 这符合"本地解析指的是直接使用上游服务器upstreamDNS进行解析, 而不是dnssecUpstreamDNS"的要求
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
// 更新备选响应
|
||||||
// 默认使用顺序请求模式
|
if !hasBackup {
|
||||||
for _, dnssecServer := range dnssecServers {
|
backupResponse = response
|
||||||
response, rtt, err := s.resolver.Exchange(r, dnssecServer)
|
backupRtt = rtt
|
||||||
if err == nil && response != nil {
|
hasBackup = true
|
||||||
// 更新服务器统计信息
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} 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记录
|
// 检查最佳响应是否包含DNSSEC记录
|
||||||
bestHasDNSSEC := s.hasDNSSECRecords(bestResponse)
|
bestHasDNSSEC := s.hasDNSSECRecords(bestResponse)
|
||||||
|
|
||||||
// 如果启用了DNSSEC且最佳响应不包含DNSSEC记录,尝试使用本地解析
|
// 如果启用了DNSSEC且最佳响应不包含DNSSEC记录,尝试使用本地解析(使用upstreamDNS服务器)
|
||||||
if s.config.EnableDNSSEC && !bestHasDNSSEC {
|
// 但如果域名匹配了domainSpecificDNS配置,则不执行此逻辑,只使用指定的DNS服务器
|
||||||
logger.Debug("最佳响应不包含DNSSEC记录,尝试使用本地解析", "domain", domain)
|
if s.config.EnableDNSSEC && !bestHasDNSSEC && !domainMatched {
|
||||||
if ip, exists := s.shieldManager.GetHostsIP(domain); exists {
|
logger.Debug("最佳响应不包含DNSSEC记录,尝试使用本地解析(upstreamDNS)", "domain", domain)
|
||||||
// 本地解析成功,构建响应
|
// 选择一个upstreamDNS服务器进行解析(使用加权随机算法)
|
||||||
localResponse := new(dns.Msg)
|
localServer := s.selectWeightedRandomServer(s.config.UpstreamDNS)
|
||||||
localResponse.SetReply(r)
|
if localServer != "" {
|
||||||
|
// 设置超时上下文
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
localResponse.AuthenticatedData = false
|
// 使用带超时的方式执行Exchange
|
||||||
localResponse.Rcode = dns.RcodeSuccess
|
resultChan := make(chan struct {
|
||||||
|
response *dns.Msg
|
||||||
|
rtt time.Duration
|
||||||
|
err error
|
||||||
|
}, 1)
|
||||||
|
|
||||||
if len(r.Question) > 0 {
|
go func() {
|
||||||
q := r.Question[0]
|
resp, r, e := s.resolver.Exchange(r, localServer)
|
||||||
answer := new(dns.A)
|
resultChan <- struct {
|
||||||
answer.Hdr = dns.RR_Header{
|
response *dns.Msg
|
||||||
Name: q.Name,
|
rtt time.Duration
|
||||||
Rrtype: q.Qtype,
|
err error
|
||||||
Class: q.Qclass,
|
}{resp, r, e}
|
||||||
Ttl: 300,
|
}()
|
||||||
}
|
|
||||||
answer.A = net.ParseIP(ip)
|
var localResponse *dns.Msg
|
||||||
localResponse.Answer = append(localResponse.Answer, answer)
|
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)
|
||||||
|
|
||||||
// 记录解析域名统计
|
// 检查是否包含DNSSEC记录
|
||||||
s.updateResolvedDomainStats(domain)
|
localHasDNSSEC := s.hasDNSSECRecords(localResponse)
|
||||||
|
|
||||||
// 更新域名的DNSSEC状态为false
|
// 验证DNSSEC记录(如果存在),但不影响最终响应
|
||||||
s.updateDomainDNSSECStatus(domain, false)
|
if localHasDNSSEC {
|
||||||
|
signatureValid := s.verifyDNSSEC(localResponse)
|
||||||
|
localResponse.AuthenticatedData = signatureValid
|
||||||
|
|
||||||
s.updateStats(func(stats *Stats) {
|
if signatureValid {
|
||||||
stats.Allowed++
|
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记录
|
// 验证所有RRSIG记录
|
||||||
signatureValid := true
|
signatureValid := true
|
||||||
|
// 用于记录已经警告过的DNSKEY tag,避免重复警告
|
||||||
|
warnedKeyTags := make(map[uint16]bool)
|
||||||
for _, rrsig := range rrsigs {
|
for _, rrsig := range rrsigs {
|
||||||
// 查找对应的DNSKEY
|
// 查找对应的DNSKEY
|
||||||
dnskey, exists := dnskeys[rrsig.KeyTag]
|
dnskey, exists := dnskeys[rrsig.KeyTag]
|
||||||
if !exists {
|
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
|
signatureValid = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1892,18 +1801,60 @@ func (s *Server) selectWeightedRandomServer(servers []string) string {
|
|||||||
var totalWeight int64
|
var totalWeight int64
|
||||||
weights := make([]serverWeight, 0, len(servers))
|
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 {
|
for _, server := range servers {
|
||||||
stats := s.getServerStats(server)
|
stats := s.getServerStats(server)
|
||||||
|
|
||||||
// 计算权重:成功次数 - 失败次数 * 2(失败权重更高)
|
// 计算基础权重:成功次数 - 失败次数 * 2(失败权重更高)
|
||||||
// 确保权重至少为1
|
// 确保权重至少为1
|
||||||
weight := stats.SuccessCount - stats.FailureCount*2
|
baseWeight := stats.SuccessCount - stats.FailureCount*2
|
||||||
if weight < 1 {
|
if baseWeight < 1 {
|
||||||
weight = 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