From 744a75b25d69b958b35f82faa1b55c58b86924d6 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sat, 16 May 2026 21:32:09 +0800 Subject: [PATCH] checker: resolve relative NS labels using the zone origin NS hostnames stored in happyDomain's abstract.Origin service are encoded relative to the zone apex (e.g. "ns0" instead of "ns0.example.com."). normalizeNSList was calling dns.Fqdn() directly, turning "ns0" into the useless single-label "ns0." rather than the correct FQDN. Expose domain_name in ServiceOpts so happyDomain auto-fills the zone apex at service scope, then use sdk.JoinRelative in normalizeNSList to absolutize relative labels. Absolute FQDNs (trailing dot) are kept as-is so external nameservers round-trip safely. --- checker/collect.go | 10 +++++++--- checker/collect_test.go | 8 +++++--- checker/definition.go | 5 +++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/checker/collect.go b/checker/collect.go index 05134da..c0a1887 100644 --- a/checker/collect.go +++ b/checker/collect.go @@ -32,7 +32,7 @@ func (p *authoritativeConsistencyProvider) Collect(ctx context.Context, opts sdk data := &ObservationData{ Zone: dns.Fqdn(zone), HasSOA: svc.SOA != nil, - DeclaredNS: normalizeNSList(svc.NameServers), + DeclaredNS: normalizeNSList(svc.NameServers, zone), Results: map[string]*NSResult{}, } if svc.SOA != nil { @@ -167,13 +167,17 @@ func loadZone(opts sdk.CheckerOptions, svc *originService) (string, error) { return "", fmt.Errorf("no zone name provided (missing 'domain_name' option and SOA header)") } -func normalizeNSList(ns []*dns.NS) []string { +func normalizeNSList(ns []*dns.NS, origin string) []string { out := make([]string, 0, len(ns)) for _, n := range ns { if n == nil { continue } - out = append(out, strings.ToLower(dns.Fqdn(n.Ns))) + name := n.Ns + if !strings.HasSuffix(name, ".") { + name = sdk.JoinRelative(name, strings.TrimSuffix(origin, ".")) + } + out = append(out, strings.ToLower(dns.Fqdn(name))) } sort.Strings(out) return out diff --git a/checker/collect_test.go b/checker/collect_test.go index 94db089..e9b8bed 100644 --- a/checker/collect_test.go +++ b/checker/collect_test.go @@ -80,13 +80,15 @@ func TestDiffStringSets_Equal(t *testing.T) { } func TestNormalizeNSList(t *testing.T) { + // Relative labels (no trailing dot) are joined with the zone origin. + // Absolute FQDNs (trailing dot) are kept as-is. in := []*dns.NS{ - {Ns: "NS2.Example.COM"}, + {Ns: "ns2"}, nil, {Ns: "ns1.example.com."}, - {Ns: "NS1.example.com"}, + {Ns: "ns1"}, } - got := normalizeNSList(in) + got := normalizeNSList(in, "example.com.") want := []string{"ns1.example.com.", "ns1.example.com.", "ns2.example.com."} if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) diff --git a/checker/definition.go b/checker/definition.go index 32798e9..29a339f 100644 --- a/checker/definition.go +++ b/checker/definition.go @@ -110,6 +110,11 @@ func (p *authoritativeConsistencyProvider) Definition() *sdk.CheckerDefinition { Label: "Origin service", AutoFill: sdk.AutoFillService, }, + { + Id: "domain_name", + Label: "Zone name", + AutoFill: sdk.AutoFillDomainName, + }, }, }, Rules: Rules(),