Skip to content

Commit

Permalink
Add DNAME support, and update Basic01
Browse files Browse the repository at this point in the history
DNAME:
 - Add new attribute 'dname' to Engine::Zone objects
 - Update implementation to account for that new attribute and make queries to the appropriate delegation name, if one exists

Basic01:
 - Update to latest specification (zonemaster/zonemaster#1082)
  • Loading branch information
tgreenx committed Mar 22, 2023
1 parent 264b30a commit b2bda3d
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 33 deletions.
134 changes: 117 additions & 17 deletions lib/Zonemaster/Engine/Test/Basic.pm
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,15 @@ sub metadata {
],
basic01 => [
qw(
NO_PARENT
HAS_PARENT
B01_CHILD_IS_ALIAS
B01_CHILD_FOUND
B01_CHILD_NOT_EXIST
B01_INCONSISTENT_ALIAS
B01_INCONSISTENT_DELEGATION
B01_NO_CHILD
B01_PARENT_FOUND
B01_PARENT_UNDETERMINED
B01_UNEXPECTED_NS_RESPONSE
TEST_CASE_END
TEST_CASE_START
)
Expand Down Expand Up @@ -152,6 +159,45 @@ Readonly my %TAG_DESCRIPTIONS => (
__x # BASIC:A_QUERY_NO_RESPONSES
'Nameservers did not respond to A query.';
},
B01_CHILD_IS_ALIAS => sub {
__x # BASIC:B01_CHILD_IS_ALIAS
'"{domain_child}" is not a zone. It is an alias for "{domain_target}". Run a test for "{domain_target}" instead. '
. 'Returned from name servers "{ns_ip_list}.', @_;
},
B01_CHILD_FOUND => sub {
__x # BASIC:B01_CHILD_FOUND
'The zone "{domain}" is found.', @_;
},
B01_CHILD_NOT_EXIST => sub {
__x # BASIC:B01_CHILD_NOT_EXIST
'"{domain}" does not exist as it is not delegated.', @_;
},
B01_INCONSISTENT_ALIAS => sub {
__x # BASIC:B01_INCONSISTENT_ALIAS
'The alias for "{domain}" is inconsistent between name servers.', @_;
},
B01_INCONSISTENT_DELEGATION => sub {
__x # BASIC:B01_INCONSISTENT_DELEGATION
'The name servers for parent zone "{domain_parent}" give inconsistent delegation of "{domain_child}". '
. 'Returned from name servers "{ns_ip_list}".', @_;
},
B01_NO_CHILD => sub {
__x # BASIC:B01_NO_CHILD
'"{domain_child}" does not exist as a DNS zone. Try to test "{domain_super}" instead.', @_;
},
B01_PARENT_FOUND => sub {
__x # BASIC:B01_PARENT_FOUND
'The parent zone is "{domain}" as returned from name servers "{ns_ip_list}".', @_;
},
B01_PARENT_UNDETERMINED => sub {
__x # BASIC:B01_PARENT_UNDETERMINED
'The parent zone cannot be determined on name servers "{ns_ip_list}".', @_;
},
B01_UNEXPECTED_NS_RESPONSE => sub {
__x # BASIC:B01_UNEXPECTED_NS_RESPONSE
'Name servers for parent domain "{domain_parent}" give an incorrect response on SOA query for "{domain_child}". '
. 'Returned from name servers "{ns_ip_list}".', @_;
},
DOMAIN_NAME_LABEL_TOO_LONG => sub {
__x # BASIC:DOMAIN_NAME_LABEL_TOO_LONG
'Domain name ({domain}) has a label ({label}) too long ({dlength}/{max}).', @_;
Expand All @@ -176,10 +222,6 @@ Readonly my %TAG_DESCRIPTIONS => (
__x # BASIC:HAS_NAMESERVERS
'Nameserver {ns} listed these servers as glue: {nsnlist}.', @_;
},
HAS_PARENT => sub {
__x # BASIC:HAS_PARENT
'Parent domain \'{pname}\' was found for the tested domain.', @_;
},
IPV4_DISABLED => sub {
__x # BASIC:IPV4_DISABLED
'IPv4 is disabled, not sending "{rrtype}" query to {ns}.', @_;
Expand All @@ -204,10 +246,6 @@ Readonly my %TAG_DESCRIPTIONS => (
__x # BASIC:NO_GLUE_PREVENTS_NAMESERVER_TESTS
'No NS records for tested zone from parent. NS tests skipped.', @_;
},
NO_PARENT => sub {
__x # BASIC:NO_PARENT
'No parent domain could be found for the domain under test.', @_;
},
NS_FAILED => sub {
__x # BASIC:NS_FAILED
'Nameserver {ns} did not return NS records. RCODE was {rcode}.', @_;
Expand Down Expand Up @@ -339,23 +377,85 @@ sub basic00 {
sub basic01 {
my ( $class, $zone ) = @_;
push my @results, info( TEST_CASE_START => { testcase => (split /::/, (caller(0))[3])[-1] } );

if ( $zone->name eq '.' ) {
push @results,
info(
B01_CHILD_FOUND => {
domain => $zone->name
}
);
return ( @results, info( TEST_CASE_END => { testcase => (split /::/, (caller(0))[3])[-1] } ) );
}

my $parent = $zone->parent;

if ( not $parent ) {
push @results,
info(
NO_PARENT => {
zone => $zone->name->string,
}
B01_PARENT_UNDETERMINED => {
ns_ip_list => join( q{;}, Zonemaster::Engine::Nameserver->all_known_nameservers() )
}
);
}
else {
push @results,
info(
HAS_PARENT => {
zone => $zone->name->string,
pname => $parent->name->string,
}
B01_PARENT_FOUND => {
domain => $parent->name,
ns_ip_list => join( q{;}, @{$parent->glue} )
}
);
}

if ( scalar @{$zone->ns} ) {
push @results,
info(
B01_CHILD_FOUND => {
domain => $zone->name
}
);
}
else {
if ( Zonemaster::Engine::Recursor->has_fake_addresses( $zone->name->string ) ){
push @results,
info(
B01_CHILD_NOT_EXIST => {
domain => $zone->name
}
);
}
else {
push @results,
info(
B01_NO_CHILD => {
domain_child => $zone->name,
domain_super => $zone->name->next_higher()
}
);

}
}

if ( scalar @{$zone->dname} ) {
foreach my $dname ( @{$zone->dname} ) {
push @results,
info(
B01_CHILD_IS_ALIAS => {
domain_child => $zone->name,
domain_target => $dname->name,
ns_ip_list => join( q{;}, @{$parent->ns} )
}
);
}
}

if ( scalar @{$zone->dname} > 1 ) {
push @results,
info(
B01_INCONSISTENT_ALIAS => {
domain => $zone->name
}
);
}

Expand Down
78 changes: 68 additions & 10 deletions lib/Zonemaster/Engine/Zone.pm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package Zonemaster::Engine::Zone;

use version; our $VERSION = version->declare("v1.1.9");
use version; our $VERSION = version->declare("v2.0.0");

use 5.014002;
use strict;
Expand All @@ -17,6 +17,7 @@ use Zonemaster::Engine::Constants qw[:ip];

has 'name' => ( is => 'ro', isa => 'Zonemaster::Engine::DNSName', required => 1 );
has 'parent' => ( is => 'ro', isa => 'Maybe[Zonemaster::Engine::Zone]', lazy_build => 1 );
has 'dname' => ( is => 'ro', isa => 'ArrayRef[Zonemaster::Engine::Zone]', lazy_build => 1 );
has [ 'ns', 'glue' ] => ( is => 'ro', isa => 'ArrayRef', lazy_build => 1 );
has [ 'ns_names', 'glue_names' ] => ( is => 'ro', isa => 'ArrayRef[Zonemaster::Engine::DNSName]', lazy_build => 1 );
has 'glue_addresses' => ( is => 'ro', isa => 'ArrayRef[Zonemaster::LDNS::RR]', lazy_build => 1 );
Expand All @@ -38,25 +39,63 @@ sub _build_parent {
return __PACKAGE__->new( { name => $pname } );
}

sub _build_dname {
my ( $self ) = @_;

if ( $self->name eq '.' or not $self->parent ) {
return [];
}

my $p = $self->parent->query_persistent( $self->name, 'DNAME' );

return [] if not defined $p;

my @name_labels = @{$self->name->labels};
my $dname;
my $owner;
my @dnames;

foreach my $rr ( $p->get_records( 'DNAME', q{answer} ) ) {
if ( index( $self->name->fqdn, lc($rr->owner) ) != -1 ) {
$dname = Zonemaster::Engine::DNSName->new( lc($rr->dname) );
$owner = Zonemaster::Engine::DNSName->new( lc($rr->owner) );

splice @name_labels, scalar @name_labels - scalar @{$owner->labels}, scalar @name_labels, @{$dname->labels};

push @dnames, Zonemaster::Engine::DNSName->new( lc( join( q{.}, @name_labels ) ) );
}
}

return [ map { __PACKAGE__->new( { name => $_ } ) } @dnames ];
}

sub _build_glue_names {
my ( $self ) = @_;
my $zname = $self->name;
my $p;

if ( not $self->parent ) {
return [];
}

my $p = $self->parent->query_persistent( $self->name, 'NS' );
if ( scalar @{$self->dname} ) {
$zname = @{$self->dname}[0]->name;
$p = @{$self->dname}[0]->parent->query_persistent( $zname, 'NS' );
}
else {
$p = $self->parent->query_persistent( $zname, 'NS' );
}

return [] if not defined $p;

return [ uniq sort map { Zonemaster::Engine::DNSName->new( lc( $_->nsdname ) ) }
$p->get_records_for_name( 'ns', $self->name->string ) ];
$p->get_records_for_name( 'ns', $zname->string ) ];
}

sub _build_glue {
my ( $self ) = @_;
my @glue_names = @{ $self->glue_names };
my $zname = $self->name->string;
my @glue_names = @{$self->glue_names};

if ( Zonemaster::Engine::Recursor->has_fake_addresses( $zname ) ) {
my @ns_list;
Expand All @@ -78,24 +117,34 @@ sub _build_glue {

sub _build_ns_names {
my ( $self ) = @_;
my $zname = $self->name;
my $servers;
my $p;
my $i = 0;

if ( $self->name eq '.' ) {
my %u;
$u{$_} = $_ for map { $_->name } @{ $self->ns };
return [ sort values %u ];
}

my $p;
my $i = 0;
while ( my $s = $self->glue->[$i] ) {
$p = $s->query( $self->name, 'NS' );
if ( scalar @{$self->dname} ) {
$zname = @{$self->dname}[0]->name;
$servers = @{$self->dname}[0]->glue;
}
else {
$servers = $self->glue;
}

while ( my $s = $servers->[$i] ) {
$p = $s->query( $zname, 'NS' );
last if ( defined( $p ) and ( $p->type eq 'answer' ) and ( $p->rcode eq 'NOERROR' ) );
$i += 1;
}
return [] if not defined $p;

return [ uniq sort map { Zonemaster::Engine::DNSName->new( lc( $_->nsdname ) ) }
$p->get_records_for_name( 'ns', $self->name->string ) ];
$p->get_records_for_name( 'ns', $zname ) ];
} ## end sub _build_ns_names

sub _build_ns {
Expand All @@ -113,12 +162,21 @@ sub _build_ns {

sub _build_glue_addresses {
my ( $self ) = @_;
my $zname = $self->name;
my $p;

if ( not $self->parent ) {
return [];
}

my $p = $self->parent->query_one( $self->name, 'NS' );
if ( scalar @{$self->dname} ) {
$zname = @{$self->dname}[0]->name;
$p = @{$self->dname}[0]->parent->query_one( $zname, 'NS' );
}
else {
$p = $self->parent->query_one( $zname, 'NS' );
}

croak "Failed to get glue addresses" if not defined( $p );

return [ $p->get_records( 'a' ), $p->get_records( 'aaaa' ) ];
Expand Down
11 changes: 9 additions & 2 deletions share/profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,26 @@
},
"BASIC" : {
"A_QUERY_NO_RESPONSES" : "INFO",
"B01_CHILD_IS_ALIAS" : "NOTICE",
"B01_CHILD_FOUND" : "INFO",
"B01_CHILD_NOT_EXIST" : "INFO",
"B01_INCONSISTENT_ALIAS" : "ERROR",
"B01_INCONSISTENT_DELEGATION" : "ERROR",
"B01_NO_CHILD" : "ERROR",
"B01_PARENT_FOUND" : "INFO",
"B01_PARENT_UNDETERMINED" : "WARNING",
"B01_UNEXPECTED_NS_RESPONSE" : "WARNING",
"DOMAIN_NAME_LABEL_TOO_LONG" : "CRITICAL",
"DOMAIN_NAME_TOO_LONG" : "CRITICAL",
"DOMAIN_NAME_ZERO_LENGTH_LABEL" : "CRITICAL",
"HAS_A_RECORDS" : "ERROR",
"HAS_NAMESERVER_NO_WWW_A_TEST" : "INFO",
"HAS_NAMESERVERS" : "INFO",
"HAS_PARENT" : "INFO",
"IPV4_DISABLED" : "DEBUG",
"IPV4_ENABLED" : "DEBUG",
"IPV6_DISABLED" : "DEBUG",
"IPV6_ENABLED" : "DEBUG",
"NO_GLUE_PREVENTS_NAMESERVER_TESTS" : "CRITICAL",
"NO_PARENT" : "CRITICAL",
"NS_FAILED" : "ERROR",
"NS_NO_RESPONSE" : "DEBUG",
"TEST_CASE_END" : "DEBUG",
Expand Down
11 changes: 9 additions & 2 deletions t/profiles/Test-all-levels.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,26 @@
},
"BASIC" : {
"A_QUERY_NO_RESPONSES" : "INFO",
"B01_CHILD_IS_ALIAS" : "NOTICE",
"B01_CHILD_FOUND" : "INFO",
"B01_CHILD_NOT_EXIST" : "INFO",
"B01_INCONSISTENT_ALIAS" : "ERROR",
"B01_INCONSISTENT_DELEGATION" : "ERROR",
"B01_NO_CHILD" : "ERROR",
"B01_PARENT_FOUND" : "INFO",
"B01_PARENT_UNDETERMINED" : "WARNING",
"B01_UNEXPECTED_NS_RESPONSE" : "WARNING",
"DOMAIN_NAME_LABEL_TOO_LONG" : "CRITICAL",
"DOMAIN_NAME_ZERO_LENGTH_LABEL" : "CRITICAL",
"DOMAIN_NAME_TOO_LONG" : "CRITICAL",
"HAS_A_RECORDS" : "ERROR",
"HAS_NAMESERVERS" : "INFO",
"HAS_NAMESERVER_NO_WWW_A_TEST" : "INFO",
"HAS_PARENT" : "INFO",
"IPV4_DISABLED" : "INFO",
"IPV4_ENABLED" : "INFO",
"IPV6_DISABLED" : "INFO",
"IPV6_ENABLED" : "INFO",
"NO_GLUE_PREVENTS_NAMESERVER_TESTS" : "CRITICAL",
"NO_PARENT" : "CRITICAL",
"NS_FAILED" : "ERROR",
"NS_NO_RESPONSE" : "NOTICE"
},
Expand Down
4 changes: 2 additions & 2 deletions t/translator.t
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ $profile_tmp = Zonemaster::Engine::Profile->from_json( $json );
Zonemaster::Engine::Profile->effective->merge( $profile_tmp );

my $trans = new_ok( 'Zonemaster::Engine::Translator' => [ { locale => 'C' } ] );
ok( exists $trans->data->{BASIC}{HAS_PARENT}, 'expected key from file exists' );
ok( exists $trans->data->{BASIC}{B01_PARENT_FOUND}, 'expected key from file exists' );
ok( exists $trans->data->{DNSSEC}{ALGORITHM_OK}, 'expected key from module exists' );

my $entry = Zonemaster::Engine::Logger::Entry->new(
{
module => 'BASIC',
tag => 'HAS_PARENT',
tag => 'B01_PARENT_FOUND',
args => { pname => 'nothing.nowhere' }
}
);
Expand Down

0 comments on commit b2bda3d

Please sign in to comment.