From 4a9233d0b6568924a88f7d3de41095392cb15cc0 Mon Sep 17 00:00:00 2001 From: Thomas Limoncelli Date: Wed, 3 Dec 2025 21:46:00 -0500 Subject: [PATCH] fix .NameFQDN* and other nits --- models/record.go | 10 +- models/t_parse.go | 4 +- pkg/rtype/rp.go | 16 -- pkg/rtypecontrol/import.go | 42 ++-- pkg/rtypecontrol/setrecordnames_test.go | 199 ++++++++++++++++++ providers/bind/bindProvider.go | 1 - providers/cloudflare/cloudflareProvider.go | 11 +- .../rtypes/cfsingleredirect/from.go | 90 -------- 8 files changed, 228 insertions(+), 145 deletions(-) create mode 100644 pkg/rtypecontrol/setrecordnames_test.go delete mode 100644 providers/cloudflare/rtypes/cfsingleredirect/from.go diff --git a/models/record.go b/models/record.go index 740979b80..1cc7b67ec 100644 --- a/models/record.go +++ b/models/record.go @@ -536,16 +536,10 @@ func (rc *RecordConfig) GetSVCBValue() []dns.SVCBKeyValue { return nil } +// IsModernType returns true if this RecordConfig uses the new "F" field to store its rdata. +// Once all record types have been migrated to use "F", this function can be removed. func (rc *RecordConfig) IsModernType() bool { - //fmt.Printf("DEBUG: IsModernType rtype=%s\n", rc.Type) return rc.F != nil - - // switch rc.Type { - // case "CLOUDFLAREAPI_SINGLE_REDIRECT", "RP": - // return true - // } - - // return false } // Records is a list of *RecordConfig. diff --git a/models/t_parse.go b/models/t_parse.go index 8d8c47531..9f8544f45 100644 --- a/models/t_parse.go +++ b/models/t_parse.go @@ -59,9 +59,7 @@ func (rc *RecordConfig) PopulateFromStringFunc(rtype, contents, origin string, t return fmt.Errorf("assertion failed: rtype already set (%s) (%s)", rtype, rc.Type) } - rc.Type = rtype - - switch rtype { // #rtype_variations + switch rc.Type = rtype; rtype { // #rtype_variations case "A": ip := net.ParseIP(contents) if ip == nil || ip.To4() == nil { diff --git a/pkg/rtype/rp.go b/pkg/rtype/rp.go index 4adcc69da..6bb0681e6 100644 --- a/pkg/rtype/rp.go +++ b/pkg/rtype/rp.go @@ -48,19 +48,3 @@ func (handle *RP) CopyToLegacyFields(rec *models.RecordConfig) { rp := rec.F.(*RP) _ = rec.SetTarget(rp.Mbox + " " + rp.Txt) } - -// func (handle *RP) TestData() { -// return []itest.TestGroup[ -// itest.Testgroup("RP", -// itest.tc("Create RP", rp("foo", "user.example.com.", "bar.com.")), -// itest.tc("Create RP", rp("foo", "other.example.com.", "bar.com.")), -// itest.tc("Create RP", rp("foo", "other.example.com.", "example.com.")), -// ), -// itest.Testgroup("RP-apex", -// itest.tc("Create RP", rp("@", "user.example.com.", "bar.com.")), -// itest.tc("Create RP", rp("@", "other.example.com.", "bar.com.")), -// itest.tc("Create RP", rp("@", "other.example.com.", "example.com.")), -// ), -// ] - -// } diff --git a/pkg/rtypecontrol/import.go b/pkg/rtypecontrol/import.go index ce3e8a410..eca23d9f9 100644 --- a/pkg/rtypecontrol/import.go +++ b/pkg/rtypecontrol/import.go @@ -2,11 +2,11 @@ package rtypecontrol import ( "fmt" + "strings" "github.com/StackExchange/dnscontrol/v4/models" "github.com/StackExchange/dnscontrol/v4/pkg/domaintags" "github.com/miekg/dns" - "github.com/miekg/dns/dnsutil" ) // ImportRawRecords imports the RawRecordConfigs into RecordConfigs. @@ -16,10 +16,10 @@ func ImportRawRecords(domains []*models.DomainConfig) error { for _, rawRec := range dc.RawRecords { rec, err := NewRecordConfigFromRaw(rawRec.Type, rawRec.TTL, rawRec.Args, dc) - rec.FilePos = models.FixPosition(rawRec.FilePos) if err != nil { - return fmt.Errorf("%s: %w", rec.FilePos, err) + return err } + rec.FilePos = models.FixPosition(rawRec.FilePos) // Free memeory: clear(rawRec.Args) @@ -106,31 +106,39 @@ func setRecordNames(rec *models.RecordConfig, dc *models.DomainConfig, n string) rec.Name = "@" rec.NameRaw = "@" rec.NameUnicode = "@" + rec.NameFQDN = dc.Name + rec.NameFQDNRaw = dc.NameRaw + rec.NameFQDNUnicode = dc.NameUnicode + rec.NameFQDN = dc.Name + rec.NameFQDNRaw = dc.NameRaw + rec.NameFQDNUnicode = dc.NameUnicode } else { - rec.Name = domaintags.EfficientToASCII(n) + rec.Name = strings.ToLower(domaintags.EfficientToASCII(n)) rec.NameRaw = n rec.NameUnicode = domaintags.EfficientToUnicode(n) + rec.NameFQDN = rec.Name + "." + dc.Name + rec.NameFQDNRaw = rec.NameRaw + "." + dc.NameRaw + rec.NameFQDNUnicode = rec.NameUnicode + "." + dc.NameUnicode } - rec.NameFQDN = dc.Name - rec.NameFQDNRaw = dc.NameRaw - rec.NameFQDNUnicode = dc.NameUnicode } else { - // _EXTEND() mode: - // FIXME(tlim): Not implemented. + // D_EXTEND() mode: sdRaw := rec.SubDomain - sdIDN := domaintags.EfficientToASCII(rec.SubDomain) - sdUnicode := domaintags.EfficientToUnicode(rec.SubDomain) + sdASCII := strings.ToLower(domaintags.EfficientToASCII(rec.SubDomain)) + sdUnicode := domaintags.EfficientToUnicode(sdASCII) if n == "@" { - rec.Name = sdIDN + rec.Name = sdASCII rec.NameRaw = sdRaw rec.NameUnicode = sdUnicode + rec.NameFQDN = rec.Name + "." + dc.Name + rec.NameFQDNRaw = rec.NameRaw + "." + dc.NameRaw + rec.NameFQDNUnicode = rec.NameUnicode + "." + dc.NameUnicode } else { - rec.Name = domaintags.EfficientToASCII(n + "." + sdIDN) + rec.Name = domaintags.EfficientToASCII(n) + "." + sdASCII rec.NameRaw = n + "." + sdRaw - rec.NameUnicode = domaintags.EfficientToUnicode(n + "." + sdUnicode) + rec.NameUnicode = domaintags.EfficientToUnicode(rec.Name) + rec.NameFQDN = rec.Name + "." + dc.Name + rec.NameFQDNRaw = rec.NameRaw + "." + dc.NameRaw + rec.NameFQDNUnicode = rec.NameUnicode + "." + dc.NameUnicode } - rec.NameFQDN = dnsutil.AddOrigin(rec.Name, dc.Name) - rec.NameFQDNRaw = dnsutil.AddOrigin(rec.NameRaw, dc.NameRaw) - rec.NameFQDNUnicode = dnsutil.AddOrigin(rec.NameUnicode, dc.NameUnicode) } } diff --git a/pkg/rtypecontrol/setrecordnames_test.go b/pkg/rtypecontrol/setrecordnames_test.go new file mode 100644 index 000000000..3ed0956f4 --- /dev/null +++ b/pkg/rtypecontrol/setrecordnames_test.go @@ -0,0 +1,199 @@ +package rtypecontrol + +import ( + "testing" + + "github.com/StackExchange/dnscontrol/v4/models" +) + +func TestSetRecordNames(t *testing.T) { + dc := &models.DomainConfig{ + Name: "example.com", + NameRaw: "example.com", + NameUnicode: "example.com", + } + dcIDN := &models.DomainConfig{ + Name: "xn--bcher-kva.com", + NameRaw: "bücher.com", + NameUnicode: "bücher.com", + } + + tests := []struct { + name string + rec *models.RecordConfig + dc *models.DomainConfig + n string + expectedRec *models.RecordConfig + }{ + { + name: "normal_at", + rec: &models.RecordConfig{}, + dc: dc, + n: "@", + expectedRec: &models.RecordConfig{ + Name: "@", + NameRaw: "@", + NameUnicode: "@", + NameFQDN: "example.com", + NameFQDNRaw: "example.com", + NameFQDNUnicode: "example.com", + }, + }, + { + name: "normal_label", + rec: &models.RecordConfig{}, + dc: dc, + n: "www", + expectedRec: &models.RecordConfig{ + Name: "www", + NameRaw: "www", + NameUnicode: "www", + NameFQDN: "www.example.com", + NameFQDNRaw: "www.example.com", + NameFQDNUnicode: "www.example.com", + }, + }, + { + name: "normal_idn_label", + rec: &models.RecordConfig{}, + dc: dc, + n: "bücher", + expectedRec: &models.RecordConfig{ + Name: "xn--bcher-kva", + NameRaw: "bücher", + NameUnicode: "bücher", + NameFQDN: "xn--bcher-kva.example.com", + NameFQDNRaw: "bücher.example.com", + NameFQDNUnicode: "bücher.example.com", + }, + }, + { + name: "normal_idn_domain", + rec: &models.RecordConfig{}, + dc: dcIDN, + n: "www", + expectedRec: &models.RecordConfig{ + Name: "www", + NameRaw: "www", + NameUnicode: "www", + NameFQDN: "www.xn--bcher-kva.com", + NameFQDNRaw: "www.bücher.com", + NameFQDNUnicode: "www.bücher.com", + }, + }, + { + name: "extend_at", + rec: &models.RecordConfig{SubDomain: "sub"}, + dc: dc, + n: "@", + expectedRec: &models.RecordConfig{ + SubDomain: "sub", + Name: "sub", + NameRaw: "sub", + NameUnicode: "sub", + NameFQDN: "sub.example.com", + NameFQDNRaw: "sub.example.com", + NameFQDNUnicode: "sub.example.com", + }, + }, + { + name: "extend_label", + rec: &models.RecordConfig{SubDomain: "sub"}, + dc: dc, + n: "www", + expectedRec: &models.RecordConfig{ + SubDomain: "sub", + Name: "www.sub", + NameRaw: "www.sub", + NameUnicode: "www.sub", + NameFQDN: "www.sub.example.com", + NameFQDNRaw: "www.sub.example.com", + NameFQDNUnicode: "www.sub.example.com", + }, + }, + { + name: "extend_idn_subdomain", + rec: &models.RecordConfig{SubDomain: "bücher"}, + dc: dc, + n: "www", + expectedRec: &models.RecordConfig{ + SubDomain: "bücher", + Name: "www.xn--bcher-kva", + NameRaw: "www.bücher", + NameUnicode: "www.bücher", + NameFQDN: "www.xn--bcher-kva.example.com", + NameFQDNRaw: "www.bücher.example.com", + NameFQDNUnicode: "www.bücher.example.com", + }, + }, + { + name: "extend_idn_label", + rec: &models.RecordConfig{SubDomain: "sub"}, + dc: dc, + n: "bücher", + expectedRec: &models.RecordConfig{ + SubDomain: "sub", + Name: "xn--bcher-kva.sub", + NameRaw: "bücher.sub", + NameUnicode: "bücher.sub", + NameFQDN: "xn--bcher-kva.sub.example.com", + NameFQDNRaw: "bücher.sub.example.com", + NameFQDNUnicode: "bücher.sub.example.com", + }, + }, + { + name: "extend_idn_subdomain_and_label", + rec: &models.RecordConfig{SubDomain: "bücher"}, + dc: dc, + n: "könig", + expectedRec: &models.RecordConfig{ + SubDomain: "bücher", + Name: "xn--knig-5qa.xn--bcher-kva", + NameRaw: "könig.bücher", + NameUnicode: "könig.bücher", + NameFQDN: "xn--knig-5qa.xn--bcher-kva.example.com", + NameFQDNRaw: "könig.bücher.example.com", + NameFQDNUnicode: "könig.bücher.example.com", + }, + }, + { + name: "extend_idn_domain_and_subdomain", + rec: &models.RecordConfig{SubDomain: "bücher"}, + dc: dcIDN, + n: "www", + expectedRec: &models.RecordConfig{ + SubDomain: "bücher", + Name: "www.xn--bcher-kva", + NameRaw: "www.bücher", + NameUnicode: "www.bücher", + NameFQDN: "www.xn--bcher-kva.xn--bcher-kva.com", + NameFQDNRaw: "www.bücher.bücher.com", + NameFQDNUnicode: "www.bücher.bücher.com", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setRecordNames(tt.rec, tt.dc, tt.n) + if tt.rec.Name != tt.expectedRec.Name { + t.Errorf("Name: got %q, want %q", tt.rec.Name, tt.expectedRec.Name) + } + if tt.rec.NameRaw != tt.expectedRec.NameRaw { + t.Errorf("NameRaw: got %q, want %q", tt.rec.NameRaw, tt.expectedRec.NameRaw) + } + if tt.rec.NameUnicode != tt.expectedRec.NameUnicode { + t.Errorf("NameUnicode: got %q, want %q", tt.rec.NameUnicode, tt.expectedRec.NameUnicode) + } + if tt.rec.NameFQDN != tt.expectedRec.NameFQDN { + t.Errorf("NameFQDN: got %q, want %q", tt.rec.NameFQDN, tt.expectedRec.NameFQDN) + } + if tt.rec.NameFQDNRaw != tt.expectedRec.NameFQDNRaw { + t.Errorf("NameFQDNRaw: got %q, want %q", tt.rec.NameFQDNRaw, tt.expectedRec.NameFQDNRaw) + } + if tt.rec.NameFQDNUnicode != tt.expectedRec.NameFQDNUnicode { + t.Errorf("NameFQDNUnicode: got %q, want %q", tt.rec.NameFQDNUnicode, tt.expectedRec.NameFQDNUnicode) + } + }) + } +} diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go index 6ed6c6284..a4b7b17c1 100644 --- a/providers/bind/bindProvider.go +++ b/providers/bind/bindProvider.go @@ -222,7 +222,6 @@ func ParseZoneContents(content string, zoneName string, zonefileName string) (mo } rec = *prec rec.TTL = rr.Header().Ttl - fmt.Printf("DEBUG: RP record parsed as %+v\n", rec) default: // Legacy types: rec, err = models.RRtoRCTxtBug(rr, zoneName) diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go index cfc3ae029..81361ce74 100644 --- a/providers/cloudflare/cloudflareProvider.go +++ b/providers/cloudflare/cloudflareProvider.go @@ -73,14 +73,8 @@ func init() { RecordAuditor: AuditRecords, } providers.RegisterDomainServiceProviderType(providerName, fns, features) - //providers.RegisterCustomRecordType("CF_REDIRECT", providerName, "") - //providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", providerName, "") providers.RegisterCustomRecordType("CF_WORKER_ROUTE", providerName, "") providers.RegisterMaintainer(providerName, providerMaintainer) - - // providers.SupportedRecordTypes(provderName, - // "CLOUDFLAREAPI_SINGLE_REDIRECT", - // ) } // cloudflareProvider is the handle for API calls. @@ -455,7 +449,6 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error { // A and CNAMEs: Validate. If null, set to default. // else: Make sure it wasn't set. Set to default. // iterate backwards so first defined page rules have highest priority - //prPriority := 0 for i := len(dc.Records) - 1; i >= 0; i-- { rec := dc.Records[i] if rec.Metadata == nil { @@ -605,8 +598,7 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS parsedMeta := &struct { IPConversions string `json:"ip_conversions"` IgnoredLabels []string `json:"ignored_labels"` - //ManageRedirects bool `json:"manage_redirects"` // Old-style PAGE_RULE-based redirects - ManageWorkers bool `json:"manage_workers"` + ManageWorkers bool `json:"manage_workers"` // ManageSingleRedirects bool `json:"manage_single_redirects"` // New-style Dynamic "Single Redirects" TranscodeLogFilename string `json:"transcode_log"` // Log the PAGE_RULE conversions. @@ -616,7 +608,6 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS return nil, err } api.manageSingleRedirects = parsedMeta.ManageSingleRedirects - //api.manageRedirects = parsedMeta.ManageRedirects api.tcLogFilename = parsedMeta.TranscodeLogFilename api.manageWorkers = parsedMeta.ManageWorkers // ignored_labels: diff --git a/providers/cloudflare/rtypes/cfsingleredirect/from.go b/providers/cloudflare/rtypes/cfsingleredirect/from.go deleted file mode 100644 index 168aa818b..000000000 --- a/providers/cloudflare/rtypes/cfsingleredirect/from.go +++ /dev/null @@ -1,90 +0,0 @@ -package cfsingleredirect - -// // MakePageRule updates a RecordConfig to be a PAGE_RULE using PAGE_RULE data. -// func MakePageRule(rc *models.RecordConfig, priority int, code uint16, when, then string) error { -// if rc == nil { -// return errors.New("RecordConfig cannot be nil") -// } -// if when == "" || then == "" { -// return errors.New("when and then parameters cannot be empty") -// } - -// display := mkPageRuleBlob(priority, code, when, then) - -// rc.Type = "PAGE_RULE" -// rc.TTL = 1 -// rc.CloudflareRedirect = &models.CloudflareSingleRedirectConfig{ -// Code: code, -// // -// PRWhen: when, -// PRThen: then, -// PRPriority: priority, -// PRDisplay: display, -// } -// return rc.SetTarget(display) -// } - -// // mkPageRuleBlob creates the 1,301,when,then string used in displays. -// func mkPageRuleBlob(priority int, code uint16, when, then string) string { -// return fmt.Sprintf("%03d,%03d,%s,%s", priority, code, when, then) -// } - -// func MakeSingleRedirectFromAPI(rc *models.RecordConfig, code uint16, name, when, then string) error { -// return rtypecontrol.Func["CLOUDFLAREAPI_SINGLE_REDIRECT"].FromArgs(rc, []any{name, code, when, then}) -// } - -// // MakeSingleRedirectFromAPI updatese a RecordConfig to be a SINGLEREDIRECT using data downloaded via the API. -// func MakeSingleRedirectFromAPI(rc *models.RecordConfig, code uint16, name, when, then string) error { -// // The target is the same as the name. It is the responsibility of the record creator to name it something diffable. -// target := targetFromAPIData(name, code, when, then) - -// rc.CloudflareRedirect = &CloudflareSingleRedirectConfig{ -// Code: code, -// // -// PRWhen: "UNKNOWABLE", -// PRThen: "UNKNOWABLE", -// PRPriority: 0, -// PRDisplay: "UNKNOWABLE", -// // -// SRName: name, -// SRWhen: when, -// SRThen: then, -// SRDisplay: target, -// } -// return rc.SetTarget(rc.CloudflareRedirect.SRDisplay) -// } - -// // targetFromAPIData creates the display text used for a Redirect as received from Cloudflare's API. -// func targetFromAPIData(name string, code uint16, when, then string) string { -// return fmt.Sprintf("%s code=(%03d) when=(%s) then=(%s)", -// name, -// code, -// when, -// then, -// ) -// } - -// // makeSingleRedirectFromConvert updates a RecordConfig to be a SINGLEREDIRECT using data from a PAGE_RULE conversion. -// func makeSingleRedirectFromConvert(rc *models.RecordConfig, -// priority int, -// prWhen, prThen string, -// code uint16, -// srName, srWhen, srThen string, -// ) error { -// srDisplay := targetFromConverted(priority, code, prWhen, prThen, srWhen, srThen) - -// sr := rc.CloudflareRedirect -// sr.Code = code - -// sr.SRName = srName -// sr.SRWhen = srWhen -// sr.SRThen = srThen -// sr.SRDisplay = srDisplay - -// return rc.SetTarget(rc.CloudflareRedirect.SRDisplay) -// } - -// // targetFromConverted makes the display text used when a redirect was the result of converting a PAGE_RULE. -// func targetFromConverted(prPriority int, code uint16, prWhen, prThen, srWhen, srThen string) string { -// return fmt.Sprintf("%03d,%03d,%s,%s code=(%03d) when=(%s) then=(%s)", prPriority, code, prWhen, prThen, code, srWhen, srThen) -// }