增加查询日志详情界面点击域名列表,显示解析日志的详细信息.

This commit is contained in:
Alex Yang
2025-12-25 20:33:07 +08:00
parent 151042e675
commit 01f2152e46
17 changed files with 1138 additions and 3480 deletions

View File

@@ -45,22 +45,30 @@ type IPGeolocation struct {
Expiry time.Time `json:"expiry"` // 缓存过期时间
}
// DNSAnswer DNS解析记录
type DNSAnswer struct {
Type string `json:"type"` // 记录类型
Value string `json:"value"` // 记录值
TTL uint32 `json:"ttl"` // 生存时间
}
// QueryLog 查询日志记录
type QueryLog struct {
Timestamp time.Time // 查询时间
ClientIP string // 客户端IP
Location string // IP地理位置国家 城市)
Domain string // 查询域名
QueryType string // 查询类型
ResponseTime int64 // 响应时间(ms)
Result string // 查询结果allowed, blocked, error
BlockRule string // 屏蔽规则(如果被屏蔽)
BlockType string // 屏蔽类型(如果被屏蔽)
FromCache bool // 是否来自缓存
DNSSEC bool // 是否使用了DNSSEC
EDNS bool // 是否使用了EDNS
DNSServer string // 使用的DNS服务器
DNSSECServer string // 使用的DNSSEC专用服务器
Timestamp time.Time // 查询时间
ClientIP string // 客户端IP
Location string // IP地理位置国家 城市)
Domain string // 查询域名
QueryType string // 查询类型
ResponseTime int64 // 响应时间(ms)
Result string // 查询结果allowed, blocked, error
BlockRule string // 屏蔽规则(如果被屏蔽)
BlockType string // 屏蔽类型(如果被屏蔽)
FromCache bool // 是否来自缓存
DNSSEC bool // 是否使用了DNSSEC
EDNS bool // 是否使用了EDNS
DNSServer string // 使用的DNS服务器
DNSSECServer string // 使用的DNSSEC专用服务器
Answers []DNSAnswer `json:"answers"` // 解析记录
}
// StatsData 用于持久化的统计数据结构
@@ -348,6 +356,29 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
s.updateStats(func(stats *Stats) {
stats.QueryTypes[queryType]++
})
// 检查是否是AAAA记录查询且IPv6解析已禁用
if qType == dns.TypeAAAA && !s.config.EnableIPv6 {
// 返回NXDOMAIN响应域名不存在
response := new(dns.Msg)
response.SetReply(r)
response.SetRcode(r, dns.RcodeNameError)
w.WriteMsg(response)
// 更新统计信息
responseTime := int64(0)
s.updateStats(func(stats *Stats) {
stats.TotalResponseTime += responseTime
if stats.Queries > 0 {
stats.AvgResponseTime = float64(stats.TotalResponseTime) / float64(stats.Queries)
}
})
// 添加查询日志
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil)
logger.Debug("IPv6解析已禁用拒绝AAAA记录查询", "domain", domain)
return
}
}
logger.Debug("接收到DNS查询", "domain", domain, "type", queryType, "client", w.RemoteAddr())
@@ -370,7 +401,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
})
// 添加查询日志
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "")
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil)
return
}
@@ -386,8 +417,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
}
})
// 添加查询日志
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, false, true, "缓存", "无")
// 该方法内部未直接调用addQueryLog而是在handleDNSRequest中处理
return
}
@@ -409,7 +439,8 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
})
// 添加查询日志
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true, "无", "无")
blockedAnswers := []DNSAnswer{}
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true, "无", "无", blockedAnswers)
return
}
@@ -481,8 +512,20 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
})
}
// 从缓存响应中提取解析记录
cachedAnswers := []DNSAnswer{}
if cachedResponse != nil {
for _, rr := range cachedResponse.Answer {
cachedAnswers = append(cachedAnswers, DNSAnswer{
Type: dns.TypeToString[rr.Header().Rrtype],
Value: rr.String(),
TTL: rr.Header().Ttl,
})
}
}
// 添加查询日志 - 标记为缓存
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true, "缓存", "无")
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true, "缓存", "无", cachedAnswers)
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC)
return
}
@@ -566,8 +609,20 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
logger.Debug("DNS响应已缓存", "domain", domain, "type", queryType, "ttl", defaultCacheTTL, "dnssec", responseDNSSEC)
}
// 从响应中提取解析记录
responseAnswers := []DNSAnswer{}
if response != nil {
for _, rr := range response.Answer {
responseAnswers = append(responseAnswers, DNSAnswer{
Type: dns.TypeToString[rr.Header().Rrtype],
Value: rr.String(),
TTL: rr.Header().Ttl,
})
}
}
// 添加查询日志 - 标记为实时
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true, dnsServer, dnssecServer)
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true, dnsServer, dnssecServer, responseAnswers)
}
// handleHostsResponse 处理hosts文件匹配的响应
@@ -731,14 +786,33 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 2. 如果没有匹配的域名特定配置
if !domainMatched {
// 如果启用了DNSSEC且有配置DNSSEC专用服务器并且域名不匹配NoDNSSECDomains则使用DNSSEC专用服务器
// 创建一个新的切片来存储最终的上游服务器列表
var finalUpstreamDNS []string
// 首先添加用户配置的上游DNS服务器
finalUpstreamDNS = append(finalUpstreamDNS, s.config.UpstreamDNS...)
logger.Debug("使用用户配置的上游DNS服务器", "servers", finalUpstreamDNS)
// 如果启用了DNSSEC且有配置DNSSEC专用服务器并且域名不匹配NoDNSSECDomains则将DNSSEC专用服务器添加到列表中
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 && !noDNSSEC {
selectedUpstreamDNS = s.config.DNSSECUpstreamDNS
logger.Debug("使用DNSSEC专用服务器", "servers", selectedUpstreamDNS)
} else {
// 否则使用默认的上游DNS服务器
selectedUpstreamDNS = s.config.UpstreamDNS
// 合并DNSSEC专用服务器到上游服务器列表避免重复
for _, dnssecServer := range s.config.DNSSECUpstreamDNS {
hasDuplicate := false
for _, upstream := range finalUpstreamDNS {
if upstream == dnssecServer {
hasDuplicate = true
break
}
}
if !hasDuplicate {
finalUpstreamDNS = append(finalUpstreamDNS, dnssecServer)
}
}
logger.Debug("合并DNSSEC专用服务器到上游服务器列表", "servers", finalUpstreamDNS)
}
// 使用最终合并后的服务器列表
selectedUpstreamDNS = finalUpstreamDNS
}
// 1. 首先尝试所有配置的上游DNS服务器
@@ -837,8 +911,25 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
if resp.response.Rcode == dns.RcodeSuccess {
// 处理成功响应
// 优先选择带有DNSSEC记录的响应
if containsDNSSEC {
// 检查当前服务器是否是用户配置的上游DNS服务器优先使用用户配置的服务器
isUserUpstream := false
for _, userServer := range s.config.UpstreamDNS {
if userServer == resp.server {
isUserUpstream = true
break
}
}
// 优先选择用户配置的上游DNS服务器的响应
if isUserUpstream {
bestResponse = resp.response
bestRtt = resp.rtt
hasBestResponse = true
hasDNSSECResponse = containsDNSSEC
usedDNSServer = resp.server
logger.Debug("使用用户配置的上游服务器响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
} else if containsDNSSEC {
// 如果不是用户配置的服务器优先选择带有DNSSEC记录的响应
bestResponse = resp.response
bestRtt = resp.rtt
hasBestResponse = true
@@ -882,9 +973,32 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
case "loadbalance":
// 负载均衡模式 - 使用加权随机选择算法
// 1. 选择一个加权随机服务器
selectedServer := s.selectWeightedRandomServer(selectedUpstreamDNS)
if selectedServer != "" {
// 1. 尝试所有可用的服务器,直到找到一个能正常工作的
var triedServers []string
for len(triedServers) < len(selectedUpstreamDNS) {
// 从剩余的服务器中选择一个加权随机服务器
var availableServers []string
for _, server := range selectedUpstreamDNS {
found := false
for _, tried := range triedServers {
if server == tried {
found = true
break
}
}
if !found {
availableServers = append(availableServers, server)
}
}
selectedServer := s.selectWeightedRandomServer(availableServers)
if selectedServer == "" {
break
}
triedServers = append(triedServers, selectedServer)
logger.Debug("在负载均衡模式下选择服务器", "domain", domain, "server", selectedServer, "triedServers", triedServers)
// 设置超时上下文
timeoutCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.config.Timeout)*time.Millisecond)
defer cancel()
@@ -997,10 +1111,12 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
backupRtt = rtt
hasBackup = true
}
break // 找到有效响应,退出循环
}
} else {
// 更新服务器统计信息(失败)
s.updateServerStats(selectedServer, false, 0)
logger.Debug("服务器请求失败,尝试下一个", "domain", domain, "server", selectedServer, "error", err)
}
}
@@ -1967,7 +2083,7 @@ func (s *Server) updateStats(update func(*Stats)) {
}
// addQueryLog 添加查询日志
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool, dnsServer, dnssecServer string) {
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool, dnsServer, dnssecServer string, answers []DNSAnswer) {
// 获取IP地理位置
location := s.getIpGeolocation(clientIP)
@@ -1987,6 +2103,7 @@ func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime in
EDNS: edns,
DNSServer: dnsServer,
DNSSECServer: dnssecServer,
Answers: answers,
}
// 添加到日志列表
@@ -2441,12 +2558,8 @@ func (s *Server) fetchIpGeolocationFromAPI(ip string) (map[string]interface{}, e
// loadStatsData 从文件加载统计数据
func (s *Server) loadStatsData() {
if s.config.StatsFile == "" {
return
}
// 检查文件是否存在
data, err := ioutil.ReadFile(s.config.StatsFile)
data, err := ioutil.ReadFile("data/stats.json")
if err != nil {
if !os.IsNotExist(err) {
logger.Error("读取统计数据文件失败", "error", err)
@@ -2515,14 +2628,10 @@ func (s *Server) loadStatsData() {
// loadQueryLogs 从文件加载查询日志
func (s *Server) loadQueryLogs() {
if s.config.StatsFile == "" {
return
}
// 获取绝对路径
statsFilePath, err := filepath.Abs(s.config.StatsFile)
statsFilePath, err := filepath.Abs("data/stats.json")
if err != nil {
logger.Error("获取统计文件绝对路径失败", "path", s.config.StatsFile, "error", err)
logger.Error("获取统计文件绝对路径失败", "path", "data/stats.json", "error", err)
return
}
@@ -2564,14 +2673,10 @@ func (s *Server) loadQueryLogs() {
// saveStatsData 保存统计数据到文件
func (s *Server) saveStatsData() {
if s.config.StatsFile == "" {
return
}
// 获取绝对路径以避免工作目录问题
statsFilePath, err := filepath.Abs(s.config.StatsFile)
statsFilePath, err := filepath.Abs("data/stats.json")
if err != nil {
logger.Error("获取统计文件绝对路径失败", "path", s.config.StatsFile, "error", err)
logger.Error("获取统计文件绝对路径失败", "path", "data/stats.json", "error", err)
return
}
@@ -2754,15 +2859,15 @@ func getSystemCpuUsage(prevIdle, prevTotal *uint64) (float64, error) {
// startAutoSave 启动自动保存功能
func (s *Server) startAutoSave() {
if s.config.StatsFile == "" || s.config.SaveInterval <= 0 {
if s.config.SaveInterval <= 0 {
return
}
// 设置定时器
// 初始化定时器
s.saveTicker = time.NewTicker(time.Duration(s.config.SaveInterval) * time.Second)
defer s.saveTicker.Stop()
logger.Info("启动统计数据自动保存功能", "interval", s.config.SaveInterval, "file", s.config.StatsFile)
logger.Info("启动统计数据自动保存功能", "interval", s.config.SaveInterval, "file", "data/stats.json")
// 定期保存数据
for {