From 3e056e4f8d1d1a714090996d80d428b8a9acaf95 Mon Sep 17 00:00:00 2001 From: "[Thomas Green]" Date: Tue, 7 Apr 2026 11:50:21 +0200 Subject: [PATCH 1/9] Update CNAME resolution - `Zonemaster::Engine::Recursor::_resolve_cname()` now returns undef when the CNAME resolution fails, along with a new message tag: `CNAME_UNRESOLVABLE` - Update unit tests: move the content of scenario `TOO-LONG-CNAME-CHAIN` to a new scenario `TOO-MANY-CNAME`, and update scenario `TOO-LONG-CNAME-CHAIN` to check for tag `CNAME_CHAIN_TOO_LONG` instead --- lib/Zonemaster/Engine/Recursor.pm | 10 +++++----- share/profile.json | 1 + share/profile.yaml | 1 + t/recursor-A.t | 23 ++++++++++++++++++++--- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/Zonemaster/Engine/Recursor.pm b/lib/Zonemaster/Engine/Recursor.pm index c6fdf16a8..4c0719dae 100644 --- a/lib/Zonemaster/Engine/Recursor.pm +++ b/lib/Zonemaster/Engine/Recursor.pm @@ -268,7 +268,7 @@ sub _resolve_cname { return ( undef, $state ); } - # Safe-guard against anormaly long consecutive CNAME chains; no need to recurse + # Safe-guard against anormaly long consecutive CNAME lookups; no need to recurse $state->{tseen}{lc( $target )} = 1; $state->{tcount} += 1; @@ -282,12 +282,12 @@ sub _resolve_cname { Zonemaster::Engine->logger->add( CNAME_FOLLOWED_OUT_OF_ZONE => { name => $name, target => $target } ); ( $p, $state ) = $class->_recurse( $target, $type, $dns_class, { ns => [ root_servers() ], count => 0, common => 0, seen => {}, tseen => $state->{tseen}, tcount => $state->{tcount}, glue => {}, in_progress => $state->{in_progress} }); - } - else { - # What do do here? + return ( $p, $state ); } - return ( $p, $state ); + # Catch-all; unforeseen problem in CNAME resolution + Zonemaster::Engine->logger->add( CNAME_UNRESOLVABLE => { name => $name, type => $type, target => $target } ); + return ( undef, $state ); } sub _recurse { diff --git a/share/profile.json b/share/profile.json index ebf832271..8066689c1 100644 --- a/share/profile.json +++ b/share/profile.json @@ -479,6 +479,7 @@ "CNAME_RECORDS_MULTIPLE_FOR_NAME" : "ERROR", "CNAME_RECORDS_TOO_MANY" : "ERROR", "CNAME_START" : "DEBUG", + "CNAME_UNRESOLVABLE" : "ERROR", "DEPENDENCY_VERSION" : "DEBUG", "EMPTY_RETURN" : "DEBUG3", "EXTERNAL_RESPONSE" : "DEBUG3", diff --git a/share/profile.yaml b/share/profile.yaml index 8ed2e59ee..76cd00815 100644 --- a/share/profile.yaml +++ b/share/profile.yaml @@ -541,6 +541,7 @@ test_levels: CNAME_RECORDS_MULTIPLE_FOR_NAME: ERROR CNAME_RECORDS_TOO_MANY: ERROR CNAME_START: DEBUG + CNAME_UNRESOLVABLE: ERROR DEPENDENCY_VERSION: DEBUG EMPTY_RETURN: DEBUG3 EXTERNAL_RESPONSE: DEBUG3 diff --git a/t/recursor-A.t b/t/recursor-A.t index 5781a27c0..76c8500fb 100644 --- a/t/recursor-A.t +++ b/t/recursor-A.t @@ -159,7 +159,7 @@ subtest 'LOOPED-CNAME-IN-ZONE-1' => sub { Zonemaster::Engine->logger->clear_history; my $p = Zonemaster::Engine->recurse( 'looped-cname-in-zone-1.cname.recursor.engine.xa' ); is( $p, undef, "undefined as expected"); - + my %res = map { $_->tag => $_ } @{ Zonemaster::Engine->logger->entries }; ok( $res{CNAME_START}, q{should emit CNAME_START} ); ok( $res{CNAME_LOOP_INNER}, q{should emit CNAME_LOOP_INNER} ); @@ -185,7 +185,7 @@ subtest 'LOOPED-CNAME-IN-ZONE-3' => sub { ok( $res{CNAME_LOOP_INNER}, q{should emit CNAME_LOOP_INNER} ); }; -subtest 'LOOPED-CNAME-OUT-OF-ZONE' => sub { +subtest 'LOOPED-CNAME-OUT-OF-ZONE' => sub { Zonemaster::Engine->logger->clear_history; my $p = Zonemaster::Engine->recurse( 'looped-cname-out-of-zone.sub2.cname.recursor.engine.xa' ); is( $p, undef, "undefined as expected"); @@ -197,7 +197,18 @@ subtest 'LOOPED-CNAME-OUT-OF-ZONE' => sub { subtest 'TOO-LONG-CNAME-CHAIN' => sub { Zonemaster::Engine->logger->clear_history; - my $p = Zonemaster::Engine->recurse( 'too-long-cname-chain.cname.recursor.engine.xa' ); + + my $p = Zonemaster::Engine->recurse( 'too-long-cname-chain.sub2.cname.recursor.engine.xa' ); + is( $p, undef, "undefined as expected"); + + my %res = map { $_->tag => $_ } @{ Zonemaster::Engine->logger->entries }; + ok( $res{CNAME_START}, q{should emit CNAME_START} ); + ok( $res{CNAME_CHAIN_TOO_LONG}, q{should emit CNAME_CHAIN_TOO_LONG} ); +}; + +subtest 'TOO-MANY-CNAME' => sub { + Zonemaster::Engine->logger->clear_history; + my $p = Zonemaster::Engine->recurse( 'too-many-cname.cname.recursor.engine.xa' ); is( $p, undef, "undefined as expected"); my %res = map { $_->tag => $_ } @{ Zonemaster::Engine->logger->entries }; @@ -259,4 +270,10 @@ if ( $ENV{ZONEMASTER_RECORD} ) { Zonemaster::Engine::Nameserver->save( $datafile ); } +TODO: { + local $TODO = "Need to create zones with those errors: "; + my @missing_tags = qw( CNAME_UNRESOLVABLE ); + warn $TODO, "\n\t", join("\n\t", @missing_tags), "\n"; +} + done_testing; From bf3d3074e6e48f407898857a57db30408b0b82cb Mon Sep 17 00:00:00 2001 From: tgreenx <96772376+tgreenx@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:21:34 +0200 Subject: [PATCH 2/9] Apply suggestions from code review Co-authored-by: Marc van der Wal <103426270+marc-vanderwal@users.noreply.github.com> --- lib/Zonemaster/Engine/Recursor.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Zonemaster/Engine/Recursor.pm b/lib/Zonemaster/Engine/Recursor.pm index 4c0719dae..5d935dfe7 100644 --- a/lib/Zonemaster/Engine/Recursor.pm +++ b/lib/Zonemaster/Engine/Recursor.pm @@ -268,7 +268,7 @@ sub _resolve_cname { return ( undef, $state ); } - # Safe-guard against anormaly long consecutive CNAME lookups; no need to recurse + # Safeguard against abnormally long consecutive CNAME lookups; no need to recurse $state->{tseen}{lc( $target )} = 1; $state->{tcount} += 1; From 917cba3b6a9c80b61f70dd0a18089ec202a6d9da Mon Sep 17 00:00:00 2001 From: "[Thomas Green]" Date: Tue, 12 May 2026 15:55:32 +0200 Subject: [PATCH 3/9] Update unit test --- t/recursor-A.t | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/t/recursor-A.t b/t/recursor-A.t index 76c8500fb..399d6777d 100644 --- a/t/recursor-A.t +++ b/t/recursor-A.t @@ -266,14 +266,18 @@ subtest 'EXTRA-CNAME-IN-ANSWER' => sub { ok( scalar ( grep { index($_->tag, 'CNAME') != -1 } @{ Zonemaster::Engine->logger->entries } ) == 0, 'empty CNAME message tags' ); }; +subtest 'UNRESOLVABLE-CNAME' => sub { + Zonemaster::Engine->logger->clear_history; + my $p = Zonemaster::Engine->recurse( 'unresolvable-cname.cname.recursor.engine.xa' ); + is( $p, undef ); + + my %res = map { $_->tag => $_ } @{ Zonemaster::Engine->logger->entries }; + ok( $res{CNAME_START}, q{should emit CNAME_START} ); + ok( $res{CNAME_UNRESOLVABLE}, q{should emit CNAME_UNRESOLVABLE} ); +}; + if ( $ENV{ZONEMASTER_RECORD} ) { Zonemaster::Engine::Nameserver->save( $datafile ); } -TODO: { - local $TODO = "Need to create zones with those errors: "; - my @missing_tags = qw( CNAME_UNRESOLVABLE ); - warn $TODO, "\n\t", join("\n\t", @missing_tags), "\n"; -} - done_testing; From bf674f0c9bdecdea4ea4ee66eb34b8a31c619d94 Mon Sep 17 00:00:00 2001 From: "[Thomas Green]" Date: Tue, 9 Jun 2026 17:14:01 +0200 Subject: [PATCH 4/9] Handle NODATA responses for in-bailiwick names --- lib/Zonemaster/Engine/Recursor.pm | 37 +++++++++++++++++++----------- share/profile.json | 1 + share/profile.yaml | 1 + t/recursor-A.data | Bin 22533 -> 43022 bytes t/recursor-A.t | 16 ++++++++++--- 5 files changed, 38 insertions(+), 17 deletions(-) diff --git a/lib/Zonemaster/Engine/Recursor.pm b/lib/Zonemaster/Engine/Recursor.pm index 5d935dfe7..984857b11 100644 --- a/lib/Zonemaster/Engine/Recursor.pm +++ b/lib/Zonemaster/Engine/Recursor.pm @@ -249,19 +249,6 @@ sub _resolve_cname { return ( undef, $state ); } - # Check if there are RRs of queried type (QTYPE) in the answer section of the response; - if ( scalar $p->get_records( $type, 'answer' ) ) { - # RR of type QTYPE for CNAME target is already in the response; no need to recurse - if ( $p->has_rrs_of_type_for_name( $type, $target ) ) { - Zonemaster::Engine->logger->add( CNAME_FOLLOWED_IN_ZONE => { name => $name, type => $type, target => $target } ); - return ( $p, $state ); - } - - # There is a record of type QTYPE but with different owner name than CNAME target; no need to recurse - Zonemaster::Engine->logger->add( CNAME_NO_MATCH => { name => $name, type => $type, target => $target, owner_names => join( ';', map { $_->owner } $p->get_records( $type ) ) } ); - return ( undef, $state ); - } - # CNAME target has already been followed (outer loop); no need to recurse if ( exists $state->{in_progress}{lc( $target )}{$type} ) { Zonemaster::Engine->logger->add( CNAME_LOOP_OUTER => { name => $name, target => $target, targets_seen => join( ';', keys %{ $state->{tseen} } ) } ); @@ -277,13 +264,35 @@ sub _resolve_cname { return ( undef, $state ); } - # Make sure that the CNAME target is out of zone before making a new recursive lookup for CNAME target + # Check if there are RRs of queried type (QTYPE) in the answer section of the response; + if ( scalar $p->get_records( $type, 'answer' ) ) { + # RR of type QTYPE for CNAME target is already in the response; no need to recurse + if ( $p->has_rrs_of_type_for_name( $type, $target ) ) { + Zonemaster::Engine->logger->add( CNAME_FOLLOWED_IN_ZONE => { name => $name, type => $type, target => $target } ); + return ( $p, $state ); + } + + # There is a record of type QTYPE but with different owner name than CNAME target; we have to recurse again + Zonemaster::Engine->logger->add( CNAME_NO_MATCH => { name => $name, type => $type, target => $target, owner_names => join( ';', map { $_->owner } $p->get_records( $type ) ) } ); + } + + # CNAME target is out of zone, so make a new recursive lookup unless ( $name->is_in_bailiwick( $target ) ) { Zonemaster::Engine->logger->add( CNAME_FOLLOWED_OUT_OF_ZONE => { name => $name, target => $target } ); ( $p, $state ) = $class->_recurse( $target, $type, $dns_class, { ns => [ root_servers() ], count => 0, common => 0, seen => {}, tseen => $state->{tseen}, tcount => $state->{tcount}, glue => {}, in_progress => $state->{in_progress} }); return ( $p, $state ); } + # Final attempt to resolve the CNAME target for in-bailiwick names + else { + ( $p, $state ) = $class->_recurse( $target, $type, $dns_class, + { ns => [ root_servers() ], count => 0, common => 0, seen => {}, tseen => $state->{tseen}, tcount => $state->{tcount}, glue => {}, in_progress => $state->{in_progress} }); + + if ( $p and $p->aa and $p->rcode eq 'NOERROR' ) { + Zonemaster::Engine->logger->add( CNAME_TO_NODATA => { name => $name, type => $type, target => $target } ); + return ( $p, $state ); + } + } # Catch-all; unforeseen problem in CNAME resolution Zonemaster::Engine->logger->add( CNAME_UNRESOLVABLE => { name => $name, type => $type, target => $target } ); diff --git a/share/profile.json b/share/profile.json index 8066689c1..57de188bb 100644 --- a/share/profile.json +++ b/share/profile.json @@ -479,6 +479,7 @@ "CNAME_RECORDS_MULTIPLE_FOR_NAME" : "ERROR", "CNAME_RECORDS_TOO_MANY" : "ERROR", "CNAME_START" : "DEBUG", + "CNAME_TO_NODATA" : "DEBUG", "CNAME_UNRESOLVABLE" : "ERROR", "DEPENDENCY_VERSION" : "DEBUG", "EMPTY_RETURN" : "DEBUG3", diff --git a/share/profile.yaml b/share/profile.yaml index 76cd00815..817408b73 100644 --- a/share/profile.yaml +++ b/share/profile.yaml @@ -541,6 +541,7 @@ test_levels: CNAME_RECORDS_MULTIPLE_FOR_NAME: ERROR CNAME_RECORDS_TOO_MANY: ERROR CNAME_START: DEBUG + CNAME_TO_NODATA: DEBUG CNAME_UNRESOLVABLE: ERROR DEPENDENCY_VERSION: DEBUG EMPTY_RETURN: DEBUG3 diff --git a/t/recursor-A.data b/t/recursor-A.data index e6d5c13438875adb68d9de079ba6bc68f951ec88..cbc5b57cb480f507f2da1e5298e3c5508335d372 100644 GIT binary patch literal 43022 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 R_FBhI=Eg?T`NgX>{|A3b_!$5I literal 22533 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 sub { subtest 'TARGET-NO-MATCH-CNAME' => sub { Zonemaster::Engine->logger->clear_history; my $p = Zonemaster::Engine->recurse( 'target-no-match-cname.cname.recursor.engine.xa' ); - is( $p, undef, "undefined as expected"); + isa_ok( $p, 'Zonemaster::Engine::Packet' ); my %res = map { $_->tag => $_ } @{ Zonemaster::Engine->logger->entries }; ok( $res{CNAME_START}, q{should emit CNAME_START} ); @@ -266,10 +266,20 @@ subtest 'EXTRA-CNAME-IN-ANSWER' => sub { ok( scalar ( grep { index($_->tag, 'CNAME') != -1 } @{ Zonemaster::Engine->logger->entries } ) == 0, 'empty CNAME message tags' ); }; -subtest 'UNRESOLVABLE-CNAME' => sub { +subtest 'CNAME-CHAIN-TO-NODATA' => sub { + Zonemaster::Engine->logger->clear_history; + my $p = Zonemaster::Engine->recurse( 'cname-chain-to-nodata.cname.recursor.engine.xa' ); + isa_ok( $p, 'Zonemaster::Engine::Packet' ); + + my %res = map { $_->tag => $_ } @{ Zonemaster::Engine->logger->entries }; + ok( $res{CNAME_START}, q{should emit CNAME_START} ); + ok( $res{CNAME_TO_NODATA}, q{should emit CNAME_TO_NODATA} ); +}; + +subtest 'CNAME-UNRESOLVABLE' => sub { Zonemaster::Engine->logger->clear_history; my $p = Zonemaster::Engine->recurse( 'unresolvable-cname.cname.recursor.engine.xa' ); - is( $p, undef ); + is( $p, undef, "undefined as expected"); my %res = map { $_->tag => $_ } @{ Zonemaster::Engine->logger->entries }; ok( $res{CNAME_START}, q{should emit CNAME_START} ); From f7d10eb66a726255f897b5a4e1d68d91a1e788de Mon Sep 17 00:00:00 2001 From: "[Thomas Green]" Date: Tue, 1 Sep 2026 19:04:04 +0200 Subject: [PATCH 5/9] Address review comments: fix case-sensitiveness of CNAME records, documentation updates; Re-record unit tests --- lib/Zonemaster/Engine/Recursor.pm | 34 +++++++++++++++++------------- t/recursor-A.data | Bin 43022 -> 43022 bytes 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/Zonemaster/Engine/Recursor.pm b/lib/Zonemaster/Engine/Recursor.pm index 984857b11..9abba61a8 100644 --- a/lib/Zonemaster/Engine/Recursor.pm +++ b/lib/Zonemaster/Engine/Recursor.pm @@ -214,24 +214,24 @@ sub _resolve_cname { my ( %cnames, %seen_targets, %forbidden_targets ); for my $rr ( @cname_rrs ) { - my $rr_owner = name( $rr->owner ); - my $rr_target = name( $rr->cname ); + my $rr_owner = lc( name( $rr->owner ) ); + my $rr_target = lc( name( $rr->cname ) ); # Multiple CNAME records with same owner name - if ( exists $forbidden_targets{lc( $rr_owner )} ) { + if ( exists $forbidden_targets{$rr_owner} ) { Zonemaster::Engine->logger->add( CNAME_RECORDS_MULTIPLE_FOR_NAME => { name => $rr_owner } ); return ( undef, $state ); } # CNAME owner name is target, or target has already been seen in this response, or owner name cannot be a target - if ( lc( $rr_owner ) eq lc( $rr_target ) or exists $seen_targets{lc( $rr_target )} or grep { $_ eq lc( $rr_target ) } ( keys %forbidden_targets ) ) { + if ( $rr_owner eq $rr_target or exists $seen_targets{$rr_target} or grep { $_ eq $rr_target } ( keys %forbidden_targets ) ) { Zonemaster::Engine->logger->add( CNAME_LOOP_INNER => { name => join( ';', map { $_->owner } @cname_rrs ), target => join( ';', map { $_->cname } @cname_rrs ) } ); return ( undef, $state ); } - $seen_targets{lc( $rr_target )} = 1; - $forbidden_targets{lc( $rr_owner )} = 1; - $cnames{$rr_owner} = $rr_target; + $seen_targets{$rr_target} = 1; + $forbidden_targets{$rr_owner} = 1; + $cnames{$rr_owner} = name( $rr->cname ); # Preserve case of $rr_target } # Get final CNAME target @@ -249,7 +249,7 @@ sub _resolve_cname { return ( undef, $state ); } - # CNAME target has already been followed (outer loop); no need to recurse + # CNAME target has already been followed previously (outer loop); no need to recurse if ( exists $state->{in_progress}{lc( $target )}{$type} ) { Zonemaster::Engine->logger->add( CNAME_LOOP_OUTER => { name => $name, target => $target, targets_seen => join( ';', keys %{ $state->{tseen} } ) } ); return ( undef, $state ); @@ -276,14 +276,14 @@ sub _resolve_cname { Zonemaster::Engine->logger->add( CNAME_NO_MATCH => { name => $name, type => $type, target => $target, owner_names => join( ';', map { $_->owner } $p->get_records( $type ) ) } ); } - # CNAME target is out of zone, so make a new recursive lookup + # CNAME target is not in-domain, so make a new recursive lookup unless ( $name->is_in_bailiwick( $target ) ) { Zonemaster::Engine->logger->add( CNAME_FOLLOWED_OUT_OF_ZONE => { name => $name, target => $target } ); ( $p, $state ) = $class->_recurse( $target, $type, $dns_class, { ns => [ root_servers() ], count => 0, common => 0, seen => {}, tseen => $state->{tseen}, tcount => $state->{tcount}, glue => {}, in_progress => $state->{in_progress} }); return ( $p, $state ); } - # Final attempt to resolve the CNAME target for in-bailiwick names + # Final attempt to resolve the CNAME target for in-domain names else { ( $p, $state ) = $class->_recurse( $target, $type, $dns_class, { ns => [ root_servers() ], count => 0, common => 0, seen => {}, tseen => $state->{tseen}, tcount => $state->{tcount}, glue => {}, in_progress => $state->{in_progress} }); @@ -654,9 +654,9 @@ Returns a list of L objects. my ( $p, $state_hash ) = _resolve_cname( $name, $type_string, $dns_class_string, $p, $state_hash ); -Performs CNAME resolution for the given arguments. Used by the L helper method in this module. -If CNAMEs are successfully resolved, a L (which could be C) is returned and -one of the following message tags is logged: +Performs a CNAME resolution for the given arguments. Used by the L helper method in this module. +If CNAMEs are successfully resolved, the first return value will be a L (which could in +turn be C) and one of the following message tags is logged: =over @@ -664,10 +664,12 @@ one of the following message tags is logged: =item CNAME_FOLLOWED_OUT_OF_ZONE +=item CNAME_TO_NODATA + =back -Note that CNAME records are also validated and, in case of an error, an empty (C) L -is returned and one of the following message tags will be logged: +Note that the resolution has multiple validation steps and, in case of an error, the first return value will be C and one of the +following message tags is logged: =over @@ -685,6 +687,8 @@ is returned and one of the following message tags will be logged: =item CNAME_RECORDS_TOO_MANY +=item CNAME_UNRESOLVABLE + =back Takes a L object, a string (query type), a string (DNS class), a L, and a reference to a hash. diff --git a/t/recursor-A.data b/t/recursor-A.data index cbc5b57cb480f507f2da1e5298e3c5508335d372..07f042b2669e744941606f7b0d5498e82b6d6f4f 100644 GIT binary patch delta 2758 zcmY*bX;4#F6b33HE@5#2L?lrJ3&a3kmskzH`q#cggpC_f_7vsl0D9 zw5{#$+5}bh^2}`2!hBWU+RU8nnEbGSpn#zIUar=XlT9-1m!VdrbEaC6ALLp`!bH8$NIO^Q1_`6)B`ZohdhN6i4dyFAlE?%_s@q6M+adCZ9CQ7k9=Ud{pulEsR&h9e*e6;Yt>+Pn9`}2ZyU%XJ={$SqLPaKwi zM~0Dkx|`Se&&P=AH52)i05Luu&zA&>amzrS3ld_s_eH)fSd1ri@U|g7)&t09*_>9Z z($c8xwPPVS)PikRHr$BWK$R%jowSoWpr}k)uDcPF2mU@B8Q1EvvdOd zveygde345_nBvhy4{}(vISdL)yr3`~(;B-`qD(qyl_u{U-x=a2Y7YtDBLpZw1EgpsIJ;0HQgW^(@7LH0~W^lz@@!XD42mKVhbWrXc2M$(MGKP zA+Lz{5gI)^!DlCUU~JevlvgK;anv=UsS22sgdMoNCqzQq;eHB2tv^|ZXvEQ72GU~^ zxL7mZG{l?>;$_L6!o|L`F-ZyPQXNUzU?M9#n{Mn%J}XryGxYtJubwRvrbJE`xhezZ zgf9fn70PpNkaPk`O^3v}xChNFQYQh~I9a*{hTk8m=Ta`JMJ zADmq9Bbsy0(H@k%oqn8Nku$h2K!fBnOC8%Yuj^VM19v zNtvu$Xn)25?YcRA0K6{(I#K(A*qYh0Z>Kx-b+h#BZM29xy^1d`4iS7$RndcFg74j< zWyYb`3DNcuGef^HG=+$^hi?&6-XPi?7HeefqRq{NXUm-7Qdt@8q1m{Gkgbrsq1b~9 zEwykwe3CSeP7%fxlY4Q7H(PNMBMs-(6|N{c?(qTMxJ`()Mn}=^T4-;>`!^O=Cxxym zTtnBsrH9>pszNI3-A44VJ5zdAhK1c>6Z~G-3=dZMLcdiqbkct6vvfuM*$blU#h6yp z|5gX;*lH7G-J2}T4=1rnffmLmq&9nsrniE;Ah>Jx>{~+9-QBQ#3i+z2fCUF})Nmky zaMTRr$MGJAgii#nXbrD8EXB*xhN+NmX79U0x6ND~LekqoeiWNAAIrO0X|_J8X;>$} zMVQ%LZ5VWgFmorNp)=GR7yh4VuX-5aky<>JuAoaa!e?|KRDq&ep-s+5XrcYJt{{i5Es1X1F delta 2963 zcmZWrYfw{H5H=_}z$AnOL?j?jj@AcuMy*;O^+j!~Xr~?8icZhjdlN$YE4%yc?%8|qx8KR`HmBWf zPJM3O>O9gkaDb@q4ma=SJ0sw7Q5=+tR=QHxdUr&x(giVatw;{il5r63RK>Ea`4`}- z)9=hQly^a5@+9cfXCHH$G_KV3gEM_DqI(obg|oX8vn>x}!7p+OJn8q0xy^2CLBD+p zc)5JZT=KXR5~8%=>iUSeT*+)$8?A+LZjFdeoD09XwKCIsY$u3z#lhPGF;u)40Vn#) zV4*mcL8kdPAW1hI+Qi?WTONXLi-dQttwXob9bI$Zlhoz=%v#6y@$tYu=I}<$tn}bB z(|4jYG2o&27CW8thP#UESUS=K+4aGY7^DVqK_HA$ZNeTm{b@J>-m2=E zYw{cqpAWgkOp;d!*NzTgL&6FF2;p}Kg!{)am`k_=sp{9vY;K=G(uM}%02Xx=*&86( z3tGwfK*26|A)UhlFvqmQER2GrFg}_{d`Ae--X}Ot^+umJrSM%HS0+bnG$ zdqV_!>Tq&)lwfZhN;*OX+n^`DVSyqsozUDN?^Rl=&Bz@Mp*nApI!20vG;_IhVJcyT zwi*}4c*$X5ieOTBHG4ek!iRKWCPHdyILxY1L&o@Q7B(#Hhs#s8)dwoJD50W64V4oR zYJ7QISR@scZH;h*nu^?@!Z`{KY>R}S#$bPo z!iQTV3V5LV7@^NZ2sOGh=njV3J4upmC?0S2bwRk&gRiXguveOAZAYjrwu@DnOAn%( zCFe7<!#bEZ8lD?y(V1JWc3MDH5| zA&FRT9X+0|R+-J^+S75hq)9i~9GvS|@;phwf9d3YRlc;o&L0Zq;2{QX5x+_*jAr>M zpk*$;2&1c+U#rznI|t7(CU%jMd3-0Xb{Ji$Sa>lXx5UtUoWNxjoT(3@Yo&ymMSRt< z`xL6NyMMl+s})Dztr)r*A{gn|lw?MO!G5_pudlZCjAov8mT)S8MR z60#WyyDtrI8f#sy!#x13$Q1tvPa<&|3|o#uYARG>s72BpI^T|=M)&Se7Yo_$0_UPAh(-Fhou}+B@XN;6WzmM(dCJ1< z4zN|M@1S?UL-t_Sm{+L(U+31M+jlA@DI%O=ChEL?{XvE_SMP>V`Hz`txQ)s_c>`V< zM)fa(&Vzov1Tr^xQO#Gt%a7zxw-GNrW6C0weFkeks9-I)lb4aF%1xhON7WI5r#AUl z!jjGSbjC|k)ZDkKI9tf%rbe#5EvRF~ByC0yj=uQN%YGdME?+EV$LVHMh1F4o)oe_L z2RjE4Q=tdX*H&k8xhOz{L_M?8MpPm#@j~3XYOElymvPYamkzROVxcD2G&@_%wqC`M zzEqIW@0n}4aD>vd5ZczuU{Kjs%y@B*=(l^~5#(GfAcW zsjD!>QDKE@^TO5qnXzfMO0zy6LyjtI%f2#{Bv%i^N4Gd%w^NswSS7v0a9T@*)=uBL z_!;)_IvM_b;k6e330fnoi2MG**pT~cnjo@M;nV?a#Cg_G9`As`D`XIT2w!jWmY=)z zR_TZsE-v}6?mno+S7hw(!qfa8_Wg{DPf|+vXv#Hg7o>L@C8`X_r+Ah@9{)cHc4@%L zjHmmP?0O9jZtB(Qpufl>GGEn$*FCkDzRh69?8vBI2`L$AYcf)n=cnZ4uF1^M<&W2d QYC;oIGjr0D^K4Pkzq=G~=>Px# From e6d7842f31d358c379b34e4134b3fc1b8ca546b7 Mon Sep 17 00:00:00 2001 From: "[Thomas Green]" Date: Tue, 22 Sep 2026 19:39:25 +0200 Subject: [PATCH 6/9] Add documentation for CNAME message tags --- lib/Zonemaster/Engine/Recursor.pm | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/Zonemaster/Engine/Recursor.pm b/lib/Zonemaster/Engine/Recursor.pm index 9abba61a8..de59a625d 100644 --- a/lib/Zonemaster/Engine/Recursor.pm +++ b/lib/Zonemaster/Engine/Recursor.pm @@ -632,7 +632,7 @@ This list can be replaced like so: my ( $p, $state_hash ) = _recurse( $name, $type_string, $dns_class_string, $p, $state_hash ); -Performs a recursive lookup resolution for the given arguments. Used by the L method in this module. +Performs a recursive lookup resolution for the given arguments. Used by the L method in this module. Takes a L object, a string (query type), a string (DNS class), a L object, and a reference to a hash. The mandatory keys for that hash are 'ns' (arrayref), 'count' (integer), 'common' (integer), 'seen' (hash), 'glue' (hash) and optional keys are 'in_progress' @@ -662,10 +662,17 @@ turn be C) and one of the following message tags is logged: =item CNAME_FOLLOWED_IN_ZONE +This message tag indicates that the CNAME target was found in the current response and has the requested record type, +so no additional recursion is needed. + =item CNAME_FOLLOWED_OUT_OF_ZONE +This message tag indicates that the CNAME target is outside the queried name's zone and is resolved through a new recursive lookup. + =item CNAME_TO_NODATA +This message tag indicates that the in-bailiwick CNAME target was resolved authoritatively but without the requested record type. + =back Note that the resolution has multiple validation steps and, in case of an error, the first return value will be C and one of the @@ -675,20 +682,36 @@ following message tags is logged: =item CNAME_CHAIN_TOO_LONG +This message tag indicates that the consecutive CNAME chain lookups are longer than the maximum allowed length. + =item CNAME_LOOP_INNER +This message tag indicates that there is a loop in the CNAME RRset of the current response. + =item CNAME_LOOP_OUTER +This message tag indicates that the CNAME target has already been followed in a previous lookup. + =item CNAME_NO_MATCH +This message tag indicates that there is a record of the requested type but with different owner name than the CNAME target. + =item CNAME_RECORDS_CHAIN_BROKEN +This message tag indicates that the CNAME chain from the RRset of the current response is broken. + =item CNAME_RECORDS_MULTIPLE_FOR_NAME +This message tag indicates that there are more than one CNAME record with the same owner name in the RRset of the current response. + =item CNAME_RECORDS_TOO_MANY +This message tag indicates that there are too many CNAME records in the RRset of the current response. + =item CNAME_UNRESOLVABLE +This message tag indicates that the CNAME resolution could not be successfully completed. + =back Takes a L object, a string (query type), a string (DNS class), a L, and a reference to a hash. From 61e08fb1e75d9327fe477a665805aa5b95e44ce5 Mon Sep 17 00:00:00 2001 From: tgreenx <96772376+tgreenx@users.noreply.github.com> Date: Tue, 22 Sep 2026 19:41:20 +0200 Subject: [PATCH 7/9] Update lib/Zonemaster/Engine/Recursor.pm Co-authored-by: Mats Dufberg --- lib/Zonemaster/Engine/Recursor.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Zonemaster/Engine/Recursor.pm b/lib/Zonemaster/Engine/Recursor.pm index de59a625d..683530853 100644 --- a/lib/Zonemaster/Engine/Recursor.pm +++ b/lib/Zonemaster/Engine/Recursor.pm @@ -276,7 +276,7 @@ sub _resolve_cname { Zonemaster::Engine->logger->add( CNAME_NO_MATCH => { name => $name, type => $type, target => $target, owner_names => join( ';', map { $_->owner } $p->get_records( $type ) ) } ); } - # CNAME target is not in-domain, so make a new recursive lookup + # CNAME target is not in the same zone as the CNAME owner name, so make a new recursive lookup unless ( $name->is_in_bailiwick( $target ) ) { Zonemaster::Engine->logger->add( CNAME_FOLLOWED_OUT_OF_ZONE => { name => $name, target => $target } ); ( $p, $state ) = $class->_recurse( $target, $type, $dns_class, From 18842f3cc12229ff6c0decc503a0a2e4511094df Mon Sep 17 00:00:00 2001 From: tgreenx <96772376+tgreenx@users.noreply.github.com> Date: Thu, 24 Sep 2026 15:37:31 +0200 Subject: [PATCH 8/9] Apply batched suggestions from code review Co-authored-by: Mats Dufberg --- lib/Zonemaster/Engine/Recursor.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Zonemaster/Engine/Recursor.pm b/lib/Zonemaster/Engine/Recursor.pm index 683530853..5627826a5 100644 --- a/lib/Zonemaster/Engine/Recursor.pm +++ b/lib/Zonemaster/Engine/Recursor.pm @@ -671,7 +671,7 @@ This message tag indicates that the CNAME target is outside the queried name's z =item CNAME_TO_NODATA -This message tag indicates that the in-bailiwick CNAME target was resolved authoritatively but without the requested record type. +This message tag indicates that the CNAME target was resolved authoritatively. The name exists but not with requested record type. =back @@ -682,7 +682,7 @@ following message tags is logged: =item CNAME_CHAIN_TOO_LONG -This message tag indicates that the consecutive CNAME chain lookups are longer than the maximum allowed length. +This message tag indicates that the resolved chain of CNAME records is longer than the maximum allowed length. =item CNAME_LOOP_INNER From 20c8b3c2a5e8aa5972b826f539541033a54035aa Mon Sep 17 00:00:00 2001 From: "[Thomas Green]" Date: Thu, 24 Sep 2026 17:49:47 +0200 Subject: [PATCH 9/9] More documentation for CNAME message tags --- lib/Zonemaster/Engine/Recursor.pm | 47 ++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/Zonemaster/Engine/Recursor.pm b/lib/Zonemaster/Engine/Recursor.pm index 5627826a5..1b2e2a5a6 100644 --- a/lib/Zonemaster/Engine/Recursor.pm +++ b/lib/Zonemaster/Engine/Recursor.pm @@ -655,8 +655,8 @@ Returns a list of L objects. my ( $p, $state_hash ) = _resolve_cname( $name, $type_string, $dns_class_string, $p, $state_hash ); Performs a CNAME resolution for the given arguments. Used by the L helper method in this module. -If CNAMEs are successfully resolved, the first return value will be a L (which could in -turn be C) and one of the following message tags is logged: +If the CNAME resolution is successful, the first return value will be a L (which could be C) +and at least one of the following message tags is logged: =over @@ -671,46 +671,61 @@ This message tag indicates that the CNAME target is outside the queried name's z =item CNAME_TO_NODATA -This message tag indicates that the CNAME target was resolved authoritatively. The name exists but not with requested record type. +This message tag indicates that the CNAME target was resolved authoritatively. The name exists but not with the requested record type. =back -Note that the resolution has multiple validation steps and, in case of an error, the first return value will be C and one of the -following message tags is logged: +Note that the resolution has multiple validation steps and, in case of an error, the first return value will be C and at least +one of the following message tags is logged: =over =item CNAME_CHAIN_TOO_LONG -This message tag indicates that the resolved chain of CNAME records is longer than the maximum allowed length. +This message tag indicates that the number of distinct lookups made to resolve a CNAME chain is longer than the maximum allowed length +with respect to L<$CNAME_MAX_CHAIN_LENGTH|Zonemaster::Engine::Constants/CNAME_MAX_CHAIN_LENGTH>. =item CNAME_LOOP_INNER -This message tag indicates that there is a loop in the CNAME RRset of the current response. +This message tag indicates that there is a loop in the CNAME chain within the same zone. =item CNAME_LOOP_OUTER -This message tag indicates that the CNAME target has already been followed in a previous lookup. - -=item CNAME_NO_MATCH - -This message tag indicates that there is a record of the requested type but with different owner name than the CNAME target. +This message tag indicates that there is a loop in the CNAME chain across different zones. =item CNAME_RECORDS_CHAIN_BROKEN -This message tag indicates that the CNAME chain from the RRset of the current response is broken. +This message tag indicates that the CNAME chain in the current response is broken, i.e. that with multiple CNAME records in a response +there is at least one CNAME record with a target that does not point to a corresponding CNAME record in the same response. =item CNAME_RECORDS_MULTIPLE_FOR_NAME -This message tag indicates that there are more than one CNAME record with the same owner name in the RRset of the current response. +This message tag indicates that there are more than one CNAME record with the same owner name in the current response. =item CNAME_RECORDS_TOO_MANY -This message tag indicates that there are too many CNAME records in the RRset of the current response. +This message tag indicates that there are too many, with respect to L<$CNAME_MAX_RECORDS|Zonemaster::Engine::Constants/CNAME_MAX_RECORDS>, +non-duplicate CNAME records in the current response. =item CNAME_UNRESOLVABLE -This message tag indicates that the CNAME resolution could not be successfully completed. +This message tag indicates that the CNAME resolution could not be completed and is there as a catch-all to handle unforeseen problems +in the CNAME resolution process. + +=back + +Finally the following message tags can be also be logged alongside all of the above message tags: + +=over + +=item CNAME_RECORDS_DUPLICATES + +This message tag indicates that there are duplicate CNAME records in the current response. + +=item CNAME_NO_MATCH + +This message tag indicates that there is a record of the requested type in the answer section of the response but with a different owner name +than the CNAME target within that same response. =back