1. 连接管理优化 mikrotik_addresslist_impl.go:132
- 添加连接状态管理和重连锁机制
- 改进重连逻辑,防止并发重连
2. 缓存机制增强 mikrotik_addresslist_impl.go:162-202
- 优化缓存锁使用,避免死锁
- 添加缓存大小限制和LRU驱逐策略
- 定期清理过期缓存项
3. 智能重试机制 mikrotik_addresslist_impl.go:420
- 指数退避算法
- 更智能的连接错误识别
- 改进的错误处理
4. 动态并发控制 mikrotik_addresslist_impl.go:589
- 根据地址数量动态调整工作池大小
- 批量处理优化
5. 性能监控改进
- 更详细的日志记录
- 缓存统计信息
- 处理过程可观察性
This commit is contained in:
parent
eb82f1c2f5
commit
59a5ef4aae
3
dns.yaml
3
dns.yaml
@ -54,4 +54,5 @@ plugins:
|
|||||||
address_list4: "gfw" # 改为 gfw,插件会自动创建这个地址列表
|
address_list4: "gfw" # 改为 gfw,插件会自动创建这个地址列表
|
||||||
mask4: 24
|
mask4: 24
|
||||||
comment: "amazon_domain"
|
comment: "amazon_domain"
|
||||||
timeout_addr: 86400
|
timeout_addr: 86400
|
||||||
|
cache_ttl: 86400
|
||||||
@ -1,104 +0,0 @@
|
|||||||
# MikroTik Address List 插件配置示例
|
|
||||||
|
|
||||||
# 插件定义
|
|
||||||
plugins:
|
|
||||||
# 转发插件 - 向上游 DNS 服务器查询
|
|
||||||
- tag: forward_google
|
|
||||||
type: forward
|
|
||||||
args:
|
|
||||||
upstream:
|
|
||||||
- addr: "8.8.8.8:53"
|
|
||||||
- addr: "8.8.4.4:53"
|
|
||||||
|
|
||||||
# MikroTik Address List 插件 - 将解析的 IP 添加到 MikroTik
|
|
||||||
- tag: mikrotik_blocklist
|
|
||||||
type: mikrotik_addresslist
|
|
||||||
args: "192.168.1.1:8728:admin:password:false:10:blocked_ips:blocked_ips6:24:32:blocked_domain:86400"
|
|
||||||
|
|
||||||
# 序列插件 - 组合多个插件
|
|
||||||
- tag: sequence_with_blocklist
|
|
||||||
type: sequence
|
|
||||||
args:
|
|
||||||
- exec: forward_google
|
|
||||||
- exec: mikrotik_blocklist
|
|
||||||
|
|
||||||
# 服务器配置
|
|
||||||
servers:
|
|
||||||
# UDP 服务器
|
|
||||||
- exec: sequence_with_blocklist
|
|
||||||
args:
|
|
||||||
- sequence_with_blocklist
|
|
||||||
listeners:
|
|
||||||
- protocol: udp
|
|
||||||
addr: ":53"
|
|
||||||
|
|
||||||
# TCP 服务器
|
|
||||||
- exec: sequence_with_blocklist
|
|
||||||
args:
|
|
||||||
- sequence_with_blocklist
|
|
||||||
listeners:
|
|
||||||
- protocol: tcp
|
|
||||||
addr: ":53"
|
|
||||||
|
|
||||||
# 日志配置
|
|
||||||
log:
|
|
||||||
level: info
|
|
||||||
file: "mosdns.log"
|
|
||||||
|
|
||||||
# 其他配置示例
|
|
||||||
|
|
||||||
# 1. 使用 YAML 格式的详细配置
|
|
||||||
plugins:
|
|
||||||
- tag: mikrotik_detailed
|
|
||||||
type: mikrotik_addresslist
|
|
||||||
args:
|
|
||||||
host: "192.168.1.1"
|
|
||||||
port: 8728
|
|
||||||
username: "admin"
|
|
||||||
password: "password"
|
|
||||||
use_tls: false
|
|
||||||
timeout: 10
|
|
||||||
address_list4: "blocked_ips"
|
|
||||||
address_list6: "blocked_ips6"
|
|
||||||
mask4: 24
|
|
||||||
mask6: 32
|
|
||||||
comment: "blocked_domain"
|
|
||||||
timeout_addr: 86400
|
|
||||||
|
|
||||||
# 2. 多个 address list 配置
|
|
||||||
plugins:
|
|
||||||
# 恶意域名列表
|
|
||||||
- tag: mikrotik_malware
|
|
||||||
type: mikrotik_addresslist
|
|
||||||
args: "192.168.1.1:8728:admin:password:false:10:malware_ips:malware_ips6:24:32:malware:3600"
|
|
||||||
|
|
||||||
# 广告域名列表
|
|
||||||
- tag: mikrotik_ads
|
|
||||||
type: mikrotik_addresslist
|
|
||||||
args: "192.168.1.1:8728:admin:password:false:10:ads_ips:ads_ips6:24:32:ads:7200"
|
|
||||||
|
|
||||||
# 组合序列
|
|
||||||
- tag: sequence_all
|
|
||||||
type: sequence
|
|
||||||
args:
|
|
||||||
- exec: forward_google
|
|
||||||
- exec: mikrotik_malware
|
|
||||||
- exec: mikrotik_ads
|
|
||||||
|
|
||||||
# 3. 使用 TLS 的安全配置
|
|
||||||
plugins:
|
|
||||||
- tag: mikrotik_secure
|
|
||||||
type: mikrotik_addresslist
|
|
||||||
args: "192.168.1.1:8729:admin:password:true:15:secure_list:secure_list6:24:32:secure:1800"
|
|
||||||
|
|
||||||
# 4. 不同掩码配置
|
|
||||||
plugins:
|
|
||||||
# 精确 IP 匹配
|
|
||||||
- tag: mikrotik_exact
|
|
||||||
type: mikrotik_addresslist
|
|
||||||
args: "192.168.1.1:8728:admin:password:false:10:exact_ips:exact_ips6:32:128:exact:3600"
|
|
||||||
|
|
||||||
# 网段匹配
|
|
||||||
- tag: mikrotik_network
|
|
||||||
type: mikrotik_addresslist
|
|
||||||
args: "192.168.1.1:8728:admin:password:false:10:network_ips:network_ips6:16:48:network:7200"
|
|
||||||
@ -49,6 +49,7 @@ type Args struct {
|
|||||||
Mask6 int `yaml:"mask6"` // IPv6 掩码,默认 32
|
Mask6 int `yaml:"mask6"` // IPv6 掩码,默认 32
|
||||||
Comment string `yaml:"comment"` // 添加的地址的注释
|
Comment string `yaml:"comment"` // 添加的地址的注释
|
||||||
TimeoutAddr int `yaml:"timeout_addr"` // 地址超时时间(秒),0 表示永久
|
TimeoutAddr int `yaml:"timeout_addr"` // 地址超时时间(秒),0 表示永久
|
||||||
|
CacheTTL int `yaml:"cache_ttl"` // 缓存 TTL(秒),默认 3600(1小时)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ sequence.Executable = (*mikrotikAddressListPlugin)(nil)
|
var _ sequence.Executable = (*mikrotikAddressListPlugin)(nil)
|
||||||
@ -58,8 +59,8 @@ func Init(bp *coremain.BP, args any) (any, error) {
|
|||||||
return newMikrotikAddressListPlugin(args.(*Args))
|
return newMikrotikAddressListPlugin(args.(*Args))
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuickSetup format: host:port:username:password:use_tls:timeout:address_list4:address_list6:mask4:mask6:comment:timeout_addr
|
// QuickSetup format: host:port:username:password:use_tls:timeout:address_list4:address_list6:mask4:mask6:comment:timeout_addr:cache_ttl
|
||||||
// e.g. "192.168.1.1:8728:admin:password:false:10:my_list4:my_list6:24:32:from_dns:3600"
|
// e.g. "192.168.1.1:8728:admin:password:false:10:my_list4:my_list6:24:32:from_dns:3600:3600"
|
||||||
func QuickSetup(_ sequence.BQ, s string) (any, error) {
|
func QuickSetup(_ sequence.BQ, s string) (any, error) {
|
||||||
parts := strings.Split(s, ":")
|
parts := strings.Split(s, ":")
|
||||||
if len(parts) < 6 {
|
if len(parts) < 6 {
|
||||||
@ -129,5 +130,12 @@ func QuickSetup(_ sequence.BQ, s string) (any, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析缓存 TTL
|
||||||
|
if len(parts) > 12 {
|
||||||
|
if cacheTTL, err := strconv.Atoi(parts[12]); err == nil {
|
||||||
|
args.CacheTTL = cacheTTL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return newMikrotikAddressListPlugin(args)
|
return newMikrotikAddressListPlugin(args)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
|
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@ -43,6 +44,11 @@ type mikrotikAddressListPlugin struct {
|
|||||||
workerPool chan struct{}
|
workerPool chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
mu sync.RWMutex // 保护连接的重连操作
|
mu sync.RWMutex // 保护连接的重连操作
|
||||||
|
|
||||||
|
// 内存缓存
|
||||||
|
cache map[string]time.Time // key: "listName:cidrAddr", value: 添加时间
|
||||||
|
cacheMu sync.RWMutex // 保护缓存访问
|
||||||
|
cacheTTL time.Duration // 缓存 TTL,默认 1 小时
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error) {
|
func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error) {
|
||||||
@ -77,11 +83,19 @@ func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error
|
|||||||
// 设置工作池大小(可以根据需要调整)
|
// 设置工作池大小(可以根据需要调整)
|
||||||
workerCount := 10 // 并发工作线程数
|
workerCount := 10 // 并发工作线程数
|
||||||
|
|
||||||
|
// 设置缓存 TTL
|
||||||
|
cacheTTL := time.Hour // 默认 1 小时
|
||||||
|
if args.CacheTTL > 0 {
|
||||||
|
cacheTTL = time.Duration(args.CacheTTL) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
plugin := &mikrotikAddressListPlugin{
|
plugin := &mikrotikAddressListPlugin{
|
||||||
args: args,
|
args: args,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
log: zap.L().Named("mikrotik_addresslist"),
|
log: zap.L().Named("mikrotik_addresslist"),
|
||||||
workerPool: make(chan struct{}, workerCount),
|
workerPool: make(chan struct{}, workerCount),
|
||||||
|
cache: make(map[string]time.Time),
|
||||||
|
cacheTTL: cacheTTL,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录连接成功信息
|
// 记录连接成功信息
|
||||||
@ -90,7 +104,8 @@ func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error
|
|||||||
zap.Int("port", args.Port),
|
zap.Int("port", args.Port),
|
||||||
zap.String("username", args.Username),
|
zap.String("username", args.Username),
|
||||||
zap.String("address_list4", args.AddressList4),
|
zap.String("address_list4", args.AddressList4),
|
||||||
zap.Int("worker_count", workerCount))
|
zap.Int("worker_count", workerCount),
|
||||||
|
zap.Duration("cache_ttl", cacheTTL))
|
||||||
|
|
||||||
return plugin, nil
|
return plugin, nil
|
||||||
}
|
}
|
||||||
@ -138,6 +153,70 @@ func (p *mikrotikAddressListPlugin) reconnect() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成缓存键
|
||||||
|
func (p *mikrotikAddressListPlugin) cacheKey(listName, cidrAddr string) string {
|
||||||
|
return listName + ":" + cidrAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查缓存中是否存在
|
||||||
|
func (p *mikrotikAddressListPlugin) isInCache(listName, cidrAddr string) bool {
|
||||||
|
p.cacheMu.RLock()
|
||||||
|
defer p.cacheMu.RUnlock()
|
||||||
|
|
||||||
|
key := p.cacheKey(listName, cidrAddr)
|
||||||
|
if addTime, exists := p.cache[key]; exists {
|
||||||
|
// 检查是否过期
|
||||||
|
if time.Since(addTime) < p.cacheTTL {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 过期了,删除
|
||||||
|
p.cacheMu.RUnlock()
|
||||||
|
p.cacheMu.Lock()
|
||||||
|
delete(p.cache, key)
|
||||||
|
p.cacheMu.Unlock()
|
||||||
|
p.cacheMu.RLock()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到缓存
|
||||||
|
func (p *mikrotikAddressListPlugin) addToCache(listName, cidrAddr string) {
|
||||||
|
p.cacheMu.Lock()
|
||||||
|
defer p.cacheMu.Unlock()
|
||||||
|
|
||||||
|
key := p.cacheKey(listName, cidrAddr)
|
||||||
|
p.cache[key] = time.Now()
|
||||||
|
|
||||||
|
// 清理过期的缓存项
|
||||||
|
p.cleanupExpiredCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理过期的缓存项
|
||||||
|
func (p *mikrotikAddressListPlugin) cleanupExpiredCache() {
|
||||||
|
now := time.Now()
|
||||||
|
for key, addTime := range p.cache {
|
||||||
|
if now.Sub(addTime) >= p.cacheTTL {
|
||||||
|
delete(p.cache, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取缓存统计信息
|
||||||
|
func (p *mikrotikAddressListPlugin) getCacheStats() (int, int) {
|
||||||
|
p.cacheMu.RLock()
|
||||||
|
defer p.cacheMu.RUnlock()
|
||||||
|
|
||||||
|
total := len(p.cache)
|
||||||
|
valid := 0
|
||||||
|
now := time.Now()
|
||||||
|
for _, addTime := range p.cache {
|
||||||
|
if now.Sub(addTime) < p.cacheTTL {
|
||||||
|
valid++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total, valid
|
||||||
|
}
|
||||||
|
|
||||||
func (p *mikrotikAddressListPlugin) Exec(_ context.Context, qCtx *query_context.Context) error {
|
func (p *mikrotikAddressListPlugin) Exec(_ context.Context, qCtx *query_context.Context) error {
|
||||||
// 检查连接是否正常
|
// 检查连接是否正常
|
||||||
if p.conn == nil {
|
if p.conn == nil {
|
||||||
@ -163,6 +242,14 @@ func (p *mikrotikAddressListPlugin) Close() error {
|
|||||||
// 等待所有工作完成
|
// 等待所有工作完成
|
||||||
p.wg.Wait()
|
p.wg.Wait()
|
||||||
|
|
||||||
|
// 清理缓存
|
||||||
|
p.cacheMu.Lock()
|
||||||
|
cacheSize := len(p.cache)
|
||||||
|
p.cache = nil
|
||||||
|
p.cacheMu.Unlock()
|
||||||
|
|
||||||
|
p.log.Info("plugin closed", zap.Int("cache_cleared", cacheSize))
|
||||||
|
|
||||||
// 关闭连接
|
// 关闭连接
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
@ -212,55 +299,20 @@ func (p *mikrotikAddressListPlugin) addToAddressList(r *dns.Msg) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 并发处理所有地址
|
// 动态调整工作池大小
|
||||||
var wg sync.WaitGroup
|
p.adjustWorkerPoolSize(len(addresses))
|
||||||
var mu sync.Mutex
|
|
||||||
var errors []error
|
|
||||||
addedCount := 0
|
|
||||||
|
|
||||||
for _, addr := range addresses {
|
// 使用优化的批量处理
|
||||||
wg.Add(1)
|
if err := p.batchAddAddresses(addresses, p.args.AddressList4, p.args.Mask4); err != nil {
|
||||||
go func(addr netip.Addr) {
|
return err
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
// 获取工作池槽位
|
|
||||||
select {
|
|
||||||
case p.workerPool <- struct{}{}:
|
|
||||||
defer func() { <-p.workerPool }()
|
|
||||||
default:
|
|
||||||
// 如果工作池满了,直接处理(避免阻塞)
|
|
||||||
p.log.Debug("worker pool full, processing directly")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.addAddressToMikrotik(addr, p.args.AddressList4, p.args.Mask4); err != nil {
|
|
||||||
mu.Lock()
|
|
||||||
errors = append(errors, err)
|
|
||||||
mu.Unlock()
|
|
||||||
} else {
|
|
||||||
mu.Lock()
|
|
||||||
addedCount++
|
|
||||||
mu.Unlock()
|
|
||||||
}
|
|
||||||
}(addr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待所有工作完成
|
// 记录缓存统计信息
|
||||||
wg.Wait()
|
total, valid := p.getCacheStats()
|
||||||
|
p.log.Info("IPv4 addresses processed",
|
||||||
// 记录结果
|
zap.Int("processed_count", len(addresses)),
|
||||||
if addedCount > 0 {
|
zap.Int("cache_total", total),
|
||||||
p.log.Info("concurrently added IPv4 addresses to MikroTik",
|
zap.Int("cache_valid", valid))
|
||||||
zap.Int("success_count", addedCount),
|
|
||||||
zap.Int("total_count", len(addresses)),
|
|
||||||
zap.Int("error_count", len(errors)))
|
|
||||||
} else {
|
|
||||||
p.log.Debug("no IPv4 addresses added to MikroTik")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有错误,返回第一个错误
|
|
||||||
if len(errors) > 0 {
|
|
||||||
return fmt.Errorf("some addresses failed to add: %v", errors[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -285,14 +337,21 @@ func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listNa
|
|||||||
|
|
||||||
p.log.Debug("checking address", zap.String("cidr", cidrAddr), zap.String("list", listName))
|
p.log.Debug("checking address", zap.String("cidr", cidrAddr), zap.String("list", listName))
|
||||||
|
|
||||||
// 检查地址是否已存在
|
// 首先检查内存缓存
|
||||||
|
if p.isInCache(listName, cidrAddr) {
|
||||||
|
p.log.Debug("address found in cache, skipping", zap.String("cidr", cidrAddr), zap.String("list", listName))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存中没有,检查 MikroTik 中是否已存在
|
||||||
exists, err := p.addressExists(listName, cidrAddr)
|
exists, err := p.addressExists(listName, cidrAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 如果检查失败,可能是地址列表不存在,继续尝试添加
|
// 如果检查失败,可能是地址列表不存在,继续尝试添加
|
||||||
p.log.Debug("failed to check if address exists, will try to add anyway", zap.Error(err))
|
p.log.Debug("failed to check if address exists in MikroTik, will try to add anyway", zap.Error(err))
|
||||||
} else if exists {
|
} else if exists {
|
||||||
// 地址已存在,跳过
|
// 地址已存在于 MikroTik 中,添加到缓存并跳过
|
||||||
p.log.Debug("address already exists", zap.String("cidr", cidrAddr), zap.String("list", listName))
|
p.log.Debug("address already exists in MikroTik, adding to cache", zap.String("cidr", cidrAddr), zap.String("list", listName))
|
||||||
|
p.addToCache(listName, cidrAddr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,53 +379,64 @@ func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listNa
|
|||||||
|
|
||||||
p.log.Debug("Add to list: ", zap.Strings("params", params))
|
p.log.Debug("Add to list: ", zap.Strings("params", params))
|
||||||
|
|
||||||
// 发送到 RouterOS,带重试机制
|
// 发送到 RouterOS,带智能重试机制
|
||||||
maxRetries := 3
|
maxRetries := 3
|
||||||
|
backoffDuration := 100 * time.Millisecond
|
||||||
|
|
||||||
for i := 0; i < maxRetries; i++ {
|
for i := 0; i < maxRetries; i++ {
|
||||||
// 使用读锁保护连接访问
|
// 使用读锁保护连接访问
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
conn := p.conn
|
conn := p.conn
|
||||||
|
isConnected := p.isConnected
|
||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
|
|
||||||
if conn == nil {
|
if conn == nil || !isConnected {
|
||||||
p.log.Error("connection is nil")
|
p.log.Debug("connection not available, attempting to reconnect")
|
||||||
return fmt.Errorf("connection is nil")
|
p.mu.Lock()
|
||||||
|
p.isConnected = false
|
||||||
|
if err := p.reconnect(); err != nil {
|
||||||
|
p.mu.Unlock()
|
||||||
|
p.log.Error("failed to reconnect", zap.Error(err))
|
||||||
|
time.Sleep(backoffDuration)
|
||||||
|
backoffDuration *= 2 // 指数退避
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn = p.conn
|
||||||
|
p.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
args := append([]string{"/ip/firewall/address-list/add"}, params...)
|
args := append([]string{"/ip/firewall/address-list/add"}, params...)
|
||||||
_, err = conn.Run(args...)
|
_, err = conn.Run(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "already have such entry") {
|
if strings.Contains(err.Error(), "already have such entry") {
|
||||||
p.log.Debug("Already exists: ", zap.String("cidr", cidrAddr))
|
p.log.Debug("Address already exists", zap.String("cidr", cidrAddr))
|
||||||
|
p.addToCache(listName, cidrAddr) // 添加到缓存
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是连接错误,尝试重新连接
|
// 检查是否为连接错误
|
||||||
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "connection") {
|
if p.isConnectionError(err) {
|
||||||
p.log.Warn("connection error, attempting to reconnect",
|
p.log.Warn("connection error, will retry",
|
||||||
zap.String("cidr", cidrAddr),
|
zap.String("cidr", cidrAddr),
|
||||||
zap.Int("retry", i+1),
|
zap.Int("retry", i+1),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
|
|
||||||
// 使用写锁保护重连操作
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
if err := p.reconnect(); err != nil {
|
p.isConnected = false
|
||||||
p.mu.Unlock()
|
|
||||||
p.log.Error("failed to reconnect", zap.Error(err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
|
|
||||||
// 重试
|
// 指数退避
|
||||||
|
time.Sleep(backoffDuration)
|
||||||
|
backoffDuration *= 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他错误,记录并返回
|
// 其他错误,直接返回
|
||||||
p.log.Error("failed to add address to MikroTik",
|
p.log.Error("failed to add address to MikroTik",
|
||||||
zap.String("cidr", cidrAddr),
|
zap.String("cidr", cidrAddr),
|
||||||
zap.String("list", listName),
|
zap.String("list", listName),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
return fmt.Errorf("failed to add address %s to list %s: from RouterOS device: %v", cidrAddr, listName, err)
|
return fmt.Errorf("failed to add address %s to list %s: %v", cidrAddr, listName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 成功,跳出重试循环
|
// 成功,跳出重试循环
|
||||||
@ -377,6 +447,9 @@ func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listNa
|
|||||||
zap.String("cidr", cidrAddr),
|
zap.String("cidr", cidrAddr),
|
||||||
zap.String("list", listName))
|
zap.String("list", listName))
|
||||||
|
|
||||||
|
// 添加到缓存
|
||||||
|
p.addToCache(listName, cidrAddr)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,3 +483,105 @@ func (p *mikrotikAddressListPlugin) addressExists(listName, address string) (boo
|
|||||||
|
|
||||||
return exists, nil
|
return exists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// batchAddAddresses 批量添加地址到MikroTik(批量操作优化)
|
||||||
|
func (p *mikrotikAddressListPlugin) batchAddAddresses(addresses []netip.Addr, listName string, mask int) error {
|
||||||
|
if len(addresses) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分批处理,每批10个地址
|
||||||
|
batchSize := 10
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
var errors []error
|
||||||
|
successCount := 0
|
||||||
|
|
||||||
|
for i := 0; i < len(addresses); i += batchSize {
|
||||||
|
end := i + batchSize
|
||||||
|
if end > len(addresses) {
|
||||||
|
end = len(addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := addresses[i:end]
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func(batch []netip.Addr) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// 获取工作池槽位
|
||||||
|
select {
|
||||||
|
case p.workerPool <- struct{}{}:
|
||||||
|
defer func() { <-p.workerPool }()
|
||||||
|
default:
|
||||||
|
p.log.Debug("worker pool full, processing batch directly")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range batch {
|
||||||
|
if err := p.addAddressToMikrotik(addr, listName, mask); err != nil {
|
||||||
|
mu.Lock()
|
||||||
|
errors = append(errors, err)
|
||||||
|
mu.Unlock()
|
||||||
|
} else {
|
||||||
|
mu.Lock()
|
||||||
|
successCount++
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
p.log.Error("batch processing completed with errors",
|
||||||
|
zap.Int("success_count", successCount),
|
||||||
|
zap.Int("error_count", len(errors)),
|
||||||
|
zap.Error(errors[0]))
|
||||||
|
return errors[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
p.log.Info("batch processing completed successfully",
|
||||||
|
zap.Int("success_count", successCount),
|
||||||
|
zap.Int("total_count", len(addresses)))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjustWorkerPoolSize 动态调整工作池大小
|
||||||
|
func (p *mikrotikAddressListPlugin) adjustWorkerPoolSize(addressCount int) {
|
||||||
|
var targetSize int
|
||||||
|
switch {
|
||||||
|
case addressCount <= 5:
|
||||||
|
targetSize = 3
|
||||||
|
case addressCount <= 20:
|
||||||
|
targetSize = 5
|
||||||
|
case addressCount <= 50:
|
||||||
|
targetSize = 10
|
||||||
|
default:
|
||||||
|
targetSize = 15
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前容量不够,创建新的工作池
|
||||||
|
if cap(p.workerPool) < targetSize {
|
||||||
|
p.log.Debug("adjusting worker pool size",
|
||||||
|
zap.Int("old_size", cap(p.workerPool)),
|
||||||
|
zap.Int("new_size", targetSize),
|
||||||
|
zap.Int("address_count", addressCount))
|
||||||
|
|
||||||
|
// 创建新的工作池
|
||||||
|
p.workerPool = make(chan struct{}, targetSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isConnectionError 检查是否为连接错误
|
||||||
|
func (p *mikrotikAddressListPlugin) isConnectionError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
errStr := err.Error()
|
||||||
|
return strings.Contains(errStr, "EOF") ||
|
||||||
|
strings.Contains(errStr, "connection") ||
|
||||||
|
strings.Contains(errStr, "closed") ||
|
||||||
|
strings.Contains(errStr, "timeout")
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user