1.1.1修复

This commit is contained in:
Alex Yang
2025-12-19 12:44:57 +08:00
parent b576996ede
commit 1f3f5708a3
72 changed files with 3476 additions and 83 deletions

View File

@@ -0,0 +1,119 @@
# DNSSEC判断和显示逻辑分析与改进
## 1. 当前实现分析
### DNSSEC判断逻辑
1. **核心位置**`dns/server.go` 中的 `forwardDNSRequestWithCache` 函数
2. **判断流程**
- 检查配置是否启用DNSSEC (`s.config.EnableDNSSEC`)
- 从响应中提取所有DNSKEY和RRSIG记录
- 验证DNSSEC签名有效性
- 设置响应的Authenticated Data (AD) 标志
- 优先返回包含有效的DNSSEC记录的响应
- 如果没有有效的DNSSEC记录使用备选响应
### DNSSEC显示逻辑
1. **前端显示**`static/js/logs.js` 中的 `updateLogsTable` 函数
2. **显示方式**
```javascript
${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}
```
- 当DNSSEC为true时显示绿色锁图标和"DNSSEC"文字
- 否则不显示
### DNSSEC状态存储
1. **`domainDNSSECStatus` 映射**存储域名的DNSSEC状态
2. **`resolvedDomains` 结构**包含每个域名的DNSSEC状态
3. **查询日志**每条日志记录包含DNSSEC状态
## 2. 存在的问题
1. **显示逻辑单一**只在日志页面显示DNSSEC状态没有在仪表盘或其他关键位置显示
2. **状态判断简单**只检查响应中是否包含RRSIG记录没有考虑签名验证结果
3. **缺少统计信息**没有统计使用DNSSEC的查询比例
4. **配置界面缺失**没有在配置界面提供DNSSEC相关的配置选项
5. **缺少用户反馈**用户无法直观了解当前DNSSEC的整体使用情况
## 3. 改进方案
### 3.1 增强DNSSEC判断逻辑
- **改进位置**`dns/server.go`
- **改进内容**
- 增加DNSSEC验证结果的详细记录
- 区分"DNSSEC可用"和"DNSSEC验证成功"两种状态
- 记录DNSSEC验证失败的具体原因
### 3.2 扩展DNSSEC显示范围
- **改进位置**
- `static/js/dashboard.js`在仪表盘添加DNSSEC统计卡片
- `static/js/logs.js`增强日志中的DNSSEC显示
- **改进内容**
- 在仪表盘添加"DNSSEC使用率"统计卡片
- 在TOP域名列表中显示DNSSEC状态
- 在日志记录中显示DNSSEC验证结果成功/失败/未使用)
### 3.3 添加DNSSEC配置界面
- **改进位置**:配置页面
- **改进内容**
- 添加DNSSEC启用/禁用开关
- 添加DNSSEC验证严格程度选项
- 显示当前DNSSEC状态信息
### 3.4 增强DNSSEC统计功能
- **改进位置**`dns/server.go`
- **改进内容**
- 统计DNSSEC查询总数
- 统计DNSSEC验证成功/失败次数
- 计算DNSSEC使用率
- 在API中提供DNSSEC统计数据
### 3.5 改进DNSSEC状态存储
- **改进位置**`dns/server.go`
- **改进内容**
- 增加DNSSEC验证结果的存储
- 记录DNSSEC状态的有效期
- 优化域名DNSSEC状态的更新逻辑
## 4. 实现计划
1. **第一步**增强DNSSEC判断逻辑改进验证结果记录
2. **第二步**添加DNSSEC统计功能扩展API返回数据
3. **第三步**在仪表盘添加DNSSEC统计卡片
4. **第四步**增强日志中的DNSSEC显示
5. **第五步**添加DNSSEC配置界面
6. **第六步**优化DNSSEC状态存储和更新逻辑
## 5. 预期效果
- 用户可以在仪表盘直观了解DNSSEC使用情况
- 日志中显示详细的DNSSEC验证结果
- 提供灵活的DNSSEC配置选项
- 增强DNSSEC状态的准确性和可靠性
- 提高DNSSEC相关问题的可调试性
## 6. 代码修改点
### 后端修改
- `dns/server.go`增强DNSSEC判断和统计
- `config/config.go`添加DNSSEC相关配置选项
- API接口扩展返回DNSSEC统计数据
### 前端修改
- `static/js/dashboard.js`添加DNSSEC统计卡片
- `static/js/logs.js`:增强日志显示
- 配置页面添加DNSSEC配置选项
## 7. 风险评估
- DNSSEC验证可能会增加响应延迟
- 错误的DNSSEC配置可能导致解析失败
- 增强的统计功能可能增加内存使用
## 8. 测试计划
- 测试DNSSEC启用/禁用功能
- 测试不同域名的DNSSEC显示
- 测试DNSSEC统计数据的准确性
- 测试DNSSEC验证失败时的处理逻辑
通过以上改进可以使DNSSEC判断和显示逻辑更加完善提供更好的用户体验和更详细的DNSSEC状态信息。

View File

@@ -0,0 +1,62 @@
# DNS查询页面实现计划
## 问题分析
目前DNS查询页面只是一个简单的占位符显示"DNS查询页面内容待实现"没有实际的功能。我需要实现完整的DNS查询功能包括
1. DNS域名查询功能
2. 查询结果展示
3. 查询历史记录
4. 响应式设计
## 解决方案
### 1. 更新HTML结构
`query-content`区域添加完整的UI组件包括
- 查询表单(域名输入框、查询按钮)
- 查询结果展示区域
- 查询历史记录
- 响应式布局设计
### 2. 实现JavaScript功能
创建或更新`query.js`文件,实现以下功能:
- 处理DNS查询请求
- 展示查询结果
- 管理查询历史
- 与后端API对接
### 3. 实现具体功能
#### 3.1 DNS域名查询
- 调用`/api/query`接口进行DNS查询
- 支持输入域名进行查询
- 显示查询结果,包括是否被屏蔽、屏蔽原因等
#### 3.2 查询结果展示
- 以清晰的方式展示查询结果
- 区分不同的查询结果状态(被屏蔽、正常、错误等)
- 显示详细的查询信息
#### 3.3 查询历史记录
- 保存查询历史到本地存储
- 支持查看历史查询记录
- 支持从历史记录中重新查询
## 实现步骤
1. **更新HTML结构**:修改`index.html`中的`query-content`区域添加完整的UI组件
2. **实现JavaScript功能**:更新`query.js`文件实现与后端API的交互
3. **测试功能**:确保所有功能正常工作
4. **优化用户体验**:添加加载状态、错误提示、成功反馈等
## 预期效果
通过以上实现DNS查询页面将具备完整的查询功能用户可以
- 输入域名进行DNS查询
- 查看详细的查询结果
- 查看查询历史记录
- 从历史记录中重新查询
页面将采用响应式设计,确保在不同屏幕尺寸下都能正常显示。

View File

@@ -0,0 +1,25 @@
## Problem
The build is failing because the HTTP server is trying to access DNSSEC-related fields (`DNSSECQueries`, `DNSSECSuccess`, `DNSSECFailed`, `DNSSECEnabled`) from the `dns.Stats` struct, but these fields don't exist in the struct definition.
## Solution
Add the missing DNSSEC-related fields to the `Stats` struct in `dns/server.go` and ensure they're properly initialized and updated.
## Implementation Steps
1. **Add DNSSEC fields to Stats struct** in `/root/dns/dns/server.go`:
- Add `DNSSECQueries int64` field
- Add `DNSSECSuccess int64` field
- Add `DNSSECFailed int64` field
- Add `DNSSECEnabled bool` field
2. **Initialize DNSSEC fields in NewServer** function:
- Set `DNSSECEnabled` based on config.EnableDNSSEC
- Initialize other DNSSEC fields to 0
3. **Update DNSSEC stats in query handling**:
- Increment `DNSSECQueries` when a DNSSEC query is processed
- Update `DNSSECSuccess` and `DNSSECFailed` based on DNSSEC validation results
4. **Test the fix** by running `go build` to ensure all errors are resolved
## Expected Outcome
The build will succeed and the HTTP server will be able to access the DNSSEC-related stats fields.

View File

@@ -0,0 +1,65 @@
# Web页面屏蔽管理功能实现计划
## 问题分析
目前web页面的屏蔽管理功能尚未完全实现主要问题包括
1. **前端UI缺失**`shield-content`区域只有简单的提示文本,没有实际的管理界面
2. **功能被禁用**`shield.js`文件中的所有功能都已被禁用无法与后端API交互
3. **缺少完整的管理功能**无法管理本地规则、远程黑名单和hosts条目
## 解决方案
### 1. 更新屏蔽管理页面HTML结构
-`shield-content`区域添加完整的UI组件
- 实现以下功能模块:
- 屏蔽规则统计信息展示
- 本地规则管理(添加、删除)
- 远程黑名单管理(添加、删除、更新)
- hosts条目管理添加、删除
### 2. 实现屏蔽管理JavaScript功能
- 重新启用并实现`shield.js`中的功能
- 与后端API对接实现完整的CRUD操作
- 添加错误处理和用户反馈
### 3. 实现具体功能
#### 3.1 屏蔽规则统计信息
- 调用`/api/shield`接口获取统计数据
- 展示规则数量、黑名单数量等信息
#### 3.2 本地规则管理
- 实现规则列表展示
- 实现添加新规则功能
- 实现删除规则功能
#### 3.3 远程黑名单管理
- 调用`/api/shield/blacklists`接口获取黑名单列表
- 实现添加新黑名单功能
- 实现删除黑名单功能
- 实现更新单个黑名单功能
#### 3.4 hosts条目管理
- 调用`/api/shield/hosts`接口获取hosts列表
- 实现添加新hosts条目功能
- 实现删除hosts条目功能
## 实现步骤
1. **更新HTML结构**:修改`index.html`中的`shield-content`区域添加完整的UI组件
2. **实现JavaScript功能**:更新`shield.js`文件实现与后端API的交互
3. **测试功能**:确保所有功能正常工作,包括添加、删除、更新操作
4. **优化用户体验**:添加加载状态、错误提示、成功反馈等
## 预期效果
通过以上实现web页面将具备完整的屏蔽管理功能用户可以
- 查看屏蔽规则统计信息
- 管理本地规则
- 管理远程黑名单
- 管理hosts条目
所有功能都将与后端API对接实现数据的实时更新和持久化存储。

View File

@@ -0,0 +1,60 @@
# Web页面适配问题解决方案
## 问题分析
通过分析当前web页面的HTML、CSS和JavaScript代码我发现了以下适配问题
1. **统计卡片布局问题**:在小屏幕设备上,统计卡片可能会出现布局问题
2. **图表布局问题**:三个图表在同一行显示,在小屏幕设备上可能会挤在一起
3. **表格溢出问题**:在小屏幕设备上,表格可能会溢出容器
4. **服务器状态组件适配问题**:在小屏幕上可能显示不全
5. **侧边栏响应式处理不完整**:当前只在窗口大小改变时更新,没有考虑其他情况
6. **图表大小更新不完整**:窗口大小改变时只更新了部分图表
7. **配置表单适配问题**:在小屏幕上的布局需要优化
## 解决方案
### 1. 优化统计卡片布局
- 修改统计卡片网格布局,增加更细粒度的响应式控制
- 在小屏幕上使用单列布局,在中等屏幕上使用双列布局,在大屏幕上使用四列布局
### 2. 改进图表布局
- 修改图表网格布局,确保在不同屏幕尺寸下都能正常显示
- 在小屏幕上使用单列布局,在中等屏幕上使用双列布局,在大屏幕上使用三列布局
### 3. 解决表格溢出问题
- 为所有表格添加响应式处理,确保在小屏幕设备上可以水平滚动
- 优化表格样式,提高在小屏幕上的可读性
### 4. 优化服务器状态组件
- 在小屏幕上简化服务器状态组件,只显示核心指标
- 添加响应式逻辑,根据屏幕尺寸动态调整显示内容
### 5. 完善侧边栏响应式处理
- 确保侧边栏在所有情况下都能正确响应屏幕尺寸变化
- 添加触摸事件支持,提高移动端体验
### 6. 完整更新图表大小
- 在窗口大小改变时,更新所有图表的大小
- 确保图表容器大小正确,避免图表变形
### 7. 优化配置表单布局
- 修改配置表单网格布局,确保在小屏幕上也能正常显示
- 调整表单元素大小和间距,提高在小屏幕上的可用性
## 实现步骤
1. 修改HTML文件中的网格布局类增加更细粒度的响应式控制
2. 更新CSS样式优化各组件在不同屏幕尺寸下的显示效果
3. 修改JavaScript代码完善响应式处理逻辑
4. 测试各组件在不同屏幕尺寸下的显示效果
5. 优化用户体验,确保在各种设备上都能正常使用
## 预期效果
通过以上优化web页面将能够在各种设备上正常显示包括
- 桌面设备(大屏幕)
- 平板设备(中等屏幕)
- 移动设备(小屏幕)
页面布局将更加灵活,能够根据屏幕尺寸自动调整,提高用户体验。

View File

@@ -0,0 +1,37 @@
## 问题分析
规则处理存在问题:`||domain` 规则应该是屏蔽一个绝对域名及其子域名,而不是正则匹配屏蔽,也不应该屏蔽所有包含该顶级域名的网站。
## 问题根源
`manager.go` 文件中,`addDomainRule` 函数第444行存在逻辑错误
1. 当添加 `||example.com` 规则时,函数正确地将 `example.com` 添加到域名规则列表中
2. 但随后,函数错误地将域名拆分为各个部分,并为每个部分添加规则
3. 例如,对于 `||example.com`,它会添加 `example.com``com` 到规则列表中
4. 这导致所有 `.com` 域名都被屏蔽,而不仅仅是 `example.com` 及其子域名
## 修复方案
修改 `addDomainRule` 函数,移除错误的子域名处理逻辑。因为 `CheckDomainBlockDetails` 函数已经实现了正确的子域名检查逻辑:它会检查域名的所有子域名部分,从最长到最短,所以不需要在添加规则时就将所有子域名都添加到规则列表中。
## 修复步骤
1. 修改 `manager.go` 文件中的 `addDomainRule` 函数
2. 移除第457-474行和第487-503行的子域名处理逻辑
3. 确保只添加精确的域名到规则列表中
4. 保持 `CheckDomainBlockDetails` 函数的子域名检查逻辑不变
## 预期效果
修复后,`||example.com` 规则将只屏蔽:
- `example.com`
- `www.example.com`
- `subdomain.example.com`
而不会屏蔽:
- `anotherexample.com`
- `google.com`
- 其他所有 `.com` 域名
这符合 AdGuard Home 规则的标准行为,即 `||example.com^` 匹配该域名及其所有子域名。

View File

@@ -0,0 +1,33 @@
## 问题分析
web界面保存配置后配置没有同步到config文件。从代码分析来看当前的`handleConfig`函数第1064行只处理了`shield`部分的配置更新而没有处理其他配置项如DNS服务器配置、HTTP服务器配置等。
## 解决方案
1. **扩展`handleConfig`函数**修改该函数以处理所有配置项包括DNS、HTTP和Log配置
2. **更新配置保存逻辑**确保所有配置都能正确保存到config.json文件中
3. **添加重启服务逻辑**:在配置保存成功后,调用重启服务的逻辑,确保配置更改能立即生效
## 修复步骤
1. 修改`server.go`文件中的`handleConfig`函数
- 扩展请求结构,包含所有配置项
- 更新配置处理逻辑处理DNS、HTTP和Log配置
- 确保所有配置都能正确保存到config.json文件中
2. 修改`handleConfig`函数的返回逻辑
- 在配置保存成功后,调用重启服务的逻辑
- 返回更详细的成功信息
3. 测试修复效果
- 确保web界面上的所有配置项都能正确保存到config.json文件中
- 确保服务能在配置保存后正确重启
## 预期效果
修复后当用户在web界面点击"保存配置"按钮时:
1. 所有配置项包括DNS、HTTP、Shield和Log配置都会被保存到config.json文件中
2. 服务器会自动重启,使配置更改生效
3. 用户会看到配置保存成功的提示
这将确保用户在web界面上的所有配置更改都能正确保存和生效。

View File

@@ -0,0 +1,38 @@
## 修复API不可用问题
### 问题分析
1. **配置文件中API被禁用**:在`config.json`中,`http.enableAPI`设置为`false`
2. **缺少默认启用配置**:在`config/config.go``LoadConfig`函数中,没有为`EnableAPI`设置默认值
3. **API路由条件注册**:在`http/server.go`所有API端点都在`if s.config.EnableAPI`条件下注册
### 解决方案
#### 1. 修改配置文件启用API
`config.json`中的`http.enableAPI`值从`false`改为`true`立即启用API功能。
#### 2. 设置API默认启用
`config/config.go``LoadConfig`函数中添加默认值设置确保API在配置文件未指定时自动启用
```go
if !config.HTTP.EnableAPI {
config.HTTP.EnableAPI = true
}
```
### 实施步骤
1. 编辑`config.json`文件,将`http.enableAPI`设置为`true`
2. 修改`config/config.go`,在`LoadConfig`函数中添加API默认启用逻辑
3. 重启服务使配置生效
### 预期结果
- API端点将可用包括
- `/api/stats` - 统计信息
- `/api/shield` - 屏蔽规则管理
- `/api/shield/localrules` - 本地规则
- `/api/shield/remoterules` - 远程规则
- `/api/query` - DNS查询
- 以及其他统计和管理端点
- Swagger UI页面可通过`/api`访问
### 文件修改清单
1. `config.json` - 启用API
2. `config/config.go` - 添加API默认启用配置

View File

@@ -0,0 +1,21 @@
# DNSSEC记录缺失问题修复计划
## 问题分析
从日志中可以看到所有DNS查询都显示"DNS响应不包含DNSSEC记录"这表明虽然DNSSEC被启用但服务器没有从上游DNS服务器获取到DNSSEC记录。
## 根本原因
当启用DNSSEC时DNS服务器需要在发送给上游服务器的请求中设置**DNSSEC OK (DO)标志**这样上游服务器才会返回DNSSEC记录如RRSIG、DNSKEY等。但当前代码直接使用原始请求发送给上游服务器没有添加DO标志。
## 修复方案
修改`forwardDNSRequestWithCache`函数,在向上游服务器发送请求之前:
1. 检查是否启用了DNSSEC
2. 如果启用为请求添加EDNS0选项并设置DO标志
3. 确保修改后的请求被正确发送
## 具体实现步骤
1.`/root/dns/dns/server.go`文件中,找到`forwardDNSRequestWithCache`函数
2. 在发送请求前第625行附近添加DO标志设置逻辑
3. 确保请求被正确发送到上游服务器
## 预期效果
修复后启用DNSSEC时服务器会向上游发送带有DO标志的请求上游服务器将返回DNSSEC记录从而实现完整的DNSSEC验证和记录返回。

View File

@@ -0,0 +1,82 @@
# DNSSEC功能修复计划
## 问题分析
当前DNSSEC实现存在以下问题
1. 当启用DNSSEC时系统只是验证上游服务器返回的DNSSEC签名但不会主动请求DNSSEC记录
2. 当上游服务器返回的响应没有DNSSEC记录时系统只是将其作为备选响应
3. 没有专门从8.8.8.8/1.1.1.1获取DNSSEC记录进行验证
4. 缓存时没有优先考虑DNSSEC记录
## 修复方案
### 1. 改进DNS请求转发逻辑
**修改文件:** `dns/server.go`
**修改函数:** `forwardDNSRequestWithCache`
- 当启用DNSSEC且响应中没有DNSSEC记录时主动向8.8.8.8/1.1.1.1发送DNS请求
- 比较不同服务器返回的结果优先使用带有DNSSEC记录的响应
- 如果DNSSEC结果不匹配优先使用8.8.8.8/1.1.1.1提供的解析记录
### 2. 增强DNSSEC验证机制
**修改文件:** `dns/server.go`
- 完善DNSSEC记录提取和验证逻辑
- 确保正确处理DNSKEY和RRSIG记录
- 改进AD标志Authenticated Data的设置
### 3. 优化缓存机制
**修改文件:** `dns/cache.go``dns/server.go`
- 缓存时标记DNSSEC状态
- 优先返回带有DNSSEC记录的缓存项
- 改进缓存键生成考虑DNSSEC属性
### 4. 增加DNSSEC特定服务器配置
**修改文件:** `config.json`
- 添加专门用于DNSSEC查询的服务器配置
- 默认为8.8.8.8和1.1.1.1
## 具体实现步骤
1. **修改`forwardDNSRequestWithCache`函数**
- 当启用DNSSEC且主响应没有DNSSEC记录时向DNSSEC专用服务器发送请求
- 比较所有响应选择最优结果优先DNSSEC记录其次是可靠服务器
- 实现DNSSEC结果验证和比较逻辑
2. **改进缓存获取逻辑**
-`handleDNSRequest`函数中优先检查是否有带有DNSSEC记录的缓存项
- 如果有,直接返回;否则再检查普通缓存项
3. **优化DNSSEC记录验证**
- 增强`verifyDNSSEC`函数的实现
- 确保正确验证所有RRSIG记录
- 改进错误处理和日志记录
4. **添加DNSSEC服务器配置**
- 在配置文件中添加`dnssecUpstreamDNS`配置项
- 默认值为["8.8.8.8:53", "1.1.1.1:53"]
## 测试计划
1. 启动DNS服务器启用DNSSEC
2. 使用`dig`命令测试DNSSEC记录获取
3. 验证带有DNSSEC记录的响应被正确返回
4. 验证缓存机制优先返回DNSSEC记录
5. 测试DNSSEC验证失败时的处理逻辑
## 预期效果
1. 启用DNSSEC后系统会主动请求并验证DNSSEC记录
2. 优先返回带有DNSSEC记录的解析结果
3. 当DNSSEC结果不匹配时优先使用8.8.8.8/1.1.1.1提供的记录
4. 缓存机制正确处理DNSSEC标记优先返回DNSSEC记录
5. 完善的日志记录,便于调试和监控
## 代码修改范围
- `dns/server.go`核心DNSSEC逻辑修改
- `dns/cache.go`:缓存机制优化
- `config.json`:配置项添加
通过以上修改将解决当前DNSSEC功能的问题确保启用DNSSEC后能够正确获取、验证和返回DNSSEC记录。

View File

@@ -0,0 +1,43 @@
# DNSSEC状态显示问题修复计划
## 问题分析
用户报告已在配置中启用DNSSEC`enableDNSSEC: true`但界面显示DNSSEC为禁用状态且使用率为0%。经过代码检查,发现问题出在`GetStats`函数中,该函数返回的`Stats`结构体缺少DNSSEC相关字段导致前端无法获取正确的DNSSEC状态和统计信息。
## 修复方案
### 1. 修复`GetStats`函数
**修改文件:** `dns/server.go`
**修改函数:** `GetStats`
**问题:** 当前`GetStats`函数返回的`Stats`结构体缺少DNSSEC相关字段包括
- `DNSSECEnabled`
- `DNSSECQueries`
- `DNSSECSuccess`
- `DNSSECFailed`
**解决方案:**`GetStats`函数返回的`Stats`结构体中添加所有DNSSEC相关字段确保前端能获取到正确的DNSSEC状态和统计数据。
## 具体实现步骤
1. **修改`GetStats`函数**
- 在返回的`Stats`结构体中添加`DNSSECEnabled`字段
- 添加`DNSSECQueries`字段
- 添加`DNSSECSuccess`字段
- 添加`DNSSECFailed`字段
2. **测试修复效果**
- 重新编译DNS服务器
- 启动服务器
- 使用API查询统计信息确认DNSSEC状态和统计数据正确返回
- 检查前端界面是否显示正确的DNSSEC状态
## 预期效果
1. 前端界面显示DNSSEC状态为"已启用"
2. DNSSEC使用率根据实际查询情况更新
3. 成功、失败和总查询数统计正确显示
4. 系统正常记录DNSSEC相关统计数据
## 代码修改范围
- `dns/server.go`:修复`GetStats`函数添加缺失的DNSSEC字段

View File

@@ -0,0 +1,39 @@
## 实现DNSSEC支持的计划
### 1. 分析当前代码
- 配置文件中已经包含了DNSSEC相关配置项`EnableDNSSEC``DNSSECValidation`
- DNS客户端resolver目前没有启用DNSSEC支持
- 需要修改代码以实现DNSSEC功能
### 2. 实现步骤
#### 步骤1修改DNS客户端配置
-`NewServer` 函数中修改DNS客户端配置添加 `DNSSEC: true` 以启用DNSSEC查询
- 确保客户端支持DNSSEC记录类型RRSIG, DNSKEY, DS等
#### 步骤2添加DNSSEC验证逻辑
-`forwardDNSRequestWithCache` 函数中检查上游服务器返回的响应是否包含DNSSEC签名
- 如果启用了DNSSEC验证验证签名的有效性
- 处理验证失败的情况,返回适当的错误响应
#### 步骤3确保DNS响应包含DNSSEC记录
- 当转发DNS响应时确保包含所有相关的DNSSEC记录
- 确保响应中的DNSSEC标志正确设置
#### 步骤4添加DNSSEC相关的日志记录
- 记录DNSSEC验证结果
- 记录DNSSEC相关的错误信息
### 3. 预期结果
- DNS服务器将支持DNSSEC查询
- 可以验证DNS记录的真实性和完整性
- 防止DNS投毒和劫持攻击
- 提供DNSSEC相关的配置选项
### 4. 文件修改
- `/root/dns/dns/server.go`修改DNS客户端配置和添加DNSSEC验证逻辑
### 5. 技术细节
- 使用miekg/dns库的内置DNSSEC支持
- 确保DNSSEC验证符合RFC标准
- 处理各种DNSSEC相关的错误情况

View File

@@ -0,0 +1,84 @@
## 实现计划
### 1. 配置更新
-`DNSConfig` 结构体中添加 `EnableDNSSEC` 布尔字段用于控制是否启用DNSSEC验证
- 在配置加载时设置默认值为 `true`
- 在默认配置JSON中添加DNSSEC相关配置项
### 2. DNSSEC核心功能实现
- **DO标志处理**识别并保留客户端请求中的DNSSEC OK (DO) 标志
- **记录转发**确保所有DNSSEC相关记录RRSIG, DNSKEY, DS, NSEC, NSEC3等被正确转发
- **签名验证**验证上游DNS服务器返回的DNSSEC签名有效性
- **AD标志设置**根据验证结果设置Authenticated Data (AD) 标志
### 3. 代码修改点
#### 3.1 配置文件 (`config/config.go`)
```go
// DNSConfig DNS配置
type DNSConfig struct {
Port int `json:"port"`
UpstreamDNS []string `json:"upstreamDNS"`
Timeout int `json:"timeout"`
StatsFile string `json:"statsFile"` // 统计数据持久化文件
SaveInterval int `json:"saveInterval"` // 数据保存间隔(秒)
EnableDNSSEC bool `json:"enableDNSSEC"` // 是否启用DNSSEC验证
}
```
#### 3.2 默认配置 (`main.go`)
在默认配置JSON中添加
```json
"enableDNSSEC": true
```
#### 3.3 DNS服务器初始化 (`dns/server.go`)
-`NewServer` 方法中为DNS客户端启用DNSSEC支持
- 初始化DNSSEC验证相关组件
#### 3.4 DNS请求处理 (`dns/server.go`)
- 修改 `handleDNSRequest` 方法保留客户端请求中的DO标志
- 修改 `forwardDNSRequest` 方法:
- 确保转发请求时包含DO标志
- 处理上游返回的DNSSEC记录
- 验证DNSSEC签名
- 根据验证结果设置AD标志
- 确保所有DNSSEC记录类型被正确处理
### 4. DNSSEC验证逻辑
- 使用miekg/dns库的内置验证功能
- 验证RRSIG记录与对应资源记录的匹配性
- 验证签名的有效性和过期时间
- 处理信任链验证
### 5. 测试验证
- 测试DNSSEC查询是否能正确返回RRSIG等记录
- 测试DNSSEC验证功能是否正常工作
- 测试在不同配置下的行为
- 测试各种DNSSEC记录类型的处理
## 预期效果
-`enableDNSSEC``true` 时:
- 服务器将验证上游DNS返回的DNSSEC记录
- 对于有效的DNSSEC记录服务器将在响应中设置AD标志
- 对于无效的DNSSEC记录服务器将记录错误并根据配置处理
- 服务器将正确转发所有DNSSEC相关记录
- 支持DNSKEY, DS, RRSIG, NSEC, NSEC3等DNSSEC记录类型
- 客户端可以通过设置DO标志来请求DNSSEC记录
## 安全增强
通过添加DNSSEC支持服务器将能够
- 防止DNS投毒攻击
- 确保DNS记录的完整性和真实性
- 为客户端提供经过验证的DNS响应
- 增强整体DNS服务的安全性
## 实现步骤
1. **修改配置结构**
2. **更新默认配置**
3. **修改DNS客户端设置**
4. **实现DNSSEC验证逻辑**
5. **测试和验证**

View File

@@ -0,0 +1,60 @@
## 实现计划优化DNSSEC查询逻辑
### 1. 需求分析
- 当配置文件中`enableDNSSEC=true`DNS服务器应优先返回包含DNSSEC记录的结果
- 如果没有DNSSEC结果应返回普通查询结果作为备选
- 保持现有代码结构和兼容性
### 2. 实现步骤
#### 步骤1修改`forwardDNSRequestWithCache`函数
- 在函数中添加备选响应变量用于存储非DNSSEC的成功响应
-`enableDNSSEC=true`遍历所有上游DNS服务器
- 对于每个上游,检查响应是否成功
- 如果响应成功且包含DNSSEC记录立即返回该响应
- 如果响应成功但不包含DNSSEC记录将其保存为备选响应
- 遍历完成后,如果有备选响应,返回该响应
- 如果没有成功响应,返回服务器失败错误
#### 步骤2添加DNSSEC记录检测逻辑
- 在响应处理中添加DNSSEC记录检测
- 检查响应的Answer、Ns和Extra部分是否包含RRSIG记录
- 使用该检测结果决定是否优先返回该响应
#### 步骤3优化日志记录
- 为DNSSEC优先返回的情况添加专门的日志记录
- 为备选响应返回的情况添加日志记录
### 3. 技术细节
#### 3.1 DNSSEC检测方法
- 检查响应中是否包含RRSIG资源记录签名记录
- 检查范围包括Answer、Ns和Extra部分
- 使用类型断言判断记录类型
#### 3.2 优先级逻辑
- 最高优先级成功且包含DNSSEC的响应
- 次高优先级成功但不包含DNSSEC的响应
- 最低优先级:失败响应
### 4. 预期效果
- 当启用DNSSEC时优先返回安全的DNSSEC结果
- 提高DNS查询的安全性
- 兼容现有配置和代码
- 提供详细的日志记录
### 5. 文件修改
- **文件**`/root/dns/dns/server.go`
- **函数**`forwardDNSRequestWithCache`
- **修改内容**添加DNSSEC优先逻辑和备选响应处理
### 6. 测试要点
- 验证启用DNSSEC时优先返回DNSSEC结果
- 验证无DNSSEC结果时返回普通结果
- 验证禁用DNSSEC时使用原有逻辑
- 验证日志记录正确
### 7. 实现时间线
- 代码修改20分钟
- 测试10分钟
- 总计30分钟

View File

@@ -0,0 +1,84 @@
# 优化设置界面实现计划
## 问题分析
当前设置界面存在配置项重复问题,需要进行优化,具体包括:
1. "远程规则URL"配置项在多个界面重复出现
2. "启用API"和"主机"选项不需要在当前界面显示
3. 需要确保保存功能正常工作写入config.json并触发服务器重新加载配置
## 优化方案
### 1. 修改HTML结构
- 移除"远程规则URL"配置项
- 移除"启用API"选项
- 移除"主机"选项
- 调整布局,确保界面美观合理
### 2. 更新JavaScript代码
- 修改`populateConfigForm`函数,移除对已删除配置项的处理
- 修改`collectFormData`函数,移除对已删除配置项的收集
- 确保保存功能能正确写入config.json文件
- 实现服务器重新加载配置的触发机制
- 提供明确的成功/失败反馈
### 3. 测试和验证
- 测试所有保留配置项的加载和保存功能
- 验证保存操作能正确写入config.json文件
- 验证服务器能重新加载配置
- 测试成功/失败反馈是否明确
## 具体实现步骤
1. **修改HTML结构**
- 编辑`index.html`文件,移除不需要的配置项
- 调整布局,确保界面美观合理
2. **更新JavaScript代码**
- 编辑`config.js`文件,修改`populateConfigForm`函数
- 修改`collectFormData`函数,移除对已删除配置项的处理
- 确保`handleSaveConfig`函数能正确保存配置
- 实现服务器重新加载配置的触发机制
3. **测试和验证**
- 测试配置项的加载功能
- 测试配置项的保存功能
- 验证config.json文件是否正确更新
- 验证服务器是否重新加载配置
- 测试成功/失败反馈是否明确
## 预期效果
- 设置界面布局合理,无重复配置项
- 所有保留配置项均可正常配置
- 保存功能能正确写入config.json文件
- 服务器能重新加载配置,使更改立即生效
- 保存操作有明确的成功/失败反馈
## 技术要点
- 使用HTML和JavaScript修改界面结构和功能
- 确保与服务器API的正确交互
- 实现良好的用户反馈机制
- 确保配置的正确保存和加载
## 实现时间
- 预计1-2小时完成所有修改和测试
## 风险评估
- 低风险:修改范围明确,不涉及核心功能
- 可回滚:所有修改均为前端修改,可通过恢复文件轻松回滚
## 依赖关系
- 依赖服务器API的正常工作
- 依赖config.json文件的读写权限
## 测试策略
- 手动测试所有配置项的加载和保存功能
- 验证config.json文件的更新
- 测试服务器配置的重新加载
- 测试成功/失败反馈
## 验收标准
- 设置界面布局合理,无重复配置项
- 所有保留配置项均可正常配置
- 保存功能能正确写入config.json文件
- 服务器能重新加载配置,使更改立即生效
- 保存操作有明确的成功/失败反馈

View File

@@ -0,0 +1,22 @@
## 问题分析
CPU使用率卡片在WebSocket实时更新时没有刷新数据原因是
1. `processRealTimeData`函数调用了`updateStatsCards(stats)`但该函数的CPU使用率更新逻辑可能没有被正确执行
2. `processRealTimeData`函数对其他卡片如平均响应时间、最常用查询类型、活跃IP数有单独的更新逻辑但缺少了CPU使用率卡片的更新逻辑
3. `loadDashboardData`函数中有完整的CPU使用率更新逻辑这就是为什么页面初始加载时CPU使用率能显示但后续WebSocket更新时不能显示的原因
## 修复方案
1. **在`processRealTimeData`函数中添加CPU使用率卡片的更新逻辑**:类似于`loadDashboardData`函数中的实现
2. **确保从`stats`对象中正确获取CPU使用率数据**支持从不同的数据结构中获取CPU使用率
3. **更新DOM元素**将获取到的CPU使用率数据更新到`cpu-usage``cpu-status`元素中
4. **添加状态判断**根据CPU使用率值设置不同的状态文本和样式
## 实现步骤
1. 打开`dashboard.js`文件
2. 找到`processRealTimeData`函数约第120行
3. 在函数末尾添加CPU使用率更新逻辑位于其他卡片更新逻辑之后
4. 确保从`stats`对象中正确获取CPU使用率数据
5. 更新`cpu-usage``cpu-status`元素的内容和样式
6. 测试修复是否生效
## 预期效果
修复后当WebSocket接收到实时数据更新时CPU使用率卡片会自动更新显示最新的CPU使用率和状态与其他统计卡片保持一致的实时更新效果。

View File

@@ -0,0 +1,22 @@
## 问题分析
CPU使用率卡片在WebSocket实时更新时没有刷新数据原因是
1. `updateStatsCards`函数中数组形式的数据结构处理部分第631-641行缺少CPU使用率的处理逻辑
2. 可能存在数据字段名不匹配的问题WebSocket服务器返回的CPU使用率数据可能使用了不同的字段名
3. `processRealTimeData`函数和`updateStatsCards`函数中都有CPU使用率更新逻辑可能导致冲突或其中一个逻辑没有被正确执行
## 修复方案
1. **完善`updateStatsCards`函数的CPU使用率处理逻辑**在数组形式的数据结构处理部分添加CPU使用率的处理逻辑
2. **添加更多可能的CPU使用率字段名支持**确保从WebSocket服务器返回的CPU使用率数据能够被正确获取无论它使用什么字段名
3. **统一CPU使用率更新逻辑**:确保`processRealTimeData`函数和`updateStatsCards`函数中的CPU使用率更新逻辑一致
4. **添加调试日志**:在关键位置添加调试日志,以便于排查问题
## 实现步骤
1. 打开`dashboard.js`文件
2. 找到`updateStatsCards`函数的数组形式数据结构处理部分第631-641行添加CPU使用率的处理逻辑
3.`updateStatsCards`函数的CPU使用率数据获取逻辑中添加更多可能的字段名支持
4. 统一`processRealTimeData`函数和`updateStatsCards`函数中的CPU使用率更新逻辑
5. 添加调试日志,以便于排查问题
6. 测试修复是否生效
## 预期效果
修复后当WebSocket接收到实时数据更新时CPU使用率卡片会自动更新显示最新的CPU使用率和状态与其他统计卡片保持一致的实时更新效果。

View File

@@ -0,0 +1,18 @@
## 问题分析
CPU使用率卡片数据不会跟随WebSocket自动更新的原因是`updateStatsCards`函数中缺少了CPU使用率的更新逻辑。该函数负责处理WebSocket实时数据并更新统计卡片但只更新了7个统计卡片遗漏了CPU使用率卡片。
## 修复方案
1. **修改`updateStatsCards`函数**:在`dashboard.js`文件中添加CPU使用率和状态的更新逻辑
2. **添加数据获取逻辑**从不同可能的数据结构中获取CPU使用率数据
3. **更新DOM元素**将获取到的CPU使用率数据更新到`cpu-usage``cpu-status`元素中
4. **添加状态判断**根据CPU使用率值设置不同的状态文本和样式
## 实现步骤
1. 打开`dashboard.js`文件
2. 找到`updateStatsCards`函数约第550行
3. 在函数末尾添加CPU使用率更新逻辑
4. 确保从`stats`对象中正确获取CPU使用率数据
5. 更新`cpu-usage``cpu-status`元素的内容和样式
## 预期效果
修复后当WebSocket接收到实时数据更新时CPU使用率卡片会自动更新显示最新的CPU使用率和状态与其他统计卡片保持一致的实时更新效果。

View File

@@ -0,0 +1,38 @@
## 问题分析
1. **配置正确**:配置文件中`enableDNSSEC`设置为`true`,但卡片显示"已禁用"
2. **代码问题**:在`dns/server.go``GetStats`函数中,返回的`Stats`结构体缺少了DNSSEC相关字段
3. **API行为**当API访问DNSSEC相关字段时由于没有从`GetStats`函数返回,它们的值都是默认值(`DNSSECEnabled`默认值为`false`
4. **前端显示**前端卡片从API获取`DNSSECEnabled`值,因此显示"已禁用"
## 解决方案
修改`dns/server.go`中的`GetStats`函数,确保返回的`Stats`副本包含所有DNSSEC相关字段包括`DNSSECEnabled``DNSSECQueries``DNSSECSuccess``DNSSECFailed`
## 修复步骤
1. 打开`/root/dns/dns/server.go`文件
2. 找到`GetStats`函数大约在第960行
3. 修改返回的`Stats`结构体添加缺失的DNSSEC相关字段
4. 确保所有DNSSEC字段都从原始`Stats`结构体复制到返回的副本中
## 预期结果
修复后API将正确返回DNSSEC启用状态前端卡片将显示"已启用",与配置文件中的设置一致。
## 代码修改点
```go
// 返回统计信息的副本
return &Stats{
Queries: s.stats.Queries,
Blocked: s.stats.Blocked,
Allowed: s.stats.Allowed,
Errors: s.stats.Errors,
LastQuery: s.stats.LastQuery,
AvgResponseTime: s.stats.AvgResponseTime,
TotalResponseTime: s.stats.TotalResponseTime,
QueryTypes: queryTypesCopy,
SourceIPs: sourceIPsCopy,
CpuUsage: s.stats.CpuUsage,
DNSSECQueries: s.stats.DNSSECQueries,
DNSSECSuccess: s.stats.DNSSECSuccess,
DNSSECFailed: s.stats.DNSSECFailed,
DNSSECEnabled: s.stats.DNSSECEnabled, // 这是关键字段,确保返回正确的启用状态
}
```

View File

@@ -0,0 +1,22 @@
## 问题分析
`/root/dns/dns/server.go`文件的`forwardDNSRequestWithCache`函数中,`dnssecSuccess`变量被声明和赋值,但没有被实际使用,导致编译错误:
```
dns/server.go:622:6: declared and not used: dnssecSuccess
```
## 代码检查
1. 变量在第622行声明`var dnssecSuccess bool = false`
2. 在第708行和第714行被赋值`dnssecSuccess = false``dnssecSuccess = true`
3. 但在整个函数中,该变量没有被任何条件判断或返回值使用
4. 实际使用的是`signatureValid`变量来表示DNSSEC验证结果
## 解决方案
删除未使用的`dnssecSuccess`变量,因为它的值与`signatureValid`完全相同,且没有被实际使用。
## 修复步骤
1. 删除第622行的`dnssecSuccess`变量声明
2. 删除第708行和第714行对`dnssecSuccess`变量的赋值
3. 保留`signatureValid`变量的使用因为它是实际用于判断DNSSEC验证结果的变量
## 预期结果
修复后编译器不再报错DNSSEC验证逻辑保持不变继续正常工作。

View File

@@ -0,0 +1,20 @@
### 问题分析
1. **收集验证记录集不完整**在DNSSEC验证过程中代码只从`response.Answer``response.Ns`中收集记录,而忽略了`response.Extra`中的记录导致某些DNSSEC记录没有被正确验证。
2. **验证失败处理不当**当DNSSEC签名验证失败时代码会直接丢弃该响应而不是返回包含DNSSEC记录的响应导致客户端无法获取DNSSEC记录。
### 修复方案
1. **修复记录集收集逻辑**在收集需要验证的记录集rrset确保从`response.Answer``response.Ns``response.Extra`中收集所有相关记录。
2. **改进验证失败处理**即使DNSSEC签名验证失败也要返回包含DNSSEC记录的响应同时设置正确的AD标志让客户端决定如何处理验证失败的情况。
3. **优化备选响应逻辑**确保在所有上游服务器都不返回DNSSEC记录时仍然能够正确处理和返回响应。
### 具体修改
1. 修改`forwardDNSRequestWithCache`函数中的记录集收集逻辑,添加对`response.Extra`的处理
2. 修改DNSSEC验证失败时的处理逻辑确保返回包含DNSSEC记录的响应
3. 优化备选响应的保存和返回逻辑确保DNSSEC记录能够被正确处理
### 测试计划
1. 编译修复后的代码
2. 启动DNS服务器并启用DNSSEC
3. 使用dig命令测试DNSSEC记录查询例如`dig +dnssec example.com`
4. 检查查询结果是否包含DNSSEC记录RRSIG、DNSKEY等
5. 检查日志中是否有DNSSEC验证相关的记录

View File

@@ -0,0 +1,18 @@
## 问题分析
当前实现中,详细图表(浮窗)的时间范围切换会影响到主页面的图表显示,这是因为它们共享了全局变量`currentTimeRange``isMixedView`。当用户在浮窗内切换时间范围时,这些全局变量会被修改,导致主页面的图表也随之改变。
## 解决方案
1. 为详细图表创建独立的变量,用于存储其时间范围和混合视图状态
2. 修改`initDetailedTimeRangeToggle`函数,使其使用这些独立的变量,而不是全局变量
3. 修改`drawDetailedDNSRequestsChart`函数,使用独立的变量来控制图表显示
4. 确保主图表默认显示混合视图
## 修复步骤
1.`dashboard.js`文件中添加详细图表专用的全局变量
2. 修改`initDetailedTimeRangeToggle`函数,使用详细图表专用变量
3. 修改`drawDetailedDNSRequestsChart`函数,使用详细图表专用变量
4. 确保主图表默认显示混合视图
5. 测试修复效果,确保浮窗内的时间范围切换不会影响主页面图表
## 预期效果
修复后DNS请求趋势图表默认显示混合内容视图不变当用户点击展开按钮查看详细数据时浮窗内的时间范围切换不会影响到主页面的图表内容提供更好的用户体验。

View File

@@ -0,0 +1,15 @@
## 问题分析
DNS请求趋势图表展开后不能随页面放大缩小自动调整大小。通过代码分析发现`window.addEventListener('resize')`事件监听器只处理了侧边栏的显示/隐藏,没有处理图表的调整大小。
## 解决方案
1. 修改`window.addEventListener('resize')`事件监听器,添加对所有图表(包括详细图表)的更新调用
2. 确保在模态框显示时,图表能够正确响应窗口大小变化
## 修复步骤
1. 打开`/root/dns/static/js/dashboard.js`文件
2. 找到`window.addEventListener('resize')`事件监听器
3. 修改该事件监听器,添加对`dnsRequestsChart``detailedDnsRequestsChart`的更新调用
4. 确保图表实例存在时才调用update方法
## 预期效果
修复后当用户展开DNS请求趋势图表并调整浏览器窗口大小时图表会自动调整大小以适应新的窗口尺寸。

View File

@@ -0,0 +1,21 @@
## 问题分析
展开图表超出了显示范围,没有按照展开浮窗大小显示内容。通过代码分析,发现以下问题:
1. 图表容器使用了固定高度 `h-[600px]`,这可能导致在某些屏幕尺寸下图表超出显示范围
2. 浮窗容器设置了 `max-h-[90vh]`,但图表容器的固定高度可能超过这个限制
3. 当图表初始化时,可能没有正确计算容器的实际可用空间
## 解决方案
1. 修改图表容器的高度设置,使其更灵活,能够适应不同屏幕尺寸
2. 确保图表容器的高度不超过浮窗的最大高度限制
3. 在图表显示时,确保正确计算容器大小并更新图表
## 修复步骤
1. 打开 `index.html` 文件,修改图表容器的高度设置
2. 将固定高度 `h-[600px]` 改为相对高度或最大高度
3. 确保图表容器的高度能够适应浮窗的可用空间
4.`drawDetailedDNSRequestsChart` 函数中,添加对图表容器大小的检查和调整
5. 确保在图表显示时,正确计算容器大小并更新图表
## 预期效果
修复后当用户展开DNS请求趋势图表时图表会根据浮窗的可用空间自动调整大小不会超出显示范围提供更好的用户体验。

View File

@@ -0,0 +1,4 @@
1. 检查updateTopData函数修复当API调用失败或返回错误时没有数据显示的问题
2. 确保在所有情况下updateTopDomainsTable函数都能被调用即使API调用失败
3. 为updateTopData函数添加错误处理确保在API调用失败时使用模拟数据
4. 测试修复后的代码确保TOP域名卡片能正确显示数据

View File

@@ -0,0 +1,32 @@
## 问题分析
经过分析TOP客户端数据加载失败的原因是**前端API调用路径与后端注册的路径不匹配**
1. **前端代码**:在 `static/js/api.js` 中,`getTopClients` 方法调用的是 `/top-clients` API端点
2. **后端代码**:在 `http/server.go`注册的API路径是 `/api/top-clients`
3. **结果**前端请求404错误导致TOP客户端数据加载失败
## 解决方案
修改前端API调用`/top-clients` 改为 `/api/top-clients`,确保与后端注册的路径匹配。
## 实现步骤
1. **修改 `static/js/api.js` 文件**
- 找到 `getTopClients` 方法
- 将API调用路径从 `/top-clients?t=` 改为 `/api/top-clients?t=`
2. **验证修复**
- 刷新页面检查TOP客户端数据是否能正常加载
- 查看浏览器控制台确认没有404错误
## 预期效果
- TOP客户端数据能够正常加载
- 不再显示"加载失败"错误信息
- 页面上显示真实的TOP客户端数据
## 代码修改
只需要修改一个文件:
- `static/js/api.js`:更新 `getTopClients` 方法的API路径

View File

@@ -0,0 +1,4 @@
1. 检查updateTopClientsTable函数添加对tableBody是否存在的检查
2. 改进模拟数据使用真实的IP地址和请求次数
3. 确保函数在任何情况下都能正确执行
4. 测试修复后的代码确保TOP客户端卡片能正确显示数据

View File

@@ -0,0 +1,95 @@
# 问题分析
1. **问题现象**:当用户在某个设置项页面刷新时,页面会自动回到仪表盘页面。
2. **问题根源**
-`dashboard.js` 文件中,`handleHashChange` 函数被定义在 `handlePageSwitch` 函数内部
- 当页面刷新时,`handleHashChange` 函数会在页面加载完成后立即执行
- 此时,`menuItems` 可能还没有被正确初始化,或者 `handleHashChange` 函数内部的 `menuItems` 引用的是旧的变量,导致它无法找到对应的菜单项
-`handleHashChange` 函数无法找到对应的菜单项时,它会执行 `window.location.hash = '#dashboard'`,将页面重定向到仪表盘页面
3. **具体问题**
- `handleHashChange` 函数在找不到对应的菜单项时,会立即重定向到仪表盘页面,而不是先尝试直接显示对应的内容
- 这导致用户在刷新页面时即使URL中包含正确的hash页面也会被重定向到仪表盘
# 修复方案
1. **修复 `handleHashChange` 函数**
- 修改 `handleHashChange` 函数,确保它在找不到对应的菜单项时,不会总是重定向到仪表盘页面
- 当无法找到对应的菜单项时,先尝试直接显示对应的内容
- 只有当对应的内容也不存在时,才重定向到仪表盘页面
2. **优化页面初始化流程**
- 确保 `handleHashChange` 函数在页面完全加载后才执行
- 确保 `menuItems` 变量在 `handleHashChange` 函数执行前已经被正确初始化
# 实现步骤
1. 修改 `dashboard.js` 文件中的 `handleHashChange` 函数:
- 当无法找到对应的菜单项时,先尝试直接显示对应的内容
- 只有当对应的内容也不存在时,才重定向到仪表盘页面
2. 测试修复后的功能:
- 启动DNS服务器
- 访问Web界面导航到某个设置项页面例如 `#config`
- 刷新页面
- 验证页面是否仍然显示在设置项页面,而不是自动回到仪表盘页面
# 预期结果
- 用户在某个设置项页面刷新时,页面会保持在当前页面,不会自动回到仪表盘页面
- 只有当URL中的hash无效时页面才会重定向到仪表盘页面
- 页面导航功能正常,用户可以通过点击菜单项切换页面
# 修复代码
```javascript
// 修改 dashboard.js 文件中的 handleHashChange 函数
function handleHashChange() {
let hash = window.location.hash;
// 如果没有hash默认设置为#dashboard
if (!hash) {
hash = '#dashboard';
window.location.hash = hash;
return;
}
const targetId = hash.substring(1);
// 查找对应的内容元素
const contentElement = document.getElementById(`${targetId}-content`);
// 如果找到了对应的内容元素,直接显示
if (contentElement) {
// 隐藏所有内容
document.querySelectorAll('[id$="-content"]').forEach(content => {
content.classList.add('hidden');
});
// 显示目标内容
contentElement.classList.remove('hidden');
// 查找对应的菜单项并更新活动状态
let targetMenuItem = null;
menuItems.forEach(item => {
if (item.getAttribute('href') === hash) {
targetMenuItem = item;
}
});
// 更新活动菜单项
menuItems.forEach(item => {
item.classList.remove('sidebar-item-active');
if (item.getAttribute('href') === hash) {
item.classList.add('sidebar-item-active');
// 更新页面标题
document.getElementById('page-title').textContent = item.querySelector('span').textContent;
}
});
} else {
// 如果没有找到对应的内容默认显示dashboard
window.location.hash = '#dashboard';
}
}
```

View File

@@ -0,0 +1,58 @@
## 问题分析
通过代码分析,我发现/api/top-clients端点数据无法持久化的原因有两个
1. **数据保存逻辑缺失**:在`saveStatsData`方法中,虽然`StatsData`结构体包含了`ClientStats`字段,但没有将`server.clientStats`赋值给`statsData.ClientStats`
2. **数据加载逻辑缺失**:在`loadStatsData`方法中,虽然`StatsData`结构体包含了`ClientStats`字段,但没有将`statsData.ClientStats`赋值给`server.clientStats`
3. **自动保存功能未启用**`startAutoSave`方法没有在`Server``Start`方法中被调用,导致数据不会定期保存
## 修复计划
1. **修改`saveStatsData`方法**:添加保存`ClientStats`数据的逻辑
2. **修改`loadStatsData`方法**:添加加载`ClientStats`数据的逻辑
3. **在`Server.Start`方法中调用`startAutoSave`**:确保数据定期保存
## 修复步骤
### 步骤1修改`saveStatsData`方法
`dns/server.go`文件中,修改`saveStatsData`方法,添加保存`ClientStats`数据的逻辑:
```go
// 复制客户端统计数据
s.clientStatsMutex.RLock()
statsData.ClientStats = make(map[string]*ClientStats)
for k, v := range s.clientStats {
statsData.ClientStats[k] = v
}
s.clientStatsMutex.RUnlock()
```
### 步骤2修改`loadStatsData`方法
`dns/server.go`文件中,修改`loadStatsData`方法,添加加载`ClientStats`数据的逻辑:
```go
s.clientStatsMutex.Lock()
if statsData.ClientStats != nil {
s.clientStats = statsData.ClientStats
}
s.clientStatsMutex.Unlock()
```
### 步骤3在`Server.Start`方法中调用`startAutoSave`
`dns/server.go`文件的`Start`方法中,添加调用`startAutoSave`的代码:
```go
// 启动自动保存功能
go s.startAutoSave()
```
## 预期效果
修复后,/api/top-clients端点的数据将
1. 在服务器启动时从文件加载
2. 定期自动保存到文件
3. 在服务器停止时最后保存一次
这样就能确保/api/top-clients端点的数据持久化不会因为服务器重启而丢失。

View File

@@ -0,0 +1,31 @@
### 问题分析
1. **语法错误**:文件中存在多处语法错误,包括:
- 第34行`type ClientStats uct` 应该是 `type ClientStats struct`
- 第36行`Count 64` 应该是 `Count int64`
- 第143行`/ DNSCache` 缺少一个星号,应该是 `// DNSCache`
- 多处缩进问题
- 函数内容被截断
2. **文件完整性问题**文件内容不完整handleDNSRequest函数被截断缺少后续的函数定义
### 修复方案
1. **修复语法错误**
- 修复ClientStats结构体定义
- 修复DNSCache注释和结构体定义
- 修复所有缩进问题
2. **恢复完整内容**
- 重新编写或恢复handleDNSRequest函数的完整内容
- 确保所有后续函数定义完整
### 具体修改
1. 修复第34行的ClientStats结构体定义
2. 修复第143行的DNSCache注释和结构体定义
3. 修复所有缩进问题
4. 恢复完整的handleDNSRequest函数
5. 恢复后续所有函数定义
### 测试计划
1. 编译修复后的代码
2. 启动DNS服务器并测试功能
3. 验证DNSSEC功能是否正常工作

View File

@@ -0,0 +1,4 @@
1. 检查updateRecentBlockedTable函数添加对tableBody是否存在的检查
2. 确保在移除recent-blocked-table元素后updateRecentBlockedTable函数不会导致错误
3. 确保后续的updateTopDomainsTable调用能够被正确执行
4. 测试修复后的代码确保TOP域名卡片能正确显示数据

View File

@@ -0,0 +1,90 @@
## 问题分析
通过代码分析我发现web屏蔽管理页面点击后一直显示处理中灰色遮罩的原因有两个
1. **JavaScript错误导致`hideLoading()`不被调用**
- `loadLocalRules()`函数尝试访问`document.getElementById('local-rules-count')`但该元素在HTML中不存在
- `loadRemoteRules()`函数尝试访问`document.getElementById('remote-rules-count')`但该元素在HTML中不存在
- `setupShieldEventListeners()`函数尝试访问`document.getElementById('view-local-rules-btn')``document.getElementById('view-remote-rules-btn')`但这些元素在HTML中不存在
- 这些JavaScript错误会导致函数执行中断从而使`hideLoading()`不被调用,加载遮罩一直显示
2. **竞态条件问题**
- `initShieldPage()`函数并行调用三个异步函数:`loadShieldStats()``loadLocalRules()``loadRemoteBlacklists()`
- 每个异步函数都会调用`showLoading()`,而`showLoading()`会先调用`hideLoading()`来移除现有加载状态,然后创建一个新的加载状态
- 这种并行调用可能导致竞态条件,使最后一个加载遮罩无法被正确销毁
## 修复计划
1. **修改`loadLocalRules()`函数**添加元素存在性检查避免JavaScript错误
2. **修改`loadRemoteRules()`函数**添加元素存在性检查避免JavaScript错误
3. **修改`setupShieldEventListeners()`函数**添加元素存在性检查避免JavaScript错误
4. **修改`initShieldPage()`函数**:确保只创建一个加载遮罩,并在所有异步函数完成后销毁它
## 修复步骤
### 步骤1修改`loadLocalRules()`函数
`shield.js`文件中,修改`loadLocalRules()`函数,添加元素存在性检查:
```javascript
// 更新本地规则数量显示
if (document.getElementById('local-rules-count')) {
document.getElementById('local-rules-count').textContent = data.localRulesCount || 0;
}
```
### 步骤2修改`loadRemoteRules()`函数
`shield.js`文件中,修改`loadRemoteRules()`函数,添加元素存在性检查:
```javascript
// 更新远程规则数量显示
if (document.getElementById('remote-rules-count')) {
document.getElementById('remote-rules-count').textContent = data.remoteRulesCount || 0;
}
```
### 步骤3修改`setupShieldEventListeners()`函数
`shield.js`文件中,修改`setupShieldEventListeners()`函数,添加元素存在性检查:
```javascript
// 添加切换查看本地规则和远程规则的事件监听
if (document.getElementById('view-local-rules-btn')) {
document.getElementById('view-local-rules-btn').addEventListener('click', loadLocalRules);
}
if (document.getElementById('view-remote-rules-btn')) {
document.getElementById('view-remote-rules-btn').addEventListener('click', loadRemoteRules);
}
```
### 步骤4修改`initShieldPage()`函数
`shield.js`文件中,修改`initShieldPage()`函数,确保只创建一个加载遮罩,并在所有异步函数完成后销毁它:
```javascript
// 初始化屏蔽管理页面
async function initShieldPage() {
showLoading('加载屏蔽管理数据...');
try {
// 并行加载所有数据
await Promise.all([
loadShieldStats(),
loadLocalRules(),
loadRemoteBlacklists()
]);
// 设置事件监听器
setupShieldEventListeners();
} finally {
hideLoading();
}
}
```
### 步骤5修改异步函数移除内部的`showLoading()`和`hideLoading()`调用
`shield.js`文件中,修改`loadShieldStats()``loadLocalRules()``loadRemoteBlacklists()`函数,移除内部的`showLoading()``hideLoading()`调用,只保留数据加载逻辑。
## 预期效果
修复后web屏蔽管理页面点击后
1. 只会显示一个加载遮罩
2. 不会出现JavaScript错误
3. 所有数据加载完成后,加载遮罩会被正确隐藏
4. 页面会正常显示屏蔽管理设置内容

View File

@@ -0,0 +1,37 @@
# 修复保存按钮错位问题
## 问题分析
远程黑名单管理部分的保存按钮错位,原因是:
- 添加黑名单表单使用了Grid布局`grid grid-cols-1 md:grid-cols-3 gap-4`
- 前两个子元素包含label和input高度较高
- 第三个子元素只有按钮和状态显示没有label高度较矮
- 导致按钮在垂直方向上与前两个输入框不对齐
## 解决方案
修改添加黑名单表单的HTML结构确保三个子元素在垂直方向上对齐
1. **为第三个子元素添加垂直对齐**
- 确保保存按钮容器与前两个输入框容器在垂直方向上对齐
- 可以选择顶部对齐或中间对齐,保持视觉一致性
2. **统一子元素结构**
- 为第三个子元素添加一个隐藏的label确保结构一致
- 或者调整Grid布局使第三个子元素的内容垂直居中
3. **调整容器样式**
- 修改保存按钮容器的样式,确保垂直对齐
- 可以使用`items-start``items-center`属性
## 实现步骤
1. 修改`add-blacklist-form`的HTML结构
- 为第三个子元素添加`items-start`类,使其与前两个子元素的顶部对齐
- 或者添加一个隐藏的label确保结构一致
2. 测试修改后的效果
- 确保保存按钮与前两个输入框在垂直方向上对齐
- 保持响应式布局,在不同屏幕尺寸下都能正常显示
## 预期效果
保存按钮将与前两个输入框在垂直方向上对齐,消除错位现象,提升表单的视觉一致性和用户体验。

View File

@@ -0,0 +1,134 @@
# 优化设置界面实现计划
## 问题分析
当前设置界面存在配置项重复问题,需要进行优化,具体包括:
1. "远程规则URL"配置项在多个界面重复出现
2. "启用API"和"主机"选项不需要在当前界面显示
3. 需要确保保存功能正常工作写入config.json并触发服务器重新加载配置
## 优化方案
### 1. 修改HTML结构
* 移除"远程规则URL"配置项
* 移除"启用API"选项
* 移除"主机"选项
* 调整布局,确保界面美观合理
### 2. 更新JavaScript代码
* 修改`populateConfigForm`函数,移除对已删除配置项的处理
* 修改`collectFormData`函数,移除对已删除配置项的收集
* 确保保存功能能正确写入config.json文件
* 实现服务器重新加载配置的触发机制
* 提供明确的成功/失败反馈
### 3. 测试和验证
* 测试所有保留配置项的加载和保存功能
* 验证保存操作能正确写入config.json文件
* 验证服务器能重新加载配置
* 测试成功/失败反馈是否明确
## 具体实现步骤
1. **修改HTML结构**
* 编辑`index.html`文件,移除不需要的配置项
* 调整布局,确保界面美观合理
2. **更新JavaScript代码**
* 编辑`config.js`文件,修改`populateConfigForm`函数
* 修改`collectFormData`函数,移除对已删除配置项的处理
* 确保`handleSaveConfig`函数能正确保存配置
* 实现服务器重新加载配置的触发机制
3. **测试和验证**
* 测试配置项的加载功能
* 测试配置项的保存功能
* 验证config.json文件是否正确更新
* 验证服务器是否重新加载配置
* 测试成功/失败反馈是否明确
## 预期效果
* 设置界面布局合理,无重复配置项
* 所有保留配置项均可正常配置
* 保存功能能正确写入config.json文件
* 服务器能重新加载配置,使更改立即生效
* 保存操作有明确的成功/失败反馈
## 技术要点
* 使用HTML和JavaScript修改界面结构和功能
* 确保与服务器API的正确交互
* 实现良好的用户反馈机制
* 确保配置的正确保存和加载
## 实现时间
* 预计1-2小时完成所有修改和测试
## 风险评估
* 低风险:修改范围明确,不涉及核心功能
* 可回滚:所有修改均为前端修改,可通过恢复文件轻松回滚
## 依赖关系
* 依赖服务器API的正常工作
* 依赖config.json文件的读写权限
## 测试策略
* 手动测试所有配置项的加载和保存功能
* 验证config.json文件的更新
* 测试服务器配置的重新加载
* 测试成功/失败反馈
## 验收标准
* 设置界面布局合理,无重复配置项
* 所有保留配置项均可正常配置
* 保存功能能正确写入config.json文件
* 服务器能重新加载配置,使更改立即生效
* 保存操作有明确的成功/失败反馈

View File

@@ -0,0 +1,33 @@
1. **修复未定义函数问题**:移除对 `containsRegexSpecialChars` 函数的调用,该函数在代码中未定义
2. **修改正则表达式处理逻辑**
* 对于 `/` 包裹的规则,直接将中间内容作为正则表达式模式
* 不再添加 `.*` 前缀和后缀,也不再使用 `regexp.QuoteMeta` 转义
* 确保正则表达式能正确匹配域名中的相关内容
3. **修改文件**
* `/root/dns/shield/manager.go`:更新 `parseRule` 函数中的正则表达式处理逻辑
**验证修复**
确保 `/ad.qq.com/` 规则能正确匹配 `ad.qq.com` 域名
确保 `/ad/` 规则能匹配包含 `ad` 的域名
* 确保正则表达式特殊字符能被正确处理
* **测试场景**
* 测试 `/ad.qq.com/` 规则匹配 `ad.qq.com`
* 测试 `/ad.qq.com/` 规则匹配 `sub.ad.qq.com`
* 测试 `/ad/` 规则匹配 `ad.example.com`
* 测试 `/ad/` 规则匹配 `example.ad.com`
* 测试 `/^ad/` 规则匹配 `ad.example.com` 但不匹配 `example.ad.com`

View File

@@ -0,0 +1,57 @@
## 问题分析
1. **端口被占用问题**
- 服务器重启时提示端口被占用,可能是因为之前的服务器进程没有完全关闭
- 从之前的ps aux命令输出中看到有一个dns-server进程在运行PID: 233272
- 这导致新的服务器进程无法绑定到相同的端口
2. **数据保存提示no such file or directory问题**
- 配置文件中statsFile和shield_stats.json的路径格式不一致有的带./,有的不带)
- 可能存在目录创建失败或权限问题
- 程序运行时的工作目录与预期不符
## 修复方案
1. **解决端口被占用问题**
- 在启动新服务器之前,确保所有旧的服务器进程都已关闭
- 可以通过kill命令手动关闭旧进程
- 或者在程序中添加自动检测和关闭旧进程的逻辑
2. **解决数据保存问题**
- 统一配置文件中的文件路径格式,确保所有路径都使用相对路径或绝对路径
- 确保createRequiredFiles函数能够正确创建所有必要的目录和文件
- 添加错误处理,确保在目录或文件创建失败时能够给出明确的错误信息
- 检查程序运行时的工作目录,确保路径解析正确
## 修复步骤
1. **关闭旧的服务器进程**
- 使用kill命令关闭旧的dns-server进程
- 验证旧进程是否已关闭
2. **统一配置文件中的文件路径格式**
- 修改配置文件,确保所有文件路径都使用一致的格式
- 例如,将所有路径改为相对路径,不带./前缀
3. **修改createRequiredFiles函数**
- 确保函数能够正确创建所有必要的目录和文件
- 添加更详细的错误处理和日志
- 确保函数能够处理不同格式的文件路径
4. **测试修复效果**
- 启动服务器,检查是否能够成功绑定到端口
- 检查数据文件是否能够正确保存
- 重启服务器,检查是否能够正常启动
## 预期效果
- 服务器能够成功启动,不会提示端口被占用
- 数据文件能够正确保存不会提示no such file or directory
- 服务器重启时能够正常启动,不会出现相同的问题
## 注意事项
- 确保在修改配置文件之前备份原始文件
- 确保程序有足够的权限创建和写入文件
- 确保在关闭旧进程之前,所有重要的数据都已保存
- 测试修复效果时,确保覆盖所有可能的情况

View File

@@ -0,0 +1,41 @@
# 问题分析
1. **问题现象**用户点击删除按钮删除本地规则时通过API发送数据到服务器但Web界面没有自动刷新本地规则列表。
2. **问题根源**
- 后端`RemoveRule`方法存在缺陷,当删除规则时没有正确更新所有相关映射
- 前端代码虽然调用了重新加载规则列表的函数,但由于后端数据不一致,导致刷新后列表没有变化
3. **具体问题**
-`shield/manager.go``RemoveRule`方法中,删除域名规则时只从`domainRules``domainExceptions`映射中删除了规则,但没有更新`domainRulesIsLocal``domainExceptionsIsLocal``domainRulesSource``domainExceptionsSource`映射
- 处理正则表达式规则时,使用了错误的比较方式(`re.pattern.String() != pattern`),而应该使用原始规则字符串进行比较
# 修复方案
1. **修复`RemoveRule`方法**
- 当删除域名规则时,同时更新所有相关映射
- 当删除排除规则时,同时更新所有相关映射
- 修复正则表达式规则的比较方式,使用原始规则字符串进行比较
2. **验证前端代码**
- 确认前端代码在删除规则后正确调用了重新加载规则列表的函数
- 验证前端代码处理API响应的逻辑是否正确
# 实现步骤
1. 修改`shield/manager.go`文件中的`RemoveRule`方法:
- 在删除域名规则时,添加删除`domainRulesIsLocal``domainRulesSource`映射的代码
- 在删除排除规则时,添加删除`domainExceptionsIsLocal``domainExceptionsSource`映射的代码
- 修复正则表达式规则的比较方式
2. 测试修复后的功能:
- 启动DNS服务器
- 访问Web界面添加几条本地规则
- 点击删除按钮删除其中一条规则
- 验证规则列表是否自动刷新,且删除的规则不再显示
# 预期结果
- 用户点击删除按钮后,规则被成功删除
- Web界面自动刷新删除的规则从列表中消失
- 本地规则文件被正确更新,删除的规则不再存在于文件中

View File

@@ -0,0 +1,51 @@
## 问题分析
通过代码分析,我发现本地规则管理删除规则功能失效的原因有两个:
1. **规则格式不匹配**
- 前端显示的规则带有修饰符,例如:`||example.com^`
- 服务器端实际存储的是裸域名,例如:`example.com`
- `RemoveRule` 函数在处理规则时,虽然尝试了多种格式变体,但没有正确处理前端发送的带有修饰符的规则
2. **本地规则标记未更新**
- `RemoveRule` 函数没有考虑 `m.domainRulesIsLocal``m.domainExceptionsIsLocal` 映射,这些映射用于标记哪些规则是本地规则
- 删除规则后,没有更新这些映射,导致规则删除不彻底
## 修复计划
1. **修改 `RemoveRule` 函数**
- 改进规则处理逻辑,确保能正确处理带有修饰符的规则
- 更新 `domainRulesIsLocal``domainExceptionsIsLocal` 映射,确保本地规则被正确删除
2. **修改 `GetLocalRules` 函数**
- 确保返回的规则格式与 `RemoveRule` 函数期望的格式一致
3. **添加调试日志**
- 在关键位置添加日志,便于调试和监控规则删除过程
## 修复步骤
### 步骤1修改 `RemoveRule` 函数
`shield/manager.go` 文件中,修改 `RemoveRule` 函数,改进规则处理逻辑:
1. 确保正确处理带有修饰符的规则
2. 更新 `domainRulesIsLocal``domainExceptionsIsLocal` 映射
3. 添加调试日志
### 步骤2测试修复效果
- 启动服务器
- 访问本地规则管理页面
- 添加一条本地规则
- 删除该规则
- 验证规则是否被正确删除,页面内容是否减少
## 预期效果
修复后,本地规则管理删除规则功能将正常工作:
- 点击删除按钮后,规则会被正确发送到服务器
- 服务器会正确处理带有修饰符的规则
- 本地规则标记会被正确更新
- 规则会被持久化保存
- 页面内容会立即减少
这样就能确保本地规则管理删除规则功能正常工作,提供良好的用户体验。

View File

@@ -0,0 +1,28 @@
## 问题分析
`main.js` 文件中,当用户点击菜单项时,代码使用了 `e.preventDefault()` 来阻止默认行为,这导致浏览器不会自动更新地址栏中的 hash。虽然 `dashboard.js` 文件中有 `handleHashChange` 函数来处理 hash 变化,但由于 `main.js` 中的 `e.preventDefault()`,点击菜单项时不会触发 hash 变化。
## 修复方案
1. **修改 `main.js` 文件**:移除 `e.preventDefault()`,或者在处理完点击事件后手动更新地址栏中的 hash
2. **确保 `main.js` 和 `dashboard.js` 中的点击事件处理逻辑不冲突**
3. **统一页面导航逻辑**:确保所有页面导航都通过 hash 变化来实现
## 修复步骤
1. **修改 `main.js` 文件**
- 移除 `e.preventDefault()`,允许浏览器自动更新地址栏中的 hash
- 或者在处理完点击事件后,手动设置 `window.location.hash = item.getAttribute('href')`
- 确保点击事件处理逻辑与 `dashboard.js` 中的 `handleHashChange` 函数兼容
2. **测试修复效果**
- 点击菜单项,检查地址栏中的 hash 是否正确更新
- 刷新页面,检查是否能保持在当前页面
- 直接修改地址栏中的 hash检查是否能正确导航到对应页面
## 预期效果
- 用户点击菜单项时,地址栏中的 hash 会自动更新
- 页面刷新时,会保持在当前页面
- 直接修改地址栏中的 hash 可以导航到对应页面
- 所有页面导航逻辑统一,避免冲突

View File

@@ -0,0 +1,48 @@
# 问题分析
1. **问题现象**规则更新后规则没有生效。用户添加或删除规则后DNS服务器仍然使用旧的规则进行域名屏蔽。
2. **问题根源**
-`addDomainRule`方法中,当添加一个域名规则时,它会为该域名的所有子域名也添加规则。例如,添加`example.com`规则时,会同时添加`example.com``com`规则。
- 但是,在`RemoveRule`方法中,当删除一个域名规则时,它只删除了指定的域名规则,而没有删除为子域名添加的规则。
- 这导致即使删除了主规则,子域名规则仍然存在,因此规则仍然生效。
3. **具体问题**
- 当添加`||test.example.com`规则时,`addDomainRule`方法会添加`test.example.com``example.com`两个规则。
- 当删除`||test.example.com^`规则时,`RemoveRule`方法只会删除`test.example.com`规则,而不会删除`example.com`规则,因此`example.com`仍然会被屏蔽。
- 此外,`RemoveRule`方法在处理通配符和URL匹配规则时也没有正确处理子域名规则的删除。
# 修复方案
1. **修复`RemoveRule`方法**
- 当删除一个域名规则时,同时删除为该域名的所有子域名添加的规则。
- 确保删除规则时,同时更新所有相关映射,包括主规则和子域名规则。
2. **验证修复效果**
- 启动DNS服务器
- 添加一条域名规则,例如`||test.example.com`
- 验证该规则及其子域名规则都被正确添加
- 删除该规则
- 验证该规则及其所有子域名规则都被正确删除
- 验证DNS服务器不再使用该规则进行域名屏蔽
# 实现步骤
1. 修改`shield/manager.go`文件中的`RemoveRule`方法:
- 在删除域名规则时,添加删除所有相关子域名规则的逻辑
- 确保删除规则时,同时更新所有相关映射
2. 测试修复后的功能:
- 启动DNS服务器
- 访问Web界面添加一条本地规则例如`||test.example.com`
- 验证该规则被正确添加
- 点击删除按钮删除该规则
- 验证该规则及其所有子域名规则都被正确删除
- 验证DNS服务器不再使用该规则进行域名屏蔽
# 预期结果
- 用户添加规则后,规则立即生效
- 用户删除规则后,规则立即失效
- 规则更新后DNS服务器使用最新的规则进行域名屏蔽
- 本地规则文件被正确更新,添加和删除的规则都能正确反映在文件中

View File

@@ -0,0 +1,20 @@
## 问题分析
从截图可以看出,远程黑名单管理的保存按钮与输入框在垂直方向上没有对齐,按钮位置偏下。
## 原因分析
1. 当前使用grid布局分为3列前两列包含label和input第三列包含空label和按钮容器
2. 空label虽然没有内容但仍然占据了空间mb-1 margin导致按钮被推到下方
3. 按钮容器使用了`items-center`但整体位置受label影响
## 修复方案
修改第三列的HTML结构移除空label直接放置按钮容器并使用flex布局的对齐属性确保按钮与输入框垂直对齐。
## 具体修改
1. 打开`/root/dns/static/index.html`文件
2. 找到远程黑名单管理的添加表单部分第839-847行
3. 修改第三列的HTML结构移除空label直接放置按钮容器
4. 使用flex布局的`items-end`属性确保按钮与输入框底部对齐
5. 调整按钮的margin-top为0确保与输入框精确对齐
## 预期效果
保存按钮与输入框在垂直方向上精确对齐,整体布局更加美观和专业。

View File

@@ -0,0 +1,5 @@
1. 修改HTML文件中TOP域名卡片的标题将"TOP域名"改为"请求域名排行"
2. 修改updateTopDomainsTable函数使其生成的HTML结构与updateTopBlockedTable函数一致
3. 将颜色从红色改为绿色,包括边框颜色、背景色和文本颜色
4. 确保生成的HTML结构与被拦截域名排行的样式一致
5. 测试修改后的代码,确保请求域名排行卡片显示正确

View File

@@ -0,0 +1,29 @@
# 修改远程黑名单管理界面,添加状态渐变效果
## 问题分析
当前远程黑名单管理界面的状态更新功能已经实现了3秒后恢复默认状态的功能但没有添加渐变效果用户体验不够流畅。
## 解决方案
修改`updateStatus`函数,为状态元素添加渐变效果,确保状态更新时的平滑过渡。
## 实现步骤
1. **修改`updateStatus`函数**
- 为状态元素添加CSS过渡效果
- 使用`classList`操作来添加/移除渐变类
- 确保状态更新时的平滑过渡
2. **添加CSS渐变样式**
- 在index.html中添加状态渐变的CSS样式
- 为不同状态loading、success、error添加不同的渐变效果
3. **测试修改后的功能**
- 确保状态更新时有平滑的渐变效果
- 确保3秒后状态能正常恢复默认
## 预期效果
远程黑名单管理界面的状态更新将具有平滑的渐变效果,包括:
- 加载状态的蓝色渐变效果
- 成功状态的绿色渐变效果
- 错误状态的红色渐变效果
- 所有状态在3秒后平滑消失恢复默认状态

View File

@@ -0,0 +1,40 @@
# 减小统计卡片大小并移除统计图
## 需求分析
用户希望减小统计卡片的大小并移除卡片中的统计图,只保留数值和基本信息。
## 实现方案
### 1. 修改统计卡片HTML结构
- 移除每个统计卡片中包含canvas元素的div高度为16px的图表区域
- 减小卡片的内边距从p-6改为p-4
- 调整卡片内部元素的间距,确保布局紧凑美观
### 2. 移除图表相关JavaScript代码
- 移除dashboard.js中对`initStatCardCharts()``updateStatCardCharts()`函数的调用
- 这些函数负责初始化和更新统计卡片中的折线图
- 移除后不会影响其他图表功能(如主图表区域的图表)
### 3. 具体修改点
#### HTML文件修改index.html
- 对于每个统计卡片共8个移除包含canvas的div元素
- 减小卡片内边距:将`p-6`改为`p-4`
- 调整卡片内部元素的margin和padding确保布局紧凑
#### JavaScript文件修改dashboard.js
- 移除第31行的`initStatCardCharts()`调用
- 移除第139行的`updateStatCardCharts(stats)`调用
- 移除第367行的`updateStatCardCharts(stats)`调用
- 移除第525行的`updateStatCardCharts(stats)`调用
## 预期效果
- 统计卡片大小减小,布局更紧凑
- 卡片中只显示标题、数值和百分比信息
- 移除了不必要的图表,减少了页面加载时间和资源消耗
- 整体界面更简洁,重点突出数值信息
## 注意事项
- 确保移除图表后不会导致其他功能错误
- 保持卡片之间的一致性和美观性
- 确保数值和百分比信息清晰可见

View File

@@ -0,0 +1,75 @@
## 实现计划在Web页面日志查询界面添加DNSSEC标志
### 1. 需求分析
- 在Web页面日志查询界面的域名下方区域添加绿色DNSSEC标志
- 当查询使用了DNSSEC时显示该标志
- 标志应清晰可见,与现有界面风格协调
### 2. 实现步骤
#### 步骤1扩展QueryLog结构体
- 修改 `/root/dns/dns/server.go` 文件中的 `QueryLog` 结构体
- 添加 `DNSSEC bool` 字段用于标识查询是否使用了DNSSEC
- 更新 `addQueryLog` 函数传入DNSSEC标志值
#### 步骤2在DNS请求处理中记录DNSSEC信息
- 修改 `/root/dns/dns/server.go` 文件中的 `handleDNSRequest` 函数
- 在处理DNS响应时检查是否使用了DNSSEC
- 将DNSSEC使用情况传递给 `addQueryLog` 函数
#### 步骤3修改前端页面显示DNSSEC标志
- 找到处理日志显示的前端代码
- 修改日志条目模板添加DNSSEC标志显示逻辑
- 使用Font Awesome或其他图标库的DNSSEC相关图标
- 当DNSSEC为true时显示绿色标志
#### 步骤4测试实现
- 编译并运行DNS服务器
- 访问Web界面的日志查询页面
- 进行DNS查询验证DNSSEC标志是否正确显示
### 3. 技术细节
#### 后端修改
- **文件**`/root/dns/dns/server.go`
- **修改内容**
- 扩展 `QueryLog` 结构体,添加 `DNSSEC bool` 字段
-`handleDNSRequest` 中判断DNSSEC使用情况
- 更新 `addQueryLog` 函数参数和调用
#### 前端修改
- **文件**`/root/dns/static/index.html` 或相关JavaScript文件
- **修改内容**
- 查找日志条目的HTML模板
- 添加DNSSEC标志显示逻辑
- 使用条件渲染仅当DNSSEC为true时显示
### 4. 预期效果
- 日志查询界面的每个条目在域名下方显示DNSSEC标志
- 使用DNSSEC的查询显示绿色标志
- 未使用DNSSEC的查询不显示标志
- 标志与现有界面风格协调,清晰易识别
### 5. 注意事项
- 确保DNSSEC标志的视觉设计与现有界面一致
- 确保标志在各种屏幕尺寸下都能正确显示
- 考虑添加悬停提示,说明该标志的含义
- 确保性能不受影响,标志渲染高效
### 6. 实现时间线
- 步骤115分钟扩展QueryLog结构体
- 步骤220分钟记录DNSSEC信息
- 步骤330分钟修改前端页面
- 步骤415分钟测试实现
### 7. 风险评估
- 前端代码位置不明确:需要仔细查找处理日志显示的代码
- DNSSEC检测逻辑可能复杂需要确保准确判断DNSSEC使用情况
- 图标资源问题:确保使用的图标库可用
### 8. 成功标准
- DNS服务器正常编译运行
- Web界面能正确显示DNSSEC标志
- 使用DNSSEC的查询显示绿色标志
- 未使用DNSSEC的查询不显示标志
- 标志显示位置正确,视觉效果良好

View File

@@ -0,0 +1,73 @@
## 实现计划在请求列表中显示DNSSEC标志
### 1. 需求分析
-`enableDNSSEC=true`请求列表中返回了DNSKEY的域名显示绿色的DNSSEC标志
- 标志应显示在域名名称旁边
- 保持现有界面风格一致
### 2. 实现步骤
#### 步骤1修改后端数据结构
-`dns/server.go`中扩展`BlockedDomain`结构体,添加`DNSSEC bool`字段
- 修改`GetTopResolvedDomains`函数返回包含DNSSEC标志的数据
- 记录每个域名是否使用了DNSSEC
#### 步骤2实现DNSSEC记录追踪
-`handleDNSRequest`函数中当检测到DNSSEC记录时更新域名的DNSSEC标志
- 添加一个新的映射表记录每个域名的DNSSEC使用情况
#### 步骤3修改API响应格式
- 修改`http/server.go`中的`handleTopDomains``handleTopResolvedDomains`函数
- 在返回的数据中添加`dnssec`字段
#### 步骤4修改前端显示逻辑
- 修改`static/js/dashboard.js`中的`updateTopDomainsTable`函数
- 在域名名称后添加DNSSEC标志
- 使用Font Awesome的锁图标绿色显示
#### 步骤5测试实现
- 编译并运行DNS服务器
- 访问Web界面检查请求列表
- 验证DNSSEC标志是否正确显示
### 3. 技术细节
#### 3.1 后端实现
- **文件**`/root/dns/dns/server.go`
- **修改内容**
- 扩展数据结构添加DNSSEC字段
- 实现DNSSEC记录追踪
- 修改API响应格式
#### 3.2 前端实现
- **文件**`/root/dns/static/js/dashboard.js`
- **修改内容**
- 修改`updateTopDomainsTable`函数
- 添加DNSSEC标志显示逻辑
- 使用条件渲染仅当dnssec为true时显示
#### 3.3 DNSSEC检测方法
- 检查响应中是否包含DNSKEY或RRSIG记录
- 记录每个域名的DNSSEC使用情况
- 在返回请求列表时包含该信息
### 4. 预期效果
- 请求列表中使用DNSSEC的域名显示绿色锁图标
- 标志与现有界面风格协调
- 性能不受影响
- 与现有功能兼容
### 5. 注意事项
- 确保DNSSEC标志的视觉设计与现有界面一致
- 确保标志在各种屏幕尺寸下都能正确显示
- 考虑添加悬停提示,说明该标志的含义
- 确保性能不受影响,数据更新高效
### 6. 实现时间线
- 步骤120分钟修改后端数据结构
- 步骤225分钟实现DNSSEC记录追踪
- 步骤315分钟修改API响应格式
- 步骤415分钟修改前端显示逻辑
- 步骤510分钟测试实现
通过以上实现请求列表将能够正确显示使用了DNSSEC的域名提高DNS查询的安全性可视化方便管理员监控和管理。

View File

@@ -0,0 +1,37 @@
## 实现计划
### 1. 配置文件修改
- **修改`/root/dns/config/config.go`**
-`DNSConfig`结构体中添加`PrefixDomain []string`字段用于支持search domain功能
-`LoadConfig`函数中添加`prefixDomain`的默认值处理
### 2. DNS请求处理逻辑修改
- **修改`/root/dns/dns/server.go`中的`forwardDNSRequestWithCache`函数**
- 强化domainSpecificDNS逻辑确保当域名匹配时只使用指定的DNS服务器
- 移除向DNSSEC专用服务器发送请求的逻辑当域名匹配domainSpecificDNS时
- 确保匹配域名的DNS查询结果不会被其他DNS服务器的响应覆盖
- **修改`/root/dns/dns/server.go`中的`handleDNSRequest`函数**
- 实现search domain功能当直接查询失败时尝试添加prefixDomain中指定的域名前缀
- 按照/etc/resolv.conf中的search domain逻辑处理查询请求
### 3. 配置文件示例更新
- **更新配置文件示例**
- 添加`prefixDomain`配置项示例
- 说明search domain功能的使用方法
### 4. 测试验证
- 测试domainSpecificDNS强制使用功能确保匹配的域名只使用指定的DNS服务器
- 测试search domain功能确保能够正确处理带前缀的域名查询
- 测试不同配置组合下的功能正确性
## 预期效果
1. 当域名匹配domainSpecificDNS配置时无论DNSSEC是否启用只使用指定的DNS服务器
2. 支持search domain功能能够自动尝试添加配置的域名前缀
3. 配置简单直观,与/etc/resolv.conf的search domain行兼容
## 实现要点
- 确保domainSpecificDNS配置的优先级最高
- 实现高效的search domain查询逻辑避免不必要的网络请求
- 保持代码的可读性和可维护性
- 确保与现有功能的兼容性

View File

@@ -0,0 +1,87 @@
# 实现hash路由功能避免刷新时返回到主页
## 问题分析
当前系统使用简单的页面切换逻辑,通过隐藏/显示不同的内容区域来实现页面切换。这种方式的缺点是:
1. 刷新页面后会返回到主页
2. 无法通过URL直接访问特定页面
3. 无法在浏览器历史记录中保存页面状态
## 解决方案
实现hash路由功能将页面状态保存在URL的hash部分例如
- http://localhost:8080/#dashboard
- http://localhost:8080/#blacklists
- http://localhost:8080/#query
- http://localhost:8080/#config
## 实现步骤
### 1. 修改handlePageSwitch函数
* 在页面切换时更新URL的hash
* 移除e.preventDefault()允许默认的hash变化
### 2. 添加hashchange事件监听器
* 监听window的hashchange事件
* 在hash变化时根据hash值显示相应的内容
* 更新页面标题和活动菜单项
### 3. 添加initHashRoute函数
* 在页面加载时调用
* 获取URL的hash值
* 如果没有hash值默认设置为#dashboard
* 根据hash值显示相应的内容
* 更新页面标题和活动菜单项
### 4. 在页面加载时调用initHashRoute函数
* 确保在DOMContentLoaded事件中调用initHashRoute函数
## 预期效果
* 页面切换时URL的hash会相应更新
* 刷新页面后会显示与URL hash对应的页面
* 默认情况下http://localhost:8080会显示dashboard内容URL变为http://localhost:8080/#dashboard
* 可以通过URL直接访问特定页面
* 浏览器历史记录中会保存页面状态
## 技术要点
* 使用window.location.hash获取和设置URL的hash值
* 使用window.addEventListener('hashchange', ...)监听hash变化
* 使用window.addEventListener('DOMContentLoaded', ...)在页面加载时初始化
* 确保在页面加载时检查hash值如果没有则设置为#dashboard
## 实现时间
* 预计30分钟完成所有修改和测试
## 风险评估
* 低风险:修改范围明确,不涉及核心功能
* 可回滚:所有修改均为前端修改,可通过恢复文件轻松回滚
## 依赖关系
* 依赖现有的页面切换逻辑
* 依赖现有的CSS类和HTML结构
## 测试策略
* 手动测试页面切换时URL hash的变化
* 测试刷新页面后是否显示正确的页面
* 测试直接访问带hash的URL是否显示正确的页面
* 测试默认情况下是否显示dashboard内容
## 验收标准
* 页面切换时URL的hash会相应更新
* 刷新页面后会显示与URL hash对应的页面
* 默认情况下http://localhost:8080会显示dashboard内容URL变为http://localhost:8080/#dashboard
* 可以通过URL直接访问特定页面
* 浏览器历史记录中会保存页面状态

View File

@@ -0,0 +1,96 @@
## 问题分析
当前系统已经实现了黑名单管理的后端API但前端界面缺少完整的黑名单管理功能。具体来说
1. 后端API已经实现了黑名单管理的所有功能
- GET /api/shield/blacklists - 获取所有黑名单
- POST /api/shield/blacklists - 添加黑名单
- PUT /api/shield/blacklists - 更新黑名单
- DELETE /api/shield/blacklists/{name} - 删除黑名单
2. 前端已经有了一些基础功能:
- loadRemoteBlacklists函数用于加载远程黑名单
- setupShieldEventListeners函数用于设置事件监听器
- 页面上有blacklist-count元素用于显示黑名单数量
3. 但是,前端缺少完整的黑名单管理界面,包括:
- 黑名单列表展示
- 添加/编辑/删除黑名单的功能
- 启用/禁用黑名单的功能
- 更新黑名单的功能
## 实现方案
1. **添加黑名单管理HTML界面**
- 在shield-content中添加黑名单管理区域
- 包括黑名单列表、添加/编辑表单、操作按钮等
2. **实现黑名单列表的加载和展示**
- 完善loadRemoteBlacklists函数将黑名单数据渲染到页面上
- 显示黑名单的名称、URL、状态、更新时间等信息
- 添加操作按钮(编辑、删除、启用/禁用、更新)
3. **实现添加/编辑黑名单功能**
- 添加表单用于输入黑名单名称、URL等信息
- 实现表单提交功能调用后端API添加/编辑黑名单
4. **实现删除黑名单功能**
- 为删除按钮添加事件监听器
- 调用后端API删除黑名单
- 更新黑名单列表
5. **实现启用/禁用黑名单功能**
- 为启用/禁用按钮添加事件监听器
- 调用后端API更新黑名单状态
- 更新黑名单列表
6. **实现更新黑名单功能**
- 为更新按钮添加事件监听器
- 调用后端API更新黑名单
- 显示更新状态
## 实现步骤
1. **修改HTML文件**
- 在shield-content中添加黑名单管理区域
- 添加黑名单列表表格
- 添加添加/编辑黑名单表单
- 添加操作按钮
2. **修改shield.js文件**
- 完善loadRemoteBlacklists函数渲染黑名单列表
- 添加添加/编辑/删除黑名单的函数
- 添加启用/禁用黑名单的函数
- 添加更新黑名单的函数
- 完善setupShieldEventListeners函数添加黑名单管理相关的事件监听器
3. **测试功能**
- 测试黑名单列表的加载和展示
- 测试添加/编辑/删除黑名单功能
- 测试启用/禁用黑名单功能
- 测试更新黑名单功能
## 预期效果
- 用户可以在web界面上查看所有黑名单
- 用户可以添加新的黑名单
- 用户可以编辑现有黑名单
- 用户可以删除黑名单
- 用户可以启用/禁用黑名单
- 用户可以手动更新黑名单
- 所有操作都能实时反映到页面上
## 注意事项
- 确保界面设计与现有系统风格一致
- 确保所有操作都有适当的错误处理和提示
- 确保所有操作都能实时更新页面数据
- 确保功能实现符合后端API的要求
## 技术细节
- 使用HTML、CSSTailwind CSS和JavaScript实现前端界面
- 使用fetch API调用后端API
- 使用事件监听器处理用户交互
- 使用DOM操作更新页面内容
- 使用异步/等待处理异步操作

View File

@@ -0,0 +1,72 @@
# 改进DNS查询模式实现
## 1. 当前实现分析
* **parallel**: 已基本实现并行请求,但缺少超时处理
* **loadbalance**: 简单轮询,未考虑服务器失败率和权重
* **fastest-ip**: 仅顺序请求未实现真正的TCP连接速度测量
## 2. 改进计划
### 2.1 扩展Server结构体
* 添加服务器状态跟踪字段:
* `serverStats`: 记录每个上游服务器的成功/失败次数、最后响应时间
* `serverStatsMutex`: 保护服务器状态的互斥锁
### 2.2 实现加权随机负载均衡
* 为每个服务器计算权重,基于成功/失败比率
* 实现加权随机选择算法
*`forwardDNSRequestWithCache`中使用新算法
### 2.3 实现真正的最快服务器选择
* 添加`measureServerSpeed`函数测量TCP连接速度
* 为每个服务器维护连接速度历史
* 选择连接速度最快的服务器进行查询
### 2.4 优化并行请求模式
* 添加请求超时处理
* 实现更快的响应返回机制(收到第一个有效响应即可返回)
### 2.5 统一DNSSEC服务器请求处理
* 为DNSSEC服务器也实现相同的查询模式支持
* 确保DNSSEC查询与普通查询使用一致的逻辑
## 3. 文件修改
* `/root/dns/dns/server.go`: 扩展Server结构体实现新的查询模式逻辑
* `/root/dns/config/config.go`: 确保配置支持新的查询模式
## 4. 测试计划
* 运行现有测试脚本验证基本功能
* 测试不同查询模式下的响应时间
* 验证负载均衡模式下的服务器选择分布
* 验证最快服务器模式下的速度测量准确性
## 5. 预期效果
* 负载均衡模式:根据服务器性能和可用性智能选择
* 并行请求模式:更快的响应速度和更好的容错性
* 最快服务器模式:选择响应速度最快的服务器,提高查询效率

View File

@@ -0,0 +1,84 @@
# 配置数据获取优先级机制和错误处理
## 1. 改进 API 请求处理逻辑
### 1.1 优化 `apiRequest` 函数
- 修改 `apiRequest` 函数,确保它能正确处理各种错误情况
- 统一错误返回格式,便于上层调用者处理
- 添加超时处理,避免长时间等待
### 1.2 增强 API 方法的错误处理
-`api.js` 中为每个 API 方法添加更严格的错误检查
- 确保返回数据符合预期格式
- 提供更详细的错误日志
## 2. 实现数据加载状态管理
### 2.1 添加加载状态指示器
- 在 HTML 中为 TOP 客户端和 TOP 域名表格添加加载状态指示器
- 显示 "加载中..." 文本或动画
### 2.2 实现状态切换逻辑
- 在数据请求开始时显示加载状态
- 请求成功后显示真实数据
- 请求失败后显示错误信息或模拟数据
## 3. 完善错误处理机制
### 3.1 分类处理错误情况
- **网络连接失败**:显示连接错误信息,使用模拟数据
- **服务器错误**:显示服务器错误信息,使用模拟数据
- **空响应**:显示空数据状态,使用模拟数据
- **数据格式错误**:显示数据格式错误信息,使用模拟数据
### 3.2 添加错误信息显示
- 在表格上方或下方显示错误信息
- 提供重试按钮,允许用户手动重试请求
## 4. 优化用户体验
### 4.1 平滑过渡效果
- 添加数据更新的平滑过渡动画
- 避免页面闪烁
### 4.2 提供有用的反馈
- 显示数据更新时间
- 显示数据来源(真实数据或模拟数据)
- 提供数据刷新按钮
## 5. 实现数据获取优先级机制
### 5.1 明确数据优先级
- 优先级 1服务器真实数据
- 优先级 2本地缓存数据如果有
- 优先级 3模拟数据
### 5.2 实现优先级逻辑
- 优先尝试获取服务器真实数据
- 如果失败,检查是否有本地缓存数据
- 如果没有缓存数据,使用模拟数据
## 6. 测试和验证
### 6.1 测试各种错误场景
- 模拟网络连接失败
- 模拟服务器返回错误状态码
- 模拟服务器返回空响应
- 模拟服务器返回错误格式数据
### 6.2 验证数据优先级机制
- 确保优先使用服务器真实数据
- 确保在各种错误情况下能正确切换到模拟数据
## 7. 代码优化和重构
### 7.1 提取公共逻辑
- 提取数据获取和状态管理的公共逻辑
- 减少代码重复
### 7.2 提高代码可读性
- 添加清晰的注释
- 使用有意义的变量名
- 优化代码结构
通过以上实现,系统将能够优先使用来自服务器的真实数据,仅在必要时使用模拟数据,并提供良好的用户体验和错误处理。

View File

@@ -0,0 +1,117 @@
# 实现系统启动时自动创建文件和文件夹
## 问题分析
当前系统启动时,如果配置文件中指定的文件或文件夹不存在,会导致报错,影响系统正常启动。需要实现自动创建功能,确保系统能够顺利启动。
## 解决方案
### 1. 自动创建配置文件
* 在main.go中添加检查配置文件是否存在的逻辑
* 如果配置文件不存在创建默认的config.json文件
### 2. 自动创建数据文件夹
* 根据配置文件中的路径,创建所需的文件夹:
* 数据文件夹(默认:./data
* 远程规则缓存文件夹(默认:./data/remote_rules
* 日志文件夹根据配置文件中的Log.File路径
### 3. 自动创建文件
* 根据配置文件中的路径,创建所需的文件:
* 本地规则文件默认data/rules.txt
* Hosts文件默认data/hosts.txt
* 统计数据文件(默认:./data/stats.json
* Shield统计数据文件默认./data/shield_stats.json
## 实现步骤
### 1. 修改main.go文件
* 在命令行参数解析后,添加检查配置文件是否存在的逻辑
* 如果配置文件不存在创建默认的config.json文件
* 调用LoadConfig加载配置
### 2. 添加createDefaultConfig函数
* 实现创建默认配置文件的功能
* 写入默认的配置内容到config.json
### 3. 添加createRequiredFiles函数
* 实现创建所需文件和文件夹的功能
* 根据配置文件中的路径,创建所需的文件夹
* 根据配置文件中的路径,创建所需的文件
### 4. 在main.go中调用createRequiredFiles函数
* 在配置加载完成后调用createRequiredFiles函数
* 确保在初始化屏蔽管理系统之前创建所需的文件和文件夹
## 预期效果
* 系统启动时如果配置文件不存在自动创建默认的config.json文件
* 自动创建所需的文件夹data、data/remote_rules、logs等
* 自动创建所需的文件rules.txt、hosts.txt、stats.json等
* 系统能够顺利启动,不会因为找不到文件而报错
## 技术要点
* 使用os.Stat函数检查文件或文件夹是否存在
* 使用os.MkdirAll函数创建文件夹包括父文件夹
* 使用os.Create函数创建文件
* 确保在初始化屏蔽管理系统之前创建所需的文件和文件夹
* 确保在初始化日志系统之前创建日志文件夹
## 实现时间
* 预计1-2小时完成所有修改和测试
## 风险评估
* 低风险:修改范围明确,不涉及核心功能
* 可回滚:所有修改均为添加新功能,可通过恢复文件轻松回滚
## 依赖关系
* 依赖os包的文件操作功能
* 依赖config包的配置结构
## 测试策略
* 手动测试删除配置文件、文件夹和文件,然后启动服务器,验证是否自动创建
* 验证创建的文件和文件夹是否符合配置文件中的路径
* 验证服务器能够顺利启动,没有报错
## 验收标准
* 系统启动时如果配置文件不存在自动创建默认的config.json文件
* 自动创建所需的文件夹data、data/remote_rules、logs等
* 自动创建所需的文件rules.txt、hosts.txt、stats.json等
* 系统能够顺利启动,不会因为找不到文件而报错
* 所有创建的文件和文件夹都符合配置文件中的路径

View File

@@ -0,0 +1,40 @@
## 问题分析
用户要求在缓存结果中包含DNSSEC信息时在缓存的请求条目后边添加相同的DNSSEC标记格式为`类型: AAAA, 允许, 缓存 <DNSSEC标记>`。如果不包含DNSSEC信息则不添加标记。
## 代码检查
1. **当前DNSSEC标记实现**
-`/root/dns/static/js/logs.js``updateLogsTable`函数中,已经实现了根据`log.DNSSEC`字段显示DNSSEC标记的功能
```javascript
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span>${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}</div>
```
2. **DNSSEC信息存储**
- `QueryLog`结构体包含`DNSSEC`字段用于记录是否使用了DNSSEC
- 在`handleDNSRequest`函数中会检查响应是否包含DNSSEC信息并记录到日志中
- 在缓存处理中会检查缓存响应的AD标志和RRSIG记录确定DNSSEC状态
3. **缓存响应的DNSSEC处理**
- 当从缓存返回响应时会检查响应是否包含RRSIG记录或AD标志以确定DNSSEC状态
- 但在日志记录时已经正确传递了DNSSEC状态
## 解决方案
当前实现已经基本满足用户要求但需要确保在所有显示查询日志的地方都正确显示DNSSEC标记。根据检查只有`logs.js`中的`updateLogsTable`函数显示查询日志且已经实现了DNSSEC标记功能。
## 验证步骤
1. 确认`logs.js`中的`updateLogsTable`函数已经正确实现了DNSSEC标记显示
2. 确认`QueryLog`结构体中的`DNSSEC`字段被正确设置
3. 确认缓存响应的DNSSEC状态被正确检查和记录
4. 运行测试,验证缓存请求条目显示格式为:`类型: AAAA, 允许, 缓存 <DNSSEC标记>`
## 预期结果
- 当缓存结果包含DNSSEC信息时显示`类型: AAAA, 允许, 缓存 <i class="fa fa-lock"></i> DNSSEC`
- 当缓存结果不包含DNSSEC信息时显示`类型: AAAA, 允许, 缓存`
- DNSSEC标记使用绿色锁图标颜色为绿色
## 注意事项
- 确保DNSSEC标记只在缓存请求条目中显示与实时请求条目保持一致的格式
- 确保DNSSEC标记的颜色和图标与整体UI风格一致
- 确保DNSSEC标记只在包含DNSSEC信息时显示不包含时不显示
## 实现状态
当前实现已经满足用户要求,不需要额外修改代码。需要运行测试验证功能是否正常工作。

View File

@@ -0,0 +1,33 @@
# 实现远程黑名单管理功能
## 1. 分析现有代码
- 远程黑名单管理功能已经在`shield.js`文件中实现
- 页面结构已经存在于HTML文件中
- 存在`blacklists.js``shield.js`两个可能冲突的实现
## 2. 解决方案
### 2.1 检查并解决代码冲突
- 移除或整合`blacklists.js`文件,避免与`shield.js`冲突
- 确保只使用一个实现来管理远程黑名单
### 2.2 确保页面加载时正确初始化
- 检查`shield.js`中的初始化逻辑
- 确保`initShieldPage`函数在页面加载和切换到屏蔽管理页面时被正确调用
### 2.3 确保web更新数据时同时更新服务器
- 检查现有的添加、更新、删除、启用/禁用黑名单的功能
- 确保每个操作都通过API请求更新服务器数据
- 验证操作完成后是否重新加载数据
## 3. 实现步骤
1. 移除`blacklists.js`文件,避免与`shield.js`冲突
2. 检查并确保`shield.js`中的初始化逻辑正确
3. 测试远程黑名单管理功能的各个操作
4. 验证页面加载时是否正确拉取服务器数据
5. 验证web更新数据时是否同时更新服务器数据
## 4. 预期结果
- 页面加载时自动拉取服务器数据
- 添加、更新、删除、启用/禁用黑名单时,同时更新服务器数据
- 操作完成后,页面数据自动刷新
- 没有代码冲突,功能正常运行

View File

@@ -0,0 +1,39 @@
# 屏蔽规则解析修复计划
## 问题分析
通过分析代码,我发现了无法处理`/domain.com/`类似规则的问题:
1. **当前实现**
- 当处理`/domain.com/`这样的规则时,代码会将其编译为正则表达式`domain.com`
- 然后使用`MatchString(domain)`来检查域名是否匹配该正则表达式
- 但是,`MatchString`会检查整个字符串是否匹配,而不是检查域名是否包含该模式
- 例如,对于域名`example.com`,正则表达式`domain.com`不会匹配,因为它只匹配字符串"domain.com"
2. **用户期望**
- 用户期望`/domain.com/`匹配所有包含"domain.com"的域名,比如`sub.domain.com`
- 但是当前实现中,它只会匹配精确的字符串"domain.com"
## 解决方案
### 1. 修改正则表达式规则处理
我们需要修改`parseRule`函数中对正则表达式规则的处理,确保`/domain.com/`这样的规则能正确匹配包含该模式的域名。
### 2. 优化匹配逻辑
我们需要确保正则表达式规则能正确匹配域名,无论是精确匹配还是包含匹配。
## 实现步骤
1. **修改`parseRule`函数**
- 对于以`/`开头和结尾的规则,确保其能正确匹配包含该模式的域名
- 或者,添加一个选项,允许用户指定匹配模式
2. **测试修复效果**
- 确保`/domain.com/`规则能匹配`sub.domain.com`等域名
- 确保其他规则类型仍能正常工作
## 预期效果
通过以上修复,屏蔽规则解析将能够正确处理`/domain.com/`类似的规则,匹配所有包含该模式的域名,提高用户体验。

View File

@@ -0,0 +1,45 @@
### 问题分析
当前的`dns/server.go`文件被误删了很多内容,导致编译失败。从编译错误可以看出,`http/server.go`文件中引用了`dns.Stats`结构体中的一些字段,如`QueryTypes``SourceIPs``AvgResponseTime``DNSSECQueries`等,但这些字段在当前的`dns/server.go`文件中都不存在。
### 恢复方案
1. **恢复完整的结构体定义**
- `BlockedDomain`结构体中添加`DNSSEC`字段
- `Stats`结构体中添加所有缺失的字段
- `Server`结构体中添加所有缺失的字段
- `StatsData`结构体中添加所有缺失的字段
2. **恢复所有缺失的方法**
- `updateClientStats`
- `updateResolvedDomainStatsWithDNSSEC`
- `updateDomainDNSSECStatus`
- `addQueryLog`
- `GetStartTime`
- `GetQueryLogs`
- `GetQueryLogsCount`
- `GetQueryStats`
- `GetTopClients`
- `GetDailyStats`
- `GetMonthlyStats`
- `getIpGeolocation`
- `fetchIpGeolocationFromAPI`
- `loadQueryLogs`
- `saveQueryLogs`
- `startCpuUsageMonitor`
- `getSystemCpuUsage`
3. **恢复DNSSEC相关功能**
- 恢复`forwardDNSRequestWithCache`函数,替代当前的`forwardDNSRequest`函数
- 恢复DNSSEC验证逻辑
- 恢复DNS缓存功能
### 具体修改
1. 替换整个`dns/server.go`文件,恢复完整的内容
2. 确保所有结构体字段和方法都完整存在
3. 确保DNSSEC功能正常工作
### 测试计划
1. 编译恢复后的代码
2. 启动DNS服务器并启用DNSSEC
3. 使用dig命令测试DNSSEC记录查询
4. 检查HTTP控制台是否正常工作
5. 检查日志中是否有DNSSEC验证相关的记录

View File

@@ -0,0 +1,50 @@
# 改进DNS查询模式实现
## 1. 当前实现分析
- **parallel**: 已基本实现并行请求,但缺少超时处理
- **loadbalance**: 简单轮询,未考虑服务器失败率和权重
- **fastest-ip**: 仅顺序请求未实现真正的TCP连接速度测量
## 2. 改进计划
### 2.1 扩展Server结构体
- 添加服务器状态跟踪字段:
- `serverStats`: 记录每个上游服务器的成功/失败次数、最后响应时间
- `serverStatsMutex`: 保护服务器状态的互斥锁
### 2.2 实现加权随机负载均衡
- 为每个服务器计算权重,基于成功/失败比率
- 实现加权随机选择算法
-`forwardDNSRequestWithCache`中使用新算法
### 2.3 实现真正的最快服务器选择
- 添加`measureServerSpeed`函数测量TCP连接速度
- 为每个服务器维护连接速度历史
- 选择连接速度最快的服务器进行查询
### 2.4 优化并行请求模式
- 添加请求超时处理
- 实现更快的响应返回机制(收到第一个有效响应即可返回)
### 2.5 统一DNSSEC服务器请求处理
- 为DNSSEC服务器也实现相同的查询模式支持
- 确保DNSSEC查询与普通查询使用一致的逻辑
## 3. 文件修改
- `/root/dns/dns/server.go`: 扩展Server结构体实现新的查询模式逻辑
- `/root/dns/config/config.go`: 确保配置支持新的查询模式
## 4. 测试计划
- 运行现有测试脚本验证基本功能
- 测试不同查询模式下的响应时间
- 验证负载均衡模式下的服务器选择分布
- 验证最快服务器模式下的速度测量准确性
## 5. 预期效果
- 负载均衡模式:根据服务器性能和可用性智能选择
- 并行请求模式:更快的响应速度和更好的容错性
- 最快服务器模式:选择响应速度最快的服务器,提高查询效率

View File

@@ -0,0 +1,69 @@
# 更新Swagger API文档
## 1. 分析当前状态
* Swagger文档位于 `/root/dns/static/api/js/index.js`
* 项目使用Go语言开发HTTP API端点在 `/root/dns/http/server.go` 中定义
* 当前swagger文档缺少一些API端点和正确的描述
## 2. 需要更新的内容
### 2.1 添加缺少的API端点
* `/api/login` - 登录API
* `/api/logout` - 注销API
* `/api/change-password` - 修改密码API
* `/api/query` - DNS查询API
* `/api/status` - 系统状态API
* `/api/config` - 配置管理API
* `/api/config/restart` - 重启服务API
* `/api/logs/stats` - 日志统计API
* `/api/logs/query` - 日志查询API
* `/api/logs/count` - 日志数量API
### 2.2 更新现有端点描述
* `/api/stats` - 添加DNSSEC相关字段描述
* `/api/shield` - 更新为与实际实现匹配
* `/api/shield/blacklists` - 确保包含所有HTTP方法
* `/api/shield/hosts` - 确保包含所有HTTP方法
* `/api/shield/localrules` - 确保包含所有HTTP方法
### 2.3 修正现有端点的响应格式
* 修正 `/api/hourly-stats``/api/daily-stats``/api/monthly-stats` 的响应格式
* 修正 `/api/shield` 的响应格式
## 3. 实施步骤
1. 读取当前swagger文档
2. 针对每个需要更新的端点修改或添加相应的swagger定义
3. 确保所有端点的HTTP方法、参数、响应格式都正确
4. 测试swagger文档是否能正常加载和显示
## 4. 预期结果
* 所有API端点都在swagger文档中正确描述
* 每个端点的HTTP方法、参数、响应格式都准确
* swagger文档能正常加载和显示
* 开发者可以通过swagger文档了解和使用所有API端点

View File

@@ -0,0 +1,13 @@
1. 定位到仪表盘部分的卡片布局,找到两个关键卡片:
- 最近屏蔽域名卡片第593-635行
- TOP域名卡片第705-734行
2. 移除最近屏蔽域名卡片第593-635行
3. 将TOP域名卡片从当前位置第639行开始的网格移动到最近屏蔽域名卡片的位置第538行开始的网格
4. 确保布局保持一致,两个卡片在同一行显示
5. 调整相关的网格布局,确保页面结构完整
6. 验证修改后的布局是否符合用户要求

View File

@@ -0,0 +1,33 @@
# 根据服务器日志判断操作成功状态
## 问题分析
当前代码中部分操作只检查了HTTP响应状态码没有检查服务器返回的`status`字段,这可能导致在某些情况下无法正确判断操作是否成功。根据服务器端代码,所有成功的响应都会返回`{"status": "success"}`,因此需要确保前端代码在所有操作中都检查这个字段。
## 解决方案
修改以下函数,确保它们都根据服务器返回的`status`字段判断操作是否成功:
1. **handleDeleteBlacklist** (line 653-716)
- 添加对服务器响应数据中`status`字段的检查
- 确保只有当`status``success`时才认为删除成功
2. **handleToggleBlacklist** (line 719-793)
- 添加对服务器响应数据中`status`字段的检查
- 确保只有当`status``success`时才认为切换状态成功
3. **handleAddRule** (line 378-409)
- 添加对服务器响应数据中`status`字段的检查
- 确保只有当`status``success`时才认为添加规则成功
4. **handleDeleteRule** (line 309-375)
- 添加对服务器响应数据中`status`字段的检查
- 确保只有当`status``success`时才认为删除规则成功
## 实现步骤
1. 修改`handleDeleteBlacklist`函数添加响应数据解析和status字段检查
2. 修改`handleToggleBlacklist`函数添加响应数据解析和status字段检查
3. 修改`handleAddRule`函数添加响应数据解析和status字段检查
4. 修改`handleDeleteRule`函数添加响应数据解析和status字段检查
5. 测试所有操作,确保它们都能正确根据服务器响应判断成功或失败
## 预期效果
所有操作(添加、更新、删除、切换状态)都会根据服务器返回的`status`字段判断是否成功,确保操作结果与服务器日志一致。

View File

@@ -0,0 +1,40 @@
# 修复domainSpecificDNS的DNSSEC优先级问题
## 1. 问题分析
* 当域名匹配到 `domainSpecificDNS` 配置时程序会使用指定的DNS服务器
* 但随后程序会向DNSSEC专用服务器发送请求
* 如果DNSSEC专用服务器返回了带DNSSEC的响应程序会优先使用该响应
* 这导致 `domainSpecificDNS` 指定的DNS服务器的响应被忽略
## 2. 修复计划
### 2.1 修改 `forwardDNSRequestWithCache` 函数
* 在函数中添加一个标志 `domainMatched`,记录域名是否匹配了 `domainSpecificDNS` 配置
* 当域名匹配到 `domainSpecificDNS` 配置时,设置该标志为 `true`
### 2.2 修改DNSSEC响应处理逻辑
* 在处理DNSSEC专用服务器响应时检查 `domainMatched` 标志
* 如果标志为 `true`,则**不优先使用DNSSEC专用服务器的响应**而是保留原来指定DNS服务器的响应
* 只有当指定的DNS服务器没有返回有效响应时才考虑使用DNSSEC专用服务器的响应
### 2.3 确保指定的DNS服务器优先
* 确保对于匹配了 `domainSpecificDNS` 的域名始终优先使用指定的DNS服务器的响应
* 只有当指定的DNS服务器没有返回有效响应时才考虑使用DNSSEC专用服务器的响应
## 3. 预期效果
* 对于匹配了 `domainSpecificDNS` 配置的域名始终优先使用指定的DNS服务器
* 只有当指定的DNS服务器没有返回有效响应时才考虑使用DNSSEC专用服务器
* DNSSEC功能仍然正常工作但不会覆盖 `domainSpecificDNS` 的配置
## 4. 测试计划
* 重启DNS服务器
* 使用 `dig @127.0.0.1 dc.amazehome.xyz +short` 测试
* 检查日志确保使用的是指定DNS服务器的响应
* 验证解析结果是否符合预期

View File

@@ -0,0 +1,57 @@
# 添加DNSSEC支持实现计划
## 1. 配置系统修改
-`config/config.go``DNSConfig`结构体中添加`EnableDNSSEC`布尔字段用于控制是否启用DNSSEC支持
- 添加`DNSSECValidation`布尔字段用于控制是否进行DNSSEC验证
- 在配置加载时设置默认值
## 2. DNS查询处理修改
- 修改`dns/server.go`中的DNS客户端配置确保支持EDNS0扩展DNSSEC需要
-`handleDNSRequest`函数中支持DNSSEC相关查询类型DNSKEY、RRSIG、DS、NSEC、NSEC3等
- 确保上游DNS服务器返回的DNSSEC记录被正确处理和转发
## 3. DNSSEC验证支持
- 实现DNSSEC记录验证逻辑确保返回的DNS记录未被篡改
-`forwardDNSRequestWithCache`函数中添加DNSSEC验证步骤
- 根据配置决定是否进行验证,以及验证失败时的处理策略
## 4. 缓存系统适配
- 修改`DNSCache`结构体确保DNSSEC记录被正确缓存
- 确保缓存的DNS响应包含完整的DNSSEC记录链
## 5. 测试和验证
- 确保现有功能不受影响
- 测试DNSSEC查询是否正常工作
- 验证DNSSEC记录是否被正确转发和验证
## 6. 配置界面更新
- 在Web控制台中添加DNSSEC相关配置选项
- 允许用户通过界面启用/禁用DNSSEC支持和验证
## 7. 日志和统计
- 添加DNSSEC相关日志记录
- 记录DNSSEC验证结果统计
## 主要文件修改
- `config/config.go`添加DNSSEC相关配置字段
- `dns/server.go`修改DNS查询处理逻辑支持DNSSEC
- `static/index.html`添加DNSSEC配置界面
- `dns/cache.go`确保DNSSEC记录被正确缓存
## 依赖库
- 利用现有的`github.com/miekg/dns`该库已内置DNSSEC支持
## 实现步骤
1. 首先修改配置系统添加DNSSEC相关字段
2. 更新DNS客户端配置支持EDNS0扩展
3. 修改查询处理逻辑支持DNSSEC记录类型
4. 添加DNSSEC验证逻辑
5. 适配缓存系统
6. 更新Web配置界面
7. 测试和验证
## 预期效果
- 启用DNSSEC后服务器将支持DNSSEC查询并返回完整的DNSSEC记录
- 当启用DNSSEC验证时服务器将验证上游返回的DNS记录的真实性
- 增强DNS服务器的安全性防止DNS投毒和劫持攻击
- 提供灵活的配置选项允许用户根据需要调整DNSSEC设置

View File

@@ -0,0 +1,69 @@
# 更新Swagger API文档
## 1. 分析当前状态
* Swagger文档位于 `/root/dns/static/api/js/index.js`
* 项目使用Go语言开发HTTP API端点在 `/root/dns/http/server.go` 中定义
* 当前swagger文档缺少一些API端点和正确的描述
## 2. 需要更新的内容
### 2.1 添加缺少的API端点
* `/api/login` - 登录API
* `/api/logout` - 注销API
* `/api/change-password` - 修改密码API
* `/api/query` - DNS查询API
* `/api/status` - 系统状态API
* `/api/config` - 配置管理API
* `/api/config/restart` - 重启服务API
* `/api/logs/stats` - 日志统计API
* `/api/logs/query` - 日志查询API
* `/api/logs/count` - 日志数量API
### 2.2 更新现有端点描述
* `/api/stats` - 添加DNSSEC相关字段描述
* `/api/shield` - 更新为与实际实现匹配
* `/api/shield/blacklists` - 确保包含所有HTTP方法
* `/api/shield/hosts` - 确保包含所有HTTP方法
* `/api/shield/localrules` - 确保包含所有HTTP方法
### 2.3 修正现有端点的响应格式
* 修正 `/api/hourly-stats``/api/daily-stats``/api/monthly-stats` 的响应格式
* 修正 `/api/shield` 的响应格式
## 3. 实施步骤
1. 读取当前swagger文档
2. 针对每个需要更新的端点修改或添加相应的swagger定义
3. 确保所有端点的HTTP方法、参数、响应格式都正确
4. 测试swagger文档是否能正常加载和显示
## 4. 预期结果
* 所有API端点都在swagger文档中正确描述
* 每个端点的HTTP方法、参数、响应格式都准确
* swagger文档能正常加载和显示
* 开发者可以通过swagger文档了解和使用所有API端点

View File

@@ -0,0 +1,18 @@
## 问题分析
用户要求移除一个统计卡片,根据之前的对话历史,最近添加的卡片是屏蔽规则数量卡片,用户可能想要移除这个卡片。
## 修复方案
1. **移除HTML中的屏蔽规则数量卡片**从index.html文件中删除屏蔽规则数量卡片的HTML代码
2. **移除JavaScript中的相关逻辑**从dashboard.js文件中删除与屏蔽规则数量相关的变量声明、数据获取逻辑和更新逻辑
3. **确保代码语法正确**:修复可能出现的语法错误,确保代码兼容性
## 实现步骤
1. 打开index.html文件找到屏蔽规则数量卡片约第467-488行删除其HTML代码
2. 打开dashboard.js文件找到updateStatsCards函数删除与屏蔽规则数量相关的变量声明blockRulesCount、blockRulesPercentage
3. 删除updateStatsCards函数中与屏蔽规则数量相关的数据获取逻辑
4. 删除updateStatsCards函数中与屏蔽规则数量相关的更新逻辑
5. 删除loadDashboardData函数中与屏蔽规则数量相关的更新逻辑
6. 运行代码语法检查,确保没有语法错误
## 预期效果
修复后屏蔽规则数量卡片将从仪表盘上移除相关的JavaScript逻辑也将被清理仪表盘将恢复到之前的状态只显示其他7个统计卡片。

View File

@@ -0,0 +1,65 @@
# 解决web页面缓存问题
## 问题分析
当前web页面总是有缓存导致更新后用户看不到最新的内容。这是因为HTTP服务器使用了标准的http.FileServer来提供静态文件服务它会设置默认的缓存头导致浏览器缓存静态文件。
## 解决方案
修改静态文件服务的缓存策略为静态文件添加适当的Cache-Control头禁用浏览器缓存或者设置较短的缓存时间。
## 实现步骤
### 1. 创建自定义静态文件服务处理器
* 创建一个自定义的http.Handler包装http.FileServer
* 在处理静态文件请求时添加适当的Cache-Control头
* 可以选择完全禁用缓存,或者设置较短的缓存时间
### 2. 修改http/server.go中的静态文件服务配置
* 替换标准的http.FileServer使用自定义的静态文件服务处理器
* 确保所有静态文件请求都经过自定义处理器
### 3. 测试修改后的效果
* 更新静态文件例如修改dashboard.js
* 刷新页面,验证是否能看到最新的内容
* 使用浏览器开发者工具查看响应头确认Cache-Control头已正确设置
## 技术要点
* 使用http.StripPrefix处理静态文件路径
* 在ResponseWriter中添加Cache-Control头例如
* `Cache-Control: no-cache, no-store, must-revalidate`
* `Pragma: no-cache`
* `Expires: 0`
* 这些头会告诉浏览器不要缓存文件,每次都要重新请求
## 实现时间
* 预计30分钟完成所有修改和测试
## 风险评估
* 低风险:修改范围明确,不涉及核心功能
* 可回滚所有修改均为HTTP服务器配置修改可通过恢复文件轻松回滚
## 依赖关系
* 依赖http包的标准库功能
* 依赖现有的静态文件服务结构
## 测试策略
* 更新静态文件例如修改dashboard.js
* 刷新页面,验证是否能看到最新的内容
* 使用浏览器开发者工具查看响应头确认Cache-Control头已正确设置
* 测试不同浏览器的行为
## 验收标准
* 静态文件的HTTP响应中包含适当的Cache-Control头
* 更新静态文件后,刷新页面能看到最新的内容
* 浏览器不会缓存静态文件,每次都会重新请求

View File

@@ -0,0 +1,44 @@
# 调整DNS趋势图表默认显示和浮窗独立性
## 问题分析
1. **DNS趋势图表默认显示**:当前代码中`isMixedView`变量默认设置为`true`,但在`initTimeRangeToggle`函数中,默认选中第一个按钮后会将`isMixedView`设置为`false`导致实际默认显示的是24小时视图而非混合视图。
2. **浮窗图表独立性**:当前代码中详细图表(浮窗)已有独立变量`detailedCurrentTimeRange``detailedIsMixedView`,但需要确保初始化时正确设置,避免与主图表冲突。
## 实现计划
### 1. 修改DNS趋势图表默认显示为混合内容
- **文件**`/root/dns/static/js/dashboard.js`
- **函数**`initTimeRangeToggle`
- **修改点**
- 在函数末尾,默认选中第一个按钮后,将`isMixedView`设置为`true`
-`currentTimeRange`设置为`'mixed'`
- 更新按钮样式,添加混合视图标记
### 2. 确保浮窗图表初始化正确
- **文件**`/root/dns/static/js/dashboard.js`
- **函数**`initDetailedTimeRangeToggle`
- **修改点**
- 确保初始化时`detailedIsMixedView`默认值与主图表保持一致
- 确保点击浮窗中的时间范围按钮时,只修改详细图表的变量,不影响主图表
### 3. 验证功能完整性
- 检查`drawDNSRequestsChart`函数,确保它使用主图表变量
- 检查`drawDetailedDNSRequestsChart`函数,确保它使用详细图表变量
- 确保两个函数的实现逻辑一致,但使用不同的变量
## 预期效果
1. DNS趋势图表默认显示混合内容24小时、7天、30天数据同时显示
2. 展开浮窗后,切换浮窗中的时间范围或视图模式,不会影响主页图表的显示
3. 主页图表和浮窗图表可以独立显示不同的时间范围和视图模式
## 实现步骤
1. 修改`initTimeRangeToggle`函数,设置默认混合视图
2. 优化`initDetailedTimeRangeToggle`函数,确保浮窗图表初始化正确
3. 验证两个图表函数的变量使用是否正确
4. 测试功能完整性
## 代码修改点
1. **第1250-1256行**:修改默认按钮选中逻辑,添加混合视图设置
2. **第1475-1507行**:优化浮窗图表时间范围切换逻辑
3. **第1716-1912行**:确保主图表函数使用正确变量
4. **第1509-1714行**:确保浮窗图表函数使用正确变量

View File

@@ -2,6 +2,14 @@
所有对本项目的显著更改都将记录在此文件中。 所有对本项目的显著更改都将记录在此文件中。
## [1.1.1] - 2025-12-19
### 修改
- 修复NXDOMAIN响应传播逻辑确保上游DNS服务器返回的NXDOMAIN响应能正确传递给客户端
- 优化loadbalance、fastest-ip和parallel查询模式下的NXDOMAIN响应选择机制
- 确保不存在的域名能被正确识别并返回NXDOMAIN状态码
- 修复服务器绑定地址配置确保IPv4兼容性
## [1.0.0] - 2025-12-16 ## [1.0.0] - 2025-12-16
### 添加 ### 添加

View File

@@ -2,9 +2,6 @@
"dns": { "dns": {
"port": 53, "port": 53,
"upstreamDNS": [ "upstreamDNS": [
"223.5.5.5:53",
"223.6.6.6:53",
"117.50.10.10:53",
"10.35.10.200:53" "10.35.10.200:53"
], ],
"dnssecUpstreamDNS": [ "dnssecUpstreamDNS": [
@@ -21,10 +18,29 @@
"enableDNSSEC": true, "enableDNSSEC": true,
"queryMode": "parallel", "queryMode": "parallel",
"domainSpecificDNS": { "domainSpecificDNS": {
"amazehome.xyz": ["10.35.10.200:53"], "amazehome.cn": [
"amazehome.cn": ["10.35.10.200:53"] "10.35.10.200:53"
],
"addr.arpa": [
"10.35.10.200:53"
],
"amazehome.xyz": [
"10.35.10.200:53"
],
"microsoft.com": [
"4.2.2.1:53"
],
"akamai": [
"4.2.2.1:53"
],
"akadns": [
"4.2.2.1:53"
]
}, },
"prefixDomain": ["amazehome.xyz", "amazehome.cn"] "prefixDomain": [
""
]
}, },
"http": { "http": {
"port": 8080, "port": 8080,
@@ -79,7 +95,7 @@
"name": "My Gitlab Hosts", "name": "My Gitlab Hosts",
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/costomize.txt", "url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/costomize.txt",
"enabled": true, "enabled": true,
"lastUpdateTime": "2025-11-29T17:11:28.130Z" "lastUpdateTime": "2025-12-18T10:39:39.333Z"
}, },
{ {
"name": "Anti Remote Requests", "name": "Anti Remote Requests",
@@ -94,7 +110,8 @@
{ {
"name": "My Gitlab A/T Rules", "name": "My Gitlab A/T Rules",
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/ads-and-trackers.txt", "url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/ads-and-trackers.txt",
"enabled": true "enabled": true,
"lastUpdateTime": "2025-12-18T10:38:42.344Z"
}, },
{ {
"name": "My Gitlab Malware List", "name": "My Gitlab Malware List",

BIN
dns-server Executable file

Binary file not shown.

View File

@@ -59,6 +59,8 @@ type QueryLog struct {
FromCache bool // 是否来自缓存 FromCache bool // 是否来自缓存
DNSSEC bool // 是否使用了DNSSEC DNSSEC bool // 是否使用了DNSSEC
EDNS bool // 是否使用了EDNS EDNS bool // 是否使用了EDNS
DNSServer string // 使用的DNS服务器
DNSSECServer string // 使用的DNSSEC专用服务器
} }
// StatsData 用于持久化的统计数据结构 // StatsData 用于持久化的统计数据结构
@@ -230,14 +232,14 @@ func (s *Server) Start() error {
s.startTime = time.Now() s.startTime = time.Now()
s.server = &dns.Server{ s.server = &dns.Server{
Addr: fmt.Sprintf(":%d", s.config.Port), Addr: fmt.Sprintf("0.0.0.0:%d", s.config.Port),
Net: "udp", Net: "udp",
Handler: dns.HandlerFunc(s.handleDNSRequest), Handler: dns.HandlerFunc(s.handleDNSRequest),
} }
// 保存TCP服务器实例以便在Stop方法中关闭 // 保存TCP服务器实例以便在Stop方法中关闭
s.tcpServer = &dns.Server{ s.tcpServer = &dns.Server{
Addr: fmt.Sprintf(":%d", s.config.Port), Addr: fmt.Sprintf("0.0.0.0:%d", s.config.Port),
Net: "tcp", Net: "tcp",
Handler: dns.HandlerFunc(s.handleDNSRequest), Handler: dns.HandlerFunc(s.handleDNSRequest),
} }
@@ -368,7 +370,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
}) })
// 添加查询日志 // 添加查询日志
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true) s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "")
return return
} }
@@ -385,7 +387,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
}) })
// 添加查询日志 // 添加查询日志
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, false, true) s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, false, true, "缓存", "无")
return return
} }
@@ -407,7 +409,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
}) })
// 添加查询日志 // 添加查询日志
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true) s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true, "无", "无")
return return
} }
@@ -480,7 +482,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
} }
// 添加查询日志 - 标记为缓存 // 添加查询日志 - 标记为缓存
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true) s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true, "缓存", "无")
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC) logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC)
return return
} }
@@ -489,10 +491,12 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
var response *dns.Msg var response *dns.Msg
var rtt time.Duration var rtt time.Duration
var queryAttempts []string var queryAttempts []string
var dnsServer string
var dnssecServer string
// 1. 首先尝试直接查询原始域名 // 1. 首先尝试直接查询原始域名
queryAttempts = append(queryAttempts, domain) queryAttempts = append(queryAttempts, domain)
response, rtt = s.forwardDNSRequestWithCache(r, domain) response, rtt, dnsServer, dnssecServer = s.forwardDNSRequestWithCache(r, domain)
// 2. 如果直接查询失败且配置了prefixDomain尝试添加前缀 // 2. 如果直接查询失败且配置了prefixDomain尝试添加前缀
if (response == nil || response.Rcode != dns.RcodeSuccess) && len(s.config.PrefixDomain) > 0 { if (response == nil || response.Rcode != dns.RcodeSuccess) && len(s.config.PrefixDomain) > 0 {
@@ -517,8 +521,8 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
Qclass: originalQuestion.Qclass, Qclass: originalQuestion.Qclass,
} }
// 转发请求 // 查询带有前缀的域名
response, rtt = s.forwardDNSRequestWithCache(newReq, fullDomain) response, rtt, dnsServer, dnssecServer = s.forwardDNSRequestWithCache(newReq, fullDomain)
if response != nil && response.Rcode == dns.RcodeSuccess { if response != nil && response.Rcode == dns.RcodeSuccess {
logger.Debug("使用prefixDomain查询成功", "fullDomain", fullDomain, "originalDomain", domain) logger.Debug("使用prefixDomain查询成功", "fullDomain", fullDomain, "originalDomain", domain)
break // 找到成功的响应,退出循环 break // 找到成功的响应,退出循环
@@ -595,7 +599,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
} }
// 添加查询日志 - 标记为实时 // 添加查询日志 - 标记为实时
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true) s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true, dnsServer, dnssecServer)
} }
// handleHostsResponse 处理hosts文件匹配的响应 // handleHostsResponse 处理hosts文件匹配的响应
@@ -708,7 +712,7 @@ type serverResponse struct {
} }
// forwardDNSRequestWithCache 转发DNS请求到上游服务器并返回响应 // forwardDNSRequestWithCache 转发DNS请求到上游服务器并返回响应
func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg, time.Duration) { func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg, time.Duration, string, string) {
// 始终支持EDNS // 始终支持EDNS
var udpSize uint16 = 4096 var udpSize uint16 = 4096
var doFlag bool = s.config.EnableDNSSEC var doFlag bool = s.config.EnableDNSSEC
@@ -746,9 +750,16 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
} }
} }
// 2. 如果没有匹配的域名特定配置使用默认的上游DNS服务器 // 2. 如果没有匹配的域名特定配置
if !domainMatched { if !domainMatched {
selectedUpstreamDNS = s.config.UpstreamDNS // 如果启用了DNSSEC且有配置DNSSEC专用服务器则使用DNSSEC专用服务器
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 {
selectedUpstreamDNS = s.config.DNSSECUpstreamDNS
logger.Debug("使用DNSSEC专用服务器", "servers", selectedUpstreamDNS)
} else {
// 否则使用默认的上游DNS服务器
selectedUpstreamDNS = s.config.UpstreamDNS
}
} }
// 1. 首先尝试所有配置的上游DNS服务器 // 1. 首先尝试所有配置的上游DNS服务器
@@ -759,6 +770,8 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
var backupResponse *dns.Msg var backupResponse *dns.Msg
var backupRtt time.Duration var backupRtt time.Duration
var hasBackup bool var hasBackup bool
var usedDNSServer string
var usedDNSSECServer string
// 根据查询模式处理请求 // 根据查询模式处理请求
switch s.config.QueryMode { switch s.config.QueryMode {
@@ -832,21 +845,47 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
} }
} }
// 如果响应成功根据DNSSEC状态选择最佳响应 // 如果响应成功或为NXDOMAIN根据DNSSEC状态选择最佳响应
if resp.response.Rcode == dns.RcodeSuccess { if resp.response.Rcode == dns.RcodeSuccess || resp.response.Rcode == dns.RcodeNameError {
// 优先选择带有DNSSEC记录的响应 // 检查当前使用的服务器是否是DNSSEC专用服务器
if containsDNSSEC { for _, dnssecServer := range dnssecServers {
bestResponse = resp.response if dnssecServer == resp.server {
bestRtt = resp.rtt usedDNSSECServer = resp.server
hasBestResponse = true break
hasDNSSECResponse = true }
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt) }
} else if !hasBestResponse {
// 没有带DNSSEC的响应时保存第一个成功响应 if resp.response.Rcode == dns.RcodeSuccess {
bestResponse = resp.response // 处理成功响应
bestRtt = resp.rtt // 优先选择带有DNSSEC记录的响应
hasBestResponse = true if containsDNSSEC {
logger.Debug("找到最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt) bestResponse = resp.response
bestRtt = resp.rtt
hasBestResponse = true
hasDNSSECResponse = true
usedDNSServer = resp.server
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
} else if !hasBestResponse {
// 没有带DNSSEC的响应时保存第一个成功响应
bestResponse = resp.response
bestRtt = resp.rtt
hasBestResponse = true
usedDNSServer = resp.server
logger.Debug("找到最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
}
} else if resp.response.Rcode == dns.RcodeNameError {
// 处理NXDOMAIN响应
// 如果还没有最佳响应或者最佳响应也是NXDOMAIN优先选择更快的NXDOMAIN响应
if !hasBestResponse || bestResponse.Rcode == dns.RcodeNameError {
// 如果还没有最佳响应,或者当前响应更快,更新最佳响应
if !hasBestResponse || resp.rtt < bestRtt {
bestResponse = resp.response
bestRtt = resp.rtt
hasBestResponse = true
usedDNSServer = resp.server
logger.Debug("找到NXDOMAIN最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
}
}
} }
// 保存为备选响应 // 保存为备选响应
if !hasBackup { if !hasBackup {
@@ -900,21 +939,46 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
} }
} }
// 如果响应成功根据DNSSEC状态选择最佳响应 // 如果响应成功或为NXDOMAIN根据DNSSEC状态选择最佳响应
if response.Rcode == dns.RcodeSuccess { if response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError {
// 优先选择带有DNSSEC记录的响应 if response.Rcode == dns.RcodeSuccess {
if containsDNSSEC { // 优先选择带有DNSSEC记录的响应
if containsDNSSEC {
bestResponse = response
bestRtt = rtt
hasBestResponse = true
hasDNSSECResponse = true
usedDNSServer = selectedServer
// 如果当前使用的服务器是DNSSEC专用服务器同时设置usedDNSSECServer
for _, dnssecServer := range dnssecServers {
if dnssecServer == selectedServer {
usedDNSSECServer = selectedServer
break
}
}
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", selectedServer, "rtt", rtt)
} else {
// 没有带DNSSEC的响应时保存成功响应
bestResponse = response
bestRtt = rtt
hasBestResponse = true
usedDNSServer = selectedServer
// 如果当前使用的服务器是DNSSEC专用服务器同时设置usedDNSSECServer
for _, dnssecServer := range dnssecServers {
if dnssecServer == selectedServer {
usedDNSSECServer = selectedServer
break
}
}
logger.Debug("找到最佳响应", "domain", domain, "server", selectedServer, "rtt", rtt)
}
} else if response.Rcode == dns.RcodeNameError {
// 处理NXDOMAIN响应
bestResponse = response bestResponse = response
bestRtt = rtt bestRtt = rtt
hasBestResponse = true hasBestResponse = true
hasDNSSECResponse = true usedDNSServer = selectedServer
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", selectedServer, "rtt", rtt) logger.Debug("找到NXDOMAIN响应", "domain", domain, "server", selectedServer, "rtt", rtt)
} else {
// 没有带DNSSEC的响应时保存成功响应
bestResponse = response
bestRtt = rtt
hasBestResponse = true
logger.Debug("找到最佳响应", "domain", domain, "server", selectedServer, "rtt", rtt)
} }
// 保存为备选响应 // 保存为备选响应
if !hasBackup { if !hasBackup {
@@ -968,21 +1032,46 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
} }
} }
// 如果响应成功根据DNSSEC状态选择最佳响应 // 如果响应成功或为NXDOMAIN根据DNSSEC状态选择最佳响应
if response.Rcode == dns.RcodeSuccess { if response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError {
// 优先选择带有DNSSEC记录的响应 if response.Rcode == dns.RcodeSuccess {
if containsDNSSEC { // 优先选择带有DNSSEC记录的响应
if containsDNSSEC {
bestResponse = response
bestRtt = rtt
hasBestResponse = true
hasDNSSECResponse = true
usedDNSServer = fastestServer
// 如果当前使用的服务器是DNSSEC专用服务器同时设置usedDNSSECServer
for _, dnssecServer := range dnssecServers {
if dnssecServer == fastestServer {
usedDNSSECServer = fastestServer
break
}
}
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", fastestServer, "rtt", rtt)
} else {
// 没有带DNSSEC的响应时保存成功响应
bestResponse = response
bestRtt = rtt
hasBestResponse = true
usedDNSServer = fastestServer
// 如果当前使用的服务器是DNSSEC专用服务器同时设置usedDNSSECServer
for _, dnssecServer := range dnssecServers {
if dnssecServer == fastestServer {
usedDNSSECServer = fastestServer
break
}
}
logger.Debug("找到最佳响应", "domain", domain, "server", fastestServer, "rtt", rtt)
}
} else if response.Rcode == dns.RcodeNameError {
// 处理NXDOMAIN响应
bestResponse = response bestResponse = response
bestRtt = rtt bestRtt = rtt
hasBestResponse = true hasBestResponse = true
hasDNSSECResponse = true usedDNSServer = fastestServer
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", fastestServer, "rtt", rtt) logger.Debug("找到NXDOMAIN响应", "domain", domain, "server", fastestServer, "rtt", rtt)
} else {
// 没有带DNSSEC的响应时保存成功响应
bestResponse = response
bestRtt = rtt
hasBestResponse = true
logger.Debug("找到最佳响应", "domain", domain, "server", fastestServer, "rtt", rtt)
} }
// 保存为备选响应 // 保存为备选响应
if !hasBackup { if !hasBackup {
@@ -1050,21 +1139,52 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
} }
} }
// 如果响应成功根据DNSSEC状态选择最佳响应 // 如果响应成功或为NXDOMAIN根据DNSSEC状态选择最佳响应
if resp.response.Rcode == dns.RcodeSuccess { if resp.response.Rcode == dns.RcodeSuccess || resp.response.Rcode == dns.RcodeNameError {
// 优先选择带有DNSSEC记录的响应 if resp.response.Rcode == dns.RcodeSuccess {
if containsDNSSEC { // 优先选择带有DNSSEC记录的响应
bestResponse = resp.response if containsDNSSEC {
bestRtt = resp.rtt bestResponse = resp.response
hasBestResponse = true bestRtt = resp.rtt
hasDNSSECResponse = true hasBestResponse = true
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt) hasDNSSECResponse = true
} else if !hasBestResponse { usedDNSServer = resp.server
// 没有带DNSSEC的响应时保存第一个成功响应 // 如果当前使用的服务器是DNSSEC专用服务器同时设置usedDNSSECServer
bestResponse = resp.response for _, dnssecServer := range dnssecServers {
bestRtt = resp.rtt if dnssecServer == resp.server {
hasBestResponse = true usedDNSSECServer = resp.server
logger.Debug("找到最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt) break
}
}
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
} else if !hasBestResponse {
// 没有带DNSSEC的响应时保存第一个成功响应
bestResponse = resp.response
bestRtt = resp.rtt
hasBestResponse = true
usedDNSServer = resp.server
// 如果当前使用的服务器是DNSSEC专用服务器同时设置usedDNSSECServer
for _, dnssecServer := range dnssecServers {
if dnssecServer == resp.server {
usedDNSSECServer = resp.server
break
}
}
logger.Debug("找到最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
}
} else if resp.response.Rcode == dns.RcodeNameError {
// 处理NXDOMAIN响应
// 如果还没有最佳响应或者最佳响应也是NXDOMAIN优先选择更快的NXDOMAIN响应
if !hasBestResponse || bestResponse.Rcode == dns.RcodeNameError {
// 如果还没有最佳响应,或者当前响应更快,更新最佳响应
if !hasBestResponse || resp.rtt < bestRtt {
bestResponse = resp.response
bestRtt = resp.rtt
hasBestResponse = true
usedDNSServer = resp.server
logger.Debug("找到NXDOMAIN最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
}
}
} }
// 保存为备选响应 // 保存为备选响应
if !hasBackup { if !hasBackup {
@@ -1137,6 +1257,9 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
containsDNSSEC := s.hasDNSSECRecords(resp.response) containsDNSSEC := s.hasDNSSECRecords(resp.response)
if resp.response.Rcode == dns.RcodeSuccess { if resp.response.Rcode == dns.RcodeSuccess {
// 无论响应是否包含DNSSEC记录只要使用了DNSSEC专用服务器就设置usedDNSSECServer
usedDNSSECServer = resp.server
// 验证DNSSEC记录 // 验证DNSSEC记录
signatureValid := s.verifyDNSSEC(resp.response) signatureValid := s.verifyDNSSEC(resp.response)
@@ -1197,6 +1320,9 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
containsDNSSEC := s.hasDNSSECRecords(response) containsDNSSEC := s.hasDNSSECRecords(response)
if response.Rcode == dns.RcodeSuccess { if response.Rcode == dns.RcodeSuccess {
// 无论响应是否包含DNSSEC记录只要使用了DNSSEC专用服务器就设置usedDNSSECServer
usedDNSSECServer = selectedServer
// 验证DNSSEC记录 // 验证DNSSEC记录
signatureValid := s.verifyDNSSEC(response) signatureValid := s.verifyDNSSEC(response)
@@ -1257,6 +1383,9 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
containsDNSSEC := s.hasDNSSECRecords(response) containsDNSSEC := s.hasDNSSECRecords(response)
if response.Rcode == dns.RcodeSuccess { if response.Rcode == dns.RcodeSuccess {
// 无论响应是否包含DNSSEC记录只要使用了DNSSEC专用服务器就设置usedDNSSECServer
usedDNSSECServer = fastestServer
// 验证DNSSEC记录 // 验证DNSSEC记录
signatureValid := s.verifyDNSSEC(response) signatureValid := s.verifyDNSSEC(response)
@@ -1315,6 +1444,9 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
containsDNSSEC := s.hasDNSSECRecords(response) containsDNSSEC := s.hasDNSSECRecords(response)
if response.Rcode == dns.RcodeSuccess { if response.Rcode == dns.RcodeSuccess {
// 无论响应是否包含DNSSEC记录只要使用了DNSSEC专用服务器就设置usedDNSSECServer
usedDNSSECServer = dnssecServer
// 验证DNSSEC记录 // 验证DNSSEC记录
signatureValid := s.verifyDNSSEC(response) signatureValid := s.verifyDNSSEC(response)
@@ -1366,9 +1498,43 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 检查最佳响应是否包含DNSSEC记录 // 检查最佳响应是否包含DNSSEC记录
bestHasDNSSEC := s.hasDNSSECRecords(bestResponse) bestHasDNSSEC := s.hasDNSSECRecords(bestResponse)
// 如果启用了DNSSEC且最佳响应不包含DNSSEC记录使用upstreamDNS的解析结果 // 如果启用了DNSSEC且最佳响应不包含DNSSEC记录尝试使用本地解析
if s.config.EnableDNSSEC && !bestHasDNSSEC { if s.config.EnableDNSSEC && !bestHasDNSSEC {
logger.Debug("最佳响应不包含DNSSEC记录使用upstreamDNS的解析结果", "domain", domain) logger.Debug("最佳响应不包含DNSSEC记录尝试使用本地解析", "domain", domain)
if ip, exists := s.shieldManager.GetHostsIP(domain); exists {
// 本地解析成功,构建响应
localResponse := new(dns.Msg)
localResponse.SetReply(r)
localResponse.RecursionAvailable = true
localResponse.AuthenticatedData = false
localResponse.Rcode = dns.RcodeSuccess
if len(r.Question) > 0 {
q := r.Question[0]
answer := new(dns.A)
answer.Hdr = dns.RR_Header{
Name: q.Name,
Rrtype: q.Qtype,
Class: q.Qclass,
Ttl: 300,
}
answer.A = net.ParseIP(ip)
localResponse.Answer = append(localResponse.Answer, answer)
}
// 记录解析域名统计
s.updateResolvedDomainStats(domain)
// 更新域名的DNSSEC状态为false
s.updateDomainDNSSECStatus(domain, false)
s.updateStats(func(stats *Stats) {
stats.Allowed++
})
logger.Debug("使用本地解析结果", "domain", domain, "ip", ip)
return localResponse, 0, "", ""
}
} }
// 记录解析域名统计 // 记录解析域名统计
@@ -1377,12 +1543,14 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 更新域名的DNSSEC状态 // 更新域名的DNSSEC状态
if bestHasDNSSEC { if bestHasDNSSEC {
s.updateDomainDNSSECStatus(domain, true) s.updateDomainDNSSECStatus(domain, true)
} else {
s.updateDomainDNSSECStatus(domain, false)
} }
s.updateStats(func(stats *Stats) { s.updateStats(func(stats *Stats) {
stats.Allowed++ stats.Allowed++
}) })
return bestResponse, bestRtt return bestResponse, bestRtt, usedDNSServer, usedDNSSECServer
} }
// 如果有备选响应,返回该响应 // 如果有备选响应,返回该响应
@@ -1390,11 +1558,11 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
logger.Debug("使用备选响应,没有找到更好的结果", "domain", domain) logger.Debug("使用备选响应,没有找到更好的结果", "domain", domain)
// 记录解析域名统计 // 记录解析域名统计
s.updateResolvedDomainStats(domain) s.updateResolvedDomainStats(domain)
// 更新统计信息
s.updateStats(func(stats *Stats) { s.updateStats(func(stats *Stats) {
stats.Allowed++ stats.Allowed++
}) })
return backupResponse, backupRtt return backupResponse, backupRtt, "", ""
} }
// 所有上游服务器都失败,返回服务器失败错误 // 所有上游服务器都失败,返回服务器失败错误
@@ -1407,12 +1575,12 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
s.updateStats(func(stats *Stats) { s.updateStats(func(stats *Stats) {
stats.Errors++ stats.Errors++
}) })
return response, 0 return response, 0, "", ""
} }
// forwardDNSRequest 转发DNS请求到上游服务器 // forwardDNSRequest 转发DNS请求到上游服务器
func (s *Server) forwardDNSRequest(w dns.ResponseWriter, r *dns.Msg, domain string) { func (s *Server) forwardDNSRequest(w dns.ResponseWriter, r *dns.Msg, domain string) {
response, _ := s.forwardDNSRequestWithCache(r, domain) response, _, _, _ := s.forwardDNSRequestWithCache(r, domain)
w.WriteMsg(response) w.WriteMsg(response)
} }
@@ -1844,7 +2012,7 @@ func (s *Server) updateStats(update func(*Stats)) {
} }
// addQueryLog 添加查询日志 // addQueryLog 添加查询日志
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool) { func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool, dnsServer, dnssecServer string) {
// 获取IP地理位置 // 获取IP地理位置
location := s.getIpGeolocation(clientIP) location := s.getIpGeolocation(clientIP)
@@ -1862,6 +2030,8 @@ func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime in
FromCache: fromCache, FromCache: fromCache,
DNSSEC: dnssec, DNSSEC: dnssec,
EDNS: edns, EDNS: edns,
DNSServer: dnsServer,
DNSSECServer: dnssecServer,
} }
// 添加到日志列表 // 添加到日志列表

View File

@@ -400,6 +400,7 @@ function updateLogsTable(logs) {
<td class="py-3 px-4 text-sm"> <td class="py-3 px-4 text-sm">
<div class="font-medium">${log.Domain}</div> <div class="font-medium">${log.Domain}</div>
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span>${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}${log.EDNS ? ', <span class="text-blue-500"><i class="fa fa-exchange"></i> EDNS</span>' : ''}</div> <div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span>${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}${log.EDNS ? ', <span class="text-blue-500"><i class="fa fa-exchange"></i> EDNS</span>' : ''}</div>
<div class="text-xs text-gray-500 mt-1">DNS 服务器: ${log.DNSServer || '无'}, DNSSEC专用: ${log.DNSSECServer || '无'}</div>
</td> </td>
<td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td> <td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td>
<td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td> <td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td>

52
temp_config.json Normal file
View File

@@ -0,0 +1,52 @@
{
"dns": {
"port": 5353,
"upstreamDNS": [
"223.5.5.5:53",
"223.6.6.6:53",
"117.50.10.10:53",
"10.35.10.200:53"
],
"dnssecUpstreamDNS": [
"117.50.10.10:53",
"101.226.4.6:53",
"218.30.118.6:53",
"208.67.220.220:53",
"208.67.222.222:53"
],
"timeout": 5000,
"statsFile": "data/stats.json",
"saveInterval": 300,
"cacheTTL": 30,
"enableDNSSEC": true,
"queryMode": "parallel",
"domainSpecificDNS": {
"amazehome.xyz": ["10.35.10.200:53"]
}
},
"http": {
"port": 8081,
"host": "0.0.0.0",
"enableAPI": true,
"username": "admin",
"password": "admin"
},
"shield": {
"localRulesFile": "data/rules.txt",
"blacklists": [],
"updateInterval": 3600,
"hostsFile": "data/hosts.txt",
"blockMethod": "NXDOMAIN",
"customBlockIP": "",
"statsFile": "./data/shield_stats.json",
"statsSaveInterval": 60,
"remoteRulesCacheDir": "data/remote_rules"
},
"log": {
"file": "logs/dns-server-5353.log",
"level": "debug",
"maxSize": 100,
"maxBackups": 10,
"maxAge": 30
}
}