package gfw import ( "encoding/base64" "fmt" "os" "regexp" "strings" "sync" "dns-server/config" "dns-server/logger" ) // regexRule 正则规则结构,包含编译后的表达式和原始字符串 type regexRule struct { pattern *regexp.Regexp original string } // GFWListManager GFWList管理器 type GFWListManager struct { config *config.GFWListConfig domainRules map[string]bool regexRules []regexRule rulesMutex sync.RWMutex } // NewGFWListManager 创建GFWList管理器实例 func NewGFWListManager(config *config.GFWListConfig) *GFWListManager { return &GFWListManager{ config: config, domainRules: make(map[string]bool), regexRules: []regexRule{}, } } // LoadRules 加载GFWList规则 func (m *GFWListManager) LoadRules() error { // 如果GFWList功能未启用,不加载规则 if !m.config.Enabled { return nil } m.rulesMutex.Lock() defer m.rulesMutex.Unlock() // 清空现有规则 m.domainRules = make(map[string]bool) m.regexRules = []regexRule{} if m.config.Content == "" { return nil // 没有GFWList内容,直接返回 } // 从文件路径读取GFWList内容 fileContent, err := os.ReadFile(m.config.Content) if err != nil { return fmt.Errorf("读取GFWList文件失败: %w", err) } rawContent := string(fileContent) var ruleContent string // 过滤注释行,收集可能的Base64内容 var base64Content strings.Builder lines := strings.Split(rawContent, "\n") for _, line := range lines { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "!") || strings.HasPrefix(line, "[") { // 跳过注释行和头信息行 continue } base64Content.WriteString(line) } // 尝试Base64解码 decoded, err := base64.StdEncoding.DecodeString(base64Content.String()) if err == nil { // 解码成功,使用解码后的内容 ruleContent = string(decoded) logger.Info("GFWList文件为Base64编码,已成功解码") } else { // 解码失败,使用原始内容(可能是纯文本格式) ruleContent = rawContent logger.Info("GFWList文件为纯文本格式,直接解析") } // 按行解析规则内容 ruleLines := strings.Split(ruleContent, "\n") for _, line := range ruleLines { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "!") || strings.HasPrefix(line, "[") { // 跳过空行、注释行和头信息行 continue } m.parseRule(line) } logger.Info(fmt.Sprintf("GFWList规则加载完成,域名规则: %d, 正则规则: %d", len(m.domainRules), len(m.regexRules))) return nil } // parseRule 解析规则行 func (m *GFWListManager) parseRule(line string) { // 保存原始规则用于后续使用 originalLine := line // 处理注释 if strings.HasPrefix(line, "!") || strings.HasPrefix(line, "#") || line == "" { return } // 移除规则选项部分(暂时不处理规则选项) if strings.Contains(line, "$") { parts := strings.SplitN(line, "$", 2) line = parts[0] // 规则选项暂时不处理 } // 处理不同类型的规则 switch { case strings.HasPrefix(line, "||") && strings.HasSuffix(line, "^"): // AdGuardHome域名规则格式: ||example.com^ domain := strings.TrimSuffix(strings.TrimPrefix(line, "||"), "^") m.addDomainRule(domain, originalLine) case strings.HasPrefix(line, "||"): // 域名片段匹配规则: ||google 匹配任何包含google的域名 domain := strings.TrimPrefix(line, "||") // 添加精确域名匹配 m.addDomainRule(domain, originalLine) // 同时添加正则表达式规则,匹配任何包含该域名片段的域名 if re, err := regexp.Compile("(?i).*" + regexp.QuoteMeta(domain) + ".*"); err == nil { m.addRegexRule(re, originalLine) } case strings.HasPrefix(line, "*"): // 通配符规则,转换为正则表达式 pattern := strings.ReplaceAll(line, "*", ".*") pattern = "^" + pattern + "$" if re, err := regexp.Compile(pattern); err == nil { // 保存原始规则字符串 m.addRegexRule(re, originalLine) } case strings.HasPrefix(line, "/") && strings.HasSuffix(line, "/"): // 正则表达式匹配规则:/regex/ 格式,不区分大小写 pattern := strings.TrimPrefix(strings.TrimSuffix(line, "/"), "/") // 编译为不区分大小写的正则表达式,确保能匹配域名中任意位置 // 对于像 /domain/ 这样的规则,应该匹配包含 domain 字符串的任何域名 if re, err := regexp.Compile("(?i).*" + regexp.QuoteMeta(pattern) + ".*"); err == nil { // 保存原始规则字符串 m.addRegexRule(re, originalLine) } 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, originalLine) } case strings.HasPrefix(line, "|"): // URL开头匹配规则 urlPattern := strings.TrimPrefix(line, "|") pattern := "^" + regexp.QuoteMeta(urlPattern) if re, err := regexp.Compile(pattern); err == nil { m.addRegexRule(re, originalLine) } case strings.HasSuffix(line, "|"): // URL结尾匹配规则 urlPattern := strings.TrimSuffix(line, "|") pattern := regexp.QuoteMeta(urlPattern) + "$" if re, err := regexp.Compile(pattern); err == nil { m.addRegexRule(re, originalLine) } default: // 默认作为普通域名规则 m.addDomainRule(line, originalLine) } } // addDomainRule 添加域名规则 func (m *GFWListManager) addDomainRule(domain string, original string) { m.domainRules[domain] = true } // addRegexRule 添加正则表达式规则 func (m *GFWListManager) addRegexRule(re *regexp.Regexp, original string) { rule := regexRule{ pattern: re, original: original, } m.regexRules = append(m.regexRules, rule) } // IsMatch 检查域名是否匹配GFWList规则 func (m *GFWListManager) IsMatch(domain string) bool { m.rulesMutex.RLock() defer m.rulesMutex.RUnlock() // 预处理域名,去除可能的端口号 if strings.Contains(domain, ":") { parts := strings.Split(domain, ":") domain = parts[0] } // 检查精确域名匹配 if m.domainRules[domain] { return true } // 检查子域名匹配 parts := strings.Split(domain, ".") 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 } // GetGFWListIP 获取GFWList的目标IP地址 func (m *GFWListManager) GetGFWListIP() string { return m.config.IP }