mosdns/coremain/config_builder.go
dengxiongjian 0413ee5d44
Some checks failed
Test mosdns / build (push) Has been cancelled
二次开发
2025-10-16 21:07:48 +08:00

429 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package coremain
import (
"fmt"
"os"
"path/filepath"
"strings"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
)
// DomainRule 域名规则配置
type DomainRule struct {
Name string `json:"name"` // 规则名称
Description string `json:"description"` // 规则描述
DomainFile string `json:"domain_file"` // 域名文件路径
DNSStrategy string `json:"dns_strategy"` // DNS策略china-dns/overseas-dns/smart-fallback
EnableMikroTik bool `json:"enable_mikrotik"` // 是否启用MikroTik推送
MikroTikConfig MikroTikConfig `json:"mikrotik_config"` // MikroTik配置
Enabled bool `json:"enabled"` // 是否启用
}
// MikroTikConfig MikroTik路由器配置
type MikroTikConfig struct {
Host string `json:"host"` // 路由器地址
Port int `json:"port"` // API端口
Username string `json:"username"` // 用户名
Password string `json:"password"` // 密码
AddressList string `json:"address_list"` // 地址列表名称
Mask int `json:"mask"` // IP掩码
MaxIPs int `json:"max_ips"` // 最大IP数
CacheTTL int `json:"cache_ttl"` // 缓存时间(秒)
TimeoutAddr int `json:"timeout_addr"` // 地址超时(秒)
Comment string `json:"comment"` // 备注
}
// ConfigBuilder 高级配置构建器
type ConfigBuilder struct {
baseConfig *Config
logger *zap.Logger
rulesDir string
}
// NewConfigBuilder 创建配置构建器
func NewConfigBuilder(baseConfig *Config, logger *zap.Logger) *ConfigBuilder {
return &ConfigBuilder{
baseConfig: baseConfig,
logger: logger,
rulesDir: "config.d/rules",
}
}
// AddDomainRule 添加域名规则
func (b *ConfigBuilder) AddDomainRule(rule DomainRule) error {
if rule.Name == "" {
return fmt.Errorf("规则名称不能为空")
}
if rule.DomainFile == "" {
return fmt.Errorf("域名文件路径不能为空")
}
if rule.DNSStrategy == "" {
rule.DNSStrategy = "smart-fallback" // 默认使用智能防污染
}
// 生成规则配置文件
ruleConfig := b.generateRuleConfig(rule)
ruleFileName := rule.Name + ".yaml"
ruleFilePath := filepath.Join(b.rulesDir, ruleFileName)
// 保存规则文件
if err := b.saveRuleConfig(ruleFilePath, ruleConfig); err != nil {
return fmt.Errorf("保存规则文件失败: %w", err)
}
// 将规则添加到主配置的include中
b.addRuleToIncludes(ruleFileName)
b.logger.Info("域名规则添加成功",
zap.String("rule_name", rule.Name),
zap.String("file_path", ruleFilePath))
return nil
}
// UpdateDomainRule 更新域名规则
func (b *ConfigBuilder) UpdateDomainRule(ruleName string, rule DomainRule) error {
ruleFileName := ruleName + ".yaml"
ruleFilePath := filepath.Join(b.rulesDir, ruleFileName)
// 检查规则文件是否存在
if _, err := os.Stat(ruleFilePath); os.IsNotExist(err) {
return fmt.Errorf("规则文件不存在: %s", ruleFilePath)
}
// 生成新的规则配置
ruleConfig := b.generateRuleConfig(rule)
// 保存更新后的规则文件
if err := b.saveRuleConfig(ruleFilePath, ruleConfig); err != nil {
return fmt.Errorf("更新规则文件失败: %w", err)
}
b.logger.Info("域名规则更新成功",
zap.String("rule_name", ruleName),
zap.String("file_path", ruleFilePath))
return nil
}
// DeleteDomainRule 删除域名规则
func (b *ConfigBuilder) DeleteDomainRule(ruleName string) error {
ruleFileName := ruleName + ".yaml"
ruleFilePath := filepath.Join(b.rulesDir, ruleFileName)
// 检查规则文件是否存在
if _, err := os.Stat(ruleFilePath); os.IsNotExist(err) {
return fmt.Errorf("规则文件不存在: %s", ruleFilePath)
}
// 删除规则文件
if err := os.Remove(ruleFilePath); err != nil {
return fmt.Errorf("删除规则文件失败: %w", err)
}
// 从主配置的include中移除
b.removeRuleFromIncludes(ruleFileName)
b.logger.Info("域名规则删除成功",
zap.String("rule_name", ruleName),
zap.String("file_path", ruleFilePath))
return nil
}
// generateRuleConfig 生成规则配置文件内容
func (b *ConfigBuilder) generateRuleConfig(rule DomainRule) *Config {
config := &Config{
Plugins: []PluginConfig{},
}
// 1. 创建域名集合插件
domainSetTag := "domains_" + rule.Name
domainSetPlugin := PluginConfig{
Tag: domainSetTag,
Type: "domain_set",
Args: map[string]interface{}{
"files": []string{rule.DomainFile},
},
}
config.Plugins = append(config.Plugins, domainSetPlugin)
// 2. 创建规则执行插件
ruleTag := "rule_" + rule.Name
var ruleExec []map[string]interface{}
if rule.Enabled {
ruleExec = []map[string]interface{}{
{
"matches": "qname $" + domainSetTag,
"exec": "$" + rule.DNSStrategy,
},
}
}
rulePlugin := PluginConfig{
Tag: ruleTag,
Type: "sequence",
Args: map[string]interface{}{
"exec": ruleExec,
},
}
config.Plugins = append(config.Plugins, rulePlugin)
// 3. 如果启用MikroTik创建MikroTik推送插件
if rule.EnableMikroTik {
mikrotikTag := "mikrotik_" + rule.Name
mikrotikPlugin := PluginConfig{
Tag: mikrotikTag,
Type: "mikrotik_addresslist",
Args: map[string]interface{}{
"host": rule.MikroTikConfig.Host,
"port": rule.MikroTikConfig.Port,
"username": rule.MikroTikConfig.Username,
"password": rule.MikroTikConfig.Password,
"address_list": b.getMikroTikAddressList(rule),
"mask": rule.MikroTikConfig.Mask,
"max_ips": rule.MikroTikConfig.MaxIPs,
"cache_ttl": rule.MikroTikConfig.CacheTTL,
"timeout_addr": rule.MikroTikConfig.TimeoutAddr,
"comment": b.getMikroTikComment(rule),
},
}
config.Plugins = append(config.Plugins, mikrotikPlugin)
// 在规则执行后添加MikroTik推送
rulePlugin.Args = map[string]interface{}{
"exec": append(ruleExec, map[string]interface{}{
"matches": "has_resp",
"exec": "$" + mikrotikTag,
}),
}
}
return config
}
// getMikroTikAddressList 获取MikroTik地址列表名称
func (b *ConfigBuilder) getMikroTikAddressList(rule DomainRule) string {
if rule.MikroTikConfig.AddressList != "" {
return rule.MikroTikConfig.AddressList
}
return rule.Name // 默认使用规则名称
}
// getMikroTikComment 获取MikroTik备注
func (b *ConfigBuilder) getMikroTikComment(rule DomainRule) string {
if rule.MikroTikConfig.Comment != "" {
return rule.MikroTikConfig.Comment
}
return fmt.Sprintf("%s-AutoAdd", rule.Name) // 默认备注
}
// saveRuleConfig 保存规则配置到文件
func (b *ConfigBuilder) saveRuleConfig(filePath string, config *Config) error {
// 确保目录存在
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("创建目录失败: %w", err)
}
// 编码配置为YAML
var buf strings.Builder
encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2) // 设置缩进为2个空格
if err := encoder.Encode(config); err != nil {
return fmt.Errorf("编码YAML失败: %w", err)
}
// 写入文件
if err := os.WriteFile(filePath, []byte(buf.String()), 0644); err != nil {
return fmt.Errorf("写入文件失败: %w", err)
}
return nil
}
// addRuleToIncludes 将规则添加到主配置的include中
func (b *ConfigBuilder) addRuleToIncludes(ruleFileName string) {
rulePath := filepath.Join(b.rulesDir, ruleFileName)
// 检查是否已存在
for _, include := range b.baseConfig.Include {
if include == rulePath {
return // 已存在,无需添加
}
}
// 添加到include列表
b.baseConfig.Include = append(b.baseConfig.Include, rulePath)
}
// removeRuleFromIncludes 从主配置的include中移除规则
func (b *ConfigBuilder) removeRuleFromIncludes(ruleFileName string) {
rulePath := filepath.Join(b.rulesDir, ruleFileName)
// 找到并移除
for i, include := range b.baseConfig.Include {
if include == rulePath {
b.baseConfig.Include = append(b.baseConfig.Include[:i], b.baseConfig.Include[i+1:]...)
break
}
}
}
// ListRules 列出所有规则
func (b *ConfigBuilder) ListRules() ([]DomainRule, error) {
var rules []DomainRule
// 确保规则目录存在
if _, err := os.Stat(b.rulesDir); os.IsNotExist(err) {
return rules, nil // 目录不存在,返回空列表
}
// 遍历规则文件
entries, err := os.ReadDir(b.rulesDir)
if err != nil {
return nil, fmt.Errorf("读取规则目录失败: %w", err)
}
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yaml") {
continue
}
ruleName := strings.TrimSuffix(entry.Name(), ".yaml")
rule, err := b.GetRule(ruleName)
if err != nil {
b.logger.Warn("加载规则失败",
zap.String("rule_name", ruleName),
zap.Error(err))
continue
}
rules = append(rules, rule)
}
return rules, nil
}
// GetRule 获取指定规则
func (b *ConfigBuilder) GetRule(ruleName string) (DomainRule, error) {
ruleFileName := ruleName + ".yaml"
ruleFilePath := filepath.Join(b.rulesDir, ruleFileName)
// 检查规则文件是否存在
if _, err := os.Stat(ruleFilePath); os.IsNotExist(err) {
return DomainRule{}, fmt.Errorf("规则文件不存在: %s", ruleFilePath)
}
// 读取并解析规则文件
data, err := os.ReadFile(ruleFilePath)
if err != nil {
return DomainRule{}, fmt.Errorf("读取规则文件失败: %w", err)
}
var config Config
if err := yaml.Unmarshal(data, &config); err != nil {
return DomainRule{}, fmt.Errorf("解析规则文件失败: %w", err)
}
// 从配置中提取规则信息
rule := b.parseRuleFromConfig(ruleName, &config)
return rule, nil
}
// parseRuleFromConfig 从配置中解析规则信息
func (b *ConfigBuilder) parseRuleFromConfig(ruleName string, config *Config) DomainRule {
rule := DomainRule{
Name: ruleName,
Enabled: true, // 默认启用
}
for _, plugin := range config.Plugins {
switch plugin.Type {
case "domain_set":
if args, ok := plugin.Args.(map[string]interface{}); ok {
if files, ok := args["files"].([]interface{}); ok && len(files) > 0 {
if file, ok := files[0].(string); ok {
rule.DomainFile = file
}
}
}
case "sequence":
if plugin.Tag == "rule_"+ruleName {
if args, ok := plugin.Args.(map[string]interface{}); ok {
if exec, ok := args["exec"].([]interface{}); ok && len(exec) > 0 {
if execMap, ok := exec[0].(map[string]interface{}); ok {
if exec, ok := execMap["exec"].(string); ok {
rule.DNSStrategy = strings.TrimPrefix(exec, "$")
}
}
}
}
}
case "mikrotik_addresslist":
rule.EnableMikroTik = true
if args, ok := plugin.Args.(map[string]interface{}); ok {
rule.MikroTikConfig = MikroTikConfig{
Host: getStringValue(args, "host"),
Port: getIntValue(args, "port"),
Username: getStringValue(args, "username"),
Password: getStringValue(args, "password"),
AddressList: getStringValue(args, "address_list"),
Mask: getIntValue(args, "mask"),
MaxIPs: getIntValue(args, "max_ips"),
CacheTTL: getIntValue(args, "cache_ttl"),
TimeoutAddr: getIntValue(args, "timeout_addr"),
Comment: getStringValue(args, "comment"),
}
}
}
}
return rule
}
// 辅助函数:获取字符串值
func getStringValue(args map[string]interface{}, key string) string {
if val, ok := args[key].(string); ok {
return val
}
return ""
}
// 辅助函数:获取整数值
func getIntValue(args map[string]interface{}, key string) int {
if val, ok := args[key].(int); ok {
return val
}
return 0
}
// Save 保存主配置
func (b *ConfigBuilder) Save() error {
// 主配置文件的路径
configPath := "config.yaml"
// 编码配置为YAML
var buf strings.Builder
encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2)
if err := encoder.Encode(b.baseConfig); err != nil {
return fmt.Errorf("编码主配置失败: %w", err)
}
// 写入主配置文件
if err := os.WriteFile(configPath, []byte(buf.String()), 0644); err != nil {
return fmt.Errorf("写入主配置失败: %w", err)
}
b.logger.Info("主配置保存成功", zap.String("file", configPath))
return nil
}