6.4 KiB
6.4 KiB
问题分析
通过深入分析代码,我找到了导致所有查询都显示同一个 NXDOMAIN 错误的根本原因:
核心问题:mergeResponses 函数在合并多个 DNS 响应时,没有正确处理 Rcode 字段!
具体原因:
- 当使用并行查询模式时,DNS 服务器会向多个上游服务器发送请求
- 函数使用第一个响应作为基础来合并其他响应
- 它清空了
Answer、Ns和Extra字段,但保留了第一个响应的 Rcode - 如果第一个响应返回 NXDOMAIN(比如对于恶意域名 www.evilsnssdk.com),那么合并后的响应也会保持 NXDOMAIN 状态,即使其他响应返回成功
- 这导致所有查询都显示同一个 NXDOMAIN 错误
修复方案
修复 mergeResponses 函数
关键修改点:
- 重置 Rcode:在合并响应前,将 Rcode 重置为成功状态
- 处理 NXDOMAIN:只有当所有响应都是 NXDOMAIN 时,才返回 NXDOMAIN
- 优先使用成功响应:如果有任何响应返回成功,就使用成功的 Rcode
修复步骤
-
修改
mergeResponses函数 (/root/dns/dns/server.go:842-933)- 在合并记录前,将
mergedResponse.Rcode设置为dns.RcodeSuccess - 添加变量
allNXDOMAIN来跟踪是否所有响应都是 NXDOMAIN - 遍历所有响应,检查是否有成功响应
- 如果所有响应都是 NXDOMAIN,才将
mergedResponse.Rcode设置为dns.RcodeNameError
- 在合并记录前,将
-
优化合并逻辑
- 确保优先使用成功响应中的记录
- 避免将 NXDOMAIN 响应的记录合并到成功响应中
- 保持响应的一致性,Rcode 与记录内容匹配
修复代码
// 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 错误的问题!