diff --git a/CHANGELOG.md b/CHANGELOG.md index 312b03a..f2bf328 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ 所有对本项目的显著更改都将记录在此文件中。 +## [1.1.3] - 2025-12-19 + +### 移除 +- 移除search domain功能,不再支持自动添加域名前缀进行查询 +- 移除DNSConfig结构体中的PrefixDomain字段 +- 移除配置文件中的prefixDomain配置项 + ## [1.1.2] - 2025-12-19 ### 添加 diff --git a/config.json b/config.json index 60cfaba..328ade1 100644 --- a/config.json +++ b/config.json @@ -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", diff --git a/config/config.go b/config/config.go index 3e3a2aa..c95db85 100644 --- a/config/config.go +++ b/config/config.go @@ -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 } diff --git a/cookies.txt b/cookies.txt deleted file mode 100644 index b31cfba..0000000 --- a/cookies.txt +++ /dev/null @@ -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 diff --git a/dns-server b/dns-server deleted file mode 100755 index 44763fb..0000000 Binary files a/dns-server and /dev/null differ diff --git a/dns/server.go b/dns/server.go index 44b3092..ed70e1b 100644 --- a/dns/server.go +++ b/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 } // 随机选择一个权重 diff --git a/shield_stats.json b/shield_stats.json deleted file mode 100644 index d99d506..0000000 --- a/shield_stats.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "blockedDomainsCount": {}, - "resolvedDomainsCount": {}, - "lastSaved": "2025-11-29T02:08:50.6341349+08:00" -} \ No newline at end of file