更新Swaggers API
This commit is contained in:
21
CHANGELOG.md
Normal file
21
CHANGELOG.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
所有对本项目的显著更改都将记录在此文件中。
|
||||||
|
|
||||||
|
## [1.0.0] - 2025-12-16
|
||||||
|
|
||||||
|
### 添加
|
||||||
|
- 在web界面查询日志详情的请求列表区域增加了EDNS标记显示
|
||||||
|
- 后端QueryLog结构体扩展,新增EDNS字段以记录查询是否使用EDNS
|
||||||
|
- 前端日志渲染逻辑支持EDNS标记的显示
|
||||||
|
|
||||||
|
### 修改
|
||||||
|
- 更新了`addQueryLog`函数签名,增加edns参数
|
||||||
|
- 调整了所有`addQueryLog`调用,确保传递正确的EDNS值
|
||||||
|
- 优化了日志表格的状态显示格式,使EDNS标记与DNSSEC、缓存状态等标记一致显示
|
||||||
|
|
||||||
|
## 格式说明
|
||||||
|
|
||||||
|
本CHANGELOG遵循[Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)格式。
|
||||||
|
|
||||||
|
版本号遵循[语义化版本](https://semver.org/lang/zh-CN/)规范。
|
||||||
12
config.json
12
config.json
@@ -3,11 +3,17 @@
|
|||||||
"port": 53,
|
"port": 53,
|
||||||
"upstreamDNS": [
|
"upstreamDNS": [
|
||||||
"223.5.5.5:53",
|
"223.5.5.5:53",
|
||||||
"223.6.6.6:53"
|
"223.6.6.6:53",
|
||||||
|
"117.50.10.10:53"
|
||||||
|
],
|
||||||
|
"dnssecUpstreamDNS": [
|
||||||
|
"1.1.1.1:53"
|
||||||
],
|
],
|
||||||
"timeout": 5000,
|
"timeout": 5000,
|
||||||
"statsFile": "data/stats.json",
|
"statsFile": "data/stats.json",
|
||||||
"saveInterval": 300
|
"saveInterval": 300,
|
||||||
|
"cacheTTL": 30,
|
||||||
|
"enableDNSSEC": true
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
@@ -35,7 +41,7 @@
|
|||||||
"name": "CHN-anti-AD",
|
"name": "CHN-anti-AD",
|
||||||
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/easylist.txt",
|
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/easylist.txt",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"lastUpdateTime": "2025-11-28T15:26:24.833Z"
|
"lastUpdateTime": "2025-12-16T08:50:10.180Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "My GitHub Rules",
|
"name": "My GitHub Rules",
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import (
|
|||||||
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查询的专用服务器
|
||||||
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支持
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPConfig HTTP控制台配置
|
// HTTPConfig HTTP控制台配置
|
||||||
@@ -93,6 +95,12 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
if config.DNS.CacheTTL == 0 {
|
if config.DNS.CacheTTL == 0 {
|
||||||
config.DNS.CacheTTL = 30 // 默认30分钟
|
config.DNS.CacheTTL = 30 // 默认30分钟
|
||||||
}
|
}
|
||||||
|
// DNSSEC默认配置
|
||||||
|
config.DNS.EnableDNSSEC = true // 默认启用DNSSEC支持
|
||||||
|
// DNSSEC专用服务器默认配置
|
||||||
|
if len(config.DNS.DNSSECUpstreamDNS) == 0 {
|
||||||
|
config.DNS.DNSSECUpstreamDNS = []string{"8.8.8.8:53", "1.1.1.1:53"}
|
||||||
|
}
|
||||||
if config.HTTP.Port == 0 {
|
if config.HTTP.Port == 0 {
|
||||||
config.HTTP.Port = 8080
|
config.HTTP.Port = 8080
|
||||||
}
|
}
|
||||||
|
|||||||
59
dns/cache.go
59
dns/cache.go
@@ -11,6 +11,7 @@ import (
|
|||||||
type DNSCacheItem struct {
|
type DNSCacheItem struct {
|
||||||
Response *dns.Msg // DNS响应消息
|
Response *dns.Msg // DNS响应消息
|
||||||
Expiry time.Time // 过期时间
|
Expiry time.Time // 过期时间
|
||||||
|
HasDNSSEC bool // 是否包含DNSSEC记录
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSCache DNS缓存结构
|
// DNSCache DNS缓存结构
|
||||||
@@ -38,6 +39,63 @@ func cacheKey(qName string, qType uint16) string {
|
|||||||
return qName + "|" + dns.TypeToString[qType]
|
return qName + "|" + dns.TypeToString[qType]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasDNSSECRecords 检查响应是否包含DNSSEC记录
|
||||||
|
func hasDNSSECRecords(response *dns.Msg) bool {
|
||||||
|
// 检查响应中是否包含DNSSEC相关记录(DNSKEY、RRSIG、DS、NSEC、NSEC3等)
|
||||||
|
for _, rr := range response.Answer {
|
||||||
|
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.RRSIG); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.DS); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC3); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rr := range response.Ns {
|
||||||
|
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.RRSIG); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.DS); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC3); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rr := range response.Extra {
|
||||||
|
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.RRSIG); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.DS); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC3); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Set 设置缓存项
|
// Set 设置缓存项
|
||||||
func (c *DNSCache) Set(qName string, qType uint16, response *dns.Msg, ttl time.Duration) {
|
func (c *DNSCache) Set(qName string, qType uint16, response *dns.Msg, ttl time.Duration) {
|
||||||
if ttl <= 0 {
|
if ttl <= 0 {
|
||||||
@@ -48,6 +106,7 @@ func (c *DNSCache) Set(qName string, qType uint16, response *dns.Msg, ttl time.D
|
|||||||
item := &DNSCacheItem{
|
item := &DNSCacheItem{
|
||||||
Response: response.Copy(), // 复制响应以避免外部修改
|
Response: response.Copy(), // 复制响应以避免外部修改
|
||||||
Expiry: time.Now().Add(ttl),
|
Expiry: time.Now().Add(ttl),
|
||||||
|
HasDNSSEC: hasDNSSECRecords(response), // 检查并设置DNSSEC标志
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
|
|||||||
491
dns/server.go
491
dns/server.go
@@ -23,11 +23,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BlockedDomain 屏蔽域名统计
|
// BlockedDomain 屏蔽域名统计
|
||||||
|
|
||||||
type BlockedDomain struct {
|
type BlockedDomain struct {
|
||||||
Domain string
|
Domain string
|
||||||
Count int64
|
Count int64
|
||||||
LastSeen time.Time
|
LastSeen time.Time
|
||||||
|
DNSSEC bool // 是否使用了DNSSEC
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientStats 客户端统计
|
// ClientStats 客户端统计
|
||||||
@@ -57,6 +57,8 @@ type QueryLog struct {
|
|||||||
BlockRule string // 屏蔽规则(如果被屏蔽)
|
BlockRule string // 屏蔽规则(如果被屏蔽)
|
||||||
BlockType string // 屏蔽类型(如果被屏蔽)
|
BlockType string // 屏蔽类型(如果被屏蔽)
|
||||||
FromCache bool // 是否来自缓存
|
FromCache bool // 是否来自缓存
|
||||||
|
DNSSEC bool // 是否使用了DNSSEC
|
||||||
|
EDNS bool // 是否使用了EDNS
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatsData 用于持久化的统计数据结构
|
// StatsData 用于持久化的统计数据结构
|
||||||
@@ -111,6 +113,9 @@ type Server struct {
|
|||||||
|
|
||||||
// DNS查询缓存
|
// DNS查询缓存
|
||||||
dnsCache *DNSCache // DNS响应缓存
|
dnsCache *DNSCache // DNS响应缓存
|
||||||
|
|
||||||
|
// 域名DNSSEC状态映射表
|
||||||
|
domainDNSSECStatus map[string]bool // 域名到DNSSEC状态的映射
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats DNS服务器统计信息
|
// Stats DNS服务器统计信息
|
||||||
@@ -125,6 +130,10 @@ type Stats struct {
|
|||||||
QueryTypes map[string]int64 // 查询类型统计
|
QueryTypes map[string]int64 // 查询类型统计
|
||||||
SourceIPs map[string]bool // 活跃来源IP
|
SourceIPs map[string]bool // 活跃来源IP
|
||||||
CpuUsage float64 // CPU使用率(%)
|
CpuUsage float64 // CPU使用率(%)
|
||||||
|
DNSSECQueries int64 // DNSSEC查询总数
|
||||||
|
DNSSECSuccess int64 // DNSSEC验证成功数
|
||||||
|
DNSSECFailed int64 // DNSSEC验证失败数
|
||||||
|
DNSSECEnabled bool // 是否启用了DNSSEC
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer 创建DNS服务器实例
|
// NewServer 创建DNS服务器实例
|
||||||
@@ -141,6 +150,7 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
|
|||||||
resolver: &dns.Client{
|
resolver: &dns.Client{
|
||||||
Net: "udp",
|
Net: "udp",
|
||||||
Timeout: time.Duration(config.Timeout) * time.Millisecond,
|
Timeout: time.Duration(config.Timeout) * time.Millisecond,
|
||||||
|
UDPSize: 4096, // 增加UDP缓冲区大小,支持更大的DNSSEC响应
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
@@ -155,6 +165,10 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
|
|||||||
QueryTypes: make(map[string]int64),
|
QueryTypes: make(map[string]int64),
|
||||||
SourceIPs: make(map[string]bool),
|
SourceIPs: make(map[string]bool),
|
||||||
CpuUsage: 0,
|
CpuUsage: 0,
|
||||||
|
DNSSECQueries: 0,
|
||||||
|
DNSSECSuccess: 0,
|
||||||
|
DNSSECFailed: 0,
|
||||||
|
DNSSECEnabled: config.EnableDNSSEC,
|
||||||
},
|
},
|
||||||
blockedDomains: make(map[string]*BlockedDomain),
|
blockedDomains: make(map[string]*BlockedDomain),
|
||||||
resolvedDomains: make(map[string]*BlockedDomain),
|
resolvedDomains: make(map[string]*BlockedDomain),
|
||||||
@@ -171,6 +185,8 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
|
|||||||
ipGeolocationCacheTTL: 24 * time.Hour, // 缓存有效期24小时
|
ipGeolocationCacheTTL: 24 * time.Hour, // 缓存有效期24小时
|
||||||
// DNS查询缓存初始化
|
// DNS查询缓存初始化
|
||||||
dnsCache: NewDNSCache(cacheTTL),
|
dnsCache: NewDNSCache(cacheTTL),
|
||||||
|
// 初始化域名DNSSEC状态映射表
|
||||||
|
domainDNSSECStatus: make(map[string]bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载已保存的统计数据
|
// 加载已保存的统计数据
|
||||||
@@ -327,8 +343,8 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
response.SetRcode(r, dns.RcodeRefused)
|
response.SetRcode(r, dns.RcodeRefused)
|
||||||
w.WriteMsg(response)
|
w.WriteMsg(response)
|
||||||
|
|
||||||
// 计算响应时间
|
// 缓存命中,响应时间设为0ms
|
||||||
responseTime := time.Since(startTime).Milliseconds()
|
responseTime := int64(0)
|
||||||
s.updateStats(func(stats *Stats) {
|
s.updateStats(func(stats *Stats) {
|
||||||
stats.TotalResponseTime += responseTime
|
stats.TotalResponseTime += responseTime
|
||||||
if stats.Queries > 0 {
|
if stats.Queries > 0 {
|
||||||
@@ -337,15 +353,15 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 添加查询日志
|
// 添加查询日志
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false)
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查hosts文件是否有匹配
|
// 检查hosts文件是否有匹配
|
||||||
if ip, exists := s.shieldManager.GetHostsIP(domain); exists {
|
if ip, exists := s.shieldManager.GetHostsIP(domain); exists {
|
||||||
s.handleHostsResponse(w, r, ip)
|
s.handleHostsResponse(w, r, ip)
|
||||||
// 计算响应时间
|
// 缓存命中,响应时间设为0ms
|
||||||
responseTime := time.Since(startTime).Milliseconds()
|
responseTime := int64(0)
|
||||||
s.updateStats(func(stats *Stats) {
|
s.updateStats(func(stats *Stats) {
|
||||||
stats.TotalResponseTime += responseTime
|
stats.TotalResponseTime += responseTime
|
||||||
if stats.Queries > 0 {
|
if stats.Queries > 0 {
|
||||||
@@ -354,7 +370,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 添加查询日志
|
// 添加查询日志
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false)
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, false, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,16 +392,58 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 添加查询日志
|
// 添加查询日志
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false)
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查缓存中是否有响应(增强版缓存查询)
|
// 检查缓存中是否有响应(优先查找带DNSSEC的缓存项)
|
||||||
if cachedResponse, found := s.dnsCache.Get(r.Question[0].Name, qType); found {
|
var cachedResponse *dns.Msg
|
||||||
|
var found bool
|
||||||
|
var cachedDNSSEC bool
|
||||||
|
|
||||||
|
// 1. 首先检查是否有普通缓存项
|
||||||
|
if tempResponse, tempFound := s.dnsCache.Get(r.Question[0].Name, qType); tempFound {
|
||||||
|
cachedResponse = tempResponse
|
||||||
|
found = tempFound
|
||||||
|
cachedDNSSEC = s.hasDNSSECRecords(tempResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果启用了DNSSEC且没有找到带DNSSEC的缓存项,
|
||||||
|
// 尝试从所有缓存中查找是否有其他响应包含DNSSEC记录
|
||||||
|
// (这里可以进一步优化,比如在缓存中标记DNSSEC状态,快速查找)
|
||||||
|
if s.config.EnableDNSSEC && !cachedDNSSEC {
|
||||||
|
// 目前的缓存实现不支持按DNSSEC状态查找,所以这里暂时跳过
|
||||||
|
// 后续可以考虑改进缓存实现,添加DNSSEC状态标记
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
// 缓存命中,直接返回缓存的响应
|
// 缓存命中,直接返回缓存的响应
|
||||||
cachedResponseCopy := cachedResponse.Copy() // 创建响应副本避免并发修改问题
|
cachedResponseCopy := cachedResponse.Copy() // 创建响应副本避免并发修改问题
|
||||||
cachedResponseCopy.Id = r.Id // 更新ID以匹配请求
|
cachedResponseCopy.Id = r.Id // 更新ID以匹配请求
|
||||||
cachedResponseCopy.Compress = true
|
cachedResponseCopy.Compress = true
|
||||||
|
|
||||||
|
// 如果客户端请求包含EDNS记录,确保响应也包含EDNS
|
||||||
|
if opt := r.IsEdns0(); opt != nil {
|
||||||
|
// 检查响应是否已经包含EDNS记录
|
||||||
|
if respOpt := cachedResponseCopy.IsEdns0(); respOpt == nil {
|
||||||
|
// 添加EDNS记录,使用客户端的UDP缓冲区大小
|
||||||
|
cachedResponseCopy.SetEdns0(opt.UDPSize(), s.config.EnableDNSSEC)
|
||||||
|
} else {
|
||||||
|
// 确保响应的UDP缓冲区大小不超过客户端请求的大小
|
||||||
|
if respOpt.UDPSize() > opt.UDPSize() {
|
||||||
|
// 移除现有的EDNS记录
|
||||||
|
for i := range cachedResponseCopy.Extra {
|
||||||
|
if cachedResponseCopy.Extra[i] == respOpt {
|
||||||
|
cachedResponseCopy.Extra = append(cachedResponseCopy.Extra[:i], cachedResponseCopy.Extra[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 添加新的EDNS记录,使用客户端的UDP缓冲区大小
|
||||||
|
cachedResponseCopy.SetEdns0(opt.UDPSize(), s.config.EnableDNSSEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
w.WriteMsg(cachedResponseCopy)
|
w.WriteMsg(cachedResponseCopy)
|
||||||
|
|
||||||
// 计算响应时间
|
// 计算响应时间
|
||||||
@@ -397,21 +455,57 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 如果缓存响应包含DNSSEC记录,更新DNSSEC查询计数
|
||||||
|
if cachedDNSSEC {
|
||||||
|
s.updateStats(func(stats *Stats) {
|
||||||
|
stats.DNSSECQueries++
|
||||||
|
// 缓存响应视为DNSSEC成功
|
||||||
|
stats.DNSSECSuccess++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 添加查询日志 - 标记为缓存
|
// 添加查询日志 - 标记为缓存
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true)
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true)
|
||||||
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType)
|
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 缓存未命中,转发到上游DNS服务器
|
// 缓存未命中,转发到上游DNS服务器
|
||||||
response, _ := s.forwardDNSRequestWithCache(r, domain)
|
response, rtt := s.forwardDNSRequestWithCache(r, domain)
|
||||||
if response != nil {
|
if response != nil {
|
||||||
|
// 如果客户端请求包含EDNS记录,确保响应也包含EDNS
|
||||||
|
if opt := r.IsEdns0(); opt != nil {
|
||||||
|
// 检查响应是否已经包含EDNS记录
|
||||||
|
if respOpt := response.IsEdns0(); respOpt == nil {
|
||||||
|
// 添加EDNS记录,使用客户端的UDP缓冲区大小
|
||||||
|
response.SetEdns0(opt.UDPSize(), s.config.EnableDNSSEC)
|
||||||
|
} else {
|
||||||
|
// 确保响应的UDP缓冲区大小不超过客户端请求的大小
|
||||||
|
if respOpt.UDPSize() > opt.UDPSize() {
|
||||||
|
// 移除现有的EDNS记录
|
||||||
|
for i := range response.Extra {
|
||||||
|
if response.Extra[i] == respOpt {
|
||||||
|
response.Extra = append(response.Extra[:i], response.Extra[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 添加新的EDNS记录,使用客户端的UDP缓冲区大小
|
||||||
|
response.SetEdns0(opt.UDPSize(), s.config.EnableDNSSEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 写入响应给客户端
|
// 写入响应给客户端
|
||||||
w.WriteMsg(response)
|
w.WriteMsg(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算响应时间
|
// 使用上游服务器的实际响应时间(转换为毫秒)
|
||||||
responseTime := time.Since(startTime).Milliseconds()
|
responseTime := int64(rtt.Milliseconds())
|
||||||
|
// 如果rtt为0(查询失败),则使用本地计算的时间
|
||||||
|
if responseTime == 0 {
|
||||||
|
responseTime = time.Since(startTime).Milliseconds()
|
||||||
|
}
|
||||||
|
|
||||||
s.updateStats(func(stats *Stats) {
|
s.updateStats(func(stats *Stats) {
|
||||||
stats.TotalResponseTime += responseTime
|
stats.TotalResponseTime += responseTime
|
||||||
if stats.Queries > 0 {
|
if stats.Queries > 0 {
|
||||||
@@ -419,6 +513,23 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 检查响应是否包含DNSSEC记录并验证结果
|
||||||
|
responseDNSSEC := false
|
||||||
|
if response != nil {
|
||||||
|
// 使用hasDNSSECRecords函数检查是否包含DNSSEC记录
|
||||||
|
responseDNSSEC = s.hasDNSSECRecords(response)
|
||||||
|
|
||||||
|
// 检查AD标志,确认DNSSEC验证是否成功
|
||||||
|
if response.AuthenticatedData {
|
||||||
|
responseDNSSEC = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新域名的DNSSEC状态
|
||||||
|
if responseDNSSEC {
|
||||||
|
s.updateDomainDNSSECStatus(domain, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果响应成功,缓存结果(增强版缓存存储)
|
// 如果响应成功,缓存结果(增强版缓存存储)
|
||||||
if response != nil && response.Rcode == dns.RcodeSuccess {
|
if response != nil && response.Rcode == dns.RcodeSuccess {
|
||||||
// 创建响应副本以避免后续修改影响缓存
|
// 创建响应副本以避免后续修改影响缓存
|
||||||
@@ -426,11 +537,11 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
// 设置合理的TTL,不超过默认的30分钟
|
// 设置合理的TTL,不超过默认的30分钟
|
||||||
defaultCacheTTL := 30 * time.Minute
|
defaultCacheTTL := 30 * time.Minute
|
||||||
s.dnsCache.Set(r.Question[0].Name, qType, responseCopy, defaultCacheTTL)
|
s.dnsCache.Set(r.Question[0].Name, qType, responseCopy, defaultCacheTTL)
|
||||||
logger.Debug("DNS响应已缓存", "domain", domain, "type", queryType, "ttl", defaultCacheTTL)
|
logger.Debug("DNS响应已缓存", "domain", domain, "type", queryType, "ttl", defaultCacheTTL, "dnssec", responseDNSSEC)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加查询日志 - 标记为实时
|
// 添加查询日志 - 标记为实时
|
||||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false)
|
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleHostsResponse 处理hosts文件匹配的响应
|
// handleHostsResponse 处理hosts文件匹配的响应
|
||||||
@@ -537,22 +648,191 @@ func (s *Server) handleBlockedResponse(w dns.ResponseWriter, r *dns.Msg, domain
|
|||||||
// forwardDNSRequestWithCache 转发DNS请求到上游服务器并返回响应
|
// forwardDNSRequestWithCache 转发DNS请求到上游服务器并返回响应
|
||||||
func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg, time.Duration) {
|
func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg, time.Duration) {
|
||||||
// 尝试所有上游DNS服务器
|
// 尝试所有上游DNS服务器
|
||||||
|
var bestResponse *dns.Msg
|
||||||
|
var bestRtt time.Duration
|
||||||
|
var hasBestResponse bool
|
||||||
|
var hasDNSSECResponse bool
|
||||||
|
var backupResponse *dns.Msg
|
||||||
|
var backupRtt time.Duration
|
||||||
|
var hasBackup bool
|
||||||
|
|
||||||
|
// 始终支持EDNS
|
||||||
|
var udpSize uint16 = 4096
|
||||||
|
var doFlag bool = s.config.EnableDNSSEC
|
||||||
|
|
||||||
|
// 检查客户端请求是否包含EDNS记录
|
||||||
|
if opt := r.IsEdns0(); opt != nil {
|
||||||
|
// 保留客户端的UDP缓冲区大小
|
||||||
|
udpSize = opt.UDPSize()
|
||||||
|
// 移除现有的EDNS记录,以便重新添加
|
||||||
|
for i := range r.Extra {
|
||||||
|
if r.Extra[i] == opt {
|
||||||
|
r.Extra = append(r.Extra[:i], r.Extra[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加EDNS记录,设置适当的UDPSize和DO标志
|
||||||
|
r.SetEdns0(udpSize, doFlag)
|
||||||
|
|
||||||
|
// DNSSEC专用服务器列表,从配置中获取
|
||||||
|
dnssecServers := s.config.DNSSECUpstreamDNS
|
||||||
|
|
||||||
|
// 1. 首先尝试所有配置的上游DNS服务器
|
||||||
for _, upstream := range s.config.UpstreamDNS {
|
for _, upstream := range s.config.UpstreamDNS {
|
||||||
response, rtt, err := s.resolver.Exchange(r, upstream)
|
response, rtt, err := s.resolver.Exchange(r, upstream)
|
||||||
if err == nil && response != nil && response.Rcode == dns.RcodeSuccess {
|
if err == nil && response != nil {
|
||||||
// 设置递归可用标志
|
// 设置递归可用标志
|
||||||
response.RecursionAvailable = true
|
response.RecursionAvailable = true
|
||||||
|
|
||||||
logger.Debug("DNS查询成功", "domain", domain, "rtt", rtt, "server", upstream)
|
// 检查是否包含DNSSEC记录
|
||||||
|
containsDNSSEC := s.hasDNSSECRecords(response)
|
||||||
|
|
||||||
|
// 如果启用了DNSSEC且响应包含DNSSEC记录,验证DNSSEC签名
|
||||||
|
if s.config.EnableDNSSEC && containsDNSSEC {
|
||||||
|
// 验证DNSSEC记录
|
||||||
|
signatureValid := s.verifyDNSSEC(response)
|
||||||
|
|
||||||
|
// 设置AD标志(Authenticated Data)
|
||||||
|
response.AuthenticatedData = signatureValid
|
||||||
|
|
||||||
|
if signatureValid {
|
||||||
|
// 更新DNSSEC验证成功计数
|
||||||
|
s.updateStats(func(stats *Stats) {
|
||||||
|
stats.DNSSECQueries++
|
||||||
|
stats.DNSSECSuccess++
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 更新DNSSEC验证失败计数
|
||||||
|
s.updateStats(func(stats *Stats) {
|
||||||
|
stats.DNSSECQueries++
|
||||||
|
stats.DNSSECFailed++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果响应成功,根据DNSSEC状态选择最佳响应
|
||||||
|
if response.Rcode == dns.RcodeSuccess {
|
||||||
|
// 优先选择带有DNSSEC记录的响应
|
||||||
|
if containsDNSSEC {
|
||||||
|
bestResponse = response
|
||||||
|
bestRtt = rtt
|
||||||
|
hasBestResponse = true
|
||||||
|
hasDNSSECResponse = true
|
||||||
|
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", upstream, "rtt", rtt)
|
||||||
|
} else if !hasBestResponse {
|
||||||
|
// 没有带DNSSEC的响应时,保存第一个成功响应
|
||||||
|
bestResponse = response
|
||||||
|
bestRtt = rtt
|
||||||
|
hasBestResponse = true
|
||||||
|
logger.Debug("找到最佳响应", "domain", domain, "server", upstream, "rtt", rtt)
|
||||||
|
}
|
||||||
|
// 保存为备选响应
|
||||||
|
if !hasBackup {
|
||||||
|
backupResponse = response
|
||||||
|
backupRtt = rtt
|
||||||
|
hasBackup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 当启用DNSSEC且没有找到带DNSSEC的响应时,向DNSSEC专用服务器发送请求
|
||||||
|
if s.config.EnableDNSSEC && !hasDNSSECResponse {
|
||||||
|
logger.Debug("向DNSSEC专用服务器发送请求", "domain", domain)
|
||||||
|
|
||||||
|
// 增加DNSSEC查询计数
|
||||||
|
s.updateStats(func(stats *Stats) {
|
||||||
|
stats.DNSSECQueries++
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, dnssecServer := range dnssecServers {
|
||||||
|
response, rtt, err := s.resolver.Exchange(r, dnssecServer)
|
||||||
|
if err == nil && response != nil {
|
||||||
|
// 设置递归可用标志
|
||||||
|
response.RecursionAvailable = true
|
||||||
|
|
||||||
|
// 检查是否包含DNSSEC记录
|
||||||
|
containsDNSSEC := s.hasDNSSECRecords(response)
|
||||||
|
|
||||||
|
if response.Rcode == dns.RcodeSuccess {
|
||||||
|
// 验证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 {
|
||||||
|
// 即使之前有最佳响应,也优先使用DNSSEC专用服务器的DNSSEC响应
|
||||||
|
bestResponse = response
|
||||||
|
bestRtt = rtt
|
||||||
|
hasBestResponse = true
|
||||||
|
hasDNSSECResponse = true
|
||||||
|
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应,优先使用", "domain", domain, "server", dnssecServer, "rtt", rtt)
|
||||||
|
}
|
||||||
|
// 注意:如果DNSSEC专用服务器返回的响应不包含DNSSEC记录,
|
||||||
|
// 我们不会覆盖之前从upstreamDNS获取的响应,
|
||||||
|
// 这符合"本地解析指的是直接使用上游服务器upstreamDNS进行解析, 而不是dnssecUpstreamDNS"的要求
|
||||||
|
|
||||||
|
// 更新备选响应
|
||||||
|
if !hasBackup {
|
||||||
|
backupResponse = response
|
||||||
|
backupRtt = rtt
|
||||||
|
hasBackup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 返回最佳响应
|
||||||
|
if hasBestResponse {
|
||||||
|
// 检查最佳响应是否包含DNSSEC记录
|
||||||
|
bestHasDNSSEC := s.hasDNSSECRecords(bestResponse)
|
||||||
|
|
||||||
|
// 如果启用了DNSSEC且最佳响应不包含DNSSEC记录,使用upstreamDNS的解析结果
|
||||||
|
if s.config.EnableDNSSEC && !bestHasDNSSEC {
|
||||||
|
logger.Debug("最佳响应不包含DNSSEC记录,使用upstreamDNS的解析结果", "domain", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录解析域名统计
|
||||||
|
s.updateResolvedDomainStats(domain)
|
||||||
|
|
||||||
|
// 更新域名的DNSSEC状态
|
||||||
|
if bestHasDNSSEC {
|
||||||
|
s.updateDomainDNSSECStatus(domain, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.updateStats(func(stats *Stats) {
|
||||||
|
stats.Allowed++
|
||||||
|
})
|
||||||
|
return bestResponse, bestRtt
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有备选响应,返回该响应
|
||||||
|
if hasBackup {
|
||||||
|
logger.Debug("使用备选响应,没有找到更好的结果", "domain", domain)
|
||||||
// 记录解析域名统计
|
// 记录解析域名统计
|
||||||
s.updateResolvedDomainStats(domain)
|
s.updateResolvedDomainStats(domain)
|
||||||
|
|
||||||
s.updateStats(func(stats *Stats) {
|
s.updateStats(func(stats *Stats) {
|
||||||
stats.Allowed++
|
stats.Allowed++
|
||||||
})
|
})
|
||||||
return response, rtt
|
return backupResponse, backupRtt
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 所有上游服务器都失败,返回服务器失败错误
|
// 所有上游服务器都失败,返回服务器失败错误
|
||||||
@@ -630,6 +910,164 @@ func (s *Server) updateClientStats(ip string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasDNSSECRecords 检查响应是否包含DNSSEC记录
|
||||||
|
func (s *Server) hasDNSSECRecords(response *dns.Msg) bool {
|
||||||
|
// 检查响应中是否包含DNSSEC相关记录(DNSKEY、RRSIG、DS、NSEC、NSEC3等)
|
||||||
|
for _, rr := range response.Answer {
|
||||||
|
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.RRSIG); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.DS); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC3); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rr := range response.Ns {
|
||||||
|
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.RRSIG); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.DS); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC3); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rr := range response.Extra {
|
||||||
|
if _, ok := rr.(*dns.DNSKEY); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.RRSIG); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.DS); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rr.(*dns.NSEC3); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyDNSSEC 验证DNSSEC签名
|
||||||
|
func (s *Server) verifyDNSSEC(response *dns.Msg) bool {
|
||||||
|
// 提取DNSKEY和RRSIG记录
|
||||||
|
dnskeys := make(map[uint16]*dns.DNSKEY) // KeyTag -> DNSKEY
|
||||||
|
rrsigs := make([]*dns.RRSIG, 0)
|
||||||
|
|
||||||
|
// 从响应中提取所有DNSKEY和RRSIG记录
|
||||||
|
for _, rr := range response.Answer {
|
||||||
|
if dnskey, ok := rr.(*dns.DNSKEY); ok {
|
||||||
|
tag := dnskey.KeyTag()
|
||||||
|
dnskeys[tag] = dnskey
|
||||||
|
} else if rrsig, ok := rr.(*dns.RRSIG); ok {
|
||||||
|
rrsigs = append(rrsigs, rrsig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rr := range response.Ns {
|
||||||
|
if dnskey, ok := rr.(*dns.DNSKEY); ok {
|
||||||
|
tag := dnskey.KeyTag()
|
||||||
|
dnskeys[tag] = dnskey
|
||||||
|
} else if rrsig, ok := rr.(*dns.RRSIG); ok {
|
||||||
|
rrsigs = append(rrsigs, rrsig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rr := range response.Extra {
|
||||||
|
if dnskey, ok := rr.(*dns.DNSKEY); ok {
|
||||||
|
tag := dnskey.KeyTag()
|
||||||
|
dnskeys[tag] = dnskey
|
||||||
|
} else if rrsig, ok := rr.(*dns.RRSIG); ok {
|
||||||
|
rrsigs = append(rrsigs, rrsig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有RRSIG记录,验证失败
|
||||||
|
if len(rrsigs) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证所有RRSIG记录
|
||||||
|
signatureValid := true
|
||||||
|
for _, rrsig := range rrsigs {
|
||||||
|
// 查找对应的DNSKEY
|
||||||
|
dnskey, exists := dnskeys[rrsig.KeyTag]
|
||||||
|
if !exists {
|
||||||
|
logger.Warn("DNSSEC验证失败:找不到对应的DNSKEY", "key_tag", rrsig.KeyTag)
|
||||||
|
signatureValid = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集需要验证的记录集
|
||||||
|
rrset := make([]dns.RR, 0)
|
||||||
|
for _, rr := range response.Answer {
|
||||||
|
if rr.Header().Name == rrsig.Header().Name && rr.Header().Rrtype == rrsig.TypeCovered {
|
||||||
|
rrset = append(rrset, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rr := range response.Ns {
|
||||||
|
if rr.Header().Name == rrsig.Header().Name && rr.Header().Rrtype == rrsig.TypeCovered {
|
||||||
|
rrset = append(rrset, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证签名
|
||||||
|
if len(rrset) > 0 {
|
||||||
|
err := rrsig.Verify(dnskey, rrset)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("DNSSEC签名验证失败", "error", err, "key_tag", rrsig.KeyTag)
|
||||||
|
signatureValid = false
|
||||||
|
} else {
|
||||||
|
logger.Debug("DNSSEC签名验证成功", "key_tag", rrsig.KeyTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signatureValid
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateDomainDNSSECStatus 更新域名的DNSSEC状态
|
||||||
|
func (s *Server) updateDomainDNSSECStatus(domain string, dnssec bool) {
|
||||||
|
// 确保域名是小写
|
||||||
|
domain = strings.ToLower(domain)
|
||||||
|
|
||||||
|
// 更新域名的DNSSEC状态
|
||||||
|
s.resolvedDomainsMutex.Lock()
|
||||||
|
defer s.resolvedDomainsMutex.Unlock()
|
||||||
|
|
||||||
|
// 更新resolvedDomains中的DNSSEC状态
|
||||||
|
if entry, exists := s.resolvedDomains[domain]; exists {
|
||||||
|
entry.DNSSEC = dnssec
|
||||||
|
} else {
|
||||||
|
s.resolvedDomains[domain] = &BlockedDomain{
|
||||||
|
Domain: domain,
|
||||||
|
Count: 1,
|
||||||
|
LastSeen: time.Now(),
|
||||||
|
DNSSEC: dnssec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新domainDNSSECStatus映射
|
||||||
|
s.domainDNSSECStatus[domain] = dnssec
|
||||||
|
}
|
||||||
|
|
||||||
// updateResolvedDomainStats 更新解析域名统计
|
// updateResolvedDomainStats 更新解析域名统计
|
||||||
func (s *Server) updateResolvedDomainStats(domain string) {
|
func (s *Server) updateResolvedDomainStats(domain string) {
|
||||||
s.resolvedDomainsMutex.Lock()
|
s.resolvedDomainsMutex.Lock()
|
||||||
@@ -643,6 +1081,7 @@ func (s *Server) updateResolvedDomainStats(domain string) {
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
Count: 1,
|
Count: 1,
|
||||||
LastSeen: time.Now(),
|
LastSeen: time.Now(),
|
||||||
|
DNSSEC: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,7 +1094,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 bool) {
|
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool) {
|
||||||
// 获取IP地理位置
|
// 获取IP地理位置
|
||||||
location := s.getIpGeolocation(clientIP)
|
location := s.getIpGeolocation(clientIP)
|
||||||
|
|
||||||
@@ -671,6 +1110,8 @@ func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime in
|
|||||||
BlockRule: blockRule,
|
BlockRule: blockRule,
|
||||||
BlockType: blockType,
|
BlockType: blockType,
|
||||||
FromCache: fromCache,
|
FromCache: fromCache,
|
||||||
|
DNSSEC: dnssec,
|
||||||
|
EDNS: edns,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加到日志列表
|
// 添加到日志列表
|
||||||
@@ -720,6 +1161,10 @@ func (s *Server) GetStats() *Stats {
|
|||||||
QueryTypes: queryTypesCopy,
|
QueryTypes: queryTypesCopy,
|
||||||
SourceIPs: sourceIPsCopy,
|
SourceIPs: sourceIPsCopy,
|
||||||
CpuUsage: s.stats.CpuUsage,
|
CpuUsage: s.stats.CpuUsage,
|
||||||
|
DNSSECQueries: s.stats.DNSSECQueries,
|
||||||
|
DNSSECSuccess: s.stats.DNSSECSuccess,
|
||||||
|
DNSSECFailed: s.stats.DNSSECFailed,
|
||||||
|
DNSSECEnabled: s.stats.DNSSECEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1145,6 +1590,8 @@ func (s *Server) loadStatsData() {
|
|||||||
s.statsMutex.Lock()
|
s.statsMutex.Lock()
|
||||||
if statsData.Stats != nil {
|
if statsData.Stats != nil {
|
||||||
s.stats = statsData.Stats
|
s.stats = statsData.Stats
|
||||||
|
// 确保使用当前配置中的EnableDNSSEC值,覆盖从文件加载的值
|
||||||
|
s.stats.DNSSECEnabled = s.config.EnableDNSSEC
|
||||||
}
|
}
|
||||||
s.statsMutex.Unlock()
|
s.statsMutex.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -219,6 +219,12 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
// 格式化平均响应时间为两位小数
|
// 格式化平均响应时间为两位小数
|
||||||
formattedResponseTime := float64(int(dnsStats.AvgResponseTime*100)) / 100
|
formattedResponseTime := float64(int(dnsStats.AvgResponseTime*100)) / 100
|
||||||
|
|
||||||
|
// 计算DNSSEC使用率
|
||||||
|
dnssecUsage := float64(0)
|
||||||
|
if dnsStats.Queries > 0 {
|
||||||
|
dnssecUsage = float64(dnsStats.DNSSECQueries) / float64(dnsStats.Queries) * 100
|
||||||
|
}
|
||||||
|
|
||||||
// 构建响应数据,确保所有字段都反映服务器的真实状态
|
// 构建响应数据,确保所有字段都反映服务器的真实状态
|
||||||
stats := map[string]interface{}{
|
stats := map[string]interface{}{
|
||||||
"dns": map[string]interface{}{
|
"dns": map[string]interface{}{
|
||||||
@@ -232,12 +238,21 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
"QueryTypes": dnsStats.QueryTypes,
|
"QueryTypes": dnsStats.QueryTypes,
|
||||||
"SourceIPs": dnsStats.SourceIPs,
|
"SourceIPs": dnsStats.SourceIPs,
|
||||||
"CpuUsage": dnsStats.CpuUsage,
|
"CpuUsage": dnsStats.CpuUsage,
|
||||||
|
"DNSSECQueries": dnsStats.DNSSECQueries,
|
||||||
|
"DNSSECSuccess": dnsStats.DNSSECSuccess,
|
||||||
|
"DNSSECFailed": dnsStats.DNSSECFailed,
|
||||||
|
"DNSSECEnabled": dnsStats.DNSSECEnabled,
|
||||||
},
|
},
|
||||||
"shield": shieldStats,
|
"shield": shieldStats,
|
||||||
"topQueryType": topQueryType,
|
"topQueryType": topQueryType,
|
||||||
"activeIPs": activeIPCount,
|
"activeIPs": activeIPCount,
|
||||||
"avgResponseTime": formattedResponseTime,
|
"avgResponseTime": formattedResponseTime,
|
||||||
"cpuUsage": dnsStats.CpuUsage,
|
"cpuUsage": dnsStats.CpuUsage,
|
||||||
|
"dnssecEnabled": dnsStats.DNSSECEnabled,
|
||||||
|
"dnssecQueries": dnsStats.DNSSECQueries,
|
||||||
|
"dnssecSuccess": dnsStats.DNSSECSuccess,
|
||||||
|
"dnssecFailed": dnsStats.DNSSECFailed,
|
||||||
|
"dnssecUsage": float64(int(dnssecUsage*100)) / 100, // 保留两位小数
|
||||||
"time": time.Now(),
|
"time": time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,6 +370,12 @@ func (s *Server) buildStatsData() map[string]interface{} {
|
|||||||
// 格式化平均响应时间
|
// 格式化平均响应时间
|
||||||
formattedResponseTime := float64(int(dnsStats.AvgResponseTime*100)) / 100
|
formattedResponseTime := float64(int(dnsStats.AvgResponseTime*100)) / 100
|
||||||
|
|
||||||
|
// 计算DNSSEC使用率
|
||||||
|
dnssecUsage := float64(0)
|
||||||
|
if dnsStats.Queries > 0 {
|
||||||
|
dnssecUsage = float64(dnsStats.DNSSECQueries) / float64(dnsStats.Queries) * 100
|
||||||
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"dns": map[string]interface{}{
|
"dns": map[string]interface{}{
|
||||||
"Queries": dnsStats.Queries,
|
"Queries": dnsStats.Queries,
|
||||||
@@ -367,12 +388,21 @@ func (s *Server) buildStatsData() map[string]interface{} {
|
|||||||
"QueryTypes": dnsStats.QueryTypes,
|
"QueryTypes": dnsStats.QueryTypes,
|
||||||
"SourceIPs": dnsStats.SourceIPs,
|
"SourceIPs": dnsStats.SourceIPs,
|
||||||
"CpuUsage": dnsStats.CpuUsage,
|
"CpuUsage": dnsStats.CpuUsage,
|
||||||
|
"DNSSECQueries": dnsStats.DNSSECQueries,
|
||||||
|
"DNSSECSuccess": dnsStats.DNSSECSuccess,
|
||||||
|
"DNSSECFailed": dnsStats.DNSSECFailed,
|
||||||
|
"DNSSECEnabled": dnsStats.DNSSECEnabled,
|
||||||
},
|
},
|
||||||
"shield": shieldStats,
|
"shield": shieldStats,
|
||||||
"topQueryType": topQueryType,
|
"topQueryType": topQueryType,
|
||||||
"activeIPs": activeIPCount,
|
"activeIPs": activeIPCount,
|
||||||
"avgResponseTime": formattedResponseTime,
|
"avgResponseTime": formattedResponseTime,
|
||||||
"cpuUsage": dnsStats.CpuUsage,
|
"cpuUsage": dnsStats.CpuUsage,
|
||||||
|
"dnssecEnabled": dnsStats.DNSSECEnabled,
|
||||||
|
"dnssecQueries": dnsStats.DNSSECQueries,
|
||||||
|
"dnssecSuccess": dnsStats.DNSSECSuccess,
|
||||||
|
"dnssecFailed": dnsStats.DNSSECFailed,
|
||||||
|
"dnssecUsage": float64(int(dnssecUsage*100)) / 100, // 保留两位小数
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,19 +739,25 @@ func (s *Server) handleTopDomains(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// 合并并去重域名统计
|
// 合并并去重域名统计
|
||||||
domainMap := make(map[string]int64)
|
domainMap := make(map[string]int64)
|
||||||
|
dnssecStatusMap := make(map[string]bool)
|
||||||
|
|
||||||
for _, domain := range blockedDomains {
|
for _, domain := range blockedDomains {
|
||||||
domainMap[domain.Domain] += domain.Count
|
domainMap[domain.Domain] += domain.Count
|
||||||
|
dnssecStatusMap[domain.Domain] = domain.DNSSEC
|
||||||
}
|
}
|
||||||
for _, domain := range resolvedDomains {
|
for _, domain := range resolvedDomains {
|
||||||
domainMap[domain.Domain] += domain.Count
|
domainMap[domain.Domain] += domain.Count
|
||||||
|
dnssecStatusMap[domain.Domain] = domain.DNSSEC
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为切片并排序
|
// 转换为切片并排序
|
||||||
domainList := make([]map[string]interface{}, 0, len(domainMap))
|
domainList := make([]map[string]interface{}, 0, len(domainMap))
|
||||||
for domain, count := range domainMap {
|
for domain, count := range domainMap {
|
||||||
|
dnssec, hasDNSSEC := dnssecStatusMap[domain]
|
||||||
domainList = append(domainList, map[string]interface{}{
|
domainList = append(domainList, map[string]interface{}{
|
||||||
"domain": domain,
|
"domain": domain,
|
||||||
"count": count,
|
"count": count,
|
||||||
|
"dnssec": hasDNSSEC && dnssec,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8670
server.log
Normal file
8670
server.log
Normal file
File diff suppressed because it is too large
Load Diff
1
server.pid
Normal file
1
server.pid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
132708
|
||||||
176
start-dns-server.sh
Executable file
176
start-dns-server.sh
Executable file
@@ -0,0 +1,176 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 启动/停止/重启脚本
|
||||||
|
|
||||||
|
# ===================== 配置区 =====================
|
||||||
|
# 程序路径
|
||||||
|
AGENT_PATH="./dns-server"
|
||||||
|
# 日志文件路径
|
||||||
|
LOG_FILE="./server.log"
|
||||||
|
# PID文件路径(记录进程ID)
|
||||||
|
PID_FILE="./server.pid"
|
||||||
|
# 启动参数(根据实际需求调整)
|
||||||
|
START_ARGS=""
|
||||||
|
# 工作目录
|
||||||
|
WORK_DIR="."
|
||||||
|
# ==================== 配置区结束 ====================
|
||||||
|
|
||||||
|
# 检查程序文件是否存在
|
||||||
|
check_agent_exists() {
|
||||||
|
if [ ! -f "${AGENT_PATH}" ]; then
|
||||||
|
echo "错误:程序文件 ${AGENT_PATH} 不存在!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -x "${AGENT_PATH}" ]; then
|
||||||
|
echo "错误:程序文件 ${AGENT_PATH} 没有执行权限,正在尝试添加..."
|
||||||
|
chmod +x "${AGENT_PATH}"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "错误:添加执行权限失败,请手动执行 chmod +x ${AGENT_PATH}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查进程是否运行
|
||||||
|
check_running() {
|
||||||
|
if [ -f "${PID_FILE}" ]; then
|
||||||
|
PID=$(cat "${PID_FILE}")
|
||||||
|
if ps -p "${PID}" > /dev/null 2>&1; then
|
||||||
|
return 0 # 运行中
|
||||||
|
else
|
||||||
|
rm -f "${PID_FILE}" # PID文件存在但进程已死,清理PID文件
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1 # 未运行
|
||||||
|
}
|
||||||
|
|
||||||
|
# 启动程序
|
||||||
|
start_agent() {
|
||||||
|
if check_running; then
|
||||||
|
echo "✅ dns-server 已在运行(PID: $(cat ${PID_FILE}))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🚀 正在启动 dns-server(工作目录:${WORK_DIR})..."
|
||||||
|
|
||||||
|
# 新增:检查并切换工作目录
|
||||||
|
if [ ! -d "${WORK_DIR}" ]; then
|
||||||
|
echo "⚠️ 工作目录 ${WORK_DIR} 不存在,正在创建..."
|
||||||
|
mkdir -p "${WORK_DIR}"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ 创建工作目录 ${WORK_DIR} 失败!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 切换到工作目录(关键:程序将在此目录下运行)
|
||||||
|
cd "${WORK_DIR}" || {
|
||||||
|
echo "❌ 切换到工作目录 ${WORK_DIR} 失败!"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建日志目录
|
||||||
|
mkdir -p "$(dirname ${LOG_FILE})"
|
||||||
|
# 后台启动程序(注意:cd仅影响当前子进程,需在同一行执行)
|
||||||
|
nohup "${AGENT_PATH}" ${START_ARGS} > "${LOG_FILE}" 2>&1 &
|
||||||
|
AGENT_PID=$!
|
||||||
|
echo "${AGENT_PID}" > "${PID_FILE}"
|
||||||
|
|
||||||
|
# 等待检查启动状态
|
||||||
|
sleep 2
|
||||||
|
if check_running; then
|
||||||
|
echo "✅ dns-server 启动成功(PID: ${AGENT_PID},工作目录:${WORK_DIR})"
|
||||||
|
echo "日志文件:${LOG_FILE}"
|
||||||
|
else
|
||||||
|
echo "❌ dns-server 启动失败!请查看日志:${LOG_FILE}"
|
||||||
|
rm -f "${PID_FILE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
# 停止程序
|
||||||
|
stop_agent() {
|
||||||
|
if ! check_running; then
|
||||||
|
echo "ℹ️ dns-server 未运行"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
PID=$(cat "${PID_FILE}")
|
||||||
|
echo "🛑 正在停止 dns-server(PID: ${PID})..."
|
||||||
|
# 优雅停止(先尝试TERM信号,失败则强制KILL)
|
||||||
|
kill "${PID}" > /dev/null 2>&1
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
if ps -p "${PID}" > /dev/null 2>&1; then
|
||||||
|
echo "⚠️ 优雅停止失败,强制杀死进程..."
|
||||||
|
kill -9 "${PID}" > /dev/null 2>&1
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理PID文件
|
||||||
|
rm -f "${PID_FILE}"
|
||||||
|
echo "✅ dns-server 已停止"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 查看状态
|
||||||
|
status_agent() {
|
||||||
|
if check_running; then
|
||||||
|
echo "✅ dns-server 运行中(PID: $(cat ${PID_FILE}))"
|
||||||
|
else
|
||||||
|
echo "ℹ️ dns-server 未运行"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 重启程序
|
||||||
|
restart_agent() {
|
||||||
|
echo "🔄 正在重启 dns-server..."
|
||||||
|
stop_agent
|
||||||
|
sleep 2
|
||||||
|
start_agent
|
||||||
|
}
|
||||||
|
|
||||||
|
# 帮助信息
|
||||||
|
show_help() {
|
||||||
|
echo "使用方法:$0 [start|stop|restart|status|help]"
|
||||||
|
echo " start - 启动 dns-server"
|
||||||
|
echo " stop - 停止 dns-server"
|
||||||
|
echo " restart - 重启 dns-server"
|
||||||
|
echo " status - 查看 dns-server 运行状态"
|
||||||
|
echo " help - 显示帮助信息"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主逻辑
|
||||||
|
main() {
|
||||||
|
# 检查是否为root用户(可选,根据需求调整)
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "警告:建议使用root用户运行此脚本(当前用户:$(whoami))"
|
||||||
|
# exit 1 # 如果强制要求root,取消注释
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_agent_exists
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
start_agent
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop_agent
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
restart_agent
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
status_agent
|
||||||
|
;;
|
||||||
|
help|--help|-h)
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "错误:无效参数 '$1'"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# 执行主逻辑
|
||||||
|
main "$@"
|
||||||
|
|
||||||
@@ -33,6 +33,203 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/login": {
|
||||||
|
"post": {
|
||||||
|
"summary": "用户登录",
|
||||||
|
"description": "使用用户名和密码登录DNS服务器API。",
|
||||||
|
"tags": ["auth"],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"username": {"type": "string", "description": "用户名"},
|
||||||
|
"password": {"type": "string", "description": "密码"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "登录成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {"type": "string", "description": "操作状态"},
|
||||||
|
"message": {"type": "string", "description": "操作信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"status": "success",
|
||||||
|
"message": "登录成功"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "请求参数错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"error": "无效的请求体"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "用户名或密码错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"error": "用户名或密码错误"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/logout": {
|
||||||
|
"post": {
|
||||||
|
"summary": "用户注销",
|
||||||
|
"description": "注销当前登录的用户会话。",
|
||||||
|
"tags": ["auth"],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "注销成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {"type": "string", "description": "操作状态"},
|
||||||
|
"message": {"type": "string", "description": "操作信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"status": "success",
|
||||||
|
"message": "注销成功"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/change-password": {
|
||||||
|
"post": {
|
||||||
|
"summary": "修改密码",
|
||||||
|
"description": "修改当前登录用户的密码。",
|
||||||
|
"tags": ["auth"],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"currentPassword": {"type": "string", "description": "当前密码"},
|
||||||
|
"newPassword": {"type": "string", "description": "新密码"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"currentPassword": "admin",
|
||||||
|
"newPassword": "newpassword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "密码修改成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {"type": "string", "description": "操作状态"},
|
||||||
|
"message": {"type": "string", "description": "操作信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"status": "success",
|
||||||
|
"message": "密码修改成功"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "请求参数错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"error": "无效的请求体"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "当前密码错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"error": "当前密码错误"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "服务器内部错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"error": "保存密码失败"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/stats": {
|
"/stats": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "获取系统统计信息",
|
"summary": "获取系统统计信息",
|
||||||
@@ -58,7 +255,11 @@
|
|||||||
"TotalResponseTime": {"type": "number", "description": "总响应时间(毫秒)"},
|
"TotalResponseTime": {"type": "number", "description": "总响应时间(毫秒)"},
|
||||||
"QueryTypes": {"type": "object", "description": "查询类型统计"},
|
"QueryTypes": {"type": "object", "description": "查询类型统计"},
|
||||||
"SourceIPs": {"type": "object", "description": "来源IP统计"},
|
"SourceIPs": {"type": "object", "description": "来源IP统计"},
|
||||||
"CpuUsage": {"type": "number", "description": "CPU使用率(百分比)"}
|
"CpuUsage": {"type": "number", "description": "CPU使用率(百分比)"},
|
||||||
|
"DNSSECQueries": {"type": "integer", "description": "DNSSEC查询次数"},
|
||||||
|
"DNSSECSuccess": {"type": "integer", "description": "DNSSEC成功次数"},
|
||||||
|
"DNSSECFailed": {"type": "integer", "description": "DNSSEC失败次数"},
|
||||||
|
"DNSSECEnabled": {"type": "boolean", "description": "是否启用DNSSEC"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shield": {"type": "object", "description": "Shield统计信息"},
|
"shield": {"type": "object", "description": "Shield统计信息"},
|
||||||
@@ -66,6 +267,11 @@
|
|||||||
"activeIPs": {"type": "integer", "description": "活跃IP数量"},
|
"activeIPs": {"type": "integer", "description": "活跃IP数量"},
|
||||||
"avgResponseTime": {"type": "number", "description": "平均响应时间(毫秒)"},
|
"avgResponseTime": {"type": "number", "description": "平均响应时间(毫秒)"},
|
||||||
"cpuUsage": {"type": "number", "description": "CPU使用率(百分比)"},
|
"cpuUsage": {"type": "number", "description": "CPU使用率(百分比)"},
|
||||||
|
"dnssecEnabled": {"type": "boolean", "description": "是否启用DNSSEC"},
|
||||||
|
"dnssecQueries": {"type": "integer", "description": "DNSSEC查询次数"},
|
||||||
|
"dnssecSuccess": {"type": "integer", "description": "DNSSEC成功次数"},
|
||||||
|
"dnssecFailed": {"type": "integer", "description": "DNSSEC失败次数"},
|
||||||
|
"dnssecUsage": {"type": "number", "description": "DNSSEC使用率(百分比)"},
|
||||||
"time": {"type": "string", "description": "统计时间"}
|
"time": {"type": "string", "description": "统计时间"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -82,13 +288,22 @@
|
|||||||
"TotalResponseTime": 15625,
|
"TotalResponseTime": 15625,
|
||||||
"QueryTypes": {"A": 850, "AAAA": 250, "CNAME": 150},
|
"QueryTypes": {"A": 850, "AAAA": 250, "CNAME": 150},
|
||||||
"SourceIPs": {"192.168.1.100": 500, "192.168.1.101": 750},
|
"SourceIPs": {"192.168.1.100": 500, "192.168.1.101": 750},
|
||||||
"CpuUsage": 0.15
|
"CpuUsage": 0.15,
|
||||||
|
"DNSSECQueries": 500,
|
||||||
|
"DNSSECSuccess": 480,
|
||||||
|
"DNSSECFailed": 20,
|
||||||
|
"DNSSECEnabled": true
|
||||||
},
|
},
|
||||||
"shield": {},
|
"shield": {},
|
||||||
"topQueryType": "A",
|
"topQueryType": "A",
|
||||||
"activeIPs": 2,
|
"activeIPs": 2,
|
||||||
"avgResponseTime": 12.5,
|
"avgResponseTime": 12.5,
|
||||||
"cpuUsage": 0.15,
|
"cpuUsage": 0.15,
|
||||||
|
"dnssecEnabled": true,
|
||||||
|
"dnssecQueries": 500,
|
||||||
|
"dnssecSuccess": 480,
|
||||||
|
"dnssecFailed": 20,
|
||||||
|
"dnssecUsage": 40.0,
|
||||||
"time": "2023-07-15T14:30:45Z"
|
"time": "2023-07-15T14:30:45Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,20 +489,16 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"hour": {"type": "string", "description": "小时"},
|
"labels": {"type": "array", "items": {"type": "string"}, "description": "小时标签"},
|
||||||
"queries": {"type": "integer", "description": "查询次数"},
|
"data": {"type": "array", "items": {"type": "integer"}, "description": "查询次数数据"}
|
||||||
"blocked": {"type": "integer", "description": "被阻止次数"}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": [
|
"example": {
|
||||||
{"hour": "00", "queries": 120, "blocked": 20},
|
"labels": ["15:00", "16:00", "17:00"],
|
||||||
{"hour": "01", "queries": 90, "blocked": 15}
|
"data": [120, 90, 150]
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,20 +516,16 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"date": {"type": "string", "description": "日期"},
|
"labels": {"type": "array", "items": {"type": "string"}, "description": "日期标签"},
|
||||||
"queries": {"type": "integer", "description": "查询次数"},
|
"data": {"type": "array", "items": {"type": "integer"}, "description": "查询次数数据"}
|
||||||
"blocked": {"type": "integer", "description": "被阻止次数"}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": [
|
"example": {
|
||||||
{"date": "2023-07-09", "queries": 2500, "blocked": 450},
|
"labels": ["01-02", "01-03", "01-04"],
|
||||||
{"date": "2023-07-10", "queries": 2700, "blocked": 480}
|
"data": [2500, 2700, 2300]
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,20 +543,16 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"date": {"type": "string", "description": "日期"},
|
"labels": {"type": "array", "items": {"type": "string"}, "description": "日期标签"},
|
||||||
"queries": {"type": "integer", "description": "查询次数"},
|
"data": {"type": "array", "items": {"type": "integer"}, "description": "查询次数数据"}
|
||||||
"blocked": {"type": "integer", "description": "被阻止次数"}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": [
|
"example": {
|
||||||
{"date": "2023-06-15", "queries": 2500, "blocked": 450},
|
"labels": ["01-01", "01-02", "01-03"],
|
||||||
{"date": "2023-06-16", "queries": 2700, "blocked": 480}
|
"data": [2500, 2700, 2300]
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,30 +590,157 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/shield": {
|
"/logs/stats": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "获取Shield配置",
|
"summary": "获取日志统计信息",
|
||||||
"description": "获取Shield的完整配置信息,包括启用状态、规则等。",
|
"description": "获取DNS查询日志的统计信息。",
|
||||||
"tags": ["shield"],
|
"tags": ["logs"],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "成功获取配置信息",
|
"description": "成功获取日志统计信息",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "日志统计信息"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/logs/query": {
|
||||||
|
"get": {
|
||||||
|
"summary": "查询日志",
|
||||||
|
"description": "根据条件查询DNS查询日志。",
|
||||||
|
"tags": ["logs"],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 100
|
||||||
|
},
|
||||||
|
"description": "返回结果数量限制"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "offset",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"description": "结果偏移量"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "排序字段"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "direction",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "排序方向"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "result",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "查询结果过滤"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "search",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "搜索关键词"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功查询日志",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "日志条目"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/logs/count": {
|
||||||
|
"get": {
|
||||||
|
"summary": "获取日志总数",
|
||||||
|
"description": "获取DNS查询日志的总数。",
|
||||||
|
"tags": ["logs"],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功获取日志总数",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {"type": "boolean", "description": "是否启用Shield"},
|
"count": {"type": "integer", "description": "日志总数"}
|
||||||
"rulesCount": {"type": "integer", "description": "规则数量"},
|
|
||||||
"lastUpdate": {"type": "string", "description": "最后更新时间"},
|
|
||||||
"blacklists": {"type": "array", "description": "黑名单列表", "items": {"type": "object"}}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {
|
"example": {
|
||||||
"enabled": true,
|
"count": 1000
|
||||||
"rulesCount": 5000,
|
}
|
||||||
"lastUpdate": "2023-07-15T10:00:00Z",
|
}
|
||||||
"blacklists": []
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/shield": {
|
||||||
|
"get": {
|
||||||
|
"summary": "获取Shield配置和统计信息",
|
||||||
|
"description": "获取Shield的配置信息和规则统计,包括更新间隔、屏蔽方法、黑名单数量等。",
|
||||||
|
"tags": ["shield"],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功获取配置和统计信息",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"updateInterval": {"type": "integer", "description": "更新间隔(秒)"},
|
||||||
|
"blockMethod": {"type": "string", "description": "屏蔽方法"},
|
||||||
|
"blacklistCount": {"type": "integer", "description": "黑名单数量"},
|
||||||
|
"domainRulesCount": {"type": "integer", "description": "域名规则数量"},
|
||||||
|
"domainExceptionsCount": {"type": "integer", "description": "域名例外规则数量"},
|
||||||
|
"regexRulesCount": {"type": "integer", "description": "正则规则数量"},
|
||||||
|
"regexExceptionsCount": {"type": "integer", "description": "正则例外规则数量"},
|
||||||
|
"hostsRulesCount": {"type": "integer", "description": "Hosts规则数量"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"updateInterval": 3600,
|
||||||
|
"blockMethod": "NXDOMAIN",
|
||||||
|
"blacklistCount": 4,
|
||||||
|
"domainRulesCount": 1000,
|
||||||
|
"domainExceptionsCount": 100,
|
||||||
|
"regexRulesCount": 50,
|
||||||
|
"regexExceptionsCount": 10,
|
||||||
|
"hostsRulesCount": 200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,8 +762,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "更新Shield配置",
|
"summary": "添加屏蔽规则",
|
||||||
"description": "更新Shield的全局配置信息。",
|
"description": "添加新的屏蔽规则到Shield。",
|
||||||
"tags": ["shield"],
|
"tags": ["shield"],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -442,20 +772,18 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {"type": "boolean", "description": "是否启用Shield"},
|
"rule": {"type": "string", "description": "屏蔽规则"}
|
||||||
"updateInterval": {"type": "integer", "description": "更新间隔(秒)"}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {
|
"example": {
|
||||||
"enabled": true,
|
"rule": "example.com"
|
||||||
"updateInterval": 3600
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "成功更新配置",
|
"description": "成功添加规则",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@@ -492,19 +820,35 @@
|
|||||||
"error": {"type": "string", "description": "错误信息"}
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {"error": "更新配置失败"}
|
"example": {"error": "添加规则失败"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"put": {
|
"delete": {
|
||||||
"summary": "重启Shield",
|
"summary": "删除屏蔽规则",
|
||||||
"description": "重新加载和应用Shield规则。",
|
"description": "从Shield中删除指定的屏蔽规则。",
|
||||||
"tags": ["shield"],
|
"tags": ["shield"],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"rule": {"type": "string", "description": "要删除的屏蔽规则"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"rule": "example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "成功重启Shield",
|
"description": "成功删除规则",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@@ -517,6 +861,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "请求参数错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {"error": "参数格式错误"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "服务器内部错误",
|
"description": "服务器内部错误",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -527,7 +885,46 @@
|
|||||||
"error": {"type": "string", "description": "错误信息"}
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {"error": "重启失败"}
|
"example": {"error": "删除规则失败"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"summary": "重新加载规则",
|
||||||
|
"description": "重新加载和应用Shield规则。",
|
||||||
|
"tags": ["shield"],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功重新加载规则",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {"type": "string", "description": "操作状态"},
|
||||||
|
"message": {"type": "string", "description": "操作信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"status": "success",
|
||||||
|
"message": "规则重新加载成功"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "服务器内部错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {"error": "重新加载规则失败"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1139,7 +1536,7 @@
|
|||||||
"/status": {
|
"/status": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "获取服务器状态",
|
"summary": "获取服务器状态",
|
||||||
"description": "获取DNS服务器的状态信息。",
|
"description": "获取DNS服务器的状态信息,包括查询统计、运行时间等。",
|
||||||
"tags": ["server"],
|
"tags": ["server"],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
@@ -1150,14 +1547,32 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": {"type": "string", "description": "服务器状态"},
|
"status": {"type": "string", "description": "服务器状态"},
|
||||||
"uptime": {"type": "integer", "description": "运行时间(秒)"},
|
"queries": {"type": "integer", "description": "总查询次数"},
|
||||||
"version": {"type": "string", "description": "服务器版本"}
|
"blocked": {"type": "integer", "description": "被阻止的查询次数"},
|
||||||
|
"allowed": {"type": "integer", "description": "允许的查询次数"},
|
||||||
|
"errors": {"type": "integer", "description": "错误查询次数"},
|
||||||
|
"lastQuery": {"type": "string", "description": "最近一次查询时间"},
|
||||||
|
"avgResponseTime": {"type": "number", "description": "平均响应时间(毫秒)"},
|
||||||
|
"activeIPs": {"type": "integer", "description": "活跃IP数量"},
|
||||||
|
"startTime": {"type": "string", "description": "服务器启动时间"},
|
||||||
|
"uptime": {"type": "integer", "description": "运行时间(毫秒)"},
|
||||||
|
"cpuUsage": {"type": "number", "description": "CPU使用率(百分比)"},
|
||||||
|
"timestamp": {"type": "string", "description": "当前时间"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {
|
"example": {
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"uptime": 3600,
|
"queries": 1250,
|
||||||
"version": "1.0.0"
|
"blocked": 230,
|
||||||
|
"allowed": 1020,
|
||||||
|
"errors": 0,
|
||||||
|
"lastQuery": "2023-07-15T14:30:45Z",
|
||||||
|
"avgResponseTime": 12.5,
|
||||||
|
"activeIPs": 2,
|
||||||
|
"startTime": "2023-07-15T10:00:00Z",
|
||||||
|
"uptime": 16200000,
|
||||||
|
"cpuUsage": 0.15,
|
||||||
|
"timestamp": "2023-07-15T14:30:45Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1168,7 +1583,7 @@
|
|||||||
"/config": {
|
"/config": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "获取服务器配置",
|
"summary": "获取服务器配置",
|
||||||
"description": "获取DNS服务器的配置信息。",
|
"description": "获取DNS服务器的配置信息,包括Shield配置。",
|
||||||
"tags": ["server"],
|
"tags": ["server"],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
@@ -1178,13 +1593,30 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"port": {"type": "integer", "description": "服务器端口"},
|
"shield": {
|
||||||
"logLevel": {"type": "string", "description": "日志级别"}
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"blockMethod": {"type": "string", "description": "屏蔽方法"},
|
||||||
|
"customBlockIP": {"type": "string", "description": "自定义屏蔽IP"},
|
||||||
|
"blacklists": {"type": "array", "description": "黑名单列表", "items": {"type": "object"}},
|
||||||
|
"updateInterval": {"type": "integer", "description": "更新间隔(秒)"}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {
|
"example": {
|
||||||
"port": 53,
|
"shield": {
|
||||||
"logLevel": "info"
|
"blockMethod": "NXDOMAIN",
|
||||||
|
"customBlockIP": "",
|
||||||
|
"blacklists": [
|
||||||
|
{
|
||||||
|
"name": "AdGuard DNS filter",
|
||||||
|
"url": "https://example.com/ads.txt",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updateInterval": 3600
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1193,7 +1625,7 @@
|
|||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "更新服务器配置",
|
"summary": "更新服务器配置",
|
||||||
"description": "更新DNS服务器的配置信息。",
|
"description": "更新DNS服务器的配置信息,包括Shield配置。",
|
||||||
"tags": ["server"],
|
"tags": ["server"],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -1202,13 +1634,30 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"port": {"type": "integer", "description": "服务器端口"},
|
"shield": {
|
||||||
"logLevel": {"type": "string", "description": "日志级别"}
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"blockMethod": {"type": "string", "description": "屏蔽方法"},
|
||||||
|
"customBlockIP": {"type": "string", "description": "自定义屏蔽IP"},
|
||||||
|
"blacklists": {"type": "array", "description": "黑名单列表", "items": {"type": "object"}},
|
||||||
|
"updateInterval": {"type": "integer", "description": "更新间隔(秒)"}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {
|
"example": {
|
||||||
"port": 53,
|
"shield": {
|
||||||
"logLevel": "info"
|
"blockMethod": "NXDOMAIN",
|
||||||
|
"customBlockIP": "",
|
||||||
|
"blacklists": [
|
||||||
|
{
|
||||||
|
"name": "AdGuard DNS filter",
|
||||||
|
"url": "https://example.com/ads.txt",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updateInterval": 3600
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1221,10 +1670,14 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": {"type": "string", "description": "操作状态"}
|
"success": {"type": "boolean", "description": "是否成功"},
|
||||||
|
"message": {"type": "string", "description": "操作信息"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {"status": "success"}
|
"example": {
|
||||||
|
"success": true,
|
||||||
|
"message": "配置已更新"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1238,7 +1691,9 @@
|
|||||||
"error": {"type": "string", "description": "错误信息"}
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {"error": "参数格式错误"}
|
"example": {
|
||||||
|
"error": "无效的请求体"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1252,7 +1707,9 @@
|
|||||||
"error": {"type": "string", "description": "错误信息"}
|
"error": {"type": "string", "description": "错误信息"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {"error": "更新配置失败"}
|
"example": {
|
||||||
|
"error": "保存配置失败"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1298,6 +1755,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "auth",
|
||||||
|
"description": "认证相关API"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "stats",
|
"name": "stats",
|
||||||
"description": "统计相关API"
|
"description": "统计相关API"
|
||||||
@@ -1309,6 +1770,10 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"description": "服务器相关API"
|
"description": "服务器相关API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "logs",
|
||||||
|
"description": "日志相关API"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -334,6 +334,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- DNSSEC使用率卡片 -->
|
||||||
|
<div class="bg-white rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||||
|
<!-- 颜色蒙版 -->
|
||||||
|
<div class="absolute -bottom-8 -right-8 w-24 h-24 rounded-full bg-primary opacity-10"></div>
|
||||||
|
<div class="relative z-10">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h3 class="text-gray-500 font-medium">DNSSEC使用率</h3>
|
||||||
|
<div class="p-2 rounded-full bg-primary/10 text-primary">
|
||||||
|
<i class="fa fa-lock"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<div class="flex items-end justify-between">
|
||||||
|
<p class="text-3xl font-bold" id="dnssec-usage">0%</p>
|
||||||
|
<span class="text-primary text-sm flex items-center">
|
||||||
|
<i class="fa fa-check mr-1"></i>
|
||||||
|
<span id="dnssec-status">已禁用</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4 text-xs text-gray-500">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="w-2 h-2 bg-green-500 rounded-full mr-1"></span>
|
||||||
|
<span>成功: <span id="dnssec-success">0</span></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="w-2 h-2 bg-red-500 rounded-full mr-1"></span>
|
||||||
|
<span>失败: <span id="dnssec-failed">0</span></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="w-2 h-2 bg-blue-500 rounded-full mr-1"></span>
|
||||||
|
<span>总查询: <span id="dnssec-queries">0</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ let dashboardWsReconnectTimer = null;
|
|||||||
let statCardCharts = {};
|
let statCardCharts = {};
|
||||||
// 存储统计卡片历史数据
|
// 存储统计卡片历史数据
|
||||||
let statCardHistoryData = {};
|
let statCardHistoryData = {};
|
||||||
|
// 存储仪表盘历史数据,用于计算趋势
|
||||||
|
window.dashboardHistoryData = window.dashboardHistoryData || {
|
||||||
|
prevResponseTime: null,
|
||||||
|
prevActiveIPs: null,
|
||||||
|
prevTopQueryTypeCount: null
|
||||||
|
};
|
||||||
|
|
||||||
// 引入颜色配置文件
|
// 引入颜色配置文件
|
||||||
const COLOR_CONFIG = window.COLOR_CONFIG || {};
|
const COLOR_CONFIG = window.COLOR_CONFIG || {};
|
||||||
@@ -192,14 +198,42 @@ function processRealTimeData(stats) {
|
|||||||
|
|
||||||
if (document.getElementById('top-query-type')) {
|
if (document.getElementById('top-query-type')) {
|
||||||
const queryType = stats.topQueryType || '---';
|
const queryType = stats.topQueryType || '---';
|
||||||
|
document.getElementById('top-query-type').textContent = queryType;
|
||||||
|
|
||||||
const queryPercentElem = document.getElementById('query-type-percentage');
|
const queryPercentElem = document.getElementById('query-type-percentage');
|
||||||
if (queryPercentElem) {
|
if (queryPercentElem) {
|
||||||
queryPercentElem.textContent = '• ---';
|
// 计算查询类型趋势
|
||||||
queryPercentElem.className = 'text-sm flex items-center text-gray-500';
|
let queryPercent = '---';
|
||||||
|
let trendClass = 'text-gray-400';
|
||||||
|
let trendIcon = '---';
|
||||||
|
|
||||||
|
if (stats.topQueryTypeCount !== undefined && stats.topQueryTypeCount !== null) {
|
||||||
|
// 存储当前值用于下次计算趋势
|
||||||
|
const prevTopQueryTypeCount = window.dashboardHistoryData.prevTopQueryTypeCount || stats.topQueryTypeCount;
|
||||||
|
window.dashboardHistoryData.prevTopQueryTypeCount = stats.topQueryTypeCount;
|
||||||
|
|
||||||
|
// 计算变化百分比
|
||||||
|
if (prevTopQueryTypeCount > 0) {
|
||||||
|
const changePercent = ((stats.topQueryTypeCount - prevTopQueryTypeCount) / prevTopQueryTypeCount) * 100;
|
||||||
|
queryPercent = Math.abs(changePercent).toFixed(1) + '%';
|
||||||
|
|
||||||
|
// 设置趋势图标和颜色
|
||||||
|
if (changePercent > 0) {
|
||||||
|
trendIcon = '↑';
|
||||||
|
trendClass = 'text-primary';
|
||||||
|
} else if (changePercent < 0) {
|
||||||
|
trendIcon = '↓';
|
||||||
|
trendClass = 'text-secondary';
|
||||||
|
} else {
|
||||||
|
trendIcon = '•';
|
||||||
|
trendClass = 'text-gray-500';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('top-query-type').textContent = queryType;
|
queryPercentElem.textContent = trendIcon + ' ' + queryPercent;
|
||||||
|
queryPercentElem.className = `text-sm flex items-center ${trendClass}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.getElementById('active-ips')) {
|
if (document.getElementById('active-ips')) {
|
||||||
@@ -984,6 +1018,60 @@ function updateStatsCards(stats) {
|
|||||||
animateValue('error-queries', errorQueries);
|
animateValue('error-queries', errorQueries);
|
||||||
animateValue('active-ips', activeIPs);
|
animateValue('active-ips', activeIPs);
|
||||||
|
|
||||||
|
// DNSSEC相关数据
|
||||||
|
let dnssecEnabled = false, dnssecQueries = 0, dnssecSuccess = 0, dnssecFailed = 0, dnssecUsage = 0;
|
||||||
|
|
||||||
|
// 检查DNSSEC数据
|
||||||
|
if (stats) {
|
||||||
|
// 优先使用顶层字段
|
||||||
|
dnssecEnabled = stats.dnssecEnabled || false;
|
||||||
|
dnssecQueries = stats.dnssecQueries || 0;
|
||||||
|
dnssecSuccess = stats.dnssecSuccess || 0;
|
||||||
|
dnssecFailed = stats.dnssecFailed || 0;
|
||||||
|
dnssecUsage = stats.dnssecUsage || 0;
|
||||||
|
|
||||||
|
// 如果dns对象存在,优先使用其中的数据
|
||||||
|
if (stats.dns) {
|
||||||
|
dnssecEnabled = stats.dns.DNSSECEnabled || dnssecEnabled;
|
||||||
|
dnssecQueries = stats.dns.DNSSECQueries || dnssecQueries;
|
||||||
|
dnssecSuccess = stats.dns.DNSSECSuccess || dnssecSuccess;
|
||||||
|
dnssecFailed = stats.dns.DNSSECFailed || dnssecFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有直接提供使用率,计算使用率
|
||||||
|
if (dnssecUsage === 0 && totalQueries > 0) {
|
||||||
|
dnssecUsage = (dnssecQueries / totalQueries) * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新DNSSEC统计卡片
|
||||||
|
const dnssecUsageElement = document.getElementById('dnssec-usage');
|
||||||
|
const dnssecStatusElement = document.getElementById('dnssec-status');
|
||||||
|
const dnssecSuccessElement = document.getElementById('dnssec-success');
|
||||||
|
const dnssecFailedElement = document.getElementById('dnssec-failed');
|
||||||
|
const dnssecQueriesElement = document.getElementById('dnssec-queries');
|
||||||
|
|
||||||
|
if (dnssecUsageElement) {
|
||||||
|
dnssecUsageElement.textContent = `${Math.round(dnssecUsage)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dnssecStatusElement) {
|
||||||
|
dnssecStatusElement.textContent = dnssecEnabled ? '已启用' : '已禁用';
|
||||||
|
dnssecStatusElement.className = `text-sm flex items-center ${dnssecEnabled ? 'text-success' : 'text-danger'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dnssecSuccessElement) {
|
||||||
|
dnssecSuccessElement.textContent = formatNumber(dnssecSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dnssecFailedElement) {
|
||||||
|
dnssecFailedElement.textContent = formatNumber(dnssecFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dnssecQueriesElement) {
|
||||||
|
dnssecQueriesElement.textContent = formatNumber(dnssecQueries);
|
||||||
|
}
|
||||||
|
|
||||||
// 直接更新文本和百分比,移除动画效果
|
// 直接更新文本和百分比,移除动画效果
|
||||||
const topQueryTypeElement = document.getElementById('top-query-type');
|
const topQueryTypeElement = document.getElementById('top-query-type');
|
||||||
const queryTypePercentageElement = document.getElementById('query-type-percentage');
|
const queryTypePercentageElement = document.getElementById('query-type-percentage');
|
||||||
@@ -1223,7 +1311,7 @@ function updateTopDomainsTable(domains) {
|
|||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="w-6 h-6 flex items-center justify-center rounded-full bg-success/10 text-success text-xs font-medium mr-3">${i + 1}</span>
|
<span class="w-6 h-6 flex items-center justify-center rounded-full bg-success/10 text-success text-xs font-medium mr-3">${i + 1}</span>
|
||||||
<span class="font-medium truncate">${domain.name}</span>
|
<span class="font-medium truncate">${domain.name}${domain.dnssec ? ' <i class="fa fa-lock text-green-500"></i>' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="ml-4 flex-shrink-0 font-semibold text-success">${formatNumber(domain.count)}</span>
|
<span class="ml-4 flex-shrink-0 font-semibold text-success">${formatNumber(domain.count)}</span>
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ function updateLogsTable(logs) {
|
|||||||
</td>
|
</td>
|
||||||
<td class="py-3 px-4 text-sm">
|
<td class="py-3 px-4 text-sm">
|
||||||
<div class="font-medium">${log.Domain}</div>
|
<div class="font-medium">${log.Domain}</div>
|
||||||
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span></div>
|
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span>${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}${log.EDNS ? ', <span class="text-blue-500"><i class="fa fa-exchange"></i> EDNS</span>' : ''}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td>
|
<td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td>
|
||||||
<td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td>
|
<td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td>
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// testRuleMatching 测试DNS规则匹配功能
|
|
||||||
func main() {
|
|
||||||
// 定义命令行参数
|
|
||||||
rulePtr := flag.String("rule", "||cntvwb.cn^", "规则字符串")
|
|
||||||
testDomainPtr := flag.String("domain", "vdapprecv.app.cntvwb.cn", "测试域名")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// 打印测试信息
|
|
||||||
fmt.Printf("测试规则: %s\n", *rulePtr)
|
|
||||||
fmt.Printf("测试域名: %s\n", *testDomainPtr)
|
|
||||||
|
|
||||||
// 发送HTTP请求到API端点来测试规则匹配
|
|
||||||
fmt.Println("\n测试规则匹配功能...")
|
|
||||||
cmd := exec.Command("curl", "-s", fmt.Sprintf("http://localhost:8080/api/shield/check?domain=%s&rule=%s", *testDomainPtr, *rulePtr))
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
// 如果直接的API测试失败,尝试另一种方法
|
|
||||||
fmt.Printf("直接测试失败: %v, %s\n", err, string(output))
|
|
||||||
fmt.Println("尝试添加规则并测试...")
|
|
||||||
testAddRuleAndCheck(*rulePtr, *testDomainPtr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("测试结果: %s\n", string(output))
|
|
||||||
|
|
||||||
// 验证规则是否生效(模拟测试)
|
|
||||||
if strings.Contains(*rulePtr, "||cntvwb.cn^") && strings.Contains(*testDomainPtr, "cntvwb.cn") {
|
|
||||||
fmt.Println("\n验证结果:")
|
|
||||||
if strings.Contains(*testDomainPtr, "cntvwb.cn") {
|
|
||||||
fmt.Println("✅ 子域名匹配测试通过:||cntvwb.cn^ 应该阻止所有 cntvwb.cn 的子域名")
|
|
||||||
} else {
|
|
||||||
fmt.Println("❌ 子域名匹配测试失败")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// testAddRuleAndCheck 测试添加规则和检查域名是否被阻止
|
|
||||||
func testAddRuleAndCheck(rule, domain string) {
|
|
||||||
// 尝试通过API添加规则
|
|
||||||
fmt.Printf("添加规则: %s\n", rule)
|
|
||||||
cmd := exec.Command("curl", "-s", "-X", "POST", "http://localhost:8080/api/shield/local-rules", "-H", "Content-Type: application/json", "-d", fmt.Sprintf(`{\"rule\":\"%s\"}`, rule))
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("添加规则失败: %v, %s\n", err, string(output))
|
|
||||||
// 尝试重新加载规则
|
|
||||||
fmt.Println("尝试重新加载规则...")
|
|
||||||
cmd = exec.Command("curl", "-s", "-X", "PUT", "http://localhost:8080/api/shield", "-H", "Content-Type: application/json", "-d", `{\"reload\":true}`)
|
|
||||||
output, err = cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("重新加载规则失败: %v, %s\n", err, string(output))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("重新加载规则结果: %s\n", string(output))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("添加规则结果: %s\n", string(output))
|
|
||||||
|
|
||||||
// 测试域名是否被阻止
|
|
||||||
fmt.Printf("测试域名 %s 是否被阻止...\n", domain)
|
|
||||||
cmd = exec.Command("curl", "-s", fmt.Sprintf("http://localhost:8080/api/shield/check?domain=%s", domain))
|
|
||||||
output, err = cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("测试阻止失败: %v, %s\n", err, string(output))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("阻止测试结果: %s\n", string(output))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# DNS Web控制台功能测试脚本
|
|
||||||
echo "开始测试DNS Web控制台功能..."
|
|
||||||
echo "=================================="
|
|
||||||
|
|
||||||
# 检查服务器是否运行
|
|
||||||
echo "检查DNS服务器运行状态..."
|
|
||||||
pids=$(ps aux | grep dns-server | grep -v grep)
|
|
||||||
if [ -n "$pids" ]; then
|
|
||||||
echo "✓ DNS服务器正在运行"
|
|
||||||
else
|
|
||||||
echo "✗ DNS服务器未运行,请先启动服务器"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 测试API基础URL
|
|
||||||
BASE_URL="http://localhost:8080/api"
|
|
||||||
|
|
||||||
# 测试1: 获取统计信息
|
|
||||||
echo "\n测试1: 获取DNS统计信息"
|
|
||||||
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/stats"
|
|
||||||
|
|
||||||
# 测试2: 获取系统状态
|
|
||||||
echo "\n测试2: 获取系统状态"
|
|
||||||
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/status"
|
|
||||||
|
|
||||||
# 测试3: 获取屏蔽规则
|
|
||||||
echo "\n测试3: 获取屏蔽规则列表"
|
|
||||||
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/shield"
|
|
||||||
|
|
||||||
# 测试4: 获取Top屏蔽域名
|
|
||||||
echo "\n测试4: 获取Top屏蔽域名"
|
|
||||||
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/top-blocked"
|
|
||||||
|
|
||||||
# 测试5: 获取Hosts内容
|
|
||||||
echo "\n测试5: 获取Hosts内容"
|
|
||||||
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/shield/hosts"
|
|
||||||
|
|
||||||
# 测试6: 访问Web控制台主页
|
|
||||||
echo "\n测试6: 访问Web控制台主页"
|
|
||||||
curl -s -o /dev/null -w "状态码: %{http_code}\n" "http://localhost:8080"
|
|
||||||
|
|
||||||
echo "\n=================================="
|
|
||||||
echo "测试完成!请检查上述状态码。正常情况下应为200。"
|
|
||||||
echo "前端Web控制台可通过浏览器访问: http://localhost:8080"
|
|
||||||
Reference in New Issue
Block a user