删除 mosdns 二进制文件,并增强 README 文件,添加详细的项目概述和使用说明。为 MikroTik 地址列表插件引入新的示例配置,支持多个 IP 地址条目,并改进了默认设置,以提高可用性。
Some checks failed
Test mosdns / build (push) Has been cancelled

This commit is contained in:
dengxiongjian 2025-08-22 09:14:12 +08:00
parent c9c49f0827
commit 3f31f7f44c
6 changed files with 636 additions and 60 deletions

296
README.md
View File

@ -1,7 +1,295 @@
# mosdns
# MosDNS
功能概述、配置方式、教程等,详见: [wiki](https://irine-sistiana.gitbook.io/mosdns-wiki/)
<div align="center">
下载预编译文件、更新日志,详见: [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) | [中文说明](#中文说明)
</div>
## 中文说明
### 🚀 项目简介
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.
---
<div align="center">
**⭐ 如果这个项目对你有帮助,请给个 Star**
**⭐ If this project helps you, please give it a Star!**
</div>

BIN
mosdns

Binary file not shown.

View File

@ -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表示单个IP24表示网段 |
| `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的处理
## 安全注意事项

View File

@ -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

View File

@ -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默认 36001小时
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)
@ -74,8 +76,10 @@ func QuickSetup(_ sequence.BQ, s string) (any, error) {
Password: parts[3],
UseTLS: false,
Timeout: 10,
Mask4: 24,
Mask6: 32,
Mask4: 32, // 默认单个IP确保所有IP都被添加
Mask6: 128, // 默认单个IP确保所有IP都被添加
AddAllIPs: true, // 默认添加所有IP
MaxIPs: 0, // 默认无限制
}
// 解析端口

View File

@ -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,71 +337,194 @@ 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)")
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))
// 异步处理所有地址,包括工作池调整和批量处理
// 异步处理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))
p.log.Error("async IPv4 batch processing failed", zap.Error(err))
}
// 记录缓存统计信息
total, valid := p.getCacheStats()
p.log.Debug("async processing stats",
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))
}(addresses, p.args.AddressList4, p.args.Mask4, domain)
}(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 地址转换为网段地址(主机位清零)
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 地址转换为网段地址(主机位清零)
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),
@ -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