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:
69
main.go
69
main.go
@@ -343,7 +343,18 @@ func cacheWrite(key string, in *dns.Msg, maxTTL time.Duration) {
|
||||
}
|
||||
var ttl uint32
|
||||
var ok bool
|
||||
|
||||
// 判断负面响应(NXDOMAIN 或 NODATA)
|
||||
neg, isNodata := in.Rcode == dns.RcodeNameError, false
|
||||
if in.Rcode == dns.RcodeSuccess && len(in.Question) > 0 && !hasAnswerForType(in, in.Question[0]) {
|
||||
isNodata = true
|
||||
}
|
||||
|
||||
if ttl, ok = negativeTTL(in, maxTTL); !ok {
|
||||
if neg || isNodata {
|
||||
return // 负面但无 SOA → 不缓存
|
||||
}
|
||||
|
||||
minTTL, has := minRRsetTTL(in)
|
||||
if has {
|
||||
cfgTTL := uint32(maxTTL.Seconds())
|
||||
@@ -419,61 +430,85 @@ func queryUpstreamsLimited(
|
||||
cctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
type result struct{ msg *dns.Msg }
|
||||
type result struct {
|
||||
msg *dns.Msg
|
||||
}
|
||||
ch := make(chan result, len(servers))
|
||||
done := make(chan struct{}, len(servers))
|
||||
sem := make(chan struct{}, maxParallel)
|
||||
|
||||
// 单个上游执行
|
||||
execOne := func(svr string) {
|
||||
upReq := clampEDNSForUpstream(req, 1232) // 采用 1232 降低分片风险
|
||||
// 并发限流(可被超时取消)
|
||||
select {
|
||||
case sem <- struct{}{}:
|
||||
defer func() { <-sem }()
|
||||
case <-cctx.Done():
|
||||
// 超时/取消,直接放弃
|
||||
return
|
||||
}
|
||||
defer func() { done <- struct{}{} }()
|
||||
|
||||
// 为 UDP 上游把 EDNS UDP size 夹到 1232,降低分片风险
|
||||
upReq := clampEDNSForUpstream(req, 1232)
|
||||
|
||||
// 先走 UDP
|
||||
resp, _, err := udpClient.ExchangeContext(cctx, upReq, svr)
|
||||
// 截断且允许回退则走 TCP
|
||||
if err == nil && resp != nil && resp.Truncated && allowTCPFallback {
|
||||
log.Printf("[upstream] UDP truncated, retry TCP: %s", svr)
|
||||
tcpClient := *udpClient
|
||||
tcpClient.Net = "tcp"
|
||||
|
||||
resp, _, err = tcpClient.ExchangeContext(cctx, req.Copy(), svr)
|
||||
}
|
||||
// 失败直接返回(但不写入 ch);只在未超时情况下打印错误
|
||||
if err != nil || resp == nil {
|
||||
if err != nil && cctx.Err() == nil {
|
||||
log.Printf("[upstream] %s error: %v", svr, err)
|
||||
log.Printf("[upstream] %s: %v", svr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
// 丢弃对客户端无意义/不可靠的错误
|
||||
if resp.Rcode == dns.RcodeServerFailure || resp.Rcode == dns.RcodeRefused || resp.Rcode == dns.RcodeFormatError {
|
||||
// 过滤不可用的错误 RCODE(避免造成“假性超时”的错觉)
|
||||
if resp.Rcode == dns.RcodeServerFailure ||
|
||||
resp.Rcode == dns.RcodeRefused ||
|
||||
resp.Rcode == dns.RcodeFormatError {
|
||||
return
|
||||
}
|
||||
|
||||
// 投递可用结果(若已经超时则丢弃)
|
||||
select {
|
||||
case ch <- result{msg: resp}:
|
||||
case <-cctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// 并发发起
|
||||
for _, s := range servers {
|
||||
s := s
|
||||
go func() {
|
||||
select {
|
||||
case sem <- struct{}{}:
|
||||
defer func() { <-sem }()
|
||||
case <-cctx.Done():
|
||||
return
|
||||
}
|
||||
execOne(s)
|
||||
}()
|
||||
go execOne(s)
|
||||
}
|
||||
|
||||
for i := 0; i < len(servers); i++ {
|
||||
finished := 0
|
||||
total := len(servers)
|
||||
|
||||
// 聚合:首个可用响应直接返回;区分“真超时”与“无可用结果”
|
||||
for finished < total {
|
||||
select {
|
||||
case r := <-ch:
|
||||
if r.msg != nil {
|
||||
cancel()
|
||||
return r.msg
|
||||
}
|
||||
case <-done:
|
||||
finished++
|
||||
case <-cctx.Done():
|
||||
log.Printf("[upstream] timeout after %v", timeout)
|
||||
log.Printf("[upstream] timeout after %v (finished=%d/%d)", timeout, finished, total)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 所有上游都结束,但没有一个可用
|
||||
log.Printf("[upstream] no acceptable upstream response (finished=%d/%d)", finished, total)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user