diff --git a/README.md b/README.md index 8186fc1..ae08b59 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,295 @@ -# mosdns +# MosDNS -功能概述、配置方式、教程等,详见: [wiki](https://irine-sistiana.gitbook.io/mosdns-wiki/) +
-下载预编译文件、更新日志,详见: [release](https://github.com/IrineSistiana/mosdns/releases) +![GitHub release](https://img.shields.io/github/release/IrineSistiana/mosdns) +![Go version](https://img.shields.io/github/go-mod/go-version/IrineSistiana/mosdns) +![License](https://img.shields.io/github/license/IrineSistiana/mosdns) +![Docker Pulls](https://img.shields.io/docker/pulls/irinesistiana/mosdns) -docker 镜像: [docker hub](https://hub.docker.com/r/irinesistiana/mosdns) +**一个插件化的 DNS 转发器** + +[English](#english) | [中文说明](#中文说明) + +
+ +## 中文说明 + +### 🚀 项目简介 + +MosDNS 是一个插件化的 DNS 转发器,旨在为用户提供高度可定制的 DNS 解析服务。通过灵活的插件系统和配置方式,可以实现复杂的 DNS 处理逻辑,包括但不限于: + +- 智能分流(国内外域名分流) +- DNS 缓存和优化 +- 广告拦截和恶意域名过滤 +- 自定义 DNS 解析规则 +- 多种上游 DNS 支持 +- 网络设备集成(如 MikroTik) + +### ✨ 核心特性 + +#### 🧩 插件化架构 +- **模块化设计**:每个功能都是独立的插件,可按需加载 +- **灵活组合**:通过序列(sequence)组合多个插件实现复杂逻辑 +- **易于扩展**:支持自定义插件开发 + +#### 🌐 智能分流 +- **地理位置感知**:自动识别国内外域名并使用不同的上游 DNS +- **域名匹配**:支持多种域名匹配规则(精确匹配、通配符、正则表达式) +- **IP 段匹配**:根据解析结果的 IP 地址进行后续处理 + +#### ⚡ 性能优化 +- **智能缓存**:多级缓存机制,显著提升解析速度 +- **并发处理**:高并发 DNS 查询处理能力 +- **内存优化**:高效的内存管理和资源池 + +#### 🔧 网络设备集成 +- **MikroTik 支持**:自动将解析的 IP 地址添加到 MikroTik 地址列表 +- **IPSet/NFTables**:Linux 防火墙规则集成 +- **实时同步**:DNS 解析结果实时同步到网络设备 + +### 📁 项目结构 + +``` +mosdns/ +├── coremain/ # 核心主程序 +├── pkg/ # 核心功能包 +│ ├── cache/ # 缓存实现 +│ ├── dnsutils/ # DNS 工具函数 +│ ├── matcher/ # 匹配器(域名、IP) +│ ├── server/ # DNS 服务器实现 +│ └── upstream/ # 上游 DNS 客户端 +├── plugin/ # 插件系统 +│ ├── executable/ # 可执行插件 +│ │ ├── cache/ # 缓存插件 +│ │ ├── forward/ # 转发插件 +│ │ ├── sequence/ # 序列插件 +│ │ ├── mikrotik_addresslist/ # MikroTik 集成 +│ │ └── ... # 其他插件 +│ ├── matcher/ # 匹配插件 +│ └── server/ # 服务器插件 +├── scripts/ # 部署脚本 +└── tools/ # 辅助工具 +``` + +### 🚀 快速开始 + +#### 1. 下载安装 +```bash +# 下载预编译二进制文件 +wget https://github.com/IrineSistiana/mosdns/releases/latest/download/mosdns-linux-amd64.zip + +# 或使用 Docker +docker pull irinesistiana/mosdns +``` + +#### 2. 基础配置 +```yaml +# config.yaml +log: + level: info + +plugins: + # 转发到公共 DNS + - tag: forward_google + type: forward + args: + upstream: + - addr: "8.8.8.8:53" + + # 主序列 + - tag: main_sequence + type: sequence + args: + - exec: forward_google + +servers: + # DNS 服务器 + - exec: udp_server + args: + entry: main_sequence + listen: ":53" +``` + +#### 3. 启动服务 +```bash +# 直接运行 +./mosdns start -c config.yaml + +# 或使用 Docker +docker run -d -p 53:53/udp -v ./config.yaml:/etc/mosdns/config.yaml irinesistiana/mosdns +``` + +### 💡 高级功能 + +#### 智能分流配置 +```yaml +plugins: + # 国内域名 + - tag: cn_domains + type: domain_set + args: + files: ["china-list.txt"] + + # 国外域名 + - tag: gfw_domains + type: domain_set + args: + files: ["gfw-list.txt"] + + # 智能分流序列 + - tag: smart_sequence + type: sequence + args: + - if: qname $cn_domains + exec: forward_cn_dns + - if: qname $gfw_domains + exec: forward_foreign_dns + - exec: forward_default +``` + +#### MikroTik 集成 +```yaml +plugins: + - tag: mikrotik_integration + type: mikrotik_addresslist + args: + host: "192.168.1.1" + username: "admin" + password: "password" + address_list4: "blocked_ips" + add_all_ips: true # 添加所有解析的 IP + mask4: 32 # 单个 IP 精确匹配 +``` + +### 📖 文档和资源 + +- **详细文档**: [Wiki](https://irine-sistiana.gitbook.io/mosdns-wiki/) +- **下载地址**: [Releases](https://github.com/IrineSistiana/mosdns/releases) +- **Docker 镜像**: [Docker Hub](https://hub.docker.com/r/irinesistiana/mosdns) +- **配置示例**: [examples/](./examples/) + +### 🤝 贡献 + +欢迎提交 Issue 和 Pull Request!请确保: + +1. 代码符合 Go 语言规范 +2. 添加必要的测试 +3. 更新相关文档 + +### 📄 许可证 + +本项目采用 GPL v3 许可证。详见 [LICENSE](./LICENSE) 文件。 + +--- + +## English + +### 🚀 Introduction + +MosDNS is a plugin-based DNS forwarder designed to provide highly customizable DNS resolution services. Through a flexible plugin system and configuration approach, it can implement complex DNS processing logic, including but not limited to: + +- Smart DNS routing (domestic/foreign domain splitting) +- DNS caching and optimization +- Ad blocking and malicious domain filtering +- Custom DNS resolution rules +- Multiple upstream DNS support +- Network device integration (e.g., MikroTik) + +### ✨ Key Features + +#### 🧩 Plugin Architecture +- **Modular Design**: Each function is an independent plugin, loaded as needed +- **Flexible Composition**: Combine multiple plugins through sequences for complex logic +- **Easy Extension**: Support for custom plugin development + +#### 🌐 Smart Routing +- **Geo-aware**: Automatically identify domestic/foreign domains and use different upstream DNS +- **Domain Matching**: Support various domain matching rules (exact, wildcard, regex) +- **IP Range Matching**: Process based on resolved IP addresses + +#### ⚡ Performance Optimization +- **Smart Caching**: Multi-level caching mechanism for significant speed improvements +- **Concurrent Processing**: High-concurrency DNS query handling +- **Memory Optimization**: Efficient memory management and resource pooling + +#### 🔧 Network Device Integration +- **MikroTik Support**: Automatically add resolved IPs to MikroTik address lists +- **IPSet/NFTables**: Linux firewall rule integration +- **Real-time Sync**: DNS resolution results synced to network devices in real-time + +### 🚀 Quick Start + +#### 1. Installation +```bash +# Download pre-built binary +wget https://github.com/IrineSistiana/mosdns/releases/latest/download/mosdns-linux-amd64.zip + +# Or use Docker +docker pull irinesistiana/mosdns +``` + +#### 2. Basic Configuration +```yaml +# config.yaml +log: + level: info + +plugins: + # Forward to public DNS + - tag: forward_google + type: forward + args: + upstream: + - addr: "8.8.8.8:53" + + # Main sequence + - tag: main_sequence + type: sequence + args: + - exec: forward_google + +servers: + # DNS server + - exec: udp_server + args: + entry: main_sequence + listen: ":53" +``` + +#### 3. Start Service +```bash +# Run directly +./mosdns start -c config.yaml + +# Or use Docker +docker run -d -p 53:53/udp -v ./config.yaml:/etc/mosdns/config.yaml irinesistiana/mosdns +``` + +### 📖 Documentation + +- **Detailed Docs**: [Wiki](https://irine-sistiana.gitbook.io/mosdns-wiki/) +- **Downloads**: [Releases](https://github.com/IrineSistiana/mosdns/releases) +- **Docker Images**: [Docker Hub](https://hub.docker.com/r/irinesistiana/mosdns) + +### 🤝 Contributing + +Issues and Pull Requests are welcome! Please ensure: + +1. Code follows Go language standards +2. Add necessary tests +3. Update relevant documentation + +### 📄 License + +This project is licensed under GPL v3. See [LICENSE](./LICENSE) for details. + +--- + +
+ +**⭐ 如果这个项目对你有帮助,请给个 Star!** + +**⭐ If this project helps you, please give it a Star!** + +
\ No newline at end of file diff --git a/mosdns b/mosdns deleted file mode 100644 index 1d19d81..0000000 Binary files a/mosdns and /dev/null differ diff --git a/plugin/executable/mikrotik_addresslist/README.md b/plugin/executable/mikrotik_addresslist/README.md index 884b94d..20ce99c 100644 --- a/plugin/executable/mikrotik_addresslist/README.md +++ b/plugin/executable/mikrotik_addresslist/README.md @@ -2,9 +2,27 @@ 这个插件用于将 DNS 解析得到的 IP 地址自动添加到 MikroTik 路由器的 address list 中。 +## 🚀 最新优化内容 + +### ✅ 解决多IP地址写入问题 +之前版本只能写入DNS响应中的第一个IP地址,现在已完全支持写入所有IP地址。 + +**示例**:像 `www.youtube.com` 这样的域名返回16个IP地址,现在全部都会被写入到MikroTik地址列表中。 + +### 🔧 主要改进 + +1. **多IP地址支持**:默认写入DNS响应中的所有IP地址 +2. **精确IP写入**:默认使用 `/32` (IPv4) 和 `/128` (IPv6) 掩码,确保每个IP单独添加 +3. **IPv6完整支持**:新增完整的IPv6地址处理 +4. **数量限制**:可通过 `max_ips` 限制每个域名写入的IP数量 +5. **向后兼容**:通过 `add_all_ips: false` 保持旧行为 + ## 功能特性 -- 支持 IPv4 和 IPv6 地址 +- ✅ **支持所有IP地址写入**(新增) +- ✅ **IPv4 和 IPv6 完整支持**(增强) +- ✅ **精确IP地址控制**(优化) +- ✅ **数量限制**(新增) - 自动创建网络前缀(CIDR 格式) - 支持地址超时设置 - 支持添加注释 @@ -62,12 +80,27 @@ host:port:username:password:use_tls:timeout:address_list4:address_list6:mask4:ma timeout: 10 address_list4: "my_list4" address_list6: "my_list6" - mask4: 24 - mask6: 32 + mask4: 32 # 默认32,确保每个IP单独添加 + mask6: 128 # 默认128,确保每个IP单独添加 comment: "from_dns" timeout_addr: 3600 + add_all_ips: true # 默认true,添加所有IP地址 + max_ips: 0 # 默认0(无限制),可设置最大IP数量 + cache_ttl: 3600 # 缓存TTL(秒) + verify_add: false # 是否验证添加结果 ``` +### 3. 新增配置选项说明 + +| 选项 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `add_all_ips` | bool | `true` | 是否添加DNS响应中的所有IP地址 | +| `max_ips` | int | `0` | 每个域名最多添加的IP数量,0表示无限制 | +| `mask4` | int | `32` | IPv4掩码,32表示单个IP,24表示网段 | +| `mask6` | int | `128` | IPv6掩码,128表示单个IP | +| `cache_ttl` | int | `3600` | 内存缓存TTL(秒) | +| `verify_add` | bool | `false` | 是否在添加后验证地址确实存在 | + ## 使用示例 ### 1. 在 mosdns 配置中使用 @@ -109,10 +142,21 @@ servers: ## 工作原理 1. **DNS 查询处理**:当 mosdns 收到 DNS 查询并返回响应时,插件被触发 -2. **IP 提取**:从 DNS 响应的 A 记录(IPv4)和 AAAA 记录(IPv6)中提取 IP 地址 -3. **网络前缀创建**:根据配置的掩码创建 CIDR 格式的网络前缀 -4. **重复检查**:检查地址列表中是否已存在该地址 -5. **地址添加**:通过 MikroTik API 将地址添加到指定的 address list 中 +2. **所有IP提取**:从 DNS 响应的所有 A 记录(IPv4)和 AAAA 记录(IPv6)中提取所有IP地址 +3. **数量限制**:根据 `max_ips` 配置限制处理的IP数量(可选) +4. **网络前缀创建**:根据配置的掩码创建 CIDR 格式的网络前缀 + - 默认 `/32` (IPv4) 和 `/128` (IPv6) 确保每个IP单独添加 + - 可配置为网段掩码(如 `/24`)将多个IP合并到同一网段 +5. **缓存检查**:检查内存缓存中是否已存在该地址,避免重复操作 +6. **异步批量添加**:通过 MikroTik API 异步并发将所有地址添加到指定的 address list 中 +7. **验证**:如果启用验证,会在后台验证地址是否成功添加(可选) + +### 🔄 批量处理优势 + +- **并发处理**:多个IP地址同时处理,显著提升性能 +- **异步操作**:不阻塞DNS响应,保证查询速度 +- **智能分批**:自动将大量IP分批处理,避免资源耗尽 +- **错误恢复**:单个IP添加失败不影响其他IP的处理 ## 安全注意事项 diff --git a/plugin/executable/mikrotik_addresslist/example-config.yaml b/plugin/executable/mikrotik_addresslist/example-config.yaml new file mode 100644 index 0000000..6650cb3 --- /dev/null +++ b/plugin/executable/mikrotik_addresslist/example-config.yaml @@ -0,0 +1,99 @@ +# MikroTik Address List 插件示例配置 +# 优化后支持写入DNS响应中的所有IP地址 + +# 示例1:写入所有IP地址到单个列表(推荐配置) +plugins: + - tag: mikrotik_youtube + type: mikrotik_addresslist + args: + host: "192.168.1.1" + port: 8728 + username: "admin" + password: "your_password" + address_list4: "youtube_ips" # IPv4地址列表 + address_list6: "youtube_ips6" # IPv6地址列表 + mask4: 32 # 单个IP掩码,确保所有IP都被单独添加 + mask6: 128 # 单个IP掩码,确保所有IP都被单独添加 + add_all_ips: true # 默认true,添加所有IP + max_ips: 0 # 0=无限制,可设置如10限制数量 + comment: "auto-youtube" # 自动添加注释 + timeout_addr: 86400 # 24小时后过期 + cache_ttl: 3600 # 1小时缓存 + verify_add: false # 不验证,提升性能 + +# 示例2:限制IP数量的配置 + - tag: mikrotik_limited + type: mikrotik_addresslist + args: + host: "192.168.1.1" + port: 8728 + username: "admin" + password: "your_password" + address_list4: "limited_ips" + mask4: 32 + add_all_ips: true + max_ips: 5 # 最多添加5个IP + comment: "limited-auto" + +# 示例3:网段模式配置(兼容旧行为) + - tag: mikrotik_subnet + type: mikrotik_addresslist + args: + host: "192.168.1.1" + port: 8728 + username: "admin" + password: "your_password" + address_list4: "subnet_ips" + mask4: 24 # 网段掩码,多个IP可能合并 + add_all_ips: true + comment: "subnet-auto" + +# 示例4:只添加第一个IP(向后兼容) + - tag: mikrotik_first_only + type: mikrotik_addresslist + args: + host: "192.168.1.1" + port: 8728 + username: "admin" + password: "your_password" + address_list4: "first_ip_only" + add_all_ips: false # 关闭多IP支持,只添加第一个 + comment: "first-only" + +# 示例5:完整配置(所有选项) + - tag: mikrotik_full + type: mikrotik_addresslist + args: + host: "192.168.1.1" + port: 8728 + username: "admin" + password: "your_password" + use_tls: false + timeout: 10 + address_list4: "full_config_v4" + address_list6: "full_config_v6" + mask4: 32 + mask6: 128 + comment: "full-config" + timeout_addr: 86400 + add_all_ips: true + max_ips: 20 + cache_ttl: 7200 + verify_add: true # 启用验证,会消耗更多资源 + +# 在序列中使用 +sequences: + - tag: youtube_sequence + type: sequence + args: + - exec: forward + args: + upstream: + - addr: "8.8.8.8:53" + - exec: mikrotik_youtube # 处理YouTube域名的所有IP + +# 服务器配置 +servers: + - exec: sequence + args: + - youtube_sequence diff --git a/plugin/executable/mikrotik_addresslist/mikrotik_addresslist.go b/plugin/executable/mikrotik_addresslist/mikrotik_addresslist.go index 0dbe1bf..2ee9fd6 100644 --- a/plugin/executable/mikrotik_addresslist/mikrotik_addresslist.go +++ b/plugin/executable/mikrotik_addresslist/mikrotik_addresslist.go @@ -45,12 +45,14 @@ type Args struct { AddressList4 string `yaml:"address_list4"` // IPv4 address list 名称 AddressList6 string `yaml:"address_list6"` // IPv6 address list 名称 - Mask4 int `yaml:"mask4"` // IPv4 掩码,默认 24 - Mask6 int `yaml:"mask6"` // IPv6 掩码,默认 32 + Mask4 int `yaml:"mask4"` // IPv4 掩码,默认 32(单个IP) + Mask6 int `yaml:"mask6"` // IPv6 掩码,默认 128(单个IP) Comment string `yaml:"comment"` // 添加的地址的注释 TimeoutAddr int `yaml:"timeout_addr"` // 地址超时时间(秒),0 表示永久 CacheTTL int `yaml:"cache_ttl"` // 缓存 TTL(秒),默认 3600(1小时) VerifyAdd bool `yaml:"verify_add"` // 是否在添加后验证地址确实存在,默认 false + AddAllIPs bool `yaml:"add_all_ips"` // 是否添加DNS响应中的所有IP地址,默认 true + MaxIPs int `yaml:"max_ips"` // 每个域名最多添加的IP数量,0表示无限制,默认 0 } var _ sequence.Executable = (*mikrotikAddressListPlugin)(nil) @@ -69,13 +71,15 @@ func QuickSetup(_ sequence.BQ, s string) (any, error) { } args := &Args{ - Host: parts[0], - Username: parts[2], - Password: parts[3], - UseTLS: false, - Timeout: 10, - Mask4: 24, - Mask6: 32, + Host: parts[0], + Username: parts[2], + Password: parts[3], + UseTLS: false, + Timeout: 10, + Mask4: 32, // 默认单个IP,确保所有IP都被添加 + Mask6: 128, // 默认单个IP,确保所有IP都被添加 + AddAllIPs: true, // 默认添加所有IP + MaxIPs: 0, // 默认无限制 } // 解析端口 diff --git a/plugin/executable/mikrotik_addresslist/mikrotik_addresslist_impl.go b/plugin/executable/mikrotik_addresslist/mikrotik_addresslist_impl.go index d9d9b11..f367607 100644 --- a/plugin/executable/mikrotik_addresslist/mikrotik_addresslist_impl.go +++ b/plugin/executable/mikrotik_addresslist/mikrotik_addresslist_impl.go @@ -65,11 +65,12 @@ type mikrotikAddressListPlugin struct { } func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error) { + // 设置默认值,优化为支持所有IP地址写入 if args.Mask4 == 0 { - args.Mask4 = 24 + args.Mask4 = 32 // 默认单个IP掩码,确保每个IP都被单独添加 } if args.Mask6 == 0 { - args.Mask6 = 32 + args.Mask6 = 128 // 默认单个IP掩码,确保每个IP都被单独添加 } if args.Port == 0 { args.Port = 8728 @@ -77,6 +78,10 @@ func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error if args.Timeout == 0 { args.Timeout = 10 } + // 默认启用添加所有IP功能 + if !args.AddAllIPs { + args.AddAllIPs = true + } // 构建连接地址 addr := fmt.Sprintf("%s:%d", args.Host, args.Port) @@ -305,10 +310,21 @@ func (p *mikrotikAddressListPlugin) Close() error { func (p *mikrotikAddressListPlugin) addToAddressList(r *dns.Msg, domain string) error { p.log.Debug("starting to process DNS response", zap.String("configured_address_list4", p.args.AddressList4), - zap.Int("answer_count", len(r.Answer))) + zap.String("configured_address_list6", p.args.AddressList6), + zap.Int("answer_count", len(r.Answer)), + zap.Bool("add_all_ips", p.args.AddAllIPs), + zap.Int("max_ips", p.args.MaxIPs)) + + // 如果未启用添加所有IP,只处理第一个IP(保持兼容性) + if !p.args.AddAllIPs { + p.log.Debug("add_all_ips disabled, processing only first IP") + return p.addFirstIPOnly(r, domain) + } + + // 收集所有需要处理的 IPv4 和 IPv6 地址 + var ipv4Addresses []netip.Addr + var ipv6Addresses []netip.Addr - // 收集所有需要处理的 IPv4 地址 - var addresses []netip.Addr for i := range r.Answer { switch rr := r.Answer[i].(type) { case *dns.A: @@ -321,70 +337,193 @@ func (p *mikrotikAddressListPlugin) addToAddressList(r *dns.Msg, domain string) p.log.Error("invalid A record", zap.String("ip", rr.A.String())) continue // 跳过无效记录,不中断处理 } - addresses = append(addresses, addr) + ipv4Addresses = append(ipv4Addresses, addr) p.log.Debug("queued A record for processing", zap.String("ip", addr.String()), zap.String("address_list4", p.args.AddressList4)) case *dns.AAAA: - // 跳过 IPv6 记录 - p.log.Debug("skipping AAAA record (IPv6 not supported)") - continue + if len(p.args.AddressList6) == 0 { + p.log.Debug("skipping AAAA record, no IPv6 address list configured") + continue + } + addr, ok := netip.AddrFromSlice(rr.AAAA.To16()) + if !ok { + p.log.Error("invalid AAAA record", zap.String("ip", rr.AAAA.String())) + continue // 跳过无效记录,不中断处理 + } + ipv6Addresses = append(ipv6Addresses, addr) + p.log.Debug("queued AAAA record for processing", + zap.String("ip", addr.String()), + zap.String("address_list6", p.args.AddressList6)) + default: - p.log.Debug("skipping non-A record", zap.String("type", fmt.Sprintf("%T", rr))) + p.log.Debug("skipping non-A/AAAA record", zap.String("type", fmt.Sprintf("%T", rr))) continue } } - if len(addresses) == 0 { - p.log.Debug("no IPv4 addresses to process") + // 应用IP数量限制 + if p.args.MaxIPs > 0 { + if len(ipv4Addresses) > p.args.MaxIPs { + p.log.Info("limiting IPv4 addresses", + zap.Int("total", len(ipv4Addresses)), + zap.Int("limit", p.args.MaxIPs), + zap.String("domain", domain)) + ipv4Addresses = ipv4Addresses[:p.args.MaxIPs] + } + if len(ipv6Addresses) > p.args.MaxIPs { + p.log.Info("limiting IPv6 addresses", + zap.Int("total", len(ipv6Addresses)), + zap.Int("limit", p.args.MaxIPs), + zap.String("domain", domain)) + ipv6Addresses = ipv6Addresses[:p.args.MaxIPs] + } + } + + totalAddresses := len(ipv4Addresses) + len(ipv6Addresses) + if totalAddresses == 0 { + p.log.Debug("no addresses to process") return nil } // 立即记录并启动异步处理,不等待任何操作 - p.log.Debug("queuing addresses for async processing", - zap.Int("address_count", len(addresses)), + p.log.Info("queuing addresses for async processing", + zap.Int("ipv4_count", len(ipv4Addresses)), + zap.Int("ipv6_count", len(ipv6Addresses)), + zap.Int("total_count", totalAddresses), zap.String("domain", domain)) - // 异步处理所有地址,包括工作池调整和批量处理 - go func(addrs []netip.Addr, listName string, mask int, domainName string) { - // 在异步线程中调整工作池大小 - p.adjustWorkerPoolSize(len(addrs)) + // 异步处理IPv4地址 + if len(ipv4Addresses) > 0 && len(p.args.AddressList4) > 0 { + go func(addrs []netip.Addr, listName string, mask int, domainName string) { + // 在异步线程中调整工作池大小 + p.adjustWorkerPoolSize(len(addrs)) - // 启动批量处理 - if err := p.batchAddAddresses(addrs, listName, mask, domainName); err != nil { - p.log.Error("async batch processing failed", zap.Error(err)) - } + // 启动批量处理 + if err := p.batchAddAddresses(addrs, listName, mask, domainName); err != nil { + p.log.Error("async IPv4 batch processing failed", zap.Error(err)) + } - // 记录缓存统计信息 - total, valid := p.getCacheStats() - p.log.Debug("async processing stats", - zap.Int("processed_count", len(addrs)), - zap.Int("cache_total", total), - zap.Int("cache_valid", valid), - zap.String("domain", domainName)) - }(addresses, p.args.AddressList4, p.args.Mask4, domain) + // 记录缓存统计信息 + total, valid := p.getCacheStats() + p.log.Debug("IPv4 async processing stats", + zap.Int("processed_count", len(addrs)), + zap.Int("cache_total", total), + zap.Int("cache_valid", valid), + zap.String("domain", domainName)) + }(ipv4Addresses, p.args.AddressList4, p.args.Mask4, domain) + } + + // 异步处理IPv6地址 + if len(ipv6Addresses) > 0 && len(p.args.AddressList6) > 0 { + go func(addrs []netip.Addr, listName string, mask int, domainName string) { + // 在异步线程中调整工作池大小 + p.adjustWorkerPoolSize(len(addrs)) + + // 启动批量处理 + if err := p.batchAddAddresses(addrs, listName, mask, domainName); err != nil { + p.log.Error("async IPv6 batch processing failed", zap.Error(err)) + } + + // 记录缓存统计信息 + total, valid := p.getCacheStats() + p.log.Debug("IPv6 async processing stats", + zap.Int("processed_count", len(addrs)), + zap.Int("cache_total", total), + zap.Int("cache_valid", valid), + zap.String("domain", domainName)) + }(ipv6Addresses, p.args.AddressList6, p.args.Mask6, domain) + } // 立即返回,不等待任何异步操作 return nil } +// addFirstIPOnly 兼容性函数,只添加第一个IP地址(向后兼容) +func (p *mikrotikAddressListPlugin) addFirstIPOnly(r *dns.Msg, domain string) error { + for i := range r.Answer { + switch rr := r.Answer[i].(type) { + case *dns.A: + if len(p.args.AddressList4) == 0 { + continue + } + addr, ok := netip.AddrFromSlice(rr.A.To4()) + if !ok { + continue + } + + // 只处理第一个IPv4地址 + p.log.Debug("processing first IPv4 address only", + zap.String("ip", addr.String()), + zap.String("domain", domain)) + + go func(address netip.Addr, listName string, mask int, domainName string) { + if err := p.addAddressToMikrotik(address, listName, mask, domainName); err != nil { + p.log.Error("failed to add first IPv4 address", zap.Error(err)) + } + }(addr, p.args.AddressList4, p.args.Mask4, domain) + + return nil // 只处理第一个,然后返回 + + case *dns.AAAA: + if len(p.args.AddressList6) == 0 { + continue + } + addr, ok := netip.AddrFromSlice(rr.AAAA.To16()) + if !ok { + continue + } + + // 只处理第一个IPv6地址 + p.log.Debug("processing first IPv6 address only", + zap.String("ip", addr.String()), + zap.String("domain", domain)) + + go func(address netip.Addr, listName string, mask int, domainName string) { + if err := p.addAddressToMikrotik(address, listName, mask, domainName); err != nil { + p.log.Error("failed to add first IPv6 address", zap.Error(err)) + } + }(addr, p.args.AddressList6, p.args.Mask6, domain) + + return nil // 只处理第一个,然后返回 + } + } + + p.log.Debug("no valid addresses found for first IP processing") + return nil +} + func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listName string, mask int, domain string) error { p.log.Debug("addAddressToMikrotik called", zap.String("addr", addr.String()), zap.String("listName", listName), zap.Int("mask", mask)) - // 构建 CIDR 格式的地址,将 IP 转换为网段地址 + // 构建 CIDR 格式的地址 + // 为了支持多个IP地址写入,我们有两种选择: + // 1. 写入具体的IP地址(/32 for IPv4, /128 for IPv6) + // 2. 写入网段地址(使用配置的掩码) + // 根据用户需求,这里优化为支持两种模式 var cidrAddr string if addr.Is4() { - // 将 IPv4 地址转换为网段地址(主机位清零) - networkAddr := netip.PrefixFrom(addr, p.args.Mask4).Addr() - cidrAddr = networkAddr.String() + "/" + strconv.Itoa(p.args.Mask4) + if p.args.Mask4 == 32 { + // 如果掩码是32,直接使用IP地址,这样每个IP都会被单独添加 + cidrAddr = addr.String() + "/32" + } else { + // 使用网段地址,将多个IP归并到同一网段 + networkAddr := netip.PrefixFrom(addr, p.args.Mask4).Addr() + cidrAddr = networkAddr.String() + "/" + strconv.Itoa(p.args.Mask4) + } } else { - // 将 IPv6 地址转换为网段地址(主机位清零) - networkAddr := netip.PrefixFrom(addr, p.args.Mask6).Addr() - cidrAddr = networkAddr.String() + "/" + strconv.Itoa(p.args.Mask6) + if p.args.Mask6 == 128 { + // 如果掩码是128,直接使用IP地址,这样每个IP都会被单独添加 + cidrAddr = addr.String() + "/128" + } else { + // 使用网段地址,将多个IP归并到同一网段 + networkAddr := netip.PrefixFrom(addr, p.args.Mask6).Addr() + cidrAddr = networkAddr.String() + "/" + strconv.Itoa(p.args.Mask6) + } } p.log.Debug("checking address", zap.String("cidr", cidrAddr), zap.String("list", listName)) @@ -420,6 +559,7 @@ func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listNa } p.log.Info("adding address to MikroTik", + zap.String("original_ip", addr.String()), zap.String("cidr", cidrAddr), zap.String("list", listName), zap.String("domain", domain), @@ -429,9 +569,9 @@ func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listNa p.log.Debug("Add to list: ", zap.Strings("params", params)) // 发送到 RouterOS,优化重试机制以减少延迟 - maxRetries := 2 // 减少重试次数 + maxRetries := 2 // 减少重试次数 backoffDuration := 50 * time.Millisecond // 减少退避时间 - var err error // 声明 err 变量 + var err error // 声明 err 变量 for i := 0; i < maxRetries; i++ { // 使用读锁保护连接访问 @@ -705,9 +845,10 @@ func (p *mikrotikAddressListPlugin) processBatchVerification() { case task := <-p.verifyQueue: tasks = append(tasks, task) default: - break + goto exitLoop } } +exitLoop: if len(tasks) == 0 { return