feat(blacklist): 支持国际化域名与 hosts 文件风格黑名单
- 引入 `golang.org/x/net/idna` 实现 Unicode 域名转 ASCII(Punycode) - 黑名单加载支持通配符格式如 `*.example.com` - 支持解析 hosts 风格的文件(每行首列为 IP 地址时,其余列为域名) - 扩展 Scanner 缓冲区至 2MB 以适应大型 hosts 文件 - 注释处理优化,兼容 `#` 和 `;` 分隔符 - 加载后对规则排序并去重,提升匹配效率与一致性 fix(cache): 调整负面响应缓存逻辑与上游查询并发控制 - 明确区分 NXDOMAIN 与 NODATA 并正确处理 SOA 缺失情况 - 查询上游时引入更可靠的并发限制与超时机制 - UDP 截断时自动回退 TCP 查询 - 过滤无效 RCODE(如 SERVFAIL、REFUSED 等),防止污染结果 - 区分“全部失败”与“部分完成但无有效响应”,增强调试日志信息
This commit is contained in:
37
blacklist.go
37
blacklist.go
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// -------- Blacklist helpers (独立文件) --------
|
||||
@@ -22,7 +23,14 @@ func canonicalFQDN(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
// 允许黑名单写 "*.example.com";内部匹配用裸后缀
|
||||
s = strings.TrimPrefix(s, "*.")
|
||||
|
||||
// 先把可能的中文/Unicode 域名转成 ASCII(punycode),再规范化
|
||||
if a, err := idna.Lookup.ToASCII(s); err == nil {
|
||||
s = a
|
||||
}
|
||||
// CanonicalName 会做小写化与尾点规范化
|
||||
return dns.CanonicalName(s)
|
||||
}
|
||||
|
||||
@@ -43,38 +51,38 @@ func uniqueStrings(in []string) []string {
|
||||
}
|
||||
|
||||
// 支持 # / ; 注释;每行一个域名;支持以 "*.example.com" 书写
|
||||
// 支持 # / ; 注释;每行一个域名;支持以 "*.example.com" 书写;支持 hosts 风格(首列为 IP)
|
||||
func loadBlacklistFile(path string) ([]string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sc := bufio.NewScanner(f)
|
||||
// 默认 64KB 容量不够稳妥,这里放大到 2MB,兼容一些合并的大 hosts 列表
|
||||
sc.Buffer(make([]byte, 0, 64*1024), 2*1024*1024)
|
||||
|
||||
var rules []string
|
||||
for sc.Scan() {
|
||||
line := strings.TrimSpace(sc.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// 行首注释
|
||||
if strings.HasPrefix(line, "//") || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
|
||||
continue
|
||||
}
|
||||
// 行内注释:先 // 再 # ;
|
||||
if i := strings.Index(line, "//"); i >= 0 {
|
||||
line = strings.TrimSpace(line[:i])
|
||||
}
|
||||
// 去掉注释(# 或 ; 之后的内容)
|
||||
if i := strings.IndexAny(line, "#;"); i >= 0 {
|
||||
line = strings.TrimSpace(line[:i])
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// hosts 风格:第一个字段是 IP,则其余每个字段视为域名
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// hosts 风格:第一个字段是 IP,则其余每个字段视为域名
|
||||
start := 0
|
||||
if net.ParseIP(fields[0]) != nil {
|
||||
start = 1
|
||||
@@ -88,7 +96,10 @@ func loadBlacklistFile(path string) ([]string, error) {
|
||||
if err := sc.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uniqueStrings(rules), nil
|
||||
|
||||
sort.Strings(rules)
|
||||
rules = uniqueStrings(rules)
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
// 自动重载黑名单
|
||||
|
||||
Reference in New Issue
Block a user