新建DNS服务器

This commit is contained in:
Alex Yang
2025-11-23 18:21:29 +08:00
commit 0072e8a5c2
15 changed files with 5372 additions and 0 deletions

27
config.json Normal file
View File

@@ -0,0 +1,27 @@
{
"dns": {
"port": 53,
"upstreamDNS": ["223.5.5.5:53", "223.6.6.6:53"],
"timeout": 5000
},
"http": {
"port": 8080,
"host": "0.0.0.0",
"enableAPI": true
},
"shield": {
"localRulesFile": "rules.txt",
"remoteRules": [
"https://example.com/rules.txt"
],
"updateInterval": 3600,
"hostsFile": "hosts.txt"
},
"log": {
"file": "dns-server.log",
"level": "debug",
"maxSize": 100,
"maxBackups": 10,
"maxAge": 30
}
}

86
config/config.go Normal file
View File

@@ -0,0 +1,86 @@
package config
import (
"encoding/json"
"io/ioutil"
)
// DNSConfig DNS服务器配置
type DNSConfig struct {
Port int `json:"port"`
UpstreamDNS []string `json:"upstreamDNS"`
Timeout int `json:"timeout"`
}
// HTTPConfig HTTP控制台配置
type HTTPConfig struct {
Port int `json:"port"`
Host string `json:"host"`
EnableAPI bool `json:"enableAPI"`
}
// ShieldConfig 屏蔽规则配置
type ShieldConfig struct {
LocalRulesFile string `json:"localRulesFile"`
RemoteRules []string `json:"remoteRules"`
UpdateInterval int `json:"updateInterval"`
HostsFile string `json:"hostsFile"`
BlockMethod string `json:"blockMethod"` // 屏蔽方法: "NXDOMAIN", "refused", "emptyIP", "customIP"
CustomBlockIP string `json:"customBlockIP"` // 自定义屏蔽IP当BlockMethod为"customIP"时使用
}
// LogConfig 日志配置
type LogConfig struct {
File string `json:"file"`
Level string `json:"level"`
MaxSize int `json:"maxSize"`
MaxBackups int `json:"maxBackups"`
MaxAge int `json:"maxAge"`
}
// Config 整体配置
type Config struct {
DNS DNSConfig `json:"dns"`
HTTP HTTPConfig `json:"http"`
Shield ShieldConfig `json:"shield"`
Log LogConfig `json:"log"`
}
// LoadConfig 加载配置文件
func LoadConfig(path string) (*Config, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
var config Config
err = json.Unmarshal(data, &config)
if err != nil {
return nil, err
}
// 设置默认值
if config.DNS.Port == 0 {
config.DNS.Port = 53
}
if len(config.DNS.UpstreamDNS) == 0 {
config.DNS.UpstreamDNS = []string{"8.8.8.8:53", "1.1.1.1:53"}
}
if config.HTTP.Port == 0 {
config.HTTP.Port = 8080
}
if config.HTTP.Host == "" {
config.HTTP.Host = "0.0.0.0"
}
if config.Shield.UpdateInterval == 0 {
config.Shield.UpdateInterval = 3600
}
if config.Shield.BlockMethod == "" {
config.Shield.BlockMethod = "NXDOMAIN" // 默认屏蔽方法为NXDOMAIN
}
if config.Log.Level == "" {
config.Log.Level = "info"
}
return &config, nil
}

BIN
dns-server Executable file

Binary file not shown.

133
dns-server.log Normal file
View File

@@ -0,0 +1,133 @@
time="2025-11-23T18:04:23+08:00" level=error msg="获取远程规则失败" error="远程服务器返回错误状态码: 404" url="https://example.com/rules.txt"
time="2025-11-23T18:04:23+08:00" level=info msg="规则加载完成,域名规则: 2, 排除规则: 0, 正则规则: 2, hosts规则: 3"
time="2025-11-23T18:04:23+08:00" level=info msg="DNS服务器已启动监听端口: 53"
time="2025-11-23T18:04:23+08:00" level=info msg="HTTP控制台已启动监听端口: 8080"
time="2025-11-23T18:04:23+08:00" level=info msg="DNS TCP服务器启动监听端口: 53"
time="2025-11-23T18:04:23+08:00" level=info msg="HTTP控制台服务器启动监听地址: 0.0.0.0:8080"
time="2025-11-23T18:04:23+08:00" level=info msg="DNS UDP服务器启动监听端口: 53"
time="2025-11-23T18:05:21+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:60351" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:05:21+08:00" level=debug msg="DNS查询成功" domain=adjust.net.amazehome.xyz rtt=30.003763ms server="223.5.5.5:53"
time="2025-11-23T18:05:21+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:60352" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:05:21+08:00" level=debug msg="DNS查询成功" domain=adjust.net.amazehome.xyz rtt=40.103225ms server="223.6.6.6:53"
time="2025-11-23T18:05:21+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:60353" domain=adjust.net type=1
time="2025-11-23T18:05:21+08:00" level=debug msg="DNS查询成功" domain=adjust.net rtt=475.840213ms server="223.5.5.5:53"
time="2025-11-23T18:05:21+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:51453" domain=adjust.net type=28
time="2025-11-23T18:05:22+08:00" level=debug msg="DNS查询成功" domain=adjust.net rtt=352.101947ms server="223.5.5.5:53"
time="2025-11-23T18:05:37+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52048" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:05:37+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52048" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:37+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52049" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:05:37+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52049" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:37+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52050" domain=adjust.net type=1
time="2025-11-23T18:05:37+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52050" domain=adjust.net
time="2025-11-23T18:05:37+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52051" domain=adjust.net type=28
time="2025-11-23T18:05:37+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52051" domain=adjust.net
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59825" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:59825" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59826" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:59826" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59827" domain=adjust.net type=1
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:59827" domain=adjust.net
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59828" domain=adjust.net type=28
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:59828" domain=adjust.net
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59829" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:59829" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59830" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:59830" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59831" domain=adjust.net type=1
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:59831" domain=adjust.net
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59832" domain=adjust.net type=28
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:59832" domain=adjust.net
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:61343" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:61343" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:61344" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:61344" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:61345" domain=adjust.net type=1
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:61345" domain=adjust.net
time="2025-11-23T18:05:38+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:61346" domain=adjust.net type=28
time="2025-11-23T18:05:38+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:61346" domain=adjust.net
time="2025-11-23T18:05:42+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:63485" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:05:42+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:63485" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:42+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:63486" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:05:42+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:63486" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:42+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:63487" domain=adjust.net type=1
time="2025-11-23T18:05:42+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:63487" domain=adjust.net
time="2025-11-23T18:05:42+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:63488" domain=adjust.net type=28
time="2025-11-23T18:05:42+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:63488" domain=adjust.net
time="2025-11-23T18:05:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52914" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:05:43+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52914" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52915" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:05:43+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52915" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52916" domain=adjust.net type=1
time="2025-11-23T18:05:43+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52916" domain=adjust.net
time="2025-11-23T18:05:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52917" domain=adjust.net type=28
time="2025-11-23T18:05:43+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52917" domain=adjust.net
time="2025-11-23T18:05:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52918" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:05:43+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52918" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52919" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:05:43+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52919" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:05:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52920" domain=adjust.net type=1
time="2025-11-23T18:05:43+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52920" domain=adjust.net
time="2025-11-23T18:05:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:52921" domain=adjust.net type=28
time="2025-11-23T18:05:43+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:52921" domain=adjust.net
time="2025-11-23T18:06:49+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59892" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:06:49+08:00" level=debug msg="DNS查询成功" domain=adjust.net.amazehome.xyz rtt=4.586467ms server="223.5.5.5:53"
time="2025-11-23T18:06:49+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59893" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:06:50+08:00" level=debug msg="DNS查询成功" domain=adjust.net.amazehome.xyz rtt=26.530637ms server="223.5.5.5:53"
time="2025-11-23T18:06:50+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59894" domain=adjust.net type=1
time="2025-11-23T18:06:50+08:00" level=debug msg="DNS查询成功" domain=adjust.net rtt=5.219233ms server="223.5.5.5:53"
time="2025-11-23T18:06:50+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:59895" domain=adjust.net type=28
time="2025-11-23T18:06:50+08:00" level=debug msg="DNS查询成功" domain=adjust.net rtt=14.858315ms server="223.5.5.5:53"
time="2025-11-23T18:07:01+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:54828" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:07:01+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:54828" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:07:01+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:54829" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:07:01+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:54829" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:07:01+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:54830" domain=adjust.net type=1
time="2025-11-23T18:07:01+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:54830" domain=adjust.net
time="2025-11-23T18:07:01+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:54831" domain=adjust.net type=28
time="2025-11-23T18:07:01+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:54831" domain=adjust.net
time="2025-11-23T18:07:01+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:54832" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:07:01+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:54832" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:07:01+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:54833" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:07:01+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:54833" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:07:01+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:54834" domain=adjust.net type=1
time="2025-11-23T18:07:01+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:54834" domain=adjust.net
time="2025-11-23T18:07:01+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:54835" domain=adjust.net type=28
time="2025-11-23T18:07:01+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:54835" domain=adjust.net
time="2025-11-23T18:07:02+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:57109" domain=adjust.net.amazehome.xyz type=1
time="2025-11-23T18:07:02+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:57109" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:07:02+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:57110" domain=adjust.net.amazehome.xyz type=28
time="2025-11-23T18:07:02+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:57110" domain=adjust.net.amazehome.xyz
time="2025-11-23T18:07:02+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:57111" domain=adjust.net type=1
time="2025-11-23T18:07:02+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:57111" domain=adjust.net
time="2025-11-23T18:07:02+08:00" level=debug msg="接收到DNS查询" client="10.35.10.78:57112" domain=adjust.net type=28
time="2025-11-23T18:07:02+08:00" level=info msg="域名被屏蔽" client="10.35.10.78:57112" domain=adjust.net
time="2025-11-23T18:07:15+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:32001" domain=apd-pcdnwxstat.teg.tencent-cloud.net type=1
time="2025-11-23T18:07:15+08:00" level=debug msg="DNS查询成功" domain=apd-pcdnwxstat.teg.tencent-cloud.net rtt=6.522705ms server="223.5.5.5:53"
time="2025-11-23T18:08:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:49391" domain=magazine-drcn.theme.dbankcloud.cn type=1
time="2025-11-23T18:08:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:26101" domain=api-drcn.theme.dbankcloud.cn type=1
time="2025-11-23T18:08:43+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:37270" domain=contentcenter-drcn.dbankcdn.cn type=1
time="2025-11-23T18:08:43+08:00" level=debug msg="DNS查询成功" domain=contentcenter-drcn.dbankcdn.cn rtt=4.599463ms server="223.5.5.5:53"
time="2025-11-23T18:08:43+08:00" level=debug msg="DNS查询成功" domain=magazine-drcn.theme.dbankcloud.cn rtt=5.641158ms server="223.5.5.5:53"
time="2025-11-23T18:08:43+08:00" level=debug msg="DNS查询成功" domain=api-drcn.theme.dbankcloud.cn rtt=5.674991ms server="223.5.5.5:53"
time="2025-11-23T18:08:44+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:65533" domain=tsms-drcn.security.dbankcloud.cn type=1
time="2025-11-23T18:08:44+08:00" level=debug msg="DNS查询成功" domain=tsms-drcn.security.dbankcloud.cn rtt=4.626637ms server="223.5.5.5:53"
time="2025-11-23T18:08:46+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:4437" domain=events.op.hicloud.com type=1
time="2025-11-23T18:08:46+08:00" level=debug msg="DNS查询成功" domain=events.op.hicloud.com rtt=5.908197ms server="223.5.5.5:53"
time="2025-11-23T18:08:47+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:34826" domain=metrics1-drcn.dt.dbankcloud.cn type=1
time="2025-11-23T18:08:47+08:00" level=debug msg="DNS查询成功" domain=metrics1-drcn.dt.dbankcloud.cn rtt=6.027459ms server="223.5.5.5:53"
time="2025-11-23T18:08:47+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:31858" domain=sdkserver.op.hicloud.com type=1
time="2025-11-23T18:08:47+08:00" level=debug msg="DNS查询成功" domain=sdkserver.op.hicloud.com rtt=19.413443ms server="223.5.5.5:53"
time="2025-11-23T18:08:47+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:12581" domain=acd.op.hicloud.com type=1
time="2025-11-23T18:08:47+08:00" level=debug msg="DNS查询成功" domain=acd.op.hicloud.com rtt=6.202825ms server="223.5.5.5:53"
time="2025-11-23T18:08:48+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:4139" domain=api.cloud.huawei.com type=1
time="2025-11-23T18:08:48+08:00" level=debug msg="DNS查询成功" domain=api.cloud.huawei.com rtt=5.729772ms server="223.5.5.5:53"
time="2025-11-23T18:08:48+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:1352" domain=datacollabo-drcn.platform.dbankcloud.cn type=1
time="2025-11-23T18:08:48+08:00" level=debug msg="DNS查询成功" domain=datacollabo-drcn.platform.dbankcloud.cn rtt=5.429196ms server="223.5.5.5:53"
time="2025-11-23T18:08:48+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:51418" domain=rcm-cus-drcn.platform.dbankcloud.cn type=1
time="2025-11-23T18:08:48+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:45468" domain=abt-drcn.platform.dbankcloud.com type=1
time="2025-11-23T18:08:48+08:00" level=debug msg="DNS查询成功" domain=rcm-cus-drcn.platform.dbankcloud.cn rtt=6.646444ms server="223.5.5.5:53"
time="2025-11-23T18:08:48+08:00" level=debug msg="DNS查询成功" domain=abt-drcn.platform.dbankcloud.com rtt=7.082723ms server="223.5.5.5:53"
time="2025-11-23T18:08:48+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:31362" domain=h5hosting.dbankcdn.com type=1
time="2025-11-23T18:08:48+08:00" level=debug msg="DNS查询成功" domain=h5hosting.dbankcdn.com rtt=7.025853ms server="223.5.5.5:53"
time="2025-11-23T18:08:48+08:00" level=debug msg="接收到DNS查询" client="10.35.10.11:12618" domain=metrics1.data.hicloud.com type=1
time="2025-11-23T18:08:48+08:00" level=debug msg="DNS查询成功" domain=metrics1.data.hicloud.com rtt=5.85395ms server="223.5.5.5:53"

396
dns/server.go Normal file
View File

@@ -0,0 +1,396 @@
package dns
import (
"context"
"fmt"
"net"
"sort"
"sync"
"time"
"dns-server/config"
"dns-server/logger"
"dns-server/shield"
"github.com/miekg/dns"
)
// BlockedDomain 屏蔽域名统计
type BlockedDomain struct {
Domain string
Count int64
LastSeen time.Time
}
// Server DNS服务器
type Server struct {
config *config.DNSConfig
shieldConfig *config.ShieldConfig
shieldManager *shield.ShieldManager
server *dns.Server
resolver *dns.Client
ctx context.Context
cancel context.CancelFunc
statsMutex sync.Mutex
stats *Stats
blockedDomainsMutex sync.RWMutex
blockedDomains map[string]*BlockedDomain
hourlyStatsMutex sync.RWMutex
hourlyStats map[string]int64 // 按小时统计屏蔽数量
}
// Stats DNS服务器统计信息
type Stats struct {
Queries int64
Blocked int64
Allowed int64
Errors int64
LastQuery time.Time
}
// NewServer 创建DNS服务器实例
func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shieldManager *shield.ShieldManager) *Server {
ctx, cancel := context.WithCancel(context.Background())
return &Server{
config: config,
shieldConfig: shieldConfig,
shieldManager: shieldManager,
resolver: &dns.Client{
Net: "udp",
Timeout: time.Duration(config.Timeout) * time.Millisecond,
},
ctx: ctx,
cancel: cancel,
stats: &Stats{
Queries: 0,
Blocked: 0,
Allowed: 0,
Errors: 0,
},
blockedDomains: make(map[string]*BlockedDomain),
hourlyStats: make(map[string]int64),
}
}
// Start 启动DNS服务器
func (s *Server) Start() error {
s.server = &dns.Server{
Addr: fmt.Sprintf(":%d", s.config.Port),
Net: "udp",
Handler: dns.HandlerFunc(s.handleDNSRequest),
}
// 启动TCP服务器用于大型响应
tcpServer := &dns.Server{
Addr: fmt.Sprintf(":%d", s.config.Port),
Net: "tcp",
Handler: dns.HandlerFunc(s.handleDNSRequest),
}
// 启动UDP服务
go func() {
logger.Info(fmt.Sprintf("DNS UDP服务器启动监听端口: %d", s.config.Port))
if err := s.server.ListenAndServe(); err != nil {
logger.Error("DNS UDP服务器启动失败", "error", err)
s.cancel()
}
}()
// 启动TCP服务
go func() {
logger.Info(fmt.Sprintf("DNS TCP服务器启动监听端口: %d", s.config.Port))
if err := tcpServer.ListenAndServe(); err != nil {
logger.Error("DNS TCP服务器启动失败", "error", err)
s.cancel()
}
}()
// 等待停止信号
<-s.ctx.Done()
return nil
}
// Stop 停止DNS服务器
func (s *Server) Stop() {
if s.server != nil {
s.server.Shutdown()
}
s.cancel()
logger.Info("DNS服务器已停止")
}
// handleDNSRequest 处理DNS请求
func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
s.updateStats(func(stats *Stats) {
stats.Queries++
stats.LastQuery = time.Now()
})
// 只处理递归查询
if r.RecursionDesired == false {
response := new(dns.Msg)
response.SetReply(r)
response.RecursionAvailable = true
response.SetRcode(r, dns.RcodeRefused)
w.WriteMsg(response)
return
}
// 获取查询域名
var domain string
if len(r.Question) > 0 {
domain = r.Question[0].Name
// 移除末尾的点
if len(domain) > 0 && domain[len(domain)-1] == '.' {
domain = domain[:len(domain)-1]
}
}
logger.Debug("接收到DNS查询", "domain", domain, "type", r.Question[0].Qtype, "client", w.RemoteAddr())
// 检查hosts文件是否有匹配
if ip, exists := s.shieldManager.GetHostsIP(domain); exists {
s.handleHostsResponse(w, r, ip)
return
}
// 检查是否被屏蔽
if s.shieldManager.IsBlocked(domain) {
s.handleBlockedResponse(w, r, domain)
return
}
// 转发到上游DNS服务器
s.forwardDNSRequest(w, r, domain)
}
// handleHostsResponse 处理hosts文件匹配的响应
func (s *Server) handleHostsResponse(w dns.ResponseWriter, r *dns.Msg, ip string) {
response := new(dns.Msg)
response.SetReply(r)
response.RecursionAvailable = true
if len(r.Question) > 0 {
q := r.Question[0]
answer := new(dns.A)
answer.Hdr = dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 300,
}
answer.A = net.ParseIP(ip)
response.Answer = append(response.Answer, answer)
}
w.WriteMsg(response)
s.updateStats(func(stats *Stats) {
stats.Allowed++
})
}
// handleBlockedResponse 处理被屏蔽的域名响应
func (s *Server) handleBlockedResponse(w dns.ResponseWriter, r *dns.Msg, domain string) {
logger.Info("域名被屏蔽", "domain", domain, "client", w.RemoteAddr())
// 更新被屏蔽域名统计
s.updateBlockedDomainStats(domain)
// 更新总体统计
s.updateStats(func(stats *Stats) {
stats.Blocked++
})
response := new(dns.Msg)
response.SetReply(r)
response.RecursionAvailable = true
// 获取屏蔽方法配置
blockMethod := "NXDOMAIN" // 默认值
customBlockIP := "" // 默认值
// 从Server结构体的shieldConfig字段获取配置
if s.shieldConfig != nil {
blockMethod = s.shieldConfig.BlockMethod
customBlockIP = s.shieldConfig.CustomBlockIP
}
// 根据屏蔽方法返回不同的响应
switch blockMethod {
case "refused":
// 返回拒绝查询响应
response.SetRcode(r, dns.RcodeRefused)
case "emptyIP":
// 返回空IP响应
if len(r.Question) > 0 && r.Question[0].Qtype == dns.TypeA {
answer := new(dns.A)
answer.Hdr = dns.RR_Header{
Name: r.Question[0].Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 300,
}
answer.A = net.ParseIP("0.0.0.0") // 空IP
response.Answer = append(response.Answer, answer)
}
case "customIP":
// 返回自定义IP响应
if len(r.Question) > 0 && r.Question[0].Qtype == dns.TypeA && customBlockIP != "" {
answer := new(dns.A)
answer.Hdr = dns.RR_Header{
Name: r.Question[0].Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 300,
}
answer.A = net.ParseIP(customBlockIP)
response.Answer = append(response.Answer, answer)
}
case "NXDOMAIN", "":
fallthrough // 默认使用NXDOMAIN
default:
// 返回NXDOMAIN响应域名不存在
response.SetRcode(r, dns.RcodeNameError)
}
w.WriteMsg(response)
s.updateStats(func(stats *Stats) {
stats.Blocked++
})
}
// forwardDNSRequest 转发DNS请求到上游服务器
func (s *Server) forwardDNSRequest(w dns.ResponseWriter, r *dns.Msg, domain string) {
// 尝试所有上游DNS服务器
for _, upstream := range s.config.UpstreamDNS {
response, rtt, err := s.resolver.Exchange(r, upstream)
if err == nil && response != nil && response.Rcode == dns.RcodeSuccess {
// 设置递归可用标志
response.RecursionAvailable = true
w.WriteMsg(response)
logger.Debug("DNS查询成功", "domain", domain, "rtt", rtt, "server", upstream)
s.updateStats(func(stats *Stats) {
stats.Allowed++
})
return
}
}
// 所有上游服务器都失败,返回服务器失败错误
response := new(dns.Msg)
response.SetReply(r)
response.RecursionAvailable = true
response.SetRcode(r, dns.RcodeServerFailure)
w.WriteMsg(response)
logger.Error("DNS查询失败", "domain", domain)
s.updateStats(func(stats *Stats) {
stats.Errors++
})
}
// updateBlockedDomainStats 更新被屏蔽域名统计
func (s *Server) updateBlockedDomainStats(domain string) {
// 更新被屏蔽域名计数
s.blockedDomainsMutex.Lock()
defer s.blockedDomainsMutex.Unlock()
if entry, exists := s.blockedDomains[domain]; exists {
entry.Count++
entry.LastSeen = time.Now()
} else {
s.blockedDomains[domain] = &BlockedDomain{
Domain: domain,
Count: 1,
LastSeen: time.Now(),
}
}
// 更新小时统计
hourKey := time.Now().Format("2006-01-02-15")
s.hourlyStats[hourKey]++
}
// updateStats 更新统计信息
func (s *Server) updateStats(update func(*Stats)) {
s.statsMutex.Lock()
defer s.statsMutex.Unlock()
update(s.stats)
}
// GetStats 获取DNS服务器统计信息
func (s *Server) GetStats() *Stats {
s.statsMutex.Lock()
defer s.statsMutex.Unlock()
// 返回统计信息的副本
return &Stats{
Queries: s.stats.Queries,
Blocked: s.stats.Blocked,
Allowed: s.stats.Allowed,
Errors: s.stats.Errors,
LastQuery: s.stats.LastQuery,
}
}
// GetTopBlockedDomains 获取TOP屏蔽域名列表
func (s *Server) GetTopBlockedDomains(limit int) []BlockedDomain {
s.blockedDomainsMutex.RLock()
defer s.blockedDomainsMutex.RUnlock()
// 转换为切片
domains := make([]BlockedDomain, 0, len(s.blockedDomains))
for _, entry := range s.blockedDomains {
domains = append(domains, *entry)
}
// 按计数排序
sort.Slice(domains, func(i, j int) bool {
return domains[i].Count > domains[j].Count
})
// 返回限制数量
if len(domains) > limit {
return domains[:limit]
}
return domains
}
// GetRecentBlockedDomains 获取最近屏蔽的域名列表
func (s *Server) GetRecentBlockedDomains(limit int) []BlockedDomain {
s.blockedDomainsMutex.RLock()
defer s.blockedDomainsMutex.RUnlock()
// 转换为切片
domains := make([]BlockedDomain, 0, len(s.blockedDomains))
for _, entry := range s.blockedDomains {
domains = append(domains, *entry)
}
// 按时间排序
sort.Slice(domains, func(i, j int) bool {
return domains[i].LastSeen.After(domains[j].LastSeen)
})
// 返回限制数量
if len(domains) > limit {
return domains[:limit]
}
return domains
}
// GetHourlyStats 获取24小时屏蔽统计
func (s *Server) GetHourlyStats() map[string]int64 {
s.hourlyStatsMutex.RLock()
defer s.hourlyStatsMutex.RUnlock()
// 返回副本
result := make(map[string]int64)
for k, v := range s.hourlyStats {
result[k] = v
}
return result
}

21
go.mod Normal file
View File

@@ -0,0 +1,21 @@
module dns-server
go 1.23.0
toolchain go1.24.10
require (
github.com/miekg/dns v1.1.68
github.com/sirupsen/logrus v1.9.3
)
// 清理不需要的依赖
// 之前的go.sum可能包含lumberjack的记录但现在已经不再使用
require (
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/tools v0.33.0 // indirect
)

28
go.sum Normal file
View File

@@ -0,0 +1,28 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

6
hosts.txt Normal file
View File

@@ -0,0 +1,6 @@
# DNS Server Hosts File
# Generated by DNS Server
::1 localhost
ad.qq.com 127.0.0.1
ad.qq.com 0.0.0.0

444
http/server.go Normal file
View File

@@ -0,0 +1,444 @@
package http
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"dns-server/config"
"dns-server/dns"
"dns-server/logger"
"dns-server/shield"
)
// Server HTTP控制台服务器
type Server struct {
globalConfig *config.Config
config *config.HTTPConfig
dnsServer *dns.Server
shieldManager *shield.ShieldManager
server *http.Server
}
// NewServer 创建HTTP服务器实例
func NewServer(globalConfig *config.Config, dnsServer *dns.Server, shieldManager *shield.ShieldManager) *Server {
return &Server{
globalConfig: globalConfig,
config: &globalConfig.HTTP,
dnsServer: dnsServer,
shieldManager: shieldManager,
}
}
// Start 启动HTTP服务器
func (s *Server) Start() error {
mux := http.NewServeMux()
// API路由
if s.config.EnableAPI {
mux.HandleFunc("/api/stats", s.handleStats)
mux.HandleFunc("/api/shield", s.handleShield)
mux.HandleFunc("/api/shield/hosts", s.handleShieldHosts)
mux.HandleFunc("/api/query", s.handleQuery)
mux.HandleFunc("/api/status", s.handleStatus)
mux.HandleFunc("/api/config", s.handleConfig)
// 添加统计相关接口
mux.HandleFunc("/api/top-blocked", s.handleTopBlockedDomains)
mux.HandleFunc("/api/recent-blocked", s.handleRecentBlockedDomains)
mux.HandleFunc("/api/hourly-stats", s.handleHourlyStats)
}
// 静态文件服务(可后续添加前端界面)
mux.Handle("/", http.FileServer(http.Dir("./static")))
s.server = &http.Server{
Addr: fmt.Sprintf("%s:%d", s.config.Host, s.config.Port),
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
logger.Info(fmt.Sprintf("HTTP控制台服务器启动监听地址: %s:%d", s.config.Host, s.config.Port))
return s.server.ListenAndServe()
}
// Stop 停止HTTP服务器
func (s *Server) Stop() {
if s.server != nil {
s.server.Close()
}
logger.Info("HTTP控制台服务器已停止")
}
// handleStats 处理统计信息请求
func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
dnsStats := s.dnsServer.GetStats()
shieldStats := s.shieldManager.GetStats()
stats := map[string]interface{}{
"dns": dnsStats,
"shield": shieldStats,
"time": time.Now(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(stats)
}
// handleTopBlockedDomains 处理TOP屏蔽域名请求
func (s *Server) handleTopBlockedDomains(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
domains := s.dnsServer.GetTopBlockedDomains(10)
// 转换为前端需要的格式
result := make([]map[string]interface{}, len(domains))
for i, domain := range domains {
result[i] = map[string]interface{}{
"domain": domain.Domain,
"count": domain.Count,
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
// handleRecentBlockedDomains 处理最近屏蔽域名请求
func (s *Server) handleRecentBlockedDomains(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
domains := s.dnsServer.GetRecentBlockedDomains(10)
// 转换为前端需要的格式
result := make([]map[string]interface{}, len(domains))
for i, domain := range domains {
result[i] = map[string]interface{}{
"domain": domain.Domain,
"time": domain.LastSeen.Format("15:04:05"),
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
// handleHourlyStats 处理24小时统计请求
func (s *Server) handleHourlyStats(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
hourlyStats := s.dnsServer.GetHourlyStats()
// 生成最近24小时的数据
now := time.Now()
labels := make([]string, 24)
data := make([]int64, 24)
for i := 23; i >= 0; i-- {
hour := now.Add(time.Duration(-i) * time.Hour)
hourKey := hour.Format("2006-01-02-15")
labels[23-i] = hour.Format("15:00")
data[23-i] = hourlyStats[hourKey]
}
result := map[string]interface{}{
"labels": labels,
"data": data,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
// handleShield 处理屏蔽规则管理请求
func (s *Server) handleShield(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// 处理hosts管理子路由
if strings.HasPrefix(r.URL.Path, "/api/shield/hosts") {
s.handleShieldHosts(w, r)
return
}
switch r.Method {
case http.MethodGet:
// 获取完整规则列表
rules := s.shieldManager.GetRules()
json.NewEncoder(w).Encode(rules)
case http.MethodPost:
// 添加屏蔽规则
var req struct {
Rule string `json:"rule"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if err := s.shieldManager.AddRule(req.Rule); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
case http.MethodDelete:
// 删除屏蔽规则
var req struct {
Rule string `json:"rule"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if err := s.shieldManager.RemoveRule(req.Rule); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
case http.MethodPut:
// 重新加载规则
if err := s.shieldManager.LoadRules(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "规则重新加载成功"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// handleShieldHosts 处理hosts管理请求
func (s *Server) handleShieldHosts(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case http.MethodPost:
// 添加hosts条目
var req struct {
IP string `json:"ip"`
Domain string `json:"domain"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if req.IP == "" || req.Domain == "" {
http.Error(w, "IP and Domain are required", http.StatusBadRequest)
return
}
if err := s.shieldManager.AddHostsEntry(req.IP, req.Domain); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
case http.MethodDelete:
// 删除hosts条目
var req struct {
Domain string `json:"domain"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if err := s.shieldManager.RemoveHostsEntry(req.Domain); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
case http.MethodGet:
// 获取hosts条目列表
// 注意这需要在shieldManager中添加一个获取所有hosts条目的方法
// 暂时返回统计信息
stats := s.shieldManager.GetStats()
json.NewEncoder(w).Encode(map[string]interface{}{
"hostsCount": stats["hostsRules"],
"message": "获取hosts列表功能待实现",
})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// handleQuery 处理DNS查询请求
func (s *Server) handleQuery(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
domain := r.URL.Query().Get("domain")
if domain == "" {
http.Error(w, "Domain parameter is required", http.StatusBadRequest)
return
}
// 检查域名是否被屏蔽
blocked := s.shieldManager.IsBlocked(domain)
// 检查hosts文件是否有匹配
hostsIP, hasHosts := s.shieldManager.GetHostsIP(domain)
result := map[string]interface{}{
"domain": domain,
"blocked": blocked,
"hasHosts": hasHosts,
"hostsIP": hostsIP,
"timestamp": time.Now(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
// handleStatus 处理系统状态请求
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
stats := s.dnsServer.GetStats()
status := map[string]interface{}{
"status": "running",
"queries": stats.Queries,
"lastQuery": stats.LastQuery,
"uptime": time.Since(stats.LastQuery),
"timestamp": time.Now(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status)
}
// handleConfig 处理配置请求
func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case http.MethodGet:
// 返回当前配置(只返回必要的部分)
config := map[string]interface{}{
"shield": map[string]string{
"blockMethod": s.globalConfig.Shield.BlockMethod,
"customBlockIP": s.globalConfig.Shield.CustomBlockIP,
},
}
json.NewEncoder(w).Encode(config)
case http.MethodPost:
// 更新配置
var req struct {
Shield struct {
BlockMethod string `json:"blockMethod"`
CustomBlockIP string `json:"customBlockIP"`
} `json:"shield"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "无效的请求体", http.StatusBadRequest)
return
}
// 更新屏蔽配置
if req.Shield.BlockMethod != "" {
// 验证屏蔽方法是否有效
validMethods := map[string]bool{
"NXDOMAIN": true,
"refused": true,
"emptyIP": true,
"customIP": true,
}
if !validMethods[req.Shield.BlockMethod] {
http.Error(w, "无效的屏蔽方法", http.StatusBadRequest)
return
}
s.globalConfig.Shield.BlockMethod = req.Shield.BlockMethod
// 如果选择了customIP验证IP地址
if req.Shield.BlockMethod == "customIP" {
if req.Shield.CustomBlockIP == "" {
http.Error(w, "自定义IP不能为空", http.StatusBadRequest)
return
}
// 简单的IP地址验证
if !isValidIP(req.Shield.CustomBlockIP) {
http.Error(w, "无效的IP地址", http.StatusBadRequest)
return
}
}
}
if req.Shield.CustomBlockIP != "" {
s.globalConfig.Shield.CustomBlockIP = req.Shield.CustomBlockIP
}
// 返回成功响应
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"message": "配置已更新",
})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// isValidIP 简单验证IP地址格式
func isValidIP(ip string) bool {
// 简单的IPv4地址验证
parts := strings.Split(ip, ".")
if len(parts) != 4 {
return false
}
for _, part := range parts {
// 检查是否为数字
for _, char := range part {
if char < '0' || char > '9' {
return false
}
}
// 检查数字范围
var num int
if _, err := fmt.Sscanf(part, "%d", &num); err != nil || num < 0 || num > 255 {
return false
}
}
return true
}

1574
index.html Normal file

File diff suppressed because it is too large Load Diff

134
logger/logger.go Normal file
View File

@@ -0,0 +1,134 @@
package logger
import (
"io"
"os"
"sync"
"github.com/sirupsen/logrus"
)
var (
log *logrus.Logger
logMutex sync.Mutex
initialized bool
)
// InitLogger 初始化日志系统
func InitLogger(logFile, level string, maxSize, maxBackups, maxAge int) error {
logMutex.Lock()
defer logMutex.Unlock()
if initialized {
return nil
}
log = logrus.New()
// 配置日志格式
log.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
})
// 设置输出目标
if logFile != "" {
// 使用标准库打开文件,支持追加写入
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
logrus.Warn("无法打开日志文件,将使用标准输出", "error", err)
log.SetOutput(os.Stdout)
} else {
// 同时输出到文件和标准输出
log.SetOutput(io.MultiWriter(file, os.Stdout))
}
} else {
log.SetOutput(os.Stdout)
}
// 设置日志级别
logLevel, err := logrus.ParseLevel(level)
if err != nil {
logLevel = logrus.InfoLevel
}
log.SetLevel(logLevel)
initialized = true
return nil
}
// Close 关闭日志系统
func Close() {
logMutex.Lock()
defer logMutex.Unlock()
if !initialized {
return
}
// 执行日志刷新
log.Warn("日志系统已关闭")
initialized = false
}
// Info 记录信息级别日志
func Info(msg string, fields ...interface{}) {
if !initialized {
return
}
if len(fields) > 0 {
log.WithFields(toFields(fields)).Info(msg)
} else {
log.Info(msg)
}
}
// Error 记录错误级别日志
func Error(msg string, fields ...interface{}) {
if !initialized {
return
}
if len(fields) > 0 {
log.WithFields(toFields(fields)).Error(msg)
} else {
log.Error(msg)
}
}
// Debug 记录调试级别日志
func Debug(msg string, fields ...interface{}) {
if !initialized {
return
}
if len(fields) > 0 {
log.WithFields(toFields(fields)).Debug(msg)
} else {
log.Debug(msg)
}
}
// Warn 记录警告级别日志
func Warn(msg string, fields ...interface{}) {
if !initialized {
return
}
if len(fields) > 0 {
log.WithFields(toFields(fields)).Warn(msg)
} else {
log.Warn(msg)
}
}
// toFields 将键值对转换为logrus字段
func toFields(keyValues []interface{}) logrus.Fields {
fields := make(logrus.Fields)
for i := 0; i < len(keyValues); i += 2 {
if i+1 < len(keyValues) {
fields[keyValues[i].(string)] = keyValues[i+1]
}
}
return fields
}

74
main.go Normal file
View File

@@ -0,0 +1,74 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"dns-server/config"
"dns-server/dns"
"dns-server/http"
"dns-server/logger"
"dns-server/shield"
)
func main() {
// 解析命令行参数
configPath := flag.String("config", "config.json", "配置文件路径")
flag.Parse()
// 初始化配置
cfg, err := config.LoadConfig(*configPath)
if err != nil {
log.Fatalf("加载配置失败: %v", err)
}
// 初始化日志系统
if err := logger.InitLogger(cfg.Log.File, cfg.Log.Level, 0, 0, 0); err != nil {
log.Fatalf("初始化日志系统失败: %v", err)
}
defer logger.Close()
// 初始化屏蔽管理系统
shieldManager := shield.NewShieldManager(&cfg.Shield)
if err := shieldManager.LoadRules(); err != nil {
logger.Error("加载屏蔽规则失败", "error", err)
}
// 启动DNS服务器
dnsServer := dns.NewServer(&cfg.DNS, &cfg.Shield, shieldManager)
go func() {
if err := dnsServer.Start(); err != nil {
logger.Error("DNS服务器启动失败", "error", err)
os.Exit(1)
}
}()
// 启动HTTP控制台服务器
httpServer := http.NewServer(cfg, dnsServer, shieldManager)
go func() {
if err := httpServer.Start(); err != nil {
logger.Error("HTTP控制台服务器启动失败", "error", err)
}
}()
// 启动定时更新任务
go shieldManager.StartAutoUpdate()
logger.Info(fmt.Sprintf("DNS服务器已启动监听端口: %d", cfg.DNS.Port))
logger.Info(fmt.Sprintf("HTTP控制台已启动监听端口: %d", cfg.HTTP.Port))
// 等待退出信号
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
logger.Info("正在关闭服务...")
dnsServer.Stop()
httpServer.Stop()
shieldManager.StopAutoUpdate()
logger.Info("所有服务已关闭")
}

3
rules.txt Normal file
View File

@@ -0,0 +1,3 @@
||hm.baidu.com
/.*tracking.*/
/adjust.net/

719
shield/manager.go Normal file
View File

@@ -0,0 +1,719 @@
package shield
import (
"bufio"
"context"
"fmt"
"io/ioutil"
"net/http"
"os"
"regexp"
"strings"
"sync"
"time"
"dns-server/config"
"dns-server/logger"
)
// regexRule 正则规则结构,包含编译后的表达式和原始字符串
type regexRule struct {
pattern *regexp.Regexp
original string
}
// ShieldManager 屏蔽管理器
type ShieldManager struct {
config *config.ShieldConfig
domainRules map[string]bool
domainExceptions map[string]bool
regexRules []regexRule
regexExceptions []regexRule
hostsMap map[string]string
rulesMutex sync.RWMutex
updateCtx context.Context
updateCancel context.CancelFunc
updateRunning bool
}
// NewShieldManager 创建屏蔽管理器实例
func NewShieldManager(config *config.ShieldConfig) *ShieldManager {
ctx, cancel := context.WithCancel(context.Background())
return &ShieldManager{
config: config,
domainRules: make(map[string]bool),
domainExceptions: make(map[string]bool),
regexRules: []regexRule{},
regexExceptions: []regexRule{},
hostsMap: make(map[string]string),
updateCtx: ctx,
updateCancel: cancel,
}
}
// LoadRules 加载屏蔽规则
func (m *ShieldManager) LoadRules() error {
m.rulesMutex.Lock()
defer m.rulesMutex.Unlock()
// 清空现有规则
m.domainRules = make(map[string]bool)
m.domainExceptions = make(map[string]bool)
m.regexRules = []regexRule{}
m.regexExceptions = []regexRule{}
m.hostsMap = make(map[string]string)
// 加载本地规则文件
if err := m.loadLocalRules(); err != nil {
logger.Error("加载本地规则失败", "error", err)
// 继续执行,不返回错误
}
// 加载远程规则
if err := m.loadRemoteRules(); err != nil {
logger.Error("加载远程规则失败", "error", err)
// 继续执行,不返回错误
}
// 加载hosts文件
if err := m.loadHosts(); err != nil {
logger.Error("加载hosts文件失败", "error", err)
// 继续执行,不返回错误
}
logger.Info(fmt.Sprintf("规则加载完成,域名规则: %d, 排除规则: %d, 正则规则: %d, hosts规则: %d",
len(m.domainRules), len(m.domainExceptions), len(m.regexRules), len(m.hostsMap)))
return nil
}
// loadLocalRules 加载本地规则文件
func (m *ShieldManager) loadLocalRules() error {
if m.config.LocalRulesFile == "" {
return nil
}
file, err := os.Open(m.config.LocalRulesFile)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
m.parseRule(line)
}
return scanner.Err()
}
// loadRemoteRules 加载远程规则
func (m *ShieldManager) loadRemoteRules() error {
for _, url := range m.config.RemoteRules {
if err := m.fetchRemoteRules(url); err != nil {
logger.Error("获取远程规则失败", "url", url, "error", err)
continue
}
}
return nil
}
// fetchRemoteRules 从远程URL获取规则
func (m *ShieldManager) fetchRemoteRules(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("远程服务器返回错误状态码: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
lines := strings.Split(string(body), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
m.parseRule(line)
}
return nil
}
// loadHosts 加载hosts文件
func (m *ShieldManager) loadHosts() error {
if m.config.HostsFile == "" {
return nil
}
file, err := os.Open(m.config.HostsFile)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.Fields(line)
if len(parts) >= 2 {
ip := parts[0]
for i := 1; i < len(parts); i++ {
m.hostsMap[parts[i]] = ip
}
}
}
return scanner.Err()
}
// parseRule 解析规则行
func (m *ShieldManager) parseRule(line string) {
// 处理注释
if strings.HasPrefix(line, "!") || strings.HasPrefix(line, "#") || line == "" {
return
}
// 移除规则选项部分(暂时不处理规则选项)
if strings.Contains(line, "$") {
parts := strings.SplitN(line, "$", 2)
line = parts[0]
// 规则选项暂时不处理
}
// 处理排除规则 (@@前缀表示取消屏蔽)
isException := false
if strings.HasPrefix(line, "@@") {
isException = true
line = strings.TrimPrefix(line, "@@")
}
// 处理不同类型的规则
switch {
case strings.HasPrefix(line, "||") && strings.HasSuffix(line, "^"):
// AdGuardHome域名规则格式: ||example.com^
domain := strings.TrimSuffix(strings.TrimPrefix(line, "||"), "^")
m.addDomainRule(domain, !isException)
case strings.HasPrefix(line, "||"):
// 精确域名匹配规则
domain := strings.TrimPrefix(line, "||")
m.addDomainRule(domain, !isException)
case strings.HasPrefix(line, "*"):
// 通配符规则,转换为正则表达式
pattern := strings.ReplaceAll(line, "*", ".*")
pattern = "^" + pattern + "$"
if re, err := regexp.Compile(pattern); err == nil {
// 保存原始规则字符串
m.addRegexRule(re, line, !isException)
}
case strings.HasPrefix(line, "/") && strings.HasSuffix(line, "/"):
// 正则表达式规则
pattern := strings.TrimPrefix(strings.TrimSuffix(line, "/"), "/")
if re, err := regexp.Compile(pattern); err == nil {
// 保存原始规则字符串
m.addRegexRule(re, line, !isException)
}
case strings.HasPrefix(line, "|") && strings.HasSuffix(line, "|"):
// 完整URL匹配规则
urlPattern := strings.TrimPrefix(strings.TrimSuffix(line, "|"), "|")
// 将URL模式转换为正则表达式
pattern := "^" + regexp.QuoteMeta(urlPattern) + "$"
if re, err := regexp.Compile(pattern); err == nil {
m.addRegexRule(re, line, !isException)
}
case strings.HasPrefix(line, "|"):
// URL开头匹配规则
urlPattern := strings.TrimPrefix(line, "|")
pattern := "^" + regexp.QuoteMeta(urlPattern)
if re, err := regexp.Compile(pattern); err == nil {
m.addRegexRule(re, line, !isException)
}
case strings.HasSuffix(line, "|"):
// URL结尾匹配规则
urlPattern := strings.TrimSuffix(line, "|")
pattern := regexp.QuoteMeta(urlPattern) + "$"
if re, err := regexp.Compile(pattern); err == nil {
m.addRegexRule(re, line, !isException)
}
default:
// 默认作为普通域名规则
m.addDomainRule(line, !isException)
}
}
// parseRuleOptions 解析规则选项
func (m *ShieldManager) parseRuleOptions(optionsStr string) map[string]string {
options := make(map[string]string)
parts := strings.Split(optionsStr, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
if strings.Contains(part, "=") {
kv := strings.SplitN(part, "=", 2)
options[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
} else {
options[part] = ""
}
}
return options
}
// addDomainRule 添加域名规则,支持是否为阻止规则
func (m *ShieldManager) addDomainRule(domain string, block bool) {
if block {
m.domainRules[domain] = true
// 添加所有子域名的匹配支持
parts := strings.Split(domain, ".")
if len(parts) > 1 {
// 为二级域名和顶级域名添加规则
for i := 0; i < len(parts)-1; i++ {
subdomain := strings.Join(parts[i:], ".")
m.domainRules[subdomain] = true
}
}
} else {
// 添加到排除规则
m.domainExceptions[domain] = true
// 为子域名也添加排除规则
parts := strings.Split(domain, ".")
if len(parts) > 1 {
for i := 0; i < len(parts)-1; i++ {
subdomain := strings.Join(parts[i:], ".")
m.domainExceptions[subdomain] = true
}
}
}
}
// addRegexRule 添加正则表达式规则,支持是否为阻止规则
func (m *ShieldManager) addRegexRule(re *regexp.Regexp, original string, block bool) {
rule := regexRule{
pattern: re,
original: original,
}
if block {
m.regexRules = append(m.regexRules, rule)
} else {
// 添加到排除规则
m.regexExceptions = append(m.regexExceptions, rule)
}
}
// IsBlocked 检查域名是否被屏蔽
func (m *ShieldManager) IsBlocked(domain string) bool {
m.rulesMutex.RLock()
defer m.rulesMutex.RUnlock()
// 预处理域名,去除可能的端口号
if strings.Contains(domain, ":") {
parts := strings.Split(domain, ":")
domain = parts[0]
}
// 首先检查排除规则(优先级最高)
// 检查域名排除规则
if m.domainExceptions[domain] {
return false
}
// 检查子域名排除规则
parts := strings.Split(domain, ".")
for i := 0; i < len(parts)-1; i++ {
subdomain := strings.Join(parts[i:], ".")
if m.domainExceptions[subdomain] {
return false
}
}
// 检查正则表达式排除规则
for _, re := range m.regexExceptions {
if re.pattern.MatchString(domain) {
return false
}
}
// 然后检查阻止规则
// 检查精确域名匹配
if m.domainRules[domain] {
return true
}
// 检查子域名匹配AdGuardHome风格
// 从最长的子域名开始匹配,确保优先级正确
for i := 0; i < len(parts)-1; i++ {
subdomain := strings.Join(parts[i:], ".")
if m.domainRules[subdomain] {
return true
}
}
// 检查正则表达式匹配
for _, re := range m.regexRules {
if re.pattern.MatchString(domain) {
return true
}
}
return false
}
// GetHostsIP 获取hosts文件中的IP映射
func (m *ShieldManager) GetHostsIP(domain string) (string, bool) {
m.rulesMutex.RLock()
defer m.rulesMutex.RUnlock()
ip, exists := m.hostsMap[domain]
return ip, exists
}
// AddRule 添加屏蔽规则
func (m *ShieldManager) AddRule(rule string) error {
m.rulesMutex.Lock()
defer m.rulesMutex.Unlock()
// 解析并添加规则到内存
m.parseRule(rule)
// 持久化保存规则到文件
if m.config.LocalRulesFile != "" {
if err := m.saveRulesToFile(); err != nil {
logger.Error("保存规则到文件失败", "error", err)
return err
}
}
return nil
}
// RemoveRule 删除屏蔽规则
func (m *ShieldManager) RemoveRule(rule string) error {
m.rulesMutex.Lock()
defer m.rulesMutex.Unlock()
removed := false
// 清理规则,移除可能的修饰符
cleanRule := rule
// 移除规则结束符
cleanRule = strings.TrimSuffix(cleanRule, "^")
// 尝试多种可能的规则格式
formatsToTry := []string{cleanRule}
// 根据规则类型添加可能的格式变体
if strings.HasPrefix(cleanRule, "@@||") {
// 已有的排除规则格式,也尝试去掉前缀
formatsToTry = append(formatsToTry, strings.TrimPrefix(cleanRule, "@@||"))
} else if strings.HasPrefix(cleanRule, "||") {
// 已有的域名规则格式,也尝试去掉前缀
formatsToTry = append(formatsToTry, strings.TrimPrefix(cleanRule, "||"))
} else {
// 可能是裸域名,尝试添加前缀
formatsToTry = append(formatsToTry, "||"+cleanRule, "@@||"+cleanRule)
}
// 尝试所有可能的格式变体来删除规则
for _, format := range formatsToTry {
if removed {
break
}
// 尝试删除排除规则
if strings.HasPrefix(format, "@@||") {
domain := strings.TrimPrefix(format, "@@||")
if _, exists := m.domainExceptions[domain]; exists {
delete(m.domainExceptions, domain)
removed = true
break
}
} else if strings.HasPrefix(format, "||") {
// 尝试删除域名规则
domain := strings.TrimPrefix(format, "||")
if _, exists := m.domainRules[domain]; exists {
delete(m.domainRules, domain)
removed = true
break
}
} else {
// 尝试直接作为域名删除
if _, exists := m.domainRules[format]; exists {
delete(m.domainRules, format)
removed = true
break
}
if _, exists := m.domainExceptions[format]; exists {
delete(m.domainExceptions, format)
removed = true
break
}
}
}
// 处理正则表达式规则
if !removed && strings.HasPrefix(cleanRule, "/") && strings.HasSuffix(cleanRule, "/") {
pattern := strings.TrimPrefix(strings.TrimSuffix(cleanRule, "/"), "/")
// 检查是否在正则表达式规则中
newRegexRules := []regexRule{}
for _, re := range m.regexRules {
if re.pattern.String() != pattern {
newRegexRules = append(newRegexRules, re)
} else {
removed = true
}
}
m.regexRules = newRegexRules
// 如果没有从正则规则中找到,检查是否在正则排除规则中
if !removed {
newRegexExceptions := []regexRule{}
for _, re := range m.regexExceptions {
if re.pattern.String() != pattern {
newRegexExceptions = append(newRegexExceptions, re)
} else {
removed = true
}
}
m.regexExceptions = newRegexExceptions
}
}
// 处理通配符和URL匹配规则
if !removed && (strings.HasPrefix(cleanRule, "*") || strings.HasSuffix(cleanRule, "*") || strings.HasPrefix(cleanRule, "|") || strings.HasSuffix(cleanRule, "|")) {
// 遍历所有规则,找到匹配的规则进行删除
for domain := range m.domainRules {
if domain == cleanRule || domain == rule {
delete(m.domainRules, domain)
removed = true
break
}
}
if !removed {
for domain := range m.domainExceptions {
if domain == cleanRule || domain == rule {
delete(m.domainExceptions, domain)
removed = true
break
}
}
}
}
// 如果有规则被删除,持久化保存更改
if removed && m.config.LocalRulesFile != "" {
if err := m.saveRulesToFile(); err != nil {
logger.Error("保存规则到文件失败", "error", err)
return err
}
}
return nil
}
// StartAutoUpdate 启动自动更新
func (m *ShieldManager) StartAutoUpdate() {
if m.updateRunning {
return
}
m.updateRunning = true
go func() {
ticker := time.NewTicker(time.Duration(m.config.UpdateInterval) * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
logger.Info("开始自动更新规则")
if err := m.LoadRules(); err != nil {
logger.Error("自动更新规则失败", "error", err)
} else {
logger.Info("自动更新规则成功")
}
case <-m.updateCtx.Done():
m.updateRunning = false
return
}
}
}()
}
// StopAutoUpdate 停止自动更新
func (m *ShieldManager) StopAutoUpdate() {
m.updateCancel()
}
// saveRulesToFile 保存规则到文件
func (m *ShieldManager) saveRulesToFile() error {
var rules []string
// 添加域名规则
for domain := range m.domainRules {
rules = append(rules, "||"+domain)
}
// 添加正则表达式规则
for _, re := range m.regexRules {
rules = append(rules, re.original)
}
// 添加排除规则
for domain := range m.domainExceptions {
rules = append(rules, "@@||"+domain)
}
// 添加正则表达式排除规则
for _, re := range m.regexExceptions {
rules = append(rules, re.original)
}
// 写入文件
content := strings.Join(rules, "\n")
return ioutil.WriteFile(m.config.LocalRulesFile, []byte(content), 0644)
}
// AddHostsEntry 添加hosts条目
func (m *ShieldManager) AddHostsEntry(ip, domain string) error {
m.rulesMutex.Lock()
defer m.rulesMutex.Unlock()
m.hostsMap[domain] = ip
// 持久化保存到hosts文件
if m.config.HostsFile != "" {
if err := m.saveHostsToFile(); err != nil {
logger.Error("保存hosts到文件失败", "error", err)
return err
}
}
return nil
}
// RemoveHostsEntry 删除hosts条目
func (m *ShieldManager) RemoveHostsEntry(domain string) error {
m.rulesMutex.Lock()
defer m.rulesMutex.Unlock()
if _, exists := m.hostsMap[domain]; exists {
delete(m.hostsMap, domain)
// 持久化保存到hosts文件
if m.config.HostsFile != "" {
if err := m.saveHostsToFile(); err != nil {
logger.Error("保存hosts到文件失败", "error", err)
return err
}
}
}
return nil
}
// saveHostsToFile 保存hosts到文件
func (m *ShieldManager) saveHostsToFile() error {
var lines []string
// 添加hosts头部注释
lines = append(lines, "# DNS Server Hosts File")
lines = append(lines, "# Generated by DNS Server")
lines = append(lines, "")
// 添加localhost条目如果不存在
if _, exists := m.hostsMap["localhost"]; !exists {
lines = append(lines, "127.0.0.1 localhost")
lines = append(lines, "::1 localhost")
lines = append(lines, "")
}
// 添加所有hosts条目
for domain, ip := range m.hostsMap {
lines = append(lines, ip+"\t"+domain)
}
// 写入文件
content := strings.Join(lines, "\n")
return ioutil.WriteFile(m.config.HostsFile, []byte(content), 0644)
}
// GetStats 获取规则统计信息
func (m *ShieldManager) GetStats() map[string]interface{} {
m.rulesMutex.RLock()
defer m.rulesMutex.RUnlock()
return map[string]interface{}{
"domainRules": len(m.domainRules),
"domainExceptions": len(m.domainExceptions),
"regexRules": len(m.regexRules),
"regexExceptions": len(m.regexExceptions),
"hostsRules": len(m.hostsMap),
"updateInterval": m.config.UpdateInterval,
}
}
// GetRules 获取所有规则的详细列表
func (m *ShieldManager) GetRules() map[string]interface{} {
m.rulesMutex.RLock()
defer m.rulesMutex.RUnlock()
// 转换map和slice为字符串列表
domainRulesList := make([]string, 0, len(m.domainRules))
for domain := range m.domainRules {
domainRulesList = append(domainRulesList, "||"+domain+"^")
}
domainExceptionsList := make([]string, 0, len(m.domainExceptions))
for domain := range m.domainExceptions {
domainExceptionsList = append(domainExceptionsList, "@@||"+domain+"^")
}
// 获取正则规则原始字符串
regexRulesList := make([]string, 0, len(m.regexRules))
for _, re := range m.regexRules {
regexRulesList = append(regexRulesList, re.original)
}
// 获取正则排除规则原始字符串
regexExceptionsList := make([]string, 0, len(m.regexExceptions))
for _, re := range m.regexExceptions {
regexExceptionsList = append(regexExceptionsList, re.original)
}
// 获取hosts规则
hostsRulesList := make([]string, 0, len(m.hostsMap))
for domain, ip := range m.hostsMap {
hostsRulesList = append(hostsRulesList, ip+"\t"+domain)
}
return map[string]interface{}{
"domainRules": domainRulesList,
"domainExceptions": domainExceptionsList,
"regexRules": regexRulesList,
"regexExceptions": regexExceptionsList,
"hostsRules": hostsRulesList,
}
}

1727
static/index.html Normal file

File diff suppressed because it is too large Load Diff