日志查询界面增加操作列,修复责增加删除规则不生效的问题,修复自定义规则优先级问题

This commit is contained in:
Alex Yang
2025-12-21 21:18:17 +08:00
parent b67a0fad8e
commit 1f8bd6b97f
14 changed files with 1467 additions and 169 deletions

View File

@@ -2,6 +2,37 @@
所有对本项目的显著更改都将记录在此文件中。 所有对本项目的显著更改都将记录在此文件中。
## [1.1.4] - 2025-12-21
### 修复
- 修复规则优先级问题:确保自定义规则优先于远程规则
- 修复添加自定义规则后需要重启服务器的问题通过在添加或删除规则后清空DNS缓存实现
## [1.1.3] - 2025-12-19
### 移除
- 移除search domain功能不再支持自动添加域名前缀进行查询
- 移除DNSConfig结构体中的PrefixDomain字段
- 移除配置文件中的prefixDomain配置项
## [1.1.2] - 2025-12-19
### 添加
- 添加不验证DNSSEC的域名功能支持通过配置文件指定需要跳过DNSSEC验证的域名模式
- 在DNSConfig结构体中增加NoDNSSECDomains字段用于存储不验证DNSSEC的域名模式列表
### 修改
- 在forwardDNSRequestWithCache函数中添加域名匹配逻辑检查域名是否包含不验证DNSSEC的模式
- 在所有查询模式parallel、loadbalance、fastest-ip、default中实现跳过DNSSEC验证的功能
## [1.1.1] - 2025-12-19
### 修改
- 修复NXDOMAIN响应传播逻辑确保上游DNS服务器返回的NXDOMAIN响应能正确传递给客户端
- 优化loadbalance、fastest-ip和parallel查询模式下的NXDOMAIN响应选择机制
- 确保不存在的域名能被正确识别并返回NXDOMAIN状态码
- 修复服务器绑定地址配置确保IPv4兼容性
## [1.0.0] - 2025-12-16 ## [1.0.0] - 2025-12-16
### 添加 ### 添加

View File

@@ -23,7 +23,7 @@
- 支持域名规则和正则表达式规则 - 支持域名规则和正则表达式规则
- 支持规则例外 - 支持规则例外
- 支持远程规则列表 - 支持远程规则列表
- 支持本地规则管理 - 支持自定义规则管理
### 3. 查询日志记录和统计 ### 3. 查询日志记录和统计
- 实时记录DNS查询日志 - 实时记录DNS查询日志
@@ -116,7 +116,7 @@ http://localhost:8080
1. 登录Web控制台 1. 登录Web控制台
2. 点击左侧菜单中的"屏蔽管理" 2. 点击左侧菜单中的"屏蔽管理"
3. 在"本地规则管理"中添加或删除规则 3. 在"自定义规则管理"中添加或删除规则
4. 在"远程黑名单管理"中添加或删除远程规则列表 4. 在"远程黑名单管理"中添加或删除远程规则列表
### 查看查询日志 ### 查看查询日志

5
build.sh Executable file
View File

@@ -0,0 +1,5 @@
CGO_ENABLED=1 \
GOOS=linux \
GOARCH=amd64 \
CC=gcc \
go build -ldflags "-linkmode external -extldflags '-static -pthread'" -o dns-server main.go

View File

@@ -2,18 +2,47 @@
"dns": { "dns": {
"port": 53, "port": 53,
"upstreamDNS": [ "upstreamDNS": [
"223.5.5.5:53", "223.5.5.5:53"
"223.6.6.6:53",
"117.50.10.10:53"
], ],
"dnssecUpstreamDNS": [ "dnssecUpstreamDNS": [
"1.1.1.1:53" "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, "timeout": 5000,
"statsFile": "data/stats.json", "statsFile": "data/stats.json",
"saveInterval": 300, "saveInterval": 300,
"cacheTTL": 30, "cacheTTL": 10,
"enableDNSSEC": true "enableDNSSEC": true,
"queryMode": "loadbalance",
"domainSpecificDNS": {
"addr.arpa": [
"10.35.10.200:53"
],
"akadns": [
"4.2.2.1:53"
],
"akamai": [
"4.2.2.1:53"
],
"amazehome.cn": [
"10.35.10.200:53"
],
"amazehome.xyz": [
"10.35.10.200:53"
],
"microsoft.com": [
"4.2.2.1:53"
]
},
"noDNSSECDomains": [
"amazehome.cn",
"addr.arpa",
"amazehome.xyz",
".cn"
]
}, },
"http": { "http": {
"port": 8080, "port": 8080,
@@ -29,7 +58,7 @@
"name": "AdGuard DNS filter", "name": "AdGuard DNS filter",
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt", "url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt",
"enabled": true, "enabled": true,
"lastUpdateTime": "2025-11-28T16:13:03.564Z" "lastUpdateTime": "2025-12-21T10:46:36.629Z"
}, },
{ {
"name": "Adaway Default Blocklist", "name": "Adaway Default Blocklist",
@@ -62,13 +91,14 @@
{ {
"name": "Hate \u0026 Junk", "name": "Hate \u0026 Junk",
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hate-and-junk-extended.txt", "url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hate-and-junk-extended.txt",
"enabled": true "enabled": true,
"lastUpdateTime": "2025-12-21T10:46:43.522Z"
}, },
{ {
"name": "My Gitlab Hosts", "name": "My Gitlab Hosts",
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/costomize.txt", "url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/costomize.txt",
"enabled": true, "enabled": true,
"lastUpdateTime": "2025-11-29T17:11:28.130Z" "lastUpdateTime": "2025-12-18T10:39:39.333Z"
}, },
{ {
"name": "Anti Remote Requests", "name": "Anti Remote Requests",
@@ -83,7 +113,8 @@
{ {
"name": "My Gitlab A/T Rules", "name": "My Gitlab A/T Rules",
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/ads-and-trackers.txt", "url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/ads-and-trackers.txt",
"enabled": true "enabled": true,
"lastUpdateTime": "2025-12-18T10:38:42.344Z"
}, },
{ {
"name": "My Gitlab Malware List", "name": "My Gitlab Malware List",

View File

@@ -5,6 +5,13 @@ import (
"io/ioutil" "io/ioutil"
) )
// DomainSpecificDNS 域名特定DNS服务器配置
// 格式:{"domainMatch": ["dns1", "dns2"]}
// domainMatch: 域名匹配字符串当域名中包含该字符串时使用对应的DNS服务器
// dns1, dns2: 用于解析匹配域名的DNS服务器列表
type DomainSpecificDNS map[string][]string
// DNSConfig DNS配置 // DNSConfig DNS配置
type DNSConfig struct { type DNSConfig struct {
Port int `json:"port"` Port int `json:"port"`
@@ -15,6 +22,9 @@ type DNSConfig struct {
SaveInterval int `json:"saveInterval"` // 数据保存间隔(秒) SaveInterval int `json:"saveInterval"` // 数据保存间隔(秒)
CacheTTL int `json:"cacheTTL"` // DNS缓存过期时间分钟 CacheTTL int `json:"cacheTTL"` // DNS缓存过期时间分钟
EnableDNSSEC bool `json:"enableDNSSEC"` // 是否启用DNSSEC支持 EnableDNSSEC bool `json:"enableDNSSEC"` // 是否启用DNSSEC支持
QueryMode string `json:"queryMode"` // 查询模式:"loadbalance"(负载均衡)、"parallel"(并行请求)、"fastest-ip"最快的IP地址
DomainSpecificDNS DomainSpecificDNS `json:"domainSpecificDNS"` // 域名特定DNS服务器配置
NoDNSSECDomains []string `json:"noDNSSECDomains"` // 不验证DNSSEC的域名模式列表
} }
// HTTPConfig HTTP控制台配置 // HTTPConfig HTTP控制台配置
@@ -82,6 +92,9 @@ func LoadConfig(path string) (*Config, error) {
if config.DNS.Port == 0 { if config.DNS.Port == 0 {
config.DNS.Port = 53 config.DNS.Port = 53
} }
if config.DNS.Timeout == 0 {
config.DNS.Timeout = 5000 // 默认超时时间为5000毫秒
}
if len(config.DNS.UpstreamDNS) == 0 { if len(config.DNS.UpstreamDNS) == 0 {
config.DNS.UpstreamDNS = []string{"223.5.5.5:53", "223.6.6.6:53"} config.DNS.UpstreamDNS = []string{"223.5.5.5:53", "223.6.6.6:53"}
} }
@@ -101,6 +114,15 @@ func LoadConfig(path string) (*Config, error) {
if len(config.DNS.DNSSECUpstreamDNS) == 0 { if len(config.DNS.DNSSECUpstreamDNS) == 0 {
config.DNS.DNSSECUpstreamDNS = []string{"8.8.8.8:53", "1.1.1.1:53"} config.DNS.DNSSECUpstreamDNS = []string{"8.8.8.8:53", "1.1.1.1:53"}
} }
// 查询模式默认配置
if config.DNS.QueryMode == "" {
config.DNS.QueryMode = "parallel" // 默认使用并行请求模式
}
// 域名特定DNS服务器配置默认值
if config.DNS.DomainSpecificDNS == nil {
config.DNS.DomainSpecificDNS = make(DomainSpecificDNS) // 默认为空映射
}
if config.HTTP.Port == 0 { if config.HTTP.Port == 0 {
config.HTTP.Port = 8080 config.HTTP.Port = 8080
} }

File diff suppressed because it is too large Load Diff

View File

@@ -832,6 +832,9 @@ func (s *Server) handleShield(w http.ResponseWriter, r *http.Request) {
return return
} }
// 清空DNS缓存使新规则立即生效
s.dnsServer.DnsCache.Clear()
json.NewEncoder(w).Encode(map[string]string{"status": "success"}) json.NewEncoder(w).Encode(map[string]string{"status": "success"})
return return
case http.MethodDelete: case http.MethodDelete:
@@ -850,6 +853,9 @@ func (s *Server) handleShield(w http.ResponseWriter, r *http.Request) {
return return
} }
// 清空DNS缓存使规则变更立即生效
s.dnsServer.DnsCache.Clear()
json.NewEncoder(w).Encode(map[string]string{"status": "success"}) json.NewEncoder(w).Encode(map[string]string{"status": "success"})
return return
case http.MethodPut: case http.MethodPut:

15
main.go
View File

@@ -37,9 +37,16 @@ func createDefaultConfig(configFile string) error {
"223.5.5.5:53", "223.5.5.5:53",
"223.6.6.6:53" "223.6.6.6:53"
], ],
"dnssecUpstreamDNS": [
"8.8.8.8:53",
"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,
"queryMode": "parallel"
}, },
"http": { "http": {
"port": 8080, "port": 8080,
@@ -114,10 +121,10 @@ func createRequiredFiles(cfg *config.Config) error {
} }
} }
// 创建本地规则文件 // 创建自定义规则文件
if _, err := os.Stat(cfg.Shield.LocalRulesFile); os.IsNotExist(err) { if _, err := os.Stat(cfg.Shield.LocalRulesFile); os.IsNotExist(err) {
if err := os.WriteFile(cfg.Shield.LocalRulesFile, []byte("# 本地规则文件\n# 格式:域名\n# 例如example.com\n"), 0644); err != nil { if err := os.WriteFile(cfg.Shield.LocalRulesFile, []byte("# 自定义规则文件\n# 格式:域名\n# 例如example.com\n"), 0644); err != nil {
return fmt.Errorf("创建本地规则文件失败: %w", err) return fmt.Errorf("创建自定义规则文件失败: %w", err)
} }
} }

View File

@@ -30,7 +30,7 @@ type ShieldStatsData struct {
type regexRule struct { type regexRule struct {
pattern *regexp.Regexp pattern *regexp.Regexp
original string original string
isLocal bool // 是否为本地规则 isLocal bool // 是否为自定义规则
source string // 规则来源 source string // 规则来源
} }
@@ -39,8 +39,8 @@ type ShieldManager struct {
config *config.ShieldConfig config *config.ShieldConfig
domainRules map[string]bool domainRules map[string]bool
domainExceptions map[string]bool domainExceptions map[string]bool
domainRulesIsLocal map[string]bool // 标记域名规则是否为本地规则 domainRulesIsLocal map[string]bool // 标记域名规则是否为自定义规则
domainExceptionsIsLocal map[string]bool // 标记域名排除规则是否为本地规则 domainExceptionsIsLocal map[string]bool // 标记域名排除规则是否为自定义规则
domainRulesSource map[string]string // 标记域名规则来源 domainRulesSource map[string]string // 标记域名规则来源
domainExceptionsSource map[string]string // 标记域名排除规则来源 domainExceptionsSource map[string]string // 标记域名排除规则来源
domainRulesOriginal map[string]string // 存储域名规则的原始字符串 domainRulesOriginal map[string]string // 存储域名规则的原始字符串
@@ -54,7 +54,7 @@ type ShieldManager struct {
updateCtx context.Context updateCtx context.Context
updateCancel context.CancelFunc updateCancel context.CancelFunc
updateRunning bool updateRunning bool
localRulesCount int // 本地规则数量 localRulesCount int // 自定义规则数量
remoteRulesCount int // 远程规则数量 remoteRulesCount int // 远程规则数量
} }
@@ -109,9 +109,9 @@ func (m *ShieldManager) LoadRules() error {
m.remoteRulesCount = 0 m.remoteRulesCount = 0
// 保留计数数据,不随规则重新加载而清空 // 保留计数数据,不随规则重新加载而清空
// 加载本地规则文件 // 加载自定义规则文件
if err := m.loadLocalRules(); err != nil { if err := m.loadLocalRules(); err != nil {
logger.Error("加载本地规则失败", "error", err) logger.Error("加载自定义规则失败", "error", err)
// 继续执行,不返回错误 // 继续执行,不返回错误
} }
@@ -132,7 +132,7 @@ func (m *ShieldManager) LoadRules() error {
return nil return nil
} }
// loadLocalRules 加载本地规则文件 // loadLocalRules 加载自定义规则文件
func (m *ShieldManager) loadLocalRules() error { func (m *ShieldManager) loadLocalRules() error {
if m.config.LocalRulesFile == "" { if m.config.LocalRulesFile == "" {
return nil return nil
@@ -144,7 +144,7 @@ func (m *ShieldManager) loadLocalRules() error {
} }
defer file.Close() defer file.Close()
// 记录加载前的规则数量,用于计算本地规则数量 // 记录加载前的规则数量,用于计算自定义规则数量
beforeDomainRules := len(m.domainRules) beforeDomainRules := len(m.domainRules)
beforeRegexRules := len(m.regexRules) beforeRegexRules := len(m.regexRules)
@@ -154,10 +154,10 @@ func (m *ShieldManager) loadLocalRules() error {
if line == "" || strings.HasPrefix(line, "#") { if line == "" || strings.HasPrefix(line, "#") {
continue continue
} }
m.parseRule(line, true, "本地规则") // 本地规则isLocal=true来源为"本地规则" m.parseRule(line, true, "自定义规则") // 自定义规则isLocal=true来源为"自定义规则"
} }
// 更新本地规则计数 // 更新自定义规则计数
m.localRulesCount = (len(m.domainRules) - beforeDomainRules) + (len(m.regexRules) - beforeRegexRules) m.localRulesCount = (len(m.domainRules) - beforeDomainRules) + (len(m.regexRules) - beforeRegexRules)
return scanner.Err() return scanner.Err()
@@ -445,10 +445,16 @@ func (m *ShieldManager) parseRuleOptions(optionsStr string) map[string]string {
// addDomainRule 添加域名规则,支持是否为阻止规则 // addDomainRule 添加域名规则,支持是否为阻止规则
func (m *ShieldManager) addDomainRule(domain string, block bool, isLocal bool, source string, original string) { func (m *ShieldManager) addDomainRule(domain string, block bool, isLocal bool, source string, original string) {
if block { if block {
// 如果是远程规则,检查是否已经存在本地规则,如果存在则不覆盖 // 如果是远程规则,检查是否已经存在自定义规则(阻止或排除),如果存在则不覆盖
if !isLocal { if !isLocal {
// 检查是否存在自定义阻止规则
if _, exists := m.domainRulesIsLocal[domain]; exists && m.domainRulesIsLocal[domain] { if _, exists := m.domainRulesIsLocal[domain]; exists && m.domainRulesIsLocal[domain] {
// 已经存在本地规则,不覆盖 // 已经存在自定义规则,不覆盖
return
}
// 检查是否存在自定义排除规则
if _, exists := m.domainExceptionsIsLocal[domain]; exists && m.domainExceptionsIsLocal[domain] {
// 已经存在自定义规则,不覆盖
return return
} }
} }
@@ -458,10 +464,16 @@ func (m *ShieldManager) addDomainRule(domain string, block bool, isLocal bool, s
m.domainRulesOriginal[domain] = original m.domainRulesOriginal[domain] = original
} else { } else {
// 添加到排除规则 // 添加到排除规则
// 如果是远程规则,检查是否已经存在本地规则,如果存在则不覆盖 // 如果是远程规则,检查是否已经存在自定义规则(阻止或排除),如果存在则不覆盖
if !isLocal { if !isLocal {
// 检查是否存在自定义阻止规则
if _, exists := m.domainRulesIsLocal[domain]; exists && m.domainRulesIsLocal[domain] {
// 已经存在自定义规则,不覆盖
return
}
// 检查是否存在自定义排除规则
if _, exists := m.domainExceptionsIsLocal[domain]; exists && m.domainExceptionsIsLocal[domain] { if _, exists := m.domainExceptionsIsLocal[domain]; exists && m.domainExceptionsIsLocal[domain] {
// 已经存在本地规则,不覆盖 // 已经存在自定义规则,不覆盖
return return
} }
} }
@@ -481,11 +493,19 @@ func (m *ShieldManager) addRegexRule(re *regexp.Regexp, original string, block b
source: source, source: source,
} }
if block { if block {
// 如果是远程规则,检查是否已经存在相同的本地规则,如果存在则不添加 // 如果是远程规则,检查是否已经存在任何自定义规则,如果存在则不添加
if !isLocal { if !isLocal {
// 检查是否存在自定义阻止规则
for _, existingRule := range m.regexRules { for _, existingRule := range m.regexRules {
if existingRule.original == original && existingRule.isLocal { if existingRule.pattern.String() == re.String() && existingRule.isLocal {
// 已经存在相同的本地规则,不添加 // 已经存在相同的自定义阻止规则,不添加
return
}
}
// 检查是否存在自定义排除规则
for _, existingRule := range m.regexExceptions {
if existingRule.pattern.String() == re.String() && existingRule.isLocal {
// 已经存在相同的自定义排除规则,不添加
return return
} }
} }
@@ -493,11 +513,19 @@ func (m *ShieldManager) addRegexRule(re *regexp.Regexp, original string, block b
m.regexRules = append(m.regexRules, rule) m.regexRules = append(m.regexRules, rule)
} else { } else {
// 添加到排除规则 // 添加到排除规则
// 如果是远程规则,检查是否已经存在相同的本地规则,如果存在则不添加 // 如果是远程规则,检查是否已经存在任何自定义规则,如果存在则不添加
if !isLocal { if !isLocal {
// 检查是否存在自定义阻止规则
for _, existingRule := range m.regexRules {
if existingRule.pattern.String() == re.String() && existingRule.isLocal {
// 已经存在相同的自定义阻止规则,不添加
return
}
}
// 检查是否存在自定义排除规则
for _, existingRule := range m.regexExceptions { for _, existingRule := range m.regexExceptions {
if existingRule.original == original && existingRule.isLocal { if existingRule.pattern.String() == re.String() && existingRule.isLocal {
// 已经存在相同的本地规则,不添加 // 已经存在相同的自定义排除规则,不添加
return return
} }
} }
@@ -537,8 +565,17 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
result["hostsIP"] = hostsIP result["hostsIP"] = hostsIP
// 检查排除规则(优先级最高) // 检查排除规则(优先级最高)
// 检查域名排除规则 // 1. 先检查本地域名排除规则
if m.domainExceptions[domain] { if m.domainExceptions[domain] && m.domainExceptionsIsLocal[domain] {
result["excluded"] = true
result["excludeRule"] = m.domainExceptionsOriginal[domain]
result["excludeRuleType"] = "exact_domain"
result["blocksource"] = m.domainExceptionsSource[domain]
return result
}
// 2. 再检查远程域名排除规则
if m.domainExceptions[domain] && !m.domainExceptionsIsLocal[domain] {
result["excluded"] = true result["excluded"] = true
result["excludeRule"] = m.domainExceptionsOriginal[domain] result["excludeRule"] = m.domainExceptionsOriginal[domain]
result["excludeRuleType"] = "exact_domain" result["excludeRuleType"] = "exact_domain"
@@ -548,9 +585,11 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
// 检查子域名排除规则 // 检查子域名排除规则
parts := strings.Split(domain, ".") parts := strings.Split(domain, ".")
// 3. 先检查本地子域名排除规则
for i := 0; i < len(parts)-1; i++ { for i := 0; i < len(parts)-1; i++ {
subdomain := strings.Join(parts[i:], ".") subdomain := strings.Join(parts[i:], ".")
if m.domainExceptions[subdomain] { if m.domainExceptions[subdomain] && m.domainExceptionsIsLocal[subdomain] {
result["excluded"] = true result["excluded"] = true
result["excludeRule"] = m.domainExceptionsOriginal[subdomain] result["excludeRule"] = m.domainExceptionsOriginal[subdomain]
result["excludeRuleType"] = "subdomain" result["excludeRuleType"] = "subdomain"
@@ -559,9 +598,32 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
} }
} }
// 检查正则表达式排除规则 // 4. 再检查远程子域名排除规则
for i := 0; i < len(parts)-1; i++ {
subdomain := strings.Join(parts[i:], ".")
if m.domainExceptions[subdomain] && !m.domainExceptionsIsLocal[subdomain] {
result["excluded"] = true
result["excludeRule"] = m.domainExceptionsOriginal[subdomain]
result["excludeRuleType"] = "subdomain"
result["blocksource"] = m.domainExceptionsSource[subdomain]
return result
}
}
// 5. 先检查本地正则表达式排除规则
for _, re := range m.regexExceptions { for _, re := range m.regexExceptions {
if re.pattern.MatchString(domain) { if re.isLocal && re.pattern.MatchString(domain) {
result["excluded"] = true
result["excludeRule"] = re.original
result["excludeRuleType"] = "regex"
result["blocksource"] = re.source
return result
}
}
// 6. 再检查远程正则表达式排除规则
for _, re := range m.regexExceptions {
if !re.isLocal && re.pattern.MatchString(domain) {
result["excluded"] = true result["excluded"] = true
result["excludeRule"] = re.original result["excludeRule"] = re.original
result["excludeRuleType"] = "regex" result["excludeRuleType"] = "regex"
@@ -571,8 +633,17 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
} }
// 检查阻止规则 - 先检查精确域名匹配,再检查子域名匹配 // 检查阻止规则 - 先检查精确域名匹配,再检查子域名匹配
// 检查精确域名匹配 // 7. 先检查本地域名阻止规则
if m.domainRules[domain] { if m.domainRules[domain] && m.domainRulesIsLocal[domain] {
result["blocked"] = true
result["blockRule"] = m.domainRulesOriginal[domain]
result["blockRuleType"] = "exact_domain"
result["blocksource"] = m.domainRulesSource[domain]
return result
}
// 8. 再检查远程域名阻止规则
if m.domainRules[domain] && !m.domainRulesIsLocal[domain] {
result["blocked"] = true result["blocked"] = true
result["blockRule"] = m.domainRulesOriginal[domain] result["blockRule"] = m.domainRulesOriginal[domain]
result["blockRuleType"] = "exact_domain" result["blockRuleType"] = "exact_domain"
@@ -582,9 +653,11 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
// 检查子域名匹配AdGuardHome风格 // 检查子域名匹配AdGuardHome风格
// 从最长的子域名开始匹配,确保优先级正确 // 从最长的子域名开始匹配,确保优先级正确
// 9. 先检查本地子域名阻止规则
for i := 0; i < len(parts)-1; i++ { for i := 0; i < len(parts)-1; i++ {
subdomain := strings.Join(parts[i:], ".") subdomain := strings.Join(parts[i:], ".")
if m.domainRules[subdomain] { if m.domainRules[subdomain] && m.domainRulesIsLocal[subdomain] {
result["blocked"] = true result["blocked"] = true
result["blockRule"] = m.domainRulesOriginal[subdomain] result["blockRule"] = m.domainRulesOriginal[subdomain]
result["blockRuleType"] = "subdomain" result["blockRuleType"] = "subdomain"
@@ -593,9 +666,32 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
} }
} }
// 检查正则表达式匹配 // 10. 再检查远程子域名阻止规则
for i := 0; i < len(parts)-1; i++ {
subdomain := strings.Join(parts[i:], ".")
if m.domainRules[subdomain] && !m.domainRulesIsLocal[subdomain] {
result["blocked"] = true
result["blockRule"] = m.domainRulesOriginal[subdomain]
result["blockRuleType"] = "subdomain"
result["blocksource"] = m.domainRulesSource[subdomain]
return result
}
}
// 11. 先检查本地正则表达式阻止规则
for _, re := range m.regexRules { for _, re := range m.regexRules {
if re.pattern.MatchString(domain) { if re.isLocal && re.pattern.MatchString(domain) {
result["blocked"] = true
result["blockRule"] = re.original
result["blockRuleType"] = "regex"
result["blocksource"] = re.source
return result
}
}
// 12. 再检查远程正则表达式阻止规则
for _, re := range m.regexRules {
if !re.isLocal && re.pattern.MatchString(domain) {
result["blocked"] = true result["blocked"] = true
result["blockRule"] = re.original result["blockRule"] = re.original
result["blockRuleType"] = "regex" result["blockRuleType"] = "regex"
@@ -722,13 +818,13 @@ func (m *ShieldManager) GetHostsIP(domain string) (string, bool) {
return ip, exists return ip, exists
} }
// AddRule 添加屏蔽规则,用户添加的规则是本地规则 // AddRule 添加屏蔽规则,用户添加的规则是自定义规则
func (m *ShieldManager) AddRule(rule string) error { func (m *ShieldManager) AddRule(rule string) error {
m.rulesMutex.Lock() m.rulesMutex.Lock()
defer m.rulesMutex.Unlock() defer m.rulesMutex.Unlock()
// 解析并添加规则到内存isLocal=true表示本地规则,来源为"本地规则" // 解析并添加规则到内存isLocal=true表示自定义规则,来源为"自定义规则"
m.parseRule(rule, true, "本地规则") m.parseRule(rule, true, "自定义规则")
// 持久化保存规则到文件 // 持久化保存规则到文件
if m.config.LocalRulesFile != "" { if m.config.LocalRulesFile != "" {
@@ -957,7 +1053,7 @@ func (m *ShieldManager) StopAutoUpdate() {
logger.Info("规则自动更新已停止") logger.Info("规则自动更新已停止")
} }
// saveRulesToFile 保存规则到文件,只保存本地规则 // saveRulesToFile 保存规则到文件,只保存自定义规则
func (m *ShieldManager) saveRulesToFile() error { func (m *ShieldManager) saveRulesToFile() error {
var rules []string var rules []string
@@ -1293,12 +1389,12 @@ func (m *ShieldManager) GetHostsCount() int {
return len(m.hostsMap) return len(m.hostsMap)
} }
// GetLocalRules 获取仅本地规则 // GetLocalRules 获取仅自定义规则
func (m *ShieldManager) GetLocalRules() map[string]interface{} { func (m *ShieldManager) GetLocalRules() map[string]interface{} {
m.rulesMutex.RLock() m.rulesMutex.RLock()
defer m.rulesMutex.RUnlock() defer m.rulesMutex.RUnlock()
// 转换map和slice为字符串列表只包含本地规则 // 转换map和slice为字符串列表只包含自定义规则
domainRulesList := make([]string, 0) domainRulesList := make([]string, 0)
for domain, isLocal := range m.domainRulesIsLocal { for domain, isLocal := range m.domainRulesIsLocal {
if isLocal && m.domainRules[domain] { if isLocal && m.domainRules[domain] {
@@ -1329,7 +1425,7 @@ func (m *ShieldManager) GetLocalRules() map[string]interface{} {
} }
} }
// 计算本地规则数量 // 计算自定义规则数量
localDomainRulesCount := 0 localDomainRulesCount := 0
for _, isLocal := range m.domainRulesIsLocal { for _, isLocal := range m.domainRulesIsLocal {
if isLocal { if isLocal {

View File

@@ -1138,12 +1138,12 @@
}, },
"/shield/localrules": { "/shield/localrules": {
"get": { "get": {
"summary": "获取本地规则", "summary": "获取自定义规则",
"description": "获取Shield的本地规则列表。", "description": "获取Shield的自定义规则列表。"
"tags": ["shield"], "tags": ["shield"],
"responses": { "responses": {
"200": { "200": {
"description": "成功获取本地规则", "description": "成功获取自定义规则",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -1166,8 +1166,8 @@
} }
}, },
"post": { "post": {
"summary": "添加本地规则", "summary": "添加自定义规则",
"description": "添加新的本地规则。", "description": "添加新的自定义规则。",
"tags": ["shield"], "tags": ["shield"],
"requestBody": { "requestBody": {
"required": true, "required": true,
@@ -1205,8 +1205,8 @@
} }
}, },
"delete": { "delete": {
"summary": "删除本地规则", "summary": "删除自定义规则",
"description": "删除指定ID的本地规则。", "description": "删除指定ID的自定义规则。",
"tags": ["shield"], "tags": ["shield"],
"parameters": [ "parameters": [
{ {
@@ -1500,7 +1500,7 @@
"blocked": true, "blocked": true,
"blockRule": "example.com", "blockRule": "example.com",
"blockRuleType": "exact_domain", "blockRuleType": "exact_domain",
"blocksource": "本地规则", "blocksource": "自定义规则",
"excluded": false, "excluded": false,
"excludeRule": "", "excludeRule": "",
"excludeRuleType": "", "excludeRuleType": "",

View File

@@ -630,9 +630,9 @@
</div> </div>
</div> </div>
<!-- 本地规则管理 --> <!-- 自定义规则管理 -->
<div class="bg-white rounded-lg p-6 card-shadow"> <div class="bg-white rounded-lg p-6 card-shadow">
<h3 class="text-lg font-semibold mb-6">本地规则管理</h3> <h3 class="text-lg font-semibold mb-6">自定义规则管理</h3>
<!-- 添加规则表单 --> <!-- 添加规则表单 -->
<div id="add-rule-form" class="mb-6 bg-gray-50 p-4 rounded-lg"> <div id="add-rule-form" class="mb-6 bg-gray-50 p-4 rounded-lg">
@@ -976,11 +976,12 @@
</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-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>
</tr> </tr>
</thead> </thead>
<tbody id="logs-table-body"> <tbody id="logs-table-body">
<tr> <tr>
<td colspan="5" class="py-8 text-center text-gray-500 border-b border-gray-100"> <td colspan="6" 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>
@@ -1056,7 +1057,7 @@
<h4 class="text-md font-medium mb-4">屏蔽配置</h4> <h4 class="text-md font-medium mb-4">屏蔽配置</h4>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 gap-6">
<div> <div>
<label for="shield-local-rules-file" class="block text-sm font-medium text-gray-700 mb-1">本地规则文件</label> <label for="shield-local-rules-file" class="block text-sm font-medium text-gray-700 mb-1">自定义规则文件</label>
<input type="text" id="shield-local-rules-file" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="./rules.txt"> <input type="text" id="shield-local-rules-file" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="./rules.txt">
</div> </div>
<div> <div>

View File

@@ -282,6 +282,9 @@ function loadLogs() {
// 更新日志表格 // 更新日志表格
updateLogsTable(logs); updateLogsTable(logs);
// 绑定操作按钮事件
bindActionButtonsEvents();
// 更新分页信息 // 更新分页信息
updateLogsPagination(); updateLogsPagination();
@@ -388,6 +391,9 @@ function updateLogsTable(logs) {
const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500'; const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500';
const cacheStatusText = log.FromCache ? '缓存' : '非缓存'; const cacheStatusText = log.FromCache ? '缓存' : '非缓存';
// 检查域名是否被拦截
const isBlocked = log.Result === 'blocked';
row.innerHTML = ` row.innerHTML = `
<td class="py-3 px-4"> <td class="py-3 px-4">
<div class="text-sm font-medium">${formattedTime}</div> <div class="text-sm font-medium">${formattedTime}</div>
@@ -400,11 +406,37 @@ function updateLogsTable(logs) {
<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>${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> <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>
<div class="text-xs text-gray-500 mt-1">DNS 服务器: ${log.DNSServer || '无'}, DNSSEC专用: ${log.DNSSECServer || '无'}</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>
<td class="py-3 px-4 text-sm text-center">
${isBlocked ?
`<button class="unblock-btn px-3 py-1 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors text-xs" data-domain="${log.Domain}">放行</button>` :
`<button class="block-btn px-3 py-1 bg-red-500 text-white rounded-md hover:bg-red-600 transition-colors text-xs" data-domain="${log.Domain}">拦截</button>`
}
</td>
`; `;
// 绑定按钮事件
const blockBtn = row.querySelector('.block-btn');
if (blockBtn) {
blockBtn.addEventListener('click', (e) => {
e.preventDefault();
const domain = e.currentTarget.dataset.domain;
blockDomain(domain);
});
}
const unblockBtn = row.querySelector('.unblock-btn');
if (unblockBtn) {
unblockBtn.addEventListener('click', (e) => {
e.preventDefault();
const domain = e.currentTarget.dataset.domain;
unblockDomain(domain);
});
}
tableBody.appendChild(row); tableBody.appendChild(row);
}); });
} }
@@ -613,6 +645,146 @@ function updateLogsStatsFromWebSocket(stats) {
} }
} }
// 拦截域名
async function blockDomain(domain) {
try {
console.log(`开始拦截域名: ${domain}`);
// 创建拦截规则使用AdBlock Plus格式
const blockRule = `||${domain}^`;
console.log(`创建的拦截规则: ${blockRule}`);
// 调用API添加拦截规则
console.log(`调用API添加拦截规则路径: /shield, 方法: POST`);
const response = await apiRequest('/shield', 'POST', { rule: blockRule });
console.log(`API响应:`, response);
// 处理不同的响应格式
if (response && (response.success || response.status === 'success')) {
// 重新加载日志,显示更新后的状态
loadLogs();
// 刷新规则列表
refreshRulesList();
// 显示成功通知
if (typeof window.showNotification === 'function') {
window.showNotification(`已成功拦截域名: ${domain}`, 'success');
}
} else {
const errorMsg = response ? (response.message || '添加拦截规则失败') : '添加拦截规则失败: 无效的API响应';
console.error(`拦截域名失败: ${errorMsg}`);
throw new Error(errorMsg);
}
} catch (error) {
console.error('拦截域名失败:', error);
// 显示错误通知
if (typeof window.showNotification === 'function') {
window.showNotification(`拦截域名失败: ${error.message}`, 'danger');
}
}
}
// 绑定操作按钮事件
function bindActionButtonsEvents() {
// 绑定拦截按钮事件
const blockBtns = document.querySelectorAll('.block-btn');
blockBtns.forEach(btn => {
btn.addEventListener('click', async (e) => {
e.preventDefault();
const domain = e.currentTarget.dataset.domain;
await blockDomain(domain);
});
});
// 绑定放行按钮事件
const unblockBtns = document.querySelectorAll('.unblock-btn');
unblockBtns.forEach(btn => {
btn.addEventListener('click', async (e) => {
e.preventDefault();
const domain = e.currentTarget.dataset.domain;
await unblockDomain(domain);
});
});
}
// 刷新规则列表
async function refreshRulesList() {
try {
// 调用API重新加载规则
const response = await apiRequest('/shield', 'GET');
if (response) {
// 处理规则列表响应
let allRules = [];
if (response && typeof response === 'object') {
// 合并所有类型的规则到一个数组
if (Array.isArray(response.domainRules)) allRules = allRules.concat(response.domainRules);
if (Array.isArray(response.domainExceptions)) allRules = allRules.concat(response.domainExceptions);
if (Array.isArray(response.regexRules)) allRules = allRules.concat(response.regexRules);
if (Array.isArray(response.regexExceptions)) allRules = allRules.concat(response.regexExceptions);
}
// 更新规则列表
if (window.rules) {
rules = allRules;
filteredRules = [...rules];
// 更新规则数量统计
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
window.updateRulesCount(rules.length);
}
}
}
} catch (error) {
console.error('刷新规则列表失败:', error);
}
}
// 放行域名
async function unblockDomain(domain) {
try {
console.log(`开始放行域名: ${domain}`);
// 创建放行规则使用AdBlock Plus格式
const allowRule = `@@||${domain}^`;
console.log(`创建的放行规则: ${allowRule}`);
// 调用API添加放行规则
console.log(`调用API添加放行规则路径: /shield, 方法: POST`);
const response = await apiRequest('/shield', 'POST', { rule: allowRule });
console.log(`API响应:`, response);
// 处理不同的响应格式
if (response && (response.success || response.status === 'success')) {
// 重新加载日志,显示更新后的状态
loadLogs();
// 刷新规则列表
refreshRulesList();
// 显示成功通知
if (typeof window.showNotification === 'function') {
window.showNotification(`已成功放行域名: ${domain}`, 'success');
}
} else {
const errorMsg = response ? (response.message || '添加放行规则失败') : '添加放行规则失败: 无效的API响应';
console.error(`放行域名失败: ${errorMsg}`);
throw new Error(errorMsg);
}
} catch (error) {
console.error('放行域名失败:', error);
// 显示错误通知
if (typeof window.showNotification === 'function') {
window.showNotification(`放行域名失败: ${error.message}`, 'danger');
}
}
}
// 定期更新日志统计数据(备用方案) // 定期更新日志统计数据(备用方案)
setInterval(() => { setInterval(() => {
// 只有在查询日志页面时才更新 // 只有在查询日志页面时才更新

View File

@@ -231,7 +231,7 @@ async function loadShieldStats() {
} }
} }
// 加载本地规则 // 加载自定义规则
async function loadLocalRules() { async function loadLocalRules() {
try { try {
const response = await fetch('/api/shield/localrules'); const response = await fetch('/api/shield/localrules');
@@ -242,7 +242,7 @@ async function loadLocalRules() {
const data = await response.json(); const data = await response.json();
// 更新本地规则数量显示 // 更新自定义规则数量显示
if (document.getElementById('local-rules-count')) { if (document.getElementById('local-rules-count')) {
document.getElementById('local-rules-count').textContent = data.localRulesCount || 0; document.getElementById('local-rules-count').textContent = data.localRulesCount || 0;
} }
@@ -250,7 +250,7 @@ async function loadLocalRules() {
// 设置当前规则类型 // 设置当前规则类型
currentRulesType = 'local'; currentRulesType = 'local';
// 合并所有本地规则 // 合并所有自定义规则
let rules = []; let rules = [];
// 添加域名规则 // 添加域名规则
if (Array.isArray(data.domainRules)) { if (Array.isArray(data.domainRules)) {
@@ -271,8 +271,8 @@ async function loadLocalRules() {
updateRulesTable(rules); updateRulesTable(rules);
} catch (error) { } catch (error) {
console.error('加载本地规则失败:', error); console.error('加载自定义规则失败:', error);
showNotification('加载本地规则失败', 'error'); showNotification('加载自定义规则失败', 'error');
} }
} }
@@ -1202,7 +1202,7 @@ let currentRulesType = 'local';
// 设置事件监听器 // 设置事件监听器
function setupShieldEventListeners() { function setupShieldEventListeners() {
// 本地规则管理事件 // 自定义规则管理事件
const saveRuleBtn = document.getElementById('save-rule-btn'); const saveRuleBtn = document.getElementById('save-rule-btn');
if (saveRuleBtn) { if (saveRuleBtn) {
saveRuleBtn.addEventListener('click', handleAddRule); saveRuleBtn.addEventListener('click', handleAddRule);
@@ -1214,7 +1214,7 @@ function setupShieldEventListeners() {
saveBlacklistBtn.addEventListener('click', handleAddBlacklist); saveBlacklistBtn.addEventListener('click', handleAddBlacklist);
} }
// 添加切换查看本地规则和远程规则的事件监听 // 添加切换查看自定义规则和远程规则的事件监听
const viewLocalRulesBtn = document.getElementById('view-local-rules-btn'); const viewLocalRulesBtn = document.getElementById('view-local-rules-btn');
if (viewLocalRulesBtn) { if (viewLocalRulesBtn) {
viewLocalRulesBtn.addEventListener('click', loadLocalRules); viewLocalRulesBtn.addEventListener('click', loadLocalRules);

52
temp_config.json Normal file
View File

@@ -0,0 +1,52 @@
{
"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
}
}