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)
+
+
+
+
-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