日志详情界面更新,增加日志详情API。
This commit is contained in:
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
所有对本项目的显著更改都将记录在此文件中。
|
所有对本项目的显著更改都将记录在此文件中。
|
||||||
|
|
||||||
|
## [1.2.4] - 2025-12-26
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
- 修复DNS解析记录显示,现在显示完整格式:"A: 104.26.24.30 (ttl=193)" 而不仅仅是IP地址
|
||||||
|
- 移除了查询日志列表中的"屏蔽规则"列,但在详情弹窗中仍保留
|
||||||
|
- 在弹窗日志详情中,只有被屏蔽或者有自定义规则时才显示规则信息
|
||||||
|
- 改进了日志详情弹窗的UI/UX,使用现代化设计和动画效果
|
||||||
|
- 移除了右上角的服务器状态卡片(CPU、查询统计等)
|
||||||
|
- 实现了页面滚动时,菜单栏和顶部标题栏保持固定
|
||||||
|
- 优化了页面适应窗口大小的改变,确保在所有设备上都能正确显示
|
||||||
|
### 修复
|
||||||
|
- 修复了移动端侧边栏在打开时遮挡页面内容的问题
|
||||||
|
- 修复了侧边栏布局,分离了桌面端和移动端侧边栏,使用CSS媒体查询控制显示
|
||||||
|
|
||||||
|
## [1.2.3] - 2025-12-25
|
||||||
|
### 修复
|
||||||
|
- 修复DNS服务器地址缺少端口号导致的Server Failed问题
|
||||||
|
- 添加normalizeDNSServerAddress函数,确保DNS服务器地址始终包含端口号,默认添加53端口
|
||||||
|
- 修改所有resolver.Exchange()调用,确保传递的服务器地址包含端口号
|
||||||
|
- 优化DNSSEC服务器合并逻辑,确保DNSSEC服务器地址也包含端口号
|
||||||
|
|
||||||
## [1.2.2] - 2025-12-25
|
## [1.2.2] - 2025-12-25
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"dns": {
|
"dns": {
|
||||||
"port": 53,
|
"port": 53,
|
||||||
"upstreamDNS": [
|
"upstreamDNS": [
|
||||||
"223.5.5.5"
|
"223.5.5.5:53"
|
||||||
],
|
],
|
||||||
"dnssecUpstreamDNS": [
|
"dnssecUpstreamDNS": [
|
||||||
"117.50.10.10",
|
"117.50.10.10",
|
||||||
|
|||||||
213
dns/server.go
213
dns/server.go
@@ -22,6 +22,17 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 确保DNS服务器地址包含端口号,默认添加53端口
|
||||||
|
func normalizeDNSServerAddress(address string) string {
|
||||||
|
// 检查地址是否已经包含端口号
|
||||||
|
if _, _, err := net.SplitHostPort(address); err != nil {
|
||||||
|
// 如果没有端口号,添加默认的53端口
|
||||||
|
return net.JoinHostPort(address, "53")
|
||||||
|
}
|
||||||
|
// 已经有端口号,直接返回
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
|
||||||
// BlockedDomain 屏蔽域名统计
|
// BlockedDomain 屏蔽域名统计
|
||||||
type BlockedDomain struct {
|
type BlockedDomain struct {
|
||||||
Domain string
|
Domain string
|
||||||
@@ -54,21 +65,22 @@ type DNSAnswer struct {
|
|||||||
|
|
||||||
// QueryLog 查询日志记录
|
// QueryLog 查询日志记录
|
||||||
type QueryLog struct {
|
type QueryLog struct {
|
||||||
Timestamp time.Time // 查询时间
|
Timestamp time.Time `json:"timestamp"` // 查询时间
|
||||||
ClientIP string // 客户端IP
|
ClientIP string `json:"clientIP"` // 客户端IP
|
||||||
Location string // IP地理位置(国家 城市)
|
Location string `json:"location"` // IP地理位置(国家 城市)
|
||||||
Domain string // 查询域名
|
Domain string `json:"domain"` // 查询域名
|
||||||
QueryType string // 查询类型
|
QueryType string `json:"queryType"` // 查询类型
|
||||||
ResponseTime int64 // 响应时间(ms)
|
ResponseTime int64 `json:"responseTime"` // 响应时间(ms)
|
||||||
Result string // 查询结果(allowed, blocked, error)
|
Result string `json:"result"` // 查询结果(allowed, blocked, error)
|
||||||
BlockRule string // 屏蔽规则(如果被屏蔽)
|
BlockRule string `json:"blockRule"` // 屏蔽规则(如果被屏蔽)
|
||||||
BlockType string // 屏蔽类型(如果被屏蔽)
|
BlockType string `json:"blockType"` // 屏蔽类型(如果被屏蔽)
|
||||||
FromCache bool // 是否来自缓存
|
FromCache bool `json:"fromCache"` // 是否来自缓存
|
||||||
DNSSEC bool // 是否使用了DNSSEC
|
DNSSEC bool `json:"dnssec"` // 是否使用了DNSSEC
|
||||||
EDNS bool // 是否使用了EDNS
|
EDNS bool `json:"edns"` // 是否使用了EDNS
|
||||||
DNSServer string // 使用的DNS服务器
|
DNSServer string `json:"dnsServer"` // 使用的DNS服务器
|
||||||
DNSSECServer string // 使用的DNSSEC专用服务器
|
DNSSECServer string `json:"dnssecServer"` // 使用的DNSSEC专用服务器
|
||||||
Answers []DNSAnswer `json:"answers"` // 解析记录
|
Answers []DNSAnswer `json:"answers"` // 解析记录
|
||||||
|
ResponseCode int `json:"responseCode"` // DNS响应代码
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatsData 用于持久化的统计数据结构
|
// StatsData 用于持久化的统计数据结构
|
||||||
@@ -375,7 +387,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 添加查询日志
|
// 添加查询日志
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil)
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil, dns.RcodeNameError)
|
||||||
logger.Debug("IPv6解析已禁用,拒绝AAAA记录查询", "domain", domain)
|
logger.Debug("IPv6解析已禁用,拒绝AAAA记录查询", "domain", domain)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -401,7 +413,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 添加查询日志
|
// 添加查询日志
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil)
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil, dns.RcodeRefused)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,9 +450,16 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 添加查询日志
|
// 添加查询日志 - 被屏蔽域名
|
||||||
blockedAnswers := []DNSAnswer{}
|
blockedAnswers := []DNSAnswer{}
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true, "无", "无", blockedAnswers)
|
// 根据屏蔽方法确定响应代码
|
||||||
|
blockedRcode := dns.RcodeNameError // 默认NXDOMAIN
|
||||||
|
if blockMethod := s.shieldConfig.BlockMethod; blockMethod == "refused" {
|
||||||
|
blockedRcode = dns.RcodeRefused
|
||||||
|
} else if blockMethod == "emptyIP" || blockMethod == "customIP" {
|
||||||
|
blockedRcode = dns.RcodeSuccess
|
||||||
|
}
|
||||||
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true, "无", "无", blockedAnswers, blockedRcode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +544,12 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加查询日志 - 标记为缓存
|
// 添加查询日志 - 标记为缓存
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true, "缓存", "无", cachedAnswers)
|
// 从缓存响应中获取响应代码
|
||||||
|
cacheRcode := dns.RcodeSuccess // 默认成功
|
||||||
|
if cachedResponse != nil {
|
||||||
|
cacheRcode = cachedResponse.Rcode
|
||||||
|
}
|
||||||
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true, "缓存", "无", cachedAnswers, cacheRcode)
|
||||||
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC)
|
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -622,7 +646,12 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加查询日志 - 标记为实时
|
// 添加查询日志 - 标记为实时
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true, dnsServer, dnssecServer, responseAnswers)
|
// 从响应中获取响应代码
|
||||||
|
realRcode := dns.RcodeSuccess // 默认成功
|
||||||
|
if response != nil {
|
||||||
|
realRcode = response.Rcode
|
||||||
|
}
|
||||||
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true, dnsServer, dnssecServer, responseAnswers, realRcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleHostsResponse 处理hosts文件匹配的响应
|
// handleHostsResponse 处理hosts文件匹配的响应
|
||||||
@@ -795,17 +824,19 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
|
|
||||||
// 如果启用了DNSSEC且有配置DNSSEC专用服务器,并且域名不匹配NoDNSSECDomains,则将DNSSEC专用服务器添加到列表中
|
// 如果启用了DNSSEC且有配置DNSSEC专用服务器,并且域名不匹配NoDNSSECDomains,则将DNSSEC专用服务器添加到列表中
|
||||||
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 && !noDNSSEC {
|
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 && !noDNSSEC {
|
||||||
// 合并DNSSEC专用服务器到上游服务器列表,避免重复
|
// 合并DNSSEC专用服务器到上游服务器列表,避免重复,并确保包含端口号
|
||||||
for _, dnssecServer := range s.config.DNSSECUpstreamDNS {
|
for _, dnssecServer := range s.config.DNSSECUpstreamDNS {
|
||||||
hasDuplicate := false
|
hasDuplicate := false
|
||||||
|
// 确保DNSSEC服务器地址包含端口号
|
||||||
|
normalizedDnssecServer := normalizeDNSServerAddress(dnssecServer)
|
||||||
for _, upstream := range finalUpstreamDNS {
|
for _, upstream := range finalUpstreamDNS {
|
||||||
if upstream == dnssecServer {
|
if upstream == normalizedDnssecServer {
|
||||||
hasDuplicate = true
|
hasDuplicate = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasDuplicate {
|
if !hasDuplicate {
|
||||||
finalUpstreamDNS = append(finalUpstreamDNS, dnssecServer)
|
finalUpstreamDNS = append(finalUpstreamDNS, normalizedDnssecServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Debug("合并DNSSEC专用服务器到上游服务器列表", "servers", finalUpstreamDNS)
|
logger.Debug("合并DNSSEC专用服务器到上游服务器列表", "servers", finalUpstreamDNS)
|
||||||
@@ -843,8 +874,8 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
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, normalizeDNSServerAddress(server))
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case responses <- serverResponse{response, rtt, server, err}:
|
case responses <- serverResponse{response, rtt, server, err}:
|
||||||
@@ -899,72 +930,103 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
resp.response.AuthenticatedData = false
|
resp.response.AuthenticatedData = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果响应成功或为NXDOMAIN,根据DNSSEC状态选择最佳响应
|
// 检查当前服务器是否是DNSSEC专用服务器
|
||||||
if resp.response.Rcode == dns.RcodeSuccess || resp.response.Rcode == dns.RcodeNameError {
|
for _, dnssecServer := range dnssecServers {
|
||||||
// 检查当前使用的服务器是否是DNSSEC专用服务器
|
if dnssecServer == resp.server {
|
||||||
for _, dnssecServer := range dnssecServers {
|
usedDNSSECServer = resp.server
|
||||||
if dnssecServer == resp.server {
|
break
|
||||||
usedDNSSECServer = resp.server
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if resp.response.Rcode == dns.RcodeSuccess {
|
// 检查当前服务器是否是用户配置的上游DNS服务器
|
||||||
// 处理成功响应
|
isUserUpstream := false
|
||||||
// 检查当前服务器是否是用户配置的上游DNS服务器(优先使用用户配置的服务器)
|
for _, userServer := range s.config.UpstreamDNS {
|
||||||
isUserUpstream := false
|
if userServer == resp.server {
|
||||||
for _, userServer := range s.config.UpstreamDNS {
|
isUserUpstream = true
|
||||||
if userServer == resp.server {
|
break
|
||||||
isUserUpstream = true
|
}
|
||||||
break
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 优先选择用户配置的上游DNS服务器的响应
|
// 处理响应,优先选择用户配置的主DNS服务器
|
||||||
if isUserUpstream {
|
if resp.response.Rcode == dns.RcodeSuccess {
|
||||||
bestResponse = resp.response
|
// 成功响应,优先使用
|
||||||
bestRtt = resp.rtt
|
if isUserUpstream {
|
||||||
hasBestResponse = true
|
// 用户配置的主DNS服务器响应,直接设置为最佳响应
|
||||||
hasDNSSECResponse = containsDNSSEC
|
bestResponse = resp.response
|
||||||
usedDNSServer = resp.server
|
bestRtt = resp.rtt
|
||||||
logger.Debug("使用用户配置的上游服务器响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
hasBestResponse = true
|
||||||
} else if containsDNSSEC {
|
hasDNSSECResponse = containsDNSSEC
|
||||||
// 如果不是用户配置的服务器,优先选择带有DNSSEC记录的响应
|
usedDNSServer = resp.server
|
||||||
|
logger.Debug("使用用户配置的上游服务器响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||||
|
} else if containsDNSSEC {
|
||||||
|
// 非用户配置服务器,但有DNSSEC记录
|
||||||
|
if !hasBestResponse || !isUserUpstream {
|
||||||
|
// 如果还没有最佳响应,或者当前最佳响应不是用户配置的服务器,则更新
|
||||||
bestResponse = resp.response
|
bestResponse = resp.response
|
||||||
bestRtt = resp.rtt
|
bestRtt = resp.rtt
|
||||||
hasBestResponse = true
|
hasBestResponse = true
|
||||||
hasDNSSECResponse = true
|
hasDNSSECResponse = true
|
||||||
usedDNSServer = resp.server
|
usedDNSServer = resp.server
|
||||||
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||||
} else if !hasBestResponse {
|
}
|
||||||
// 没有带DNSSEC的响应时,保存第一个成功响应
|
} else {
|
||||||
|
// 非用户配置服务器,没有DNSSEC记录
|
||||||
|
if !hasBestResponse {
|
||||||
|
// 如果还没有最佳响应,设置为最佳响应
|
||||||
bestResponse = resp.response
|
bestResponse = resp.response
|
||||||
bestRtt = resp.rtt
|
bestRtt = resp.rtt
|
||||||
hasBestResponse = true
|
hasBestResponse = true
|
||||||
usedDNSServer = resp.server
|
usedDNSServer = resp.server
|
||||||
logger.Debug("找到最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
logger.Debug("找到最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||||
}
|
}
|
||||||
} else if resp.response.Rcode == dns.RcodeNameError {
|
}
|
||||||
// 处理NXDOMAIN响应
|
} else if resp.response.Rcode == dns.RcodeNameError {
|
||||||
// 如果还没有最佳响应,或者最佳响应也是NXDOMAIN,优先选择更快的NXDOMAIN响应
|
// NXDOMAIN响应
|
||||||
if !hasBestResponse || bestResponse.Rcode == dns.RcodeNameError {
|
if !hasBestResponse || bestResponse.Rcode == dns.RcodeNameError {
|
||||||
// 如果还没有最佳响应,或者当前响应更快,更新最佳响应
|
// 如果还没有最佳响应,或者最佳响应也是NXDOMAIN
|
||||||
if !hasBestResponse || resp.rtt < bestRtt {
|
if isUserUpstream {
|
||||||
bestResponse = resp.response
|
// 用户配置的服务器,直接使用
|
||||||
bestRtt = resp.rtt
|
bestResponse = resp.response
|
||||||
hasBestResponse = true
|
bestRtt = resp.rtt
|
||||||
usedDNSServer = resp.server
|
hasBestResponse = true
|
||||||
logger.Debug("找到NXDOMAIN最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
usedDNSServer = resp.server
|
||||||
}
|
logger.Debug("使用用户配置的上游服务器NXDOMAIN响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||||
|
} else if !hasBestResponse || resp.rtt < bestRtt {
|
||||||
|
// 非用户配置服务器,选择更快的响应
|
||||||
|
bestResponse = resp.response
|
||||||
|
bestRtt = resp.rtt
|
||||||
|
hasBestResponse = true
|
||||||
|
usedDNSServer = resp.server
|
||||||
|
logger.Debug("找到NXDOMAIN最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 保存为备选响应
|
}
|
||||||
|
|
||||||
|
// 更新备选响应,确保总有一个可用的响应
|
||||||
|
if resp.response != nil {
|
||||||
if !hasBackup {
|
if !hasBackup {
|
||||||
|
// 第一次保存备选响应
|
||||||
backupResponse = resp.response
|
backupResponse = resp.response
|
||||||
backupRtt = resp.rtt
|
backupRtt = resp.rtt
|
||||||
hasBackup = true
|
hasBackup = true
|
||||||
|
} else {
|
||||||
|
// 后续响应,优先保存用户配置的服务器响应作为备选
|
||||||
|
if isUserUpstream {
|
||||||
|
backupResponse = resp.response
|
||||||
|
backupRtt = resp.rtt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 即使响应不是成功或NXDOMAIN,也保存为最佳响应(如果还没有的话)
|
||||||
|
// 确保总有一个响应返回给客户端
|
||||||
|
if !hasBestResponse {
|
||||||
|
bestResponse = resp.response
|
||||||
|
bestRtt = resp.rtt
|
||||||
|
hasBestResponse = true
|
||||||
|
usedDNSServer = resp.server
|
||||||
|
logger.Debug("使用非成功响应作为最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt, "rcode", resp.response.Rcode)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 更新服务器统计信息(失败)
|
// 更新服务器统计信息(失败)
|
||||||
s.updateServerStats(resp.server, false, 0)
|
s.updateServerStats(resp.server, false, 0)
|
||||||
@@ -1011,7 +1073,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
}, 1)
|
}, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
response, rtt, err := s.resolver.Exchange(r, selectedServer)
|
response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(selectedServer))
|
||||||
resultChan <- struct {
|
resultChan <- struct {
|
||||||
response *dns.Msg
|
response *dns.Msg
|
||||||
rtt time.Duration
|
rtt time.Duration
|
||||||
@@ -1137,7 +1199,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
}, 1)
|
}, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
resp, r, e := s.resolver.Exchange(r, fastestServer)
|
resp, r, e := s.resolver.Exchange(r, normalizeDNSServerAddress(fastestServer))
|
||||||
resultChan <- struct {
|
resultChan <- struct {
|
||||||
response *dns.Msg
|
response *dns.Msg
|
||||||
rtt time.Duration
|
rtt time.Duration
|
||||||
@@ -1259,7 +1321,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
// 发送请求并获取响应
|
// 发送请求并获取响应
|
||||||
response, rtt, err := s.resolver.Exchange(r, server)
|
response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(server))
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case responses <- serverResponse{response, rtt, server, err}:
|
case responses <- serverResponse{response, rtt, server, err}:
|
||||||
@@ -1400,7 +1462,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
}, 1)
|
}, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
response, rtt, err := s.resolver.Exchange(r, selectedDnssecServer)
|
response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(selectedDnssecServer))
|
||||||
resultChan <- struct {
|
resultChan <- struct {
|
||||||
response *dns.Msg
|
response *dns.Msg
|
||||||
rtt time.Duration
|
rtt time.Duration
|
||||||
@@ -1498,7 +1560,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
}, 1)
|
}, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
resp, r, e := s.resolver.Exchange(r, localServer)
|
resp, r, e := s.resolver.Exchange(r, normalizeDNSServerAddress(localServer))
|
||||||
resultChan <- struct {
|
resultChan <- struct {
|
||||||
response *dns.Msg
|
response *dns.Msg
|
||||||
rtt time.Duration
|
rtt time.Duration
|
||||||
@@ -2083,7 +2145,7 @@ func (s *Server) updateStats(update func(*Stats)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addQueryLog 添加查询日志
|
// addQueryLog 添加查询日志
|
||||||
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool, dnsServer, dnssecServer string, answers []DNSAnswer) {
|
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool, dnsServer, dnssecServer string, answers []DNSAnswer, responseCode int) {
|
||||||
// 获取IP地理位置
|
// 获取IP地理位置
|
||||||
location := s.getIpGeolocation(clientIP)
|
location := s.getIpGeolocation(clientIP)
|
||||||
|
|
||||||
@@ -2104,6 +2166,7 @@ func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime in
|
|||||||
DNSServer: dnsServer,
|
DNSServer: dnsServer,
|
||||||
DNSSECServer: dnssecServer,
|
DNSSECServer: dnssecServer,
|
||||||
Answers: answers,
|
Answers: answers,
|
||||||
|
ResponseCode: responseCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加到日志列表
|
// 添加到日志列表
|
||||||
|
|||||||
@@ -18,9 +18,67 @@
|
|||||||
<body class="bg-gray-50 text-dark font-sans">
|
<body class="bg-gray-50 text-dark font-sans">
|
||||||
<div class="flex h-screen overflow-hidden">
|
<div class="flex h-screen overflow-hidden">
|
||||||
<!-- 侧边栏 -->
|
<!-- 侧边栏 -->
|
||||||
<aside id="sidebar" class="fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col transition-transform duration-300 z-50 md:relative md:translate-x-0 -translate-x-full shadow-lg">
|
<aside id="sidebar" class="fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col transition-transform duration-300 z-50 hidden md:flex">
|
||||||
|
<!-- Logo -->
|
||||||
|
<div class="flex items-center justify-center h-16 border-b border-gray-200">
|
||||||
|
<i class="fa fa-server text-3xl text-primary mr-3"></i>
|
||||||
|
<h1 class="text-xl font-bold text-primary">DNS 控制台</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 菜单 -->
|
||||||
|
<nav class="flex-1 overflow-y-auto p-4">
|
||||||
|
<ul class="space-y-1">
|
||||||
|
<li>
|
||||||
|
<a href="#dashboard" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all sidebar-item-active">
|
||||||
|
<i class="fa fa-tachometer mr-3 text-lg"></i>
|
||||||
|
<span>仪表盘</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#shield" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||||
|
<i class="fa fa-shield mr-3 text-lg"></i>
|
||||||
|
<span>屏蔽管理</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#hosts" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||||
|
<i class="fa fa-file-text mr-3 text-lg"></i>
|
||||||
|
<span>Hosts管理</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="#query" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||||
|
<i class="fa fa-search mr-3 text-lg"></i>
|
||||||
|
<span>DNS屏蔽查询</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#logs" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||||
|
<i class="fa fa-file-text-o mr-3 text-lg"></i>
|
||||||
|
<span>查询日志</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#config" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||||
|
<i class="fa fa-cog mr-3 text-lg"></i>
|
||||||
|
<span>系统设置</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- 底部信息 -->
|
||||||
|
<div class="p-4 border-t border-gray-200 text-center text-gray-500 text-sm">
|
||||||
|
<p>DNS服务器 v1.2.0</p>
|
||||||
|
<p class="mt-1" id="uptime">正常运行中</p>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- 移动端侧边栏 -->
|
||||||
|
<aside id="mobile-sidebar" class="fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col transition-transform duration-300 z-50 -translate-x-full md:hidden">
|
||||||
<!-- 移动端关闭按钮 -->
|
<!-- 移动端关闭按钮 -->
|
||||||
<div class="absolute top-4 right-4 md:hidden">
|
<div class="absolute top-4 right-4">
|
||||||
<button id="close-sidebar" class="p-2 text-gray-500 hover:text-gray-700 focus:outline-none">
|
<button id="close-sidebar" class="p-2 text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||||
<i class="fa fa-times text-xl"></i>
|
<i class="fa fa-times text-xl"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -85,9 +143,9 @@
|
|||||||
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden md:hidden"></div>
|
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden md:hidden"></div>
|
||||||
|
|
||||||
<!-- 主内容区 -->
|
<!-- 主内容区 -->
|
||||||
<main class="flex-1 overflow-y-auto">
|
<main class="flex-1 flex flex-col md:ml-64">
|
||||||
<!-- 顶部导航栏 -->
|
<!-- 顶部导航栏 -->
|
||||||
<header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6">
|
<header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6 sticky top-0 z-30">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<button id="toggle-sidebar" class="block md:hidden text-gray-500 hover:text-gray-700 focus:outline-none">
|
<button id="toggle-sidebar" class="block md:hidden text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||||
<i class="fa fa-bars text-xl"></i>
|
<i class="fa fa-bars text-xl"></i>
|
||||||
@@ -96,55 +154,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<!-- 服务器状态组件 -->
|
|
||||||
<div class="relative bg-white rounded-lg shadow-md px-3 py-2 flex items-center space-x-2 server-status-widget md:min-w-[300px] sm:min-w-[250px] min-w-[180px]" id="server-status-widget">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="text-xs font-medium text-gray-500">CPU</span>
|
|
||||||
<span id="server-cpu-value" class="ml-2 text-sm font-semibold">0%</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-16 h-1 bg-gray-100 rounded-full mt-1">
|
|
||||||
<div id="server-cpu-bar" class="h-full bg-warning rounded-full" style="width: 0%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="text-xs font-medium text-gray-500">查询</span>
|
|
||||||
<span id="server-queries-value" class="ml-2 text-sm font-semibold">0</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-16 h-1 bg-gray-100 rounded-full mt-1">
|
|
||||||
<div id="server-queries-bar" class="h-full bg-primary rounded-full" style="width: 0%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 额外指标区域 - 初始隐藏,只在非首页显示 -->
|
|
||||||
<div id="server-additional-stats" class="hidden md:flex items-center">
|
|
||||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="text-xs font-medium text-gray-500">总量</span>
|
|
||||||
<span id="server-total-queries" class="ml-2 text-sm font-semibold">0</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="text-xs font-medium text-gray-500">屏蔽</span>
|
|
||||||
<span id="server-blocked-queries" class="ml-2 text-sm font-semibold">0</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="text-xs font-medium text-gray-500">正常</span>
|
|
||||||
<span id="server-allowed-queries" class="ml-2 text-sm font-semibold">0</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="absolute top-1 right-1">
|
|
||||||
<span id="server-status-indicator" class="inline-block w-2 h-2 bg-success rounded-full"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
|
<button class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
|
||||||
<i class="fa fa-bell text-lg"></i>
|
<i class="fa fa-bell text-lg"></i>
|
||||||
@@ -170,7 +179,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- 页面内容 -->
|
<!-- 页面内容 -->
|
||||||
<div class="p-6">
|
<div class="p-6 overflow-y-auto flex-1">
|
||||||
<!-- 仪表盘部分 -->
|
<!-- 仪表盘部分 -->
|
||||||
<div id="dashboard-content" class="space-y-6">
|
<div id="dashboard-content" class="space-y-6">
|
||||||
<!-- 统计卡片 -->
|
<!-- 统计卡片 -->
|
||||||
@@ -975,13 +984,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">响应时间</th>
|
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">响应时间</th>
|
||||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">屏蔽规则</th>
|
|
||||||
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="logs-table-body">
|
<tbody id="logs-table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="py-8 text-center text-gray-500 border-b border-gray-100">
|
<td colspan="5" class="py-8 text-center text-gray-500 border-b border-gray-100">
|
||||||
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
|
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
|
||||||
<div>暂无查询日志</div>
|
<div>暂无查询日志</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -1166,7 +1174,6 @@
|
|||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
<script src="js/api.js"></script>
|
<script src="js/api.js"></script>
|
||||||
<script src="js/dashboard.js"></script>
|
<script src="js/dashboard.js"></script>
|
||||||
<script src="js/server-status.js"></script>
|
|
||||||
<script src="js/shield.js"></script>
|
<script src="js/shield.js"></script>
|
||||||
<script src="js/hosts.js"></script>
|
<script src="js/hosts.js"></script>
|
||||||
<script src="js/query.js"></script>
|
<script src="js/query.js"></script>
|
||||||
|
|||||||
@@ -2964,33 +2964,10 @@ window.addEventListener('hashchange', handleHashChange);
|
|||||||
// 初始化hash路由 - 确保在页面加载时就能被调用
|
// 初始化hash路由 - 确保在页面加载时就能被调用
|
||||||
initHashRoute();
|
initHashRoute();
|
||||||
|
|
||||||
// 侧边栏切换
|
|
||||||
function toggleSidebar() {
|
|
||||||
const sidebar = document.getElementById('sidebar');
|
|
||||||
sidebar.classList.toggle('-translate-x-full');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 响应式处理
|
// 响应式处理
|
||||||
function handleResponsive() {
|
function handleResponsive() {
|
||||||
const toggleBtn = document.getElementById('toggle-sidebar');
|
|
||||||
const sidebar = document.getElementById('sidebar');
|
|
||||||
|
|
||||||
toggleBtn.addEventListener('click', toggleSidebar);
|
|
||||||
|
|
||||||
// 初始状态处理
|
|
||||||
function updateSidebarState() {
|
|
||||||
if (window.innerWidth < 1024) {
|
|
||||||
sidebar.classList.add('-translate-x-full');
|
|
||||||
} else {
|
|
||||||
sidebar.classList.remove('-translate-x-full');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSidebarState();
|
|
||||||
|
|
||||||
// 窗口大小改变时处理
|
// 窗口大小改变时处理
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
updateSidebarState();
|
|
||||||
|
|
||||||
// 更新所有图表大小
|
// 更新所有图表大小
|
||||||
if (dnsRequestsChart) {
|
if (dnsRequestsChart) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@ function setupNavigation() {
|
|||||||
// 移动端侧边栏切换
|
// 移动端侧边栏切换
|
||||||
const toggleSidebar = document.getElementById('toggle-sidebar');
|
const toggleSidebar = document.getElementById('toggle-sidebar');
|
||||||
const closeSidebarBtn = document.getElementById('close-sidebar');
|
const closeSidebarBtn = document.getElementById('close-sidebar');
|
||||||
const sidebar = document.getElementById('sidebar');
|
const sidebar = document.getElementById('mobile-sidebar');
|
||||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||||
|
|
||||||
// 打开侧边栏函数
|
// 打开侧边栏函数
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"dns": {
|
|
||||||
"port": 5353,
|
|
||||||
"upstreamDNS": [
|
|
||||||
"223.5.5.5:53",
|
|
||||||
"223.6.6.6:53",
|
|
||||||
"117.50.10.10:53",
|
|
||||||
"10.35.10.200:53"
|
|
||||||
],
|
|
||||||
"dnssecUpstreamDNS": [
|
|
||||||
"117.50.10.10:53",
|
|
||||||
"101.226.4.6:53",
|
|
||||||
"218.30.118.6:53",
|
|
||||||
"208.67.220.220:53",
|
|
||||||
"208.67.222.222:53"
|
|
||||||
],
|
|
||||||
"timeout": 5000,
|
|
||||||
"statsFile": "data/stats.json",
|
|
||||||
"saveInterval": 300,
|
|
||||||
"cacheTTL": 30,
|
|
||||||
"enableDNSSEC": true,
|
|
||||||
"queryMode": "parallel",
|
|
||||||
"domainSpecificDNS": {
|
|
||||||
"amazehome.xyz": ["10.35.10.200:53"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"http": {
|
|
||||||
"port": 8081,
|
|
||||||
"host": "0.0.0.0",
|
|
||||||
"enableAPI": true,
|
|
||||||
"username": "admin",
|
|
||||||
"password": "admin"
|
|
||||||
},
|
|
||||||
"shield": {
|
|
||||||
"localRulesFile": "data/rules.txt",
|
|
||||||
"blacklists": [],
|
|
||||||
"updateInterval": 3600,
|
|
||||||
"hostsFile": "data/hosts.txt",
|
|
||||||
"blockMethod": "NXDOMAIN",
|
|
||||||
"customBlockIP": "",
|
|
||||||
"statsFile": "./data/shield_stats.json",
|
|
||||||
"statsSaveInterval": 60,
|
|
||||||
"remoteRulesCacheDir": "data/remote_rules"
|
|
||||||
},
|
|
||||||
"log": {
|
|
||||||
"file": "logs/dns-server-5353.log",
|
|
||||||
"level": "debug",
|
|
||||||
"maxSize": 100,
|
|
||||||
"maxBackups": 10,
|
|
||||||
"maxAge": 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user