182 lines
6.4 KiB
Markdown
182 lines
6.4 KiB
Markdown
## 问题分析
|
||
|
||
通过深入分析代码,我找到了导致所有查询都显示同一个 NXDOMAIN 错误的根本原因:
|
||
|
||
**核心问题**:`mergeResponses` 函数在合并多个 DNS 响应时,**没有正确处理 Rcode 字段**!
|
||
|
||
**具体原因**:
|
||
1. 当使用并行查询模式时,DNS 服务器会向多个上游服务器发送请求
|
||
2. 函数使用第一个响应作为基础来合并其他响应
|
||
3. 它清空了 `Answer`、`Ns` 和 `Extra` 字段,但**保留了第一个响应的 Rcode**
|
||
4. 如果第一个响应返回 NXDOMAIN(比如对于恶意域名 www.evilsnssdk.com),那么**合并后的响应也会保持 NXDOMAIN 状态**,即使其他响应返回成功
|
||
5. 这导致所有查询都显示同一个 NXDOMAIN 错误
|
||
|
||
## 修复方案
|
||
|
||
### 修复 `mergeResponses` 函数
|
||
|
||
**关键修改点**:
|
||
1. **重置 Rcode**:在合并响应前,将 Rcode 重置为成功状态
|
||
2. **处理 NXDOMAIN**:只有当所有响应都是 NXDOMAIN 时,才返回 NXDOMAIN
|
||
3. **优先使用成功响应**:如果有任何响应返回成功,就使用成功的 Rcode
|
||
|
||
### 修复步骤
|
||
|
||
1. **修改 `mergeResponses` 函数** (`/root/dns/dns/server.go:842-933`)
|
||
- 在合并记录前,将 `mergedResponse.Rcode` 设置为 `dns.RcodeSuccess`
|
||
- 添加变量 `allNXDOMAIN` 来跟踪是否所有响应都是 NXDOMAIN
|
||
- 遍历所有响应,检查是否有成功响应
|
||
- 如果所有响应都是 NXDOMAIN,才将 `mergedResponse.Rcode` 设置为 `dns.RcodeNameError`
|
||
|
||
2. **优化合并逻辑**
|
||
- 确保优先使用成功响应中的记录
|
||
- 避免将 NXDOMAIN 响应的记录合并到成功响应中
|
||
- 保持响应的一致性,Rcode 与记录内容匹配
|
||
|
||
## 修复代码
|
||
|
||
```go
|
||
// mergeResponses 合并多个DNS响应
|
||
func mergeResponses(responses []*dns.Msg) *dns.Msg {
|
||
if len(responses) == 0 {
|
||
return nil
|
||
}
|
||
|
||
// 如果只有一个响应,直接返回,避免不必要的合并操作
|
||
if len(responses) == 1 {
|
||
return responses[0].Copy()
|
||
}
|
||
|
||
// 使用第一个响应作为基础
|
||
mergedResponse := responses[0].Copy()
|
||
mergedResponse.Answer = []dns.RR{}
|
||
mergedResponse.Ns = []dns.RR{}
|
||
mergedResponse.Extra = []dns.RR{}
|
||
|
||
// 重置Rcode为成功,除非所有响应都是NXDOMAIN
|
||
mergedResponse.Rcode = dns.RcodeSuccess
|
||
|
||
// 检查是否所有响应都是NXDOMAIN
|
||
allNXDOMAIN := true
|
||
|
||
// 收集所有成功响应的记录
|
||
for _, resp := range responses {
|
||
if resp == nil {
|
||
continue
|
||
}
|
||
|
||
// 如果有任何响应是成功的,就不是allNXDOMAIN
|
||
if resp.Rcode == dns.RcodeSuccess {
|
||
allNXDOMAIN = false
|
||
}
|
||
}
|
||
|
||
// 如果所有响应都是NXDOMAIN,设置合并响应为NXDOMAIN
|
||
if allNXDOMAIN {
|
||
mergedResponse.Rcode = dns.RcodeNameError
|
||
}
|
||
|
||
// 使用map存储唯一记录,选择最长TTL
|
||
// 预分配map容量,减少扩容开销
|
||
answerMap := make(map[recordKey]dns.RR, len(responses[0].Answer)*len(responses))
|
||
nsMap := make(map[recordKey]dns.RR, len(responses[0].Ns)*len(responses))
|
||
extraMap := make(map[recordKey]dns.RR, len(responses[0].Extra)*len(responses))
|
||
|
||
for _, resp := range responses {
|
||
if resp == nil {
|
||
continue
|
||
}
|
||
|
||
// 只合并与最终Rcode匹配的响应记录
|
||
if (mergedResponse.Rcode == dns.RcodeSuccess && resp.Rcode == dns.RcodeSuccess) ||
|
||
(mergedResponse.Rcode == dns.RcodeNameError && resp.Rcode == dns.RcodeNameError) {
|
||
|
||
// 合并Answer部分
|
||
for _, rr := range resp.Answer {
|
||
key := getRecordKey(rr)
|
||
if existing, exists := answerMap[key]; exists {
|
||
// 如果存在相同记录,选择TTL更长的
|
||
if rr.Header().Ttl > existing.Header().Ttl {
|
||
answerMap[key] = rr
|
||
}
|
||
} else {
|
||
answerMap[key] = rr
|
||
}
|
||
}
|
||
|
||
// 合并Ns部分
|
||
for _, rr := range resp.Ns {
|
||
key := getRecordKey(rr)
|
||
if existing, exists := nsMap[key]; exists {
|
||
// 如果存在相同记录,选择TTL更长的
|
||
if rr.Header().Ttl > existing.Header().Ttl {
|
||
nsMap[key] = rr
|
||
}
|
||
} else {
|
||
nsMap[key] = rr
|
||
}
|
||
}
|
||
|
||
// 合并Extra部分
|
||
for _, rr := range resp.Extra {
|
||
// 跳过OPT记录,避免重复
|
||
if rr.Header().Rrtype == dns.TypeOPT {
|
||
continue
|
||
}
|
||
key := getRecordKey(rr)
|
||
if existing, exists := extraMap[key]; exists {
|
||
// 如果存在相同记录,选择TTL更长的
|
||
if rr.Header().Ttl > existing.Header().Ttl {
|
||
extraMap[key] = rr
|
||
}
|
||
} else {
|
||
extraMap[key] = rr
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 预分配切片容量,减少扩容开销
|
||
mergedResponse.Answer = make([]dns.RR, 0, len(answerMap))
|
||
mergedResponse.Ns = make([]dns.RR, 0, len(nsMap))
|
||
mergedResponse.Extra = make([]dns.RR, 0, len(extraMap))
|
||
|
||
// 将map转换回切片
|
||
for _, rr := range answerMap {
|
||
mergedResponse.Answer = append(mergedResponse.Answer, rr)
|
||
}
|
||
|
||
for _, rr := range nsMap {
|
||
mergedResponse.Ns = append(mergedResponse.Ns, rr)
|
||
}
|
||
|
||
for _, rr := range extraMap {
|
||
mergedResponse.Extra = append(mergedResponse.Extra, rr)
|
||
}
|
||
|
||
return mergedResponse
|
||
}```
|
||
|
||
## 预期效果
|
||
|
||
修复后,DNS服务器将能够:
|
||
- 正确合并多个 DNS 响应
|
||
- 确保 Rcode 与实际记录内容匹配
|
||
- 只有当所有响应都是 NXDOMAIN 时才返回 NXDOMAIN
|
||
- 避免单个恶意域名影响所有查询结果
|
||
- 正确显示各个域名的查询结果
|
||
|
||
## 修复代码位置
|
||
|
||
- **核心修改文件**:`/root/dns/dns/server.go`
|
||
- **关键函数**:`mergeResponses`
|
||
- **修改内容**:修复响应合并逻辑,正确处理 Rcode 字段
|
||
|
||
## 测试方法
|
||
|
||
1. 使用 nslookup 测试不同域名
|
||
2. 检查是否每个域名都显示正确的查询结果
|
||
3. 验证 www.evilsnssdk.com 返回 NXDOMAIN,而其他域名返回成功
|
||
4. 检查日志中是否还有大量错误信息
|
||
|
||
这个修复将彻底解决所有查询都显示同一个 NXDOMAIN 错误的问题! |