## 问题分析 通过深入分析代码,我找到了导致所有查询都显示同一个 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 错误的问题!