Update ddns.go

This commit is contained in:
77-QiQi authored and GitHub committed 2026-09-14 08:39:22 +08:00
1 parent f2f10ea7bc
commit cbc583dd3a
1 file changed
+54 -61
+54 -61
View File
@@ -8,6 +8,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/libdns/he"
"github.com/libdns/libdns" "github.com/libdns/libdns"
"github.com/miekg/dns" "github.com/miekg/dns"
@@ -22,9 +23,6 @@ const (
) )
type Provider struct { type Provider struct {
prefix string
zone string
DDNSProfile *model.DDNSProfile DDNSProfile *model.DDNSProfile
IPAddrs *model.IP IPAddrs *model.IP
Setter libdns.RecordSetter Setter libdns.RecordSetter
@@ -42,10 +40,19 @@ func (provider *Provider) UpdateDomain(ctx context.Context, overrideDomains ...s
} }
for _, domain := range domains { for _, domain := range domains {
var err error var prefix, zone string
provider.prefix, provider.zone, err = provider.splitDomainSOA(ctx, domain) var soaErr error
if err != nil {
log.Printf("NEZHA>> Failed to split domain SOA for %s: %v", domain, err) for retries := 0; retries < maxRetries; retries++ {
prefix, zone, soaErr = provider.splitDomainSOA(ctx, domain)
if soaErr == nil {
break
}
log.Printf("NEZHA>> Failed to split domain SOA for %s (attempt %d/%d): %v", domain, retries+1, maxRetries, soaErr)
}
if soaErr != nil {
log.Printf("NEZHA>> Failed to split domain SOA for %s after %d retries, skipping domain", domain, maxRetries)
continue continue
} }
@@ -55,9 +62,9 @@ func (provider *Provider) UpdateDomain(ctx context.Context, overrideDomains ...s
log.Printf("NEZHA>> Updating IPv4 record of domain %s: %d/%d", domain, retries+1, maxRetries) log.Printf("NEZHA>> Updating IPv4 record of domain %s: %d/%d", domain, retries+1, maxRetries)
var ipv4Err error var ipv4Err error
if provider.IPAddrs.IPv4Addr == "" { if provider.IPAddrs.IPv4Addr == "" {
ipv4Err = provider.deleteDomainRecord(ctx, "A") ipv4Err = provider.deleteDomainRecord(ctx, prefix, zone, "A")
} else { } else {
ipv4Err = provider.addDomainRecord(ctx, "A", provider.IPAddrs.IPv4Addr) ipv4Err = provider.addDomainRecord(ctx, prefix, zone, "A", provider.IPAddrs.IPv4Addr)
} }
if ipv4Err != nil { if ipv4Err != nil {
@@ -75,9 +82,9 @@ func (provider *Provider) UpdateDomain(ctx context.Context, overrideDomains ...s
log.Printf("NEZHA>> Updating IPv6 record of domain %s: %d/%d", domain, retries+1, maxRetries) log.Printf("NEZHA>> Updating IPv6 record of domain %s: %d/%d", domain, retries+1, maxRetries)
var ipv6Err error var ipv6Err error
if provider.IPAddrs.IPv6Addr == "" { if provider.IPAddrs.IPv6Addr == "" {
ipv6Err = provider.deleteDomainRecord(ctx, "AAAA") ipv6Err = provider.deleteDomainRecord(ctx, prefix, zone, "AAAA")
} else { } else {
ipv6Err = provider.addDomainRecord(ctx, "AAAA", provider.IPAddrs.IPv6Addr) ipv6Err = provider.addDomainRecord(ctx, prefix, zone, "AAAA", provider.IPAddrs.IPv6Addr)
} }
if ipv6Err != nil { if ipv6Err != nil {
@@ -91,16 +98,16 @@ func (provider *Provider) UpdateDomain(ctx context.Context, overrideDomains ...s
} }
} }
func (provider *Provider) addDomainRecord(ctx context.Context, recType, addr string) error { func (provider *Provider) addDomainRecord(ctx context.Context, prefix, zone, recType, addr string) error {
netipAddr, err := netip.ParseAddr(addr) netipAddr, err := netip.ParseAddr(addr)
if err != nil { if err != nil {
return fmt.Errorf("parse error: %v", err) return fmt.Errorf("parse error: %v", err)
} }
_, err = provider.Setter.SetRecords(ctx, provider.zone, _, err = provider.Setter.SetRecords(ctx, zone,
[]libdns.Record{ []libdns.Record{
libdns.Address{ libdns.Address{
Name: provider.prefix, Name: prefix,
IP: netipAddr, IP: netipAddr,
TTL: time.Minute, TTL: time.Minute,
}, },
@@ -109,8 +116,25 @@ func (provider *Provider) addDomainRecord(ctx context.Context, recType, addr str
} }
// deleteDomainRecord 用于安全删除指定类型的解析记录 // deleteDomainRecord 用于安全删除指定类型的解析记录
func (provider *Provider) deleteDomainRecord(ctx context.Context, recType string) error { func (provider *Provider) deleteDomainRecord(ctx context.Context, prefix, zone, recType string) error {
// 同时断言 RecordGetter 与 RecordDeleter,防止提供商因缺少接口导致断言错误 targetRecType := strings.ToUpper(recType)
// 针对 HE 服务商使用标准的 libdns.RR
if _, ok := provider.Setter.(*he.Provider); ok {
deleter, okDeleter := provider.Setter.(libdns.RecordDeleter)
if !okDeleter {
return fmt.Errorf("he provider does not implement RecordDeleter")
}
_, err := deleter.DeleteRecords(ctx, zone, []libdns.Record{
libdns.RR{
Name: prefix,
Type: targetRecType,
},
})
return err
}
// 检查接口
getter, okGetter := provider.Setter.(libdns.RecordGetter) getter, okGetter := provider.Setter.(libdns.RecordGetter)
deleter, okDeleter := provider.Setter.(libdns.RecordDeleter) deleter, okDeleter := provider.Setter.(libdns.RecordDeleter)
@@ -122,44 +146,15 @@ func (provider *Provider) deleteDomainRecord(ctx context.Context, recType string
cleanName := func(name string) string { cleanName := func(name string) string {
return strings.ToLower(strings.TrimSuffix(name, ".")) return strings.ToLower(strings.TrimSuffix(name, "."))
} }
cleanPrefix := cleanName(provider.prefix) cleanPrefix := cleanName(prefix)
targetRecType := strings.ToUpper(recType)
// 通过特征匹配识别 Hurricane Electric (HE) 等特殊供应商
providerType := strings.ToLower(fmt.Sprintf("%T", provider.Setter))
isHeProvider := strings.Contains(providerType, "he") ||
strings.Contains(providerType, "hurricane") ||
strings.Contains(providerType, "dns.he")
// 针对 HE 供应商:仅返回根域(@),无法枚举子域名
if isHeProvider {
log.Printf("NEZHA>> Provider identified as Hurricane Electric (HE) variant, using direct set deletion path for %s.%s", provider.prefix, provider.zone)
var dummyIP netip.Addr
if targetRecType == "A" {
dummyIP = netip.MustParseAddr("0.0.0.0")
} else {
dummyIP = netip.MustParseAddr("::")
}
_, err := provider.Setter.SetRecords(ctx, provider.zone,
[]libdns.Record{
libdns.Address{
Name: provider.prefix,
IP: dummyIP,
TTL: time.Minute,
},
})
return err
}
// 获取 DNS 记录(针对标准提供商,如 Cloudflare) // 获取 DNS 记录(针对标准提供商,如 Cloudflare)
allRecords, err := getter.GetRecords(ctx, provider.zone) allRecords, err := getter.GetRecords(ctx, zone)
if err != nil { if err != nil {
return fmt.Errorf("failed to get DNS records: %w", err) return fmt.Errorf("failed to get DNS records: %w", err)
} }
cleanZone := cleanName(provider.zone) cleanZone := cleanName(zone)
// 筛选和匹配目标记录
var targetRecords []libdns.Record var targetRecords []libdns.Record
for _, rec := range allRecords { for _, rec := range allRecords {
rr := rec.RR() rr := rec.RR()
@@ -167,7 +162,7 @@ func (provider *Provider) deleteDomainRecord(ctx context.Context, recType string
continue continue
} }
relName := libdns.RelativeName(rr.Name, provider.zone) relName := libdns.RelativeName(rr.Name, zone)
cleanRel := cleanName(relName) cleanRel := cleanName(relName)
cleanRRName := cleanName(rr.Name) cleanRRName := cleanName(rr.Name)
@@ -187,28 +182,26 @@ func (provider *Provider) deleteDomainRecord(ctx context.Context, recType string
// 若未找到对应记录(例如已经成功删除),则直接返回成功 // 若未找到对应记录(例如已经成功删除),则直接返回成功
if len(targetRecords) == 0 { if len(targetRecords) == 0 {
log.Printf("NEZHA>> No matching %s record found for deletion under zone %s, already clean", recType, provider.zone) log.Printf("NEZHA>> No matching %s record found for deletion under zone %s, already clean", recType, zone)
return nil return nil
} }
// 执行实际删除操作 _, err = deleter.DeleteRecords(ctx, zone, targetRecords)
_, err = deleter.DeleteRecords(ctx, provider.zone, targetRecords)
if err != nil { if err != nil {
return fmt.Errorf("deleter.DeleteRecords failed: %w", err) return fmt.Errorf("deleter.DeleteRecords failed: %w", err)
} }
log.Printf("NEZHA>> Successfully deleted %d matching %s record(s) for %s.%s", len(targetRecords), recType, provider.prefix, provider.zone) log.Printf("NEZHA>> Successfully deleted %d matching %s record(s) for %s.%s", len(targetRecords), recType, prefix, zone)
return nil return nil
} }
func (provider *Provider) splitDomainSOA(ctx context.Context, domain string, overrideServers ...string) (prefix string, zone string, err error) { func (provider *Provider) splitDomainSOA(ctx context.Context, domain string) (prefix string, zone string, err error) {
c := &dns.Client{Timeout: dnsTimeOut} c := &dns.Client{Timeout: dnsTimeOut}
domain += "." domain += "."
indexes := dns.Split(domain) indexes := dns.Split(domain)
servers := utils.DNSServers servers := utils.DNSServers
customDNSServers, _ := ctx.Value(DNSServerKey{}).([]string) customDNSServers, _ := ctx.Value(DNSServerKey{}).([]string)
if len(customDNSServers) > 0 { if len(customDNSServers) > 0 {
servers = customDNSServers servers = customDNSServers
@@ -224,14 +217,14 @@ func (provider *Provider) splitDomainSOA(ctx context.Context, domain string, ove
continue continue
} }
if len(r.Answer) > 0 { if r != nil && len(r.Answer) > 0 {
if soa, ok := r.Answer[0].(*dns.SOA); ok { if soa, ok := r.Answer[0].(*dns.SOA); ok {
zone := soa.Hdr.Name zoneName := soa.Hdr.Name
prefix := libdns.RelativeName(domain, zone) pfx := libdns.RelativeName(domain, zoneName)
if prefix == "@" { if pfx == "@" {
prefix = "" pfx = ""
} }
return prefix, zone, nil return pfx, zoneName, nil
} }
} }
} }