diff --git a/providers/alidns/auditrecords.go b/providers/alidns/auditrecords.go index 24c54edda..b1079acf2 100644 --- a/providers/alidns/auditrecords.go +++ b/providers/alidns/auditrecords.go @@ -55,5 +55,7 @@ func AuditRecords(records []*models.RecordConfig) []error { a.Add("TXT", rejectif.TxtHasUnpairedBackslash) // Last verified at 2025-12-03: Alibaba mishandles odd backslashes a.Add("*", labelConstraint) // Last verified at 2025-12-03: Alibaba only allows ASCII + Chinese, rejects other Unicode a.Add("CNAME", targetConstraint) // Last verified at 2025-12-03: CNAME target must be ASCII or Chinese + a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified at 2025-12-03: SRV target must not be null + a.Add("SRV", rejectif.SrvHasEmptyTarget) // Last verified at 2025-12-03: SRV target must not be empty return a.Audit(records) } diff --git a/providers/alidns/convert.go b/providers/alidns/convert.go index 4cbd094de..7be8b85ff 100644 --- a/providers/alidns/convert.go +++ b/providers/alidns/convert.go @@ -35,14 +35,26 @@ func nativeToRecord(r *alidns.Record, domain string) (*models.RecordConfig, erro return nil, fmt.Errorf("unparsable MX record received from ALIDNS: %w", err) } case "SRV": - // SRV records in Alibaba Cloud: Priority and Weight are in separate fields, - // Value contains "port target" (e.g., "5060 sipserver.example.com") - if err := rc.PopulateFromString(r.Type, fmt.Sprintf("%d %d %s", r.Priority, r.Weight, r.Value), domain); err != nil { + // SRV records in Alibaba Cloud: Value contains "priority weight port target" + // e.g., "1 1 5060 www.cloud-example.com." + // Parse the parts and normalize the target + parts := strings.Fields(r.Value) + if len(parts) != 4 { + return nil, fmt.Errorf("invalid SRV format from ALIDNS: %s", r.Value) + } + target := parts[3] + // Ensure target has trailing dot for FQDN + if target != "" && target != "." && !strings.HasSuffix(target, ".") { + target = target + "." + } + // Reconstruct with normalized target and let PopulateFromString handle it + srvValue := fmt.Sprintf("%s %s %s %s", parts[0], parts[1], parts[2], target) + if err := rc.PopulateFromString(r.Type, srvValue, domain); err != nil { return nil, fmt.Errorf("unparsable SRV record received from ALIDNS: %w", err) } case "CAA": - // CAA format in Alibaba: "0 issue letsencrypt.org" - if err := rc.PopulateFromString(r.Type, r.Value, domain); err != nil { + // Alibaba Cloud CAA format: "0 issue \"letsencrypt.org\"" + if err := rc.SetTargetCAAString(r.Value); err != nil { return nil, fmt.Errorf("unparsable CAA record received from ALIDNS: %w", err) } case "TXT": @@ -62,13 +74,10 @@ func nativeToRecord(r *alidns.Record, domain string) (*models.RecordConfig, erro // recordToNativeContent converts a RecordConfig to the Value format expected by Alibaba Cloud DNS API. func recordToNativeContent(r *models.RecordConfig) string { switch r.Type { - case "MX": - return r.GetTargetField() case "SRV": - // Alibaba Cloud SRV format: "weight port target" - return fmt.Sprintf("%d %d %s", r.SrvWeight, r.SrvPort, r.GetTargetField()) + return fmt.Sprintf("%d %d %d %s", r.SrvPriority, r.SrvWeight, r.SrvPort, r.GetTargetField()) case "CAA": - return fmt.Sprintf("%d %s %s", r.CaaFlag, r.CaaTag, r.GetTargetField()) + return fmt.Sprintf("%d %s \"%s\"", r.CaaFlag, r.CaaTag, r.GetTargetField()) case "TXT": return r.GetTargetTXTJoined() default: