优化DNSSEC

This commit is contained in:
Alex Yang
2025-12-16 16:25:55 +08:00
parent 50d2b5fbdb
commit 106b869111
7 changed files with 108 additions and 195 deletions

View File

@@ -2,12 +2,11 @@
"dns": {
"port": 53,
"upstreamDNS": [
"8.8.8.8:53",
"8.8.4.4:53",
"1.1.1.1:53"
"223.5.5.5:53",
"223.6.6.6:53",
"117.50.10.10:53"
],
"dnssecUpstreamDNS": [
"8.8.8.8:53",
"1.1.1.1:53"
],
"timeout": 5000,

View File

@@ -41,7 +41,7 @@ func cacheKey(qName string, qType uint16) string {
// hasDNSSECRecords 检查响应是否包含DNSSEC记录
func hasDNSSECRecords(response *dns.Msg) bool {
// 检查响应中是否包含DNSKEYRRSIG记录
// 检查响应中是否包含DNSSEC相关记录DNSKEYRRSIG、DS、NSEC、NSEC3等
for _, rr := range response.Answer {
if _, ok := rr.(*dns.DNSKEY); ok {
return true
@@ -49,6 +49,15 @@ func hasDNSSECRecords(response *dns.Msg) bool {
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
if _, ok := rr.(*dns.DS); ok {
return true
}
if _, ok := rr.(*dns.NSEC); ok {
return true
}
if _, ok := rr.(*dns.NSEC3); ok {
return true
}
}
for _, rr := range response.Ns {
if _, ok := rr.(*dns.DNSKEY); ok {
@@ -57,6 +66,15 @@ func hasDNSSECRecords(response *dns.Msg) bool {
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
if _, ok := rr.(*dns.DS); ok {
return true
}
if _, ok := rr.(*dns.NSEC); ok {
return true
}
if _, ok := rr.(*dns.NSEC3); ok {
return true
}
}
for _, rr := range response.Extra {
if _, ok := rr.(*dns.DNSKEY); ok {
@@ -65,6 +83,15 @@ func hasDNSSECRecords(response *dns.Msg) bool {
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
if _, ok := rr.(*dns.DS); ok {
return true
}
if _, ok := rr.(*dns.NSEC); ok {
return true
}
if _, ok := rr.(*dns.NSEC3); ok {
return true
}
}
return false
}

View File

@@ -610,6 +610,21 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
var backupRtt time.Duration
var hasBackup bool
// 如果启用了DNSSEC设置DO标志请求DNSSEC记录
if s.config.EnableDNSSEC {
// 如果请求已经包含EDNS记录移除它
if opt := r.IsEdns0(); opt != nil {
for i := range r.Extra {
if r.Extra[i] == opt {
r.Extra = append(r.Extra[:i], r.Extra[i+1:]...)
break
}
}
}
// 重新添加EDNS记录设置正确的UDPSize和DO标志
r.SetEdns0(4096, true)
}
// DNSSEC专用服务器列表从配置中获取
dnssecServers := s.config.DNSSECUpstreamDNS
@@ -623,6 +638,29 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 检查是否包含DNSSEC记录
containsDNSSEC := s.hasDNSSECRecords(response)
// 如果启用了DNSSEC且响应包含DNSSEC记录验证DNSSEC签名
if s.config.EnableDNSSEC && containsDNSSEC {
// 验证DNSSEC记录
signatureValid := s.verifyDNSSEC(response)
// 设置AD标志Authenticated Data
response.AuthenticatedData = signatureValid
if signatureValid {
// 更新DNSSEC验证成功计数
s.updateStats(func(stats *Stats) {
stats.DNSSECQueries++
stats.DNSSECSuccess++
})
} else {
// 更新DNSSEC验证失败计数
s.updateStats(func(stats *Stats) {
stats.DNSSECQueries++
stats.DNSSECFailed++
})
}
}
// 如果响应成功根据DNSSEC状态选择最佳响应
if response.Rcode == dns.RcodeSuccess {
// 优先选择带有DNSSEC记录的响应
@@ -694,13 +732,10 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
hasBestResponse = true
hasDNSSECResponse = true
logger.Debug("DNSSEC专用服务器返回带DNSSEC的响应优先使用", "domain", domain, "server", dnssecServer, "rtt", rtt)
} else if !hasBestResponse || hasBestResponse && !s.hasDNSSECRecords(bestResponse) {
// 如果没有更好的响应使用DNSSEC专用服务器的响应
bestResponse = response
bestRtt = rtt
hasBestResponse = true
logger.Debug("使用DNSSEC专用服务器的响应", "domain", domain, "server", dnssecServer, "rtt", rtt)
}
// 注意如果DNSSEC专用服务器返回的响应不包含DNSSEC记录
// 我们不会覆盖之前从upstreamDNS获取的响应
// 这符合"本地解析指的是直接使用上游服务器upstreamDNS进行解析, 而不是dnssecUpstreamDNS"的要求
// 更新备选响应
if !hasBackup {
@@ -715,12 +750,17 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 3. 返回最佳响应
if hasBestResponse {
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
// 检查最佳响应是否包含DNSSEC记录
bestHasDNSSEC := s.hasDNSSECRecords(bestResponse)
// 如果启用了DNSSEC且最佳响应不包含DNSSEC记录使用upstreamDNS的解析结果
if s.config.EnableDNSSEC && !bestHasDNSSEC {
logger.Debug("最佳响应不包含DNSSEC记录使用upstreamDNS的解析结果", "domain", domain)
}
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
// 更新域名的DNSSEC状态
if bestHasDNSSEC {
s.updateDomainDNSSECStatus(domain, true)
@@ -821,7 +861,7 @@ func (s *Server) updateClientStats(ip string) {
// hasDNSSECRecords 检查响应是否包含DNSSEC记录
func (s *Server) hasDNSSECRecords(response *dns.Msg) bool {
// 检查响应中是否包含DNSKEYRRSIG记录
// 检查响应中是否包含DNSSEC相关记录DNSKEYRRSIG、DS、NSEC、NSEC3等
for _, rr := range response.Answer {
if _, ok := rr.(*dns.DNSKEY); ok {
return true
@@ -829,6 +869,15 @@ func (s *Server) hasDNSSECRecords(response *dns.Msg) bool {
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
if _, ok := rr.(*dns.DS); ok {
return true
}
if _, ok := rr.(*dns.NSEC); ok {
return true
}
if _, ok := rr.(*dns.NSEC3); ok {
return true
}
}
for _, rr := range response.Ns {
if _, ok := rr.(*dns.DNSKEY); ok {
@@ -837,6 +886,15 @@ func (s *Server) hasDNSSECRecords(response *dns.Msg) bool {
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
if _, ok := rr.(*dns.DS); ok {
return true
}
if _, ok := rr.(*dns.NSEC); ok {
return true
}
if _, ok := rr.(*dns.NSEC3); ok {
return true
}
}
for _, rr := range response.Extra {
if _, ok := rr.(*dns.DNSKEY); ok {
@@ -845,6 +903,15 @@ func (s *Server) hasDNSSECRecords(response *dns.Msg) bool {
if _, ok := rr.(*dns.RRSIG); ok {
return true
}
if _, ok := rr.(*dns.DS); ok {
return true
}
if _, ok := rr.(*dns.NSEC); ok {
return true
}
if _, ok := rr.(*dns.NSEC3); ok {
return true
}
}
return false
}

View File

@@ -1,18 +0,0 @@
{
"name": "dns-server-console",
"version": "1.0.0",
"description": "DNS服务器Web控制台",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"tailwindcss": "^3.3.3",
"font-awesome": "^4.7.0",
"chart.js": "^4.4.8"
},
"devDependencies": {},
"keywords": ["dns", "server", "console", "web"],
"author": "",
"license": "ISC"
}

View File

@@ -1,77 +0,0 @@
package main
import (
"flag"
"fmt"
"os/exec"
"strings"
)
// testRuleMatching 测试DNS规则匹配功能
func main() {
// 定义命令行参数
rulePtr := flag.String("rule", "||cntvwb.cn^", "规则字符串")
testDomainPtr := flag.String("domain", "vdapprecv.app.cntvwb.cn", "测试域名")
flag.Parse()
// 打印测试信息
fmt.Printf("测试规则: %s\n", *rulePtr)
fmt.Printf("测试域名: %s\n", *testDomainPtr)
// 发送HTTP请求到API端点来测试规则匹配
fmt.Println("\n测试规则匹配功能...")
cmd := exec.Command("curl", "-s", fmt.Sprintf("http://localhost:8080/api/shield/check?domain=%s&rule=%s", *testDomainPtr, *rulePtr))
output, err := cmd.CombinedOutput()
if err != nil {
// 如果直接的API测试失败尝试另一种方法
fmt.Printf("直接测试失败: %v, %s\n", err, string(output))
fmt.Println("尝试添加规则并测试...")
testAddRuleAndCheck(*rulePtr, *testDomainPtr)
return
}
fmt.Printf("测试结果: %s\n", string(output))
// 验证规则是否生效(模拟测试)
if strings.Contains(*rulePtr, "||cntvwb.cn^") && strings.Contains(*testDomainPtr, "cntvwb.cn") {
fmt.Println("\n验证结果:")
if strings.Contains(*testDomainPtr, "cntvwb.cn") {
fmt.Println("✅ 子域名匹配测试通过:||cntvwb.cn^ 应该阻止所有 cntvwb.cn 的子域名")
} else {
fmt.Println("❌ 子域名匹配测试失败")
}
}
}
// testAddRuleAndCheck 测试添加规则和检查域名是否被阻止
func testAddRuleAndCheck(rule, domain string) {
// 尝试通过API添加规则
fmt.Printf("添加规则: %s\n", rule)
cmd := exec.Command("curl", "-s", "-X", "POST", "http://localhost:8080/api/shield/local-rules", "-H", "Content-Type: application/json", "-d", fmt.Sprintf(`{\"rule\":\"%s\"}`, rule))
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("添加规则失败: %v, %s\n", err, string(output))
// 尝试重新加载规则
fmt.Println("尝试重新加载规则...")
cmd = exec.Command("curl", "-s", "-X", "PUT", "http://localhost:8080/api/shield", "-H", "Content-Type: application/json", "-d", `{\"reload\":true}`)
output, err = cmd.CombinedOutput()
if err != nil {
fmt.Printf("重新加载规则失败: %v, %s\n", err, string(output))
} else {
fmt.Printf("重新加载规则结果: %s\n", string(output))
}
return
}
fmt.Printf("添加规则结果: %s\n", string(output))
// 测试域名是否被阻止
fmt.Printf("测试域名 %s 是否被阻止...\n", domain)
cmd = exec.Command("curl", "-s", fmt.Sprintf("http://localhost:8080/api/shield/check?domain=%s", domain))
output, err = cmd.CombinedOutput()
if err != nil {
fmt.Printf("测试阻止失败: %v, %s\n", err, string(output))
} else {
fmt.Printf("阻止测试结果: %s\n", string(output))
}
}

View File

@@ -1,40 +0,0 @@
{
"dns": {
"port": 5353,
"upstreamDNS": [
"223.5.5.5:53",
"223.6.6.6:53"
],
"timeout": 5000,
"statsFile": "data/test_stats.json",
"saveInterval": 300,
"cacheTTL": 30,
"enableDNSSEC": true,
"dnssecValidation": true
},
"http": {
"port": 8081,
"host": "0.0.0.0",
"enableAPI": true,
"username": "admin",
"password": "admin"
},
"shield": {
"localRulesFile": "data/test_rules.txt",
"blacklists": [],
"updateInterval": 3600,
"hostsFile": "data/test_hosts.txt",
"blockMethod": "NXDOMAIN",
"customBlockIP": "",
"statsFile": "./data/test_shield_stats.json",
"statsSaveInterval": 60,
"remoteRulesCacheDir": "data/test_remote_rules"
},
"log": {
"file": "logs/test_dns-server.log",
"level": "debug",
"maxSize": 100,
"maxBackups": 10,
"maxAge": 30
}
}

View File

@@ -1,45 +0,0 @@
#!/bin/bash
# DNS Web控制台功能测试脚本
echo "开始测试DNS Web控制台功能..."
echo "=================================="
# 检查服务器是否运行
echo "检查DNS服务器运行状态..."
pids=$(ps aux | grep dns-server | grep -v grep)
if [ -n "$pids" ]; then
echo "✓ DNS服务器正在运行"
else
echo "✗ DNS服务器未运行请先启动服务器"
fi
# 测试API基础URL
BASE_URL="http://localhost:8080/api"
# 测试1: 获取统计信息
echo "\n测试1: 获取DNS统计信息"
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/stats"
# 测试2: 获取系统状态
echo "\n测试2: 获取系统状态"
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/status"
# 测试3: 获取屏蔽规则
echo "\n测试3: 获取屏蔽规则列表"
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/shield"
# 测试4: 获取Top屏蔽域名
echo "\n测试4: 获取Top屏蔽域名"
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/top-blocked"
# 测试5: 获取Hosts内容
echo "\n测试5: 获取Hosts内容"
curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/shield/hosts"
# 测试6: 访问Web控制台主页
echo "\n测试6: 访问Web控制台主页"
curl -s -o /dev/null -w "状态码: %{http_code}\n" "http://localhost:8080"
echo "\n=================================="
echo "测试完成请检查上述状态码。正常情况下应为200。"
echo "前端Web控制台可通过浏览器访问: http://localhost:8080"