新建DNS服务器
This commit is contained in:
27
config.json
Normal file
27
config.json
Normal 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
86
config/config.go
Normal 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
BIN
dns-server
Executable file
Binary file not shown.
133
dns-server.log
Normal file
133
dns-server.log
Normal 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
396
dns/server.go
Normal 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
21
go.mod
Normal 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
28
go.sum
Normal 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
6
hosts.txt
Normal 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
444
http/server.go
Normal 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
1574
index.html
Normal file
File diff suppressed because it is too large
Load Diff
134
logger/logger.go
Normal file
134
logger/logger.go
Normal 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
74
main.go
Normal 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("所有服务已关闭")
|
||||||
|
}
|
||||||
719
shield/manager.go
Normal file
719
shield/manager.go
Normal 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
1727
static/index.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user