主要修改:
Some checks failed
Test mosdns / build (push) Has been cancelled

1. 在Exec方法中获取域名:从DNS查询中提取域名并去除末尾的点
  2. 传递域名参数:将域名参数传递给所有相关的方法
  3. 动态设置注释:优先使用域名作为注释,如果域名为空则使用配置文件中的comment
  4. 更新日志:添加域名信息到日志中便于调试
  5.添加了二次延迟,会对添加到Mikrotik中的IP进行二次验证,确定是否添加成功
This commit is contained in:
dengxiongjian 2025-08-05 22:05:58 +08:00
parent 59a5ef4aae
commit 444c01d207
2 changed files with 225 additions and 22 deletions

View File

@ -50,6 +50,7 @@ type Args struct {
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默认 36001小时 CacheTTL int `yaml:"cache_ttl"` // 缓存 TTL默认 36001小时
VerifyAdd bool `yaml:"verify_add"` // 是否在添加后验证地址确实存在,默认 false
} }
var _ sequence.Executable = (*mikrotikAddressListPlugin)(nil) var _ sequence.Executable = (*mikrotikAddressListPlugin)(nil)
@ -59,8 +60,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:cache_ttl // QuickSetup format: host:port:username:password:use_tls:timeout:address_list4:address_list6:mask4:mask6:comment:timeout_addr:cache_ttl:verify_add
// e.g. "192.168.1.1:8728:admin:password:false:10:my_list4:my_list6:24:32:from_dns:3600:3600" // e.g. "192.168.1.1:8728:admin:password:false:10:my_list4:my_list6:24:32:from_dns:3600:3600:true"
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 {
@ -137,5 +138,12 @@ func QuickSetup(_ sequence.BQ, s string) (any, error) {
} }
} }
// 解析验证开关
if len(parts) > 13 {
if verifyAdd, err := strconv.ParseBool(parts[13]); err == nil {
args.VerifyAdd = verifyAdd
}
}
return newMikrotikAddressListPlugin(args) return newMikrotikAddressListPlugin(args)
} }

View File

@ -35,20 +35,33 @@ import (
routeros "github.com/go-routeros/routeros/v3" routeros "github.com/go-routeros/routeros/v3"
) )
// verifyTask 验证任务
type verifyTask struct {
listName string
cidrAddr string
retries int
}
type mikrotikAddressListPlugin struct { type mikrotikAddressListPlugin struct {
args *Args args *Args
conn *routeros.Client conn *routeros.Client
log *zap.Logger log *zap.Logger
// 并发控制 // 并发控制
workerPool chan struct{} workerPool chan struct{}
wg sync.WaitGroup verifyPool chan struct{} // 专门用于验证的工作池
mu sync.RWMutex // 保护连接的重连操作 wg sync.WaitGroup
mu sync.RWMutex // 保护连接的重连操作
isConnected bool // 连接状态标记
// 内存缓存 // 内存缓存
cache map[string]time.Time // key: "listName:cidrAddr", value: 添加时间 cache map[string]time.Time // key: "listName:cidrAddr", value: 添加时间
cacheMu sync.RWMutex // 保护缓存访问 cacheMu sync.RWMutex // 保护缓存访问
cacheTTL time.Duration // 缓存 TTL默认 1 小时 cacheTTL time.Duration // 缓存 TTL默认 1 小时
// 验证队列
verifyQueue chan verifyTask
stopVerify chan struct{}
} }
func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error) { func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error) {
@ -82,6 +95,7 @@ func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error
// 设置工作池大小(可以根据需要调整) // 设置工作池大小(可以根据需要调整)
workerCount := 10 // 并发工作线程数 workerCount := 10 // 并发工作线程数
verifyCount := 5 // 验证工作线程数
// 设置缓存 TTL // 设置缓存 TTL
cacheTTL := time.Hour // 默认 1 小时 cacheTTL := time.Hour // 默认 1 小时
@ -90,12 +104,23 @@ func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error
} }
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), verifyPool: make(chan struct{}, verifyCount),
cacheTTL: cacheTTL, cache: make(map[string]time.Time),
cacheTTL: cacheTTL,
isConnected: true,
verifyQueue: make(chan verifyTask, 100), // 验证任务队列
stopVerify: make(chan struct{}),
}
// 启动验证工作协程
if args.VerifyAdd {
for i := 0; i < verifyCount; i++ {
go plugin.verifyWorker()
}
} }
// 记录连接成功信息 // 记录连接成功信息
@ -105,6 +130,8 @@ func newMikrotikAddressListPlugin(args *Args) (*mikrotikAddressListPlugin, error
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.Int("verify_count", verifyCount),
zap.Bool("verify_add", args.VerifyAdd),
zap.Duration("cache_ttl", cacheTTL)) zap.Duration("cache_ttl", cacheTTL))
return plugin, nil return plugin, nil
@ -226,11 +253,17 @@ func (p *mikrotikAddressListPlugin) Exec(_ context.Context, qCtx *query_context.
r := qCtx.R() r := qCtx.R()
if r != nil { if r != nil {
// 获取查询的域名
var domain string
if len(qCtx.Q().Question) > 0 {
domain = strings.TrimSuffix(qCtx.Q().Question[0].Name, ".")
}
p.log.Debug("processing DNS response", p.log.Debug("processing DNS response",
zap.String("qname", qCtx.Q().Question[0].Name), zap.String("qname", domain),
zap.Int("answer_count", len(r.Answer))) zap.Int("answer_count", len(r.Answer)))
if err := p.addToAddressList(r); err != nil { if err := p.addToAddressList(r, domain); err != nil {
p.log.Error("failed to add addresses to MikroTik", zap.Error(err)) p.log.Error("failed to add addresses to MikroTik", zap.Error(err))
return fmt.Errorf("mikrotik_addresslist: %w", err) return fmt.Errorf("mikrotik_addresslist: %w", err)
} }
@ -239,6 +272,11 @@ func (p *mikrotikAddressListPlugin) Exec(_ context.Context, qCtx *query_context.
} }
func (p *mikrotikAddressListPlugin) Close() error { func (p *mikrotikAddressListPlugin) Close() error {
// 停止验证工作器
if p.args.VerifyAdd {
close(p.stopVerify)
}
// 等待所有工作完成 // 等待所有工作完成
p.wg.Wait() p.wg.Wait()
@ -260,7 +298,7 @@ func (p *mikrotikAddressListPlugin) Close() error {
return nil return nil
} }
func (p *mikrotikAddressListPlugin) addToAddressList(r *dns.Msg) error { func (p *mikrotikAddressListPlugin) addToAddressList(r *dns.Msg, domain string) error {
p.log.Debug("starting to process DNS response", p.log.Debug("starting to process DNS response",
zap.String("configured_address_list4", p.args.AddressList4), zap.String("configured_address_list4", p.args.AddressList4),
zap.Int("answer_count", len(r.Answer))) zap.Int("answer_count", len(r.Answer)))
@ -303,7 +341,7 @@ func (p *mikrotikAddressListPlugin) addToAddressList(r *dns.Msg) error {
p.adjustWorkerPoolSize(len(addresses)) p.adjustWorkerPoolSize(len(addresses))
// 使用优化的批量处理 // 使用优化的批量处理
if err := p.batchAddAddresses(addresses, p.args.AddressList4, p.args.Mask4); err != nil { if err := p.batchAddAddresses(addresses, p.args.AddressList4, p.args.Mask4, domain); err != nil {
return err return err
} }
@ -317,7 +355,7 @@ func (p *mikrotikAddressListPlugin) addToAddressList(r *dns.Msg) error {
return nil return nil
} }
func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listName string, mask int) error { func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listName string, mask int, domain string) error {
p.log.Debug("addAddressToMikrotik called", p.log.Debug("addAddressToMikrotik called",
zap.String("addr", addr.String()), zap.String("addr", addr.String()),
zap.String("listName", listName), zap.String("listName", listName),
@ -361,9 +399,13 @@ func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listNa
"=address=" + cidrAddr, "=address=" + cidrAddr,
} }
// 添加注释(如果配置了) // 使用域名作为注释优先级高于配置文件中的comment
if p.args.Comment != "" { comment := domain
params = append(params, "=comment="+p.args.Comment) if comment == "" && p.args.Comment != "" {
comment = p.args.Comment
}
if comment != "" {
params = append(params, "=comment="+comment)
} }
// 添加超时时间(如果配置了) // 添加超时时间(如果配置了)
@ -374,7 +416,8 @@ func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listNa
p.log.Info("adding address to MikroTik", p.log.Info("adding address to MikroTik",
zap.String("cidr", cidrAddr), zap.String("cidr", cidrAddr),
zap.String("list", listName), zap.String("list", listName),
zap.String("comment", p.args.Comment), zap.String("domain", domain),
zap.String("comment", comment),
zap.Int("timeout", p.args.TimeoutAddr)) zap.Int("timeout", p.args.TimeoutAddr))
p.log.Debug("Add to list: ", zap.Strings("params", params)) p.log.Debug("Add to list: ", zap.Strings("params", params))
@ -450,6 +493,20 @@ func (p *mikrotikAddressListPlugin) addAddressToMikrotik(addr netip.Addr, listNa
// 添加到缓存 // 添加到缓存
p.addToCache(listName, cidrAddr) p.addToCache(listName, cidrAddr)
// 如果启用了验证,提交验证任务
if p.args.VerifyAdd {
select {
case p.verifyQueue <- verifyTask{
listName: listName,
cidrAddr: cidrAddr,
retries: 0,
}:
p.log.Debug("verification task queued", zap.String("cidr", cidrAddr))
default:
p.log.Warn("verification queue full, skipping verification", zap.String("cidr", cidrAddr))
}
}
return nil return nil
} }
@ -485,7 +542,7 @@ func (p *mikrotikAddressListPlugin) addressExists(listName, address string) (boo
} }
// batchAddAddresses 批量添加地址到MikroTik批量操作优化 // batchAddAddresses 批量添加地址到MikroTik批量操作优化
func (p *mikrotikAddressListPlugin) batchAddAddresses(addresses []netip.Addr, listName string, mask int) error { func (p *mikrotikAddressListPlugin) batchAddAddresses(addresses []netip.Addr, listName string, mask int, domain string) error {
if len(addresses) == 0 { if len(addresses) == 0 {
return nil return nil
} }
@ -518,7 +575,7 @@ func (p *mikrotikAddressListPlugin) batchAddAddresses(addresses []netip.Addr, li
} }
for _, addr := range batch { for _, addr := range batch {
if err := p.addAddressToMikrotik(addr, listName, mask); err != nil { if err := p.addAddressToMikrotik(addr, listName, mask, domain); err != nil {
mu.Lock() mu.Lock()
errors = append(errors, err) errors = append(errors, err)
mu.Unlock() mu.Unlock()
@ -585,3 +642,141 @@ func (p *mikrotikAddressListPlugin) isConnectionError(err error) bool {
strings.Contains(errStr, "closed") || strings.Contains(errStr, "closed") ||
strings.Contains(errStr, "timeout") strings.Contains(errStr, "timeout")
} }
// verifyWorker 验证工作器,独立处理验证任务,避免阻塞写入操作
func (p *mikrotikAddressListPlugin) verifyWorker() {
p.wg.Add(1)
defer p.wg.Done()
ticker := time.NewTicker(2 * time.Second) // 每2秒处理一批验证任务
defer ticker.Stop()
for {
select {
case <-p.stopVerify:
p.log.Info("verification worker stopping")
return
case <-ticker.C:
// 批量处理验证任务
p.processBatchVerification()
case task := <-p.verifyQueue:
// 获取验证工作池槽位
select {
case p.verifyPool <- struct{}{}:
go func(task verifyTask) {
defer func() { <-p.verifyPool }()
p.processVerificationTask(task)
}(task)
default:
// 验证池满,延迟处理
go func() {
time.Sleep(time.Second)
select {
case p.verifyQueue <- task:
default:
p.log.Warn("failed to requeue verification task",
zap.String("cidr", task.cidrAddr))
}
}()
}
}
}
}
// processBatchVerification 批量处理验证队列中的任务
func (p *mikrotikAddressListPlugin) processBatchVerification() {
var tasks []verifyTask
// 收集最多10个任务进行批量处理
for i := 0; i < 10; i++ {
select {
case task := <-p.verifyQueue:
tasks = append(tasks, task)
default:
break
}
}
if len(tasks) == 0 {
return
}
p.log.Debug("processing batch verification", zap.Int("task_count", len(tasks)))
for _, task := range tasks {
select {
case p.verifyPool <- struct{}{}:
go func(task verifyTask) {
defer func() { <-p.verifyPool }()
p.processVerificationTask(task)
}(task)
default:
// 如果池满,重新排队
select {
case p.verifyQueue <- task:
default:
p.log.Warn("verification queue full, dropping task",
zap.String("cidr", task.cidrAddr))
}
}
}
}
// processVerificationTask 处理单个验证任务
func (p *mikrotikAddressListPlugin) processVerificationTask(task verifyTask) {
// 等待一段时间再验证让MikroTik有时间处理
time.Sleep(time.Duration(500+task.retries*200) * time.Millisecond)
exists, err := p.addressExists(task.listName, task.cidrAddr)
if err != nil {
if task.retries < 3 {
// 重试
task.retries++
p.log.Debug("verification failed, retrying",
zap.String("cidr", task.cidrAddr),
zap.Int("retries", task.retries),
zap.Error(err))
select {
case p.verifyQueue <- task:
default:
p.log.Warn("failed to requeue verification task for retry",
zap.String("cidr", task.cidrAddr))
}
} else {
p.log.Error("verification failed after max retries",
zap.String("cidr", task.cidrAddr),
zap.String("list", task.listName),
zap.Error(err))
}
return
}
if !exists {
p.log.Warn("address not found in MikroTik after add operation",
zap.String("cidr", task.cidrAddr),
zap.String("list", task.listName))
// 从缓存中移除,下次会重新尝试添加
p.cacheMu.Lock()
key := p.cacheKey(task.listName, task.cidrAddr)
delete(p.cache, key)
p.cacheMu.Unlock()
// 可以选择重新添加地址
if task.retries < 2 {
p.log.Info("attempting to re-add address",
zap.String("cidr", task.cidrAddr),
zap.String("list", task.listName))
// 这里可以重新调用添加逻辑,但要避免无限循环
// 暂时只记录警告由下次DNS查询触发重新添加
}
} else {
p.log.Debug("address verification successful",
zap.String("cidr", task.cidrAddr),
zap.String("list", task.listName))
}
}