From 30d47da02272f0e5a7e38bd9eabdb6f20e386c36 Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Sun, 21 Dec 2025 21:11:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A7=84=E5=88=99=E4=BC=98?= =?UTF-8?q?=E5=85=88=E7=BA=A7=E9=97=AE=E9=A2=98,=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A=E4=B9=89=E8=A7=84=E5=88=99?= =?UTF-8?q?=E5=90=8E=E9=9C=80=E8=A6=81=E9=87=8D=E5=90=AF=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++ README.md | 4 +- config.json | 26 +++--- dns/server.go | 8 +- http/server.go | 6 ++ main.go | 6 +- shield/manager.go | 170 ++++++++++++++++++++++++++++-------- static/api/js/index.js | 16 ++-- static/index.html | 9 +- static/js/logs.js | 171 +++++++++++++++++++++++++++++++++++++ static/js/shield.js | 14 +-- temp_config.json | 52 +++++++++++ test/test_rule_matching.go | 77 ----------------- test_config.json | 40 --------- test_console.sh | 45 ---------- test_query_modes.sh | 96 --------------------- 16 files changed, 410 insertions(+), 336 deletions(-) create mode 100644 temp_config.json delete mode 100644 test/test_rule_matching.go delete mode 100644 test_config.json delete mode 100755 test_console.sh delete mode 100644 test_query_modes.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index f2bf328..fee0a06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ 所有对本项目的显著更改都将记录在此文件中。 +## [1.1.4] - 2025-12-21 + +### 修复 +- 修复规则优先级问题:确保自定义规则优先于远程规则 +- 修复添加自定义规则后需要重启服务器的问题:通过在添加或删除规则后清空DNS缓存实现 + ## [1.1.3] - 2025-12-19 ### 移除 diff --git a/README.md b/README.md index 4090699..701b6e6 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ - 支持域名规则和正则表达式规则 - 支持规则例外 - 支持远程规则列表 -- 支持本地规则管理 +- 支持自定义规则管理 ### 3. 查询日志记录和统计 - 实时记录DNS查询日志 @@ -116,7 +116,7 @@ http://localhost:8080 1. 登录Web控制台 2. 点击左侧菜单中的"屏蔽管理" -3. 在"本地规则管理"中添加或删除规则 +3. 在"自定义规则管理"中添加或删除规则 4. 在"远程黑名单管理"中添加或删除远程规则列表 ### 查看查询日志 diff --git a/config.json b/config.json index 328ade1..cb705b5 100644 --- a/config.json +++ b/config.json @@ -2,7 +2,7 @@ "dns": { "port": 53, "upstreamDNS": [ - "10.35.10.200:53" + "223.5.5.5:53" ], "dnssecUpstreamDNS": [ "117.50.10.10:53", @@ -18,10 +18,16 @@ "enableDNSSEC": true, "queryMode": "loadbalance", "domainSpecificDNS": { - "amazehome.cn": [ + "addr.arpa": [ "10.35.10.200:53" ], - "addr.arpa": [ + "akadns": [ + "4.2.2.1:53" + ], + "akamai": [ + "4.2.2.1:53" + ], + "amazehome.cn": [ "10.35.10.200:53" ], "amazehome.xyz": [ @@ -29,14 +35,7 @@ ], "microsoft.com": [ "4.2.2.1:53" - ], - "akamai": [ - "4.2.2.1:53" - ], - "akadns": [ - "4.2.2.1:53" ] - }, "noDNSSECDomains": [ "amazehome.cn", @@ -59,7 +58,7 @@ "name": "AdGuard DNS filter", "url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt", "enabled": true, - "lastUpdateTime": "2025-11-28T16:13:03.564Z" + "lastUpdateTime": "2025-12-21T10:46:36.629Z" }, { "name": "Adaway Default Blocklist", @@ -92,7 +91,8 @@ { "name": "Hate \u0026 Junk", "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", @@ -152,4 +152,4 @@ "maxBackups": 10, "maxAge": 30 } -} +} \ No newline at end of file diff --git a/dns/server.go b/dns/server.go index ed70e1b..431e64f 100644 --- a/dns/server.go +++ b/dns/server.go @@ -123,7 +123,7 @@ type Server struct { ipGeolocationCacheTTL time.Duration // 缓存有效期 // DNS查询缓存 - dnsCache *DNSCache // DNS响应缓存 + DnsCache *DNSCache // DNS响应缓存 // 域名DNSSEC状态映射表 domainDNSSECStatus map[string]bool // 域名到DNSSEC状态的映射 @@ -199,7 +199,7 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie ipGeolocationCache: make(map[string]*IPGeolocation), ipGeolocationCacheTTL: 24 * time.Hour, // 缓存有效期24小时 // DNS查询缓存初始化 - dnsCache: NewDNSCache(cacheTTL), + DnsCache: NewDNSCache(cacheTTL), // 初始化域名DNSSEC状态映射表 domainDNSSECStatus: make(map[string]bool), // 初始化服务器状态跟踪 @@ -419,7 +419,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { var cachedDNSSEC bool // 1. 首先检查是否有普通缓存项 - if tempResponse, tempFound := s.dnsCache.Get(r.Question[0].Name, qType); tempFound { + if tempResponse, tempFound := s.DnsCache.Get(r.Question[0].Name, qType); tempFound { cachedResponse = tempResponse found = tempFound cachedDNSSEC = s.hasDNSSECRecords(tempResponse) @@ -562,7 +562,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) { responseCopy := response.Copy() // 设置合理的TTL,不超过默认的30分钟 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, "dnssec", responseDNSSEC) } diff --git a/http/server.go b/http/server.go index 669e0d0..d1f0dc9 100644 --- a/http/server.go +++ b/http/server.go @@ -832,6 +832,9 @@ func (s *Server) handleShield(w http.ResponseWriter, r *http.Request) { return } + // 清空DNS缓存,使新规则立即生效 + s.dnsServer.DnsCache.Clear() + json.NewEncoder(w).Encode(map[string]string{"status": "success"}) return case http.MethodDelete: @@ -850,6 +853,9 @@ func (s *Server) handleShield(w http.ResponseWriter, r *http.Request) { return } + // 清空DNS缓存,使规则变更立即生效 + s.dnsServer.DnsCache.Clear() + json.NewEncoder(w).Encode(map[string]string{"status": "success"}) return case http.MethodPut: diff --git a/main.go b/main.go index eb249f9..20bfef7 100644 --- a/main.go +++ b/main.go @@ -121,10 +121,10 @@ func createRequiredFiles(cfg *config.Config) error { } } - // 创建本地规则文件 + // 创建自定义规则文件 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 { - return fmt.Errorf("创建本地规则文件失败: %w", err) + if err := os.WriteFile(cfg.Shield.LocalRulesFile, []byte("# 自定义规则文件\n# 格式:域名\n# 例如:example.com\n"), 0644); err != nil { + return fmt.Errorf("创建自定义规则文件失败: %w", err) } } diff --git a/shield/manager.go b/shield/manager.go index 18c0463..44eae44 100644 --- a/shield/manager.go +++ b/shield/manager.go @@ -30,7 +30,7 @@ type ShieldStatsData struct { type regexRule struct { pattern *regexp.Regexp original string - isLocal bool // 是否为本地规则 + isLocal bool // 是否为自定义规则 source string // 规则来源 } @@ -39,8 +39,8 @@ type ShieldManager struct { config *config.ShieldConfig domainRules map[string]bool domainExceptions map[string]bool - domainRulesIsLocal map[string]bool // 标记域名规则是否为本地规则 - domainExceptionsIsLocal map[string]bool // 标记域名排除规则是否为本地规则 + domainRulesIsLocal map[string]bool // 标记域名规则是否为自定义规则 + domainExceptionsIsLocal map[string]bool // 标记域名排除规则是否为自定义规则 domainRulesSource map[string]string // 标记域名规则来源 domainExceptionsSource map[string]string // 标记域名排除规则来源 domainRulesOriginal map[string]string // 存储域名规则的原始字符串 @@ -54,7 +54,7 @@ type ShieldManager struct { updateCtx context.Context updateCancel context.CancelFunc updateRunning bool - localRulesCount int // 本地规则数量 + localRulesCount int // 自定义规则数量 remoteRulesCount int // 远程规则数量 } @@ -109,9 +109,9 @@ func (m *ShieldManager) LoadRules() error { m.remoteRulesCount = 0 // 保留计数数据,不随规则重新加载而清空 - // 加载本地规则文件 + // 加载自定义规则文件 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 } -// loadLocalRules 加载本地规则文件 +// loadLocalRules 加载自定义规则文件 func (m *ShieldManager) loadLocalRules() error { if m.config.LocalRulesFile == "" { return nil @@ -144,7 +144,7 @@ func (m *ShieldManager) loadLocalRules() error { } defer file.Close() - // 记录加载前的规则数量,用于计算本地规则数量 + // 记录加载前的规则数量,用于计算自定义规则数量 beforeDomainRules := len(m.domainRules) beforeRegexRules := len(m.regexRules) @@ -154,10 +154,10 @@ func (m *ShieldManager) loadLocalRules() error { if line == "" || strings.HasPrefix(line, "#") { continue } - m.parseRule(line, true, "本地规则") // 本地规则,isLocal=true,来源为"本地规则" + m.parseRule(line, true, "自定义规则") // 自定义规则,isLocal=true,来源为"自定义规则" } - // 更新本地规则计数 + // 更新自定义规则计数 m.localRulesCount = (len(m.domainRules) - beforeDomainRules) + (len(m.regexRules) - beforeRegexRules) return scanner.Err() @@ -445,10 +445,16 @@ func (m *ShieldManager) parseRuleOptions(optionsStr string) map[string]string { // addDomainRule 添加域名规则,支持是否为阻止规则 func (m *ShieldManager) addDomainRule(domain string, block bool, isLocal bool, source string, original string) { if block { - // 如果是远程规则,检查是否已经存在本地规则,如果存在则不覆盖 + // 如果是远程规则,检查是否已经存在自定义规则(阻止或排除),如果存在则不覆盖 if !isLocal { + // 检查是否存在自定义阻止规则 if _, exists := m.domainRulesIsLocal[domain]; exists && m.domainRulesIsLocal[domain] { - // 已经存在本地规则,不覆盖 + // 已经存在自定义规则,不覆盖 + return + } + // 检查是否存在自定义排除规则 + if _, exists := m.domainExceptionsIsLocal[domain]; exists && m.domainExceptionsIsLocal[domain] { + // 已经存在自定义规则,不覆盖 return } } @@ -458,10 +464,16 @@ func (m *ShieldManager) addDomainRule(domain string, block bool, isLocal bool, s m.domainRulesOriginal[domain] = original } else { // 添加到排除规则 - // 如果是远程规则,检查是否已经存在本地规则,如果存在则不覆盖 + // 如果是远程规则,检查是否已经存在自定义规则(阻止或排除),如果存在则不覆盖 if !isLocal { + // 检查是否存在自定义阻止规则 + if _, exists := m.domainRulesIsLocal[domain]; exists && m.domainRulesIsLocal[domain] { + // 已经存在自定义规则,不覆盖 + return + } + // 检查是否存在自定义排除规则 if _, exists := m.domainExceptionsIsLocal[domain]; exists && m.domainExceptionsIsLocal[domain] { - // 已经存在本地规则,不覆盖 + // 已经存在自定义规则,不覆盖 return } } @@ -481,11 +493,19 @@ func (m *ShieldManager) addRegexRule(re *regexp.Regexp, original string, block b source: source, } if block { - // 如果是远程规则,检查是否已经存在相同的本地规则,如果存在则不添加 + // 如果是远程规则,检查是否已经存在任何自定义规则,如果存在则不添加 if !isLocal { + // 检查是否存在自定义阻止规则 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 } } @@ -493,11 +513,19 @@ func (m *ShieldManager) addRegexRule(re *regexp.Regexp, original string, block b m.regexRules = append(m.regexRules, rule) } else { // 添加到排除规则 - // 如果是远程规则,检查是否已经存在相同的本地规则,如果存在则不添加 + // 如果是远程规则,检查是否已经存在任何自定义规则,如果存在则不添加 if !isLocal { + // 检查是否存在自定义阻止规则 + for _, existingRule := range m.regexRules { + if existingRule.pattern.String() == re.String() && existingRule.isLocal { + // 已经存在相同的自定义阻止规则,不添加 + return + } + } + // 检查是否存在自定义排除规则 for _, existingRule := range m.regexExceptions { - if existingRule.original == original && existingRule.isLocal { - // 已经存在相同的本地规则,不添加 + if existingRule.pattern.String() == re.String() && existingRule.isLocal { + // 已经存在相同的自定义排除规则,不添加 return } } @@ -537,8 +565,17 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf result["hostsIP"] = hostsIP // 检查排除规则(优先级最高) - // 检查域名排除规则 - if m.domainExceptions[domain] { + // 1. 先检查本地域名排除规则 + 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["excludeRule"] = m.domainExceptionsOriginal[domain] result["excludeRuleType"] = "exact_domain" @@ -548,9 +585,11 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf // 检查子域名排除规则 parts := strings.Split(domain, ".") + + // 3. 先检查本地子域名排除规则 for i := 0; i < len(parts)-1; i++ { subdomain := strings.Join(parts[i:], ".") - if m.domainExceptions[subdomain] { + if m.domainExceptions[subdomain] && m.domainExceptionsIsLocal[subdomain] { result["excluded"] = true result["excludeRule"] = m.domainExceptionsOriginal[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 { - 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["excludeRule"] = re.original result["excludeRuleType"] = "regex" @@ -571,8 +633,17 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf } // 检查阻止规则 - 先检查精确域名匹配,再检查子域名匹配 - // 检查精确域名匹配 - if m.domainRules[domain] { + // 7. 先检查本地域名阻止规则 + 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["blockRule"] = m.domainRulesOriginal[domain] result["blockRuleType"] = "exact_domain" @@ -582,9 +653,11 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf // 检查子域名匹配(AdGuardHome风格) // 从最长的子域名开始匹配,确保优先级正确 + + // 9. 先检查本地子域名阻止规则 for i := 0; i < len(parts)-1; i++ { subdomain := strings.Join(parts[i:], ".") - if m.domainRules[subdomain] { + if m.domainRules[subdomain] && m.domainRulesIsLocal[subdomain] { result["blocked"] = true result["blockRule"] = m.domainRulesOriginal[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 { - 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["blockRule"] = re.original result["blockRuleType"] = "regex" @@ -722,13 +818,13 @@ func (m *ShieldManager) GetHostsIP(domain string) (string, bool) { return ip, exists } -// AddRule 添加屏蔽规则,用户添加的规则是本地规则 +// AddRule 添加屏蔽规则,用户添加的规则是自定义规则 func (m *ShieldManager) AddRule(rule string) error { m.rulesMutex.Lock() defer m.rulesMutex.Unlock() - // 解析并添加规则到内存,isLocal=true表示本地规则,来源为"本地规则" - m.parseRule(rule, true, "本地规则") + // 解析并添加规则到内存,isLocal=true表示自定义规则,来源为"自定义规则" + m.parseRule(rule, true, "自定义规则") // 持久化保存规则到文件 if m.config.LocalRulesFile != "" { @@ -957,7 +1053,7 @@ func (m *ShieldManager) StopAutoUpdate() { logger.Info("规则自动更新已停止") } -// saveRulesToFile 保存规则到文件,只保存本地规则 +// saveRulesToFile 保存规则到文件,只保存自定义规则 func (m *ShieldManager) saveRulesToFile() error { var rules []string @@ -1293,12 +1389,12 @@ func (m *ShieldManager) GetHostsCount() int { return len(m.hostsMap) } -// GetLocalRules 获取仅本地规则 +// GetLocalRules 获取仅自定义规则 func (m *ShieldManager) GetLocalRules() map[string]interface{} { m.rulesMutex.RLock() defer m.rulesMutex.RUnlock() - // 转换map和slice为字符串列表,只包含本地规则 + // 转换map和slice为字符串列表,只包含自定义规则 domainRulesList := make([]string, 0) for domain, isLocal := range m.domainRulesIsLocal { if isLocal && m.domainRules[domain] { @@ -1329,7 +1425,7 @@ func (m *ShieldManager) GetLocalRules() map[string]interface{} { } } - // 计算本地规则数量 + // 计算自定义规则数量 localDomainRulesCount := 0 for _, isLocal := range m.domainRulesIsLocal { if isLocal { diff --git a/static/api/js/index.js b/static/api/js/index.js index ea876cc..94fa52e 100644 --- a/static/api/js/index.js +++ b/static/api/js/index.js @@ -1138,12 +1138,12 @@ }, "/shield/localrules": { "get": { - "summary": "获取本地规则", - "description": "获取Shield的本地规则列表。", + "summary": "获取自定义规则", + "description": "获取Shield的自定义规则列表。" "tags": ["shield"], "responses": { "200": { - "description": "成功获取本地规则", + "description": "成功获取自定义规则", "content": { "application/json": { "schema": { @@ -1166,8 +1166,8 @@ } }, "post": { - "summary": "添加本地规则", - "description": "添加新的本地规则。", + "summary": "添加自定义规则", + "description": "添加新的自定义规则。", "tags": ["shield"], "requestBody": { "required": true, @@ -1205,8 +1205,8 @@ } }, "delete": { - "summary": "删除本地规则", - "description": "删除指定ID的本地规则。", + "summary": "删除自定义规则", + "description": "删除指定ID的自定义规则。", "tags": ["shield"], "parameters": [ { @@ -1500,7 +1500,7 @@ "blocked": true, "blockRule": "example.com", "blockRuleType": "exact_domain", - "blocksource": "本地规则", + "blocksource": "自定义规则", "excluded": false, "excludeRule": "", "excludeRuleType": "", diff --git a/static/index.html b/static/index.html index 363371e..f36953f 100644 --- a/static/index.html +++ b/static/index.html @@ -630,9 +630,9 @@ - +
-

本地规则管理

+

自定义规则管理

@@ -976,11 +976,12 @@ 响应时间 屏蔽规则 + 操作 - +
暂无查询日志
@@ -1056,7 +1057,7 @@

屏蔽配置

- +
diff --git a/static/js/logs.js b/static/js/logs.js index ce445ea..4f13ca1 100644 --- a/static/js/logs.js +++ b/static/js/logs.js @@ -282,6 +282,9 @@ function loadLogs() { // 更新日志表格 updateLogsTable(logs); + // 绑定操作按钮事件 + bindActionButtonsEvents(); + // 更新分页信息 updateLogsPagination(); @@ -388,6 +391,9 @@ function updateLogsTable(logs) { const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500'; const cacheStatusText = log.FromCache ? '缓存' : '非缓存'; + // 检查域名是否被拦截 + const isBlocked = log.Result === 'blocked'; + row.innerHTML = `
${formattedTime}
@@ -404,7 +410,32 @@ function updateLogsTable(logs) { ${log.ResponseTime}ms ${log.BlockRule || '-'} + + ${isBlocked ? + `` : + `` + } + `; + + // 绑定按钮事件 + 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); }); @@ -614,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(() => { // 只有在查询日志页面时才更新 diff --git a/static/js/shield.js b/static/js/shield.js index 4ece01e..a9ec599 100644 --- a/static/js/shield.js +++ b/static/js/shield.js @@ -231,7 +231,7 @@ async function loadShieldStats() { } } -// 加载本地规则 +// 加载自定义规则 async function loadLocalRules() { try { const response = await fetch('/api/shield/localrules'); @@ -242,7 +242,7 @@ async function loadLocalRules() { const data = await response.json(); - // 更新本地规则数量显示 + // 更新自定义规则数量显示 if (document.getElementById('local-rules-count')) { document.getElementById('local-rules-count').textContent = data.localRulesCount || 0; } @@ -250,7 +250,7 @@ async function loadLocalRules() { // 设置当前规则类型 currentRulesType = 'local'; - // 合并所有本地规则 + // 合并所有自定义规则 let rules = []; // 添加域名规则 if (Array.isArray(data.domainRules)) { @@ -271,8 +271,8 @@ async function loadLocalRules() { updateRulesTable(rules); } catch (error) { - console.error('加载本地规则失败:', error); - showNotification('加载本地规则失败', 'error'); + console.error('加载自定义规则失败:', error); + showNotification('加载自定义规则失败', 'error'); } } @@ -1202,7 +1202,7 @@ let currentRulesType = 'local'; // 设置事件监听器 function setupShieldEventListeners() { - // 本地规则管理事件 + // 自定义规则管理事件 const saveRuleBtn = document.getElementById('save-rule-btn'); if (saveRuleBtn) { saveRuleBtn.addEventListener('click', handleAddRule); @@ -1214,7 +1214,7 @@ function setupShieldEventListeners() { saveBlacklistBtn.addEventListener('click', handleAddBlacklist); } - // 添加切换查看本地规则和远程规则的事件监听 + // 添加切换查看自定义规则和远程规则的事件监听 const viewLocalRulesBtn = document.getElementById('view-local-rules-btn'); if (viewLocalRulesBtn) { viewLocalRulesBtn.addEventListener('click', loadLocalRules); diff --git a/temp_config.json b/temp_config.json new file mode 100644 index 0000000..237b7c2 --- /dev/null +++ b/temp_config.json @@ -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 + } +} \ No newline at end of file diff --git a/test/test_rule_matching.go b/test/test_rule_matching.go deleted file mode 100644 index ee91708..0000000 --- a/test/test_rule_matching.go +++ /dev/null @@ -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)) - } -} diff --git a/test_config.json b/test_config.json deleted file mode 100644 index 740725d..0000000 --- a/test_config.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "dns": { - "port": 5353, - "upstreamDNS": [ - "223.5.5.5:53", - "223.6.6.6:53" - ], - "timeout": 5000, - "statsFile": "data/test_stats.json", - "saveInterval": 300, - "cacheTTL": 30, - "enableDNSSEC": true, - "dnssecValidation": true - }, - "http": { - "port": 8081, - "host": "0.0.0.0", - "enableAPI": true, - "username": "admin", - "password": "admin" - }, - "shield": { - "localRulesFile": "data/test_rules.txt", - "blacklists": [], - "updateInterval": 3600, - "hostsFile": "data/test_hosts.txt", - "blockMethod": "NXDOMAIN", - "customBlockIP": "", - "statsFile": "./data/test_shield_stats.json", - "statsSaveInterval": 60, - "remoteRulesCacheDir": "data/test_remote_rules" - }, - "log": { - "file": "logs/test_dns-server.log", - "level": "debug", - "maxSize": 100, - "maxBackups": 10, - "maxAge": 30 - } -} \ No newline at end of file diff --git a/test_console.sh b/test_console.sh deleted file mode 100755 index e88a657..0000000 --- a/test_console.sh +++ /dev/null @@ -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" \ No newline at end of file diff --git a/test_query_modes.sh b/test_query_modes.sh deleted file mode 100644 index 38cbbee..0000000 --- a/test_query_modes.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/bash - -# 测试三种查询模式 - -echo "=== 测试三种查询模式 ===" - -# 确保dig命令存在 -if ! command -v dig &> /dev/null; then - echo "dig命令不存在,使用nslookup替代" - USE_DIG=false -else - USE_DIG=true -fi - -echo "1. 测试并行请求模式" -echo "修改配置文件为parallel模式" -sed -i 's/"queryMode": "[^"]*"/"queryMode": "parallel"/' config.json -cat config.json | grep queryMode - -echo "重启DNS服务器..." -pkill -f "go run main.go" -sleep 2 -go run main.go > server.log 2>&1 & -SERVER_PID=$! -echo "服务器进程ID: $SERVER_PID" -sleep 5 - -if [ -f server.log ]; then - tail -5 server.log -fi - -echo "测试DNS查询..." -if $USE_DIG; then - dig @localhost example.com +short -else - nslookup example.com localhost | grep -A 2 "Name:" -fi - -echo "2. 测试负载均衡模式" -echo "修改配置文件为loadbalance模式" -sed -i 's/"queryMode": "[^"]*"/"queryMode": "loadbalance"/' config.json -cat config.json | grep queryMode - -echo "重启DNS服务器..." -pkill -f "go run main.go" -sleep 2 -go run main.go > server.log 2>&1 & -SERVER_PID=$! -echo "服务器进程ID: $SERVER_PID" -sleep 5 - -if [ -f server.log ]; then - tail -5 server.log -fi - -echo "测试DNS查询..." -if $USE_DIG; then - dig @localhost example.com +short -else - nslookup example.com localhost | grep -A 2 "Name:" -fi - -echo "3. 测试最快的IP地址模式" -echo "修改配置文件为fastest-ip模式" -sed -i 's/"queryMode": "[^"]*"/"queryMode": "fastest-ip"/' config.json -cat config.json | grep queryMode - -echo "重启DNS服务器..." -pkill -f "go run main.go" -sleep 2 -go run main.go > server.log 2>&1 & -SERVER_PID=$! -echo "服务器进程ID: $SERVER_PID" -sleep 5 - -if [ -f server.log ]; then - tail -5 server.log -fi - -echo "测试DNS查询..." -if $USE_DIG; then - dig @localhost example.com +short -else - nslookup example.com localhost | grep -A 2 "Name:" -fi - -echo "=== 测试完成 ===" -echo "停止DNS服务器..." -pkill -f "go run main.go" - -# 删除日志文件 -rm -f server.log - -echo "恢复默认配置..." -sed -i 's/"queryMode": "[^"]*"/"queryMode": "parallel"/' config.json -cat config.json | grep queryMode