diff --git a/lib/Zonemaster/Engine/Test/Consistency.pm b/lib/Zonemaster/Engine/Test/Consistency.pm index 5e34383a6..7c889b43d 100644 --- a/lib/Zonemaster/Engine/Test/Consistency.pm +++ b/lib/Zonemaster/Engine/Test/Consistency.pm @@ -7,10 +7,12 @@ use version; our $VERSION = version->declare("v1.1.16"); use List::MoreUtils qw[uniq]; use Locale::TextDomain qw[Zonemaster-Engine]; +use Net::IP::XS; use Readonly; use Zonemaster::Engine::Profile; use Zonemaster::Engine::Constants qw[:ip :soa]; +use Zonemaster::Engine::NameserverSet; use Zonemaster::Engine::Test::Address; use Zonemaster::Engine::Util; use Zonemaster::Engine::TestMethods; @@ -139,13 +141,18 @@ sub metadata { ], consistency05 => [ qw( - ADDRESSES_MATCH - CHILD_NS_FAILED - CHILD_ZONE_LAME - EXTRA_ADDRESS_CHILD - IN_BAILIWICK_ADDR_MISMATCH - NO_RESPONSE - OUT_OF_BAILIWICK_ADDR_MISMATCH + CS05_CHILD_ZONE_LAME + CS05_DELEGATION + CS05_EXTRA_ADDR_CHILD + CS05_ID_ADDR_MISMATCH + CS05_ID_ADDR_MISSING + CS05_INCONSISTENT_DELEGATION + CS05_MISSING_GLUE_FOR_NS + CS05_MISSING_GLUE_FOR_NS_UNDEL + CS05_MISSING_GLUE_FOR_ROOT_NS + CS05_NO_MISMATCH_GLUE_ZONE + CS05_NO_NS_ADDR_CHILD + CS05_OOD_ADDR_MISMATCH TEST_CASE_END TEST_CASE_START ) @@ -182,33 +189,59 @@ Readonly my %TAG_DESCRIPTIONS => ( }, CONSISTENCY05 => sub { __x # CONSISTENCY:CONSISTENCY05 - 'Consistency between glue and authoritative data'; + 'Consistency between delegation and zone data'; }, CONSISTENCY06 => sub { __x # CONSISTENCY:CONSISTENCY06 'SOA MNAME consistency'; }, - ADDRESSES_MATCH => sub { - __x # CONSISTENCY:ADDRESSES_MATCH - 'Glue records are consistent between glue and authoritative data.', @_; + CS05_CHILD_ZONE_LAME => sub { + __x # CONSISTENCY:CS05_CHILD_ZONE_LAME + 'There is no working name server for the child zone. Tested name servers are "{ns_list}".', @_; }, - CHILD_NS_FAILED => sub { - __x # CONSISTENCY:CHILD_NS_FAILED - 'Unexpected or erroneous reply from {ns}.', @_; + CS05_DELEGATION => sub { + __x # CONSISTENCY:CS05_DELEGATION + 'Delegation of the child zone as provided by the parent name servers listed: "{ns_deleg_list}". Parent name servers: "{ns_list}".', @_; }, - CHILD_ZONE_LAME => sub { - __x # CONSISTENCY:CHILD_ZONE_LAME - 'Lame delegation.', @_; + CS05_EXTRA_ADDR_CHILD => sub { + __x # CONSISTENCY:CS05_EXTRA_ADDR_CHILD + 'There is one or more extra address records found in the child zone that are not present as glue in the delegation: "{ns_list}".', @_; }, - EXTRA_ADDRESS_CHILD => sub { - __x # CONSISTENCY:EXTRA_ADDRESS_CHILD - 'Child has extra nameserver IP address(es) not listed at parent ({ns_ip_list}).', @_; + CS05_NO_MISMATCH_GLUE_ZONE => sub { + __ # CONSISTENCY:CS05_NO_MISMATCH_GLUE_ZONE + 'There is no mismatch between delegation from parent and authoritative data in the child zone.'; }, - IN_BAILIWICK_ADDR_MISMATCH => sub { - __x # CONSISTENCY:IN_BAILIWICK_ADDR_MISMATCH - 'In-bailiwick name server listed at parent has a mismatch between glue data at parent ' - . '({parent_addresses}) and any equivalent address record in child zone ({zone_addresses}).', - @_; + CS05_ID_ADDR_MISMATCH => sub { + __x # CONSISTENCY:CS05_ID_ADDR_MISMATCH + 'For name server {nsname} the glue record in the delegation "{ns_ip_list_glue}" is different from the address record in the child zone "{ns_ip_list_zone}".', @_; + }, + CS05_ID_ADDR_MISSING => sub { + __x # CONSISTENCY:CS05_ID_ADDR_MISSING + 'Address record for {nsname}, used as glue record in delegation, is missing in the child zone.', @_; + }, + CS05_INCONSISTENT_DELEGATION => sub { + __ # CONSISTENCY:CS05_INCONSISTENT_DELEGATION + 'The delegation is inconsistent between the parent name servers.'; + }, + CS05_MISSING_GLUE_FOR_NS => sub { + __x # CONSISTENCY:CS05_MISSING_GLUE_FOR_NS + 'Expected glue record for {nsname} is missing in the delegation. Found in the parent name servers "{ns_list}".', @_; + }, + CS05_MISSING_GLUE_FOR_NS_UNDEL => sub { + __x # CONSISTENCY:CS05_MISSING_GLUE_FOR_NS_UNDEL + 'IP address (glue record) is expected but missing for {nsname} in the undelegated data.', @_; + }, + CS05_MISSING_GLUE_FOR_ROOT_NS => sub { + __x # CONSISTENCY:CS05_MISSING_GLUE_FOR_ROOT_NS + 'IP address (glue record) is expected but missing for {nsname} in the undelegated data or hint data for root.', @_; + }, + CS05_NO_NS_ADDR_CHILD => sub { + __ # CONSISTENCY:CS05_NO_NS_ADDR_CHILD + 'Child zone cannot be tested since there are no name server IP addresses for that zone.'; + }, + CS05_OOD_ADDR_MISMATCH => sub { + __x # CONSISTENCY:CS05_OOD_ADDR_MISMATCH + 'For name server {nsname} the glue record in the delegation "{ns_ip_list_glue}" is different from the address record in the child zone "{ns_ip_list_zone}".', @_; }, IPV4_DISABLED => sub { __x # CONSISTENCY:IPV4_DISABLED @@ -276,12 +309,6 @@ Readonly my %TAG_DESCRIPTIONS => ( . '(REFRESH={refresh},RETRY={retry},EXPIRE={expire},MINIMUM={minimum}).', @_; }, - OUT_OF_BAILIWICK_ADDR_MISMATCH => sub { - __x # CONSISTENCY:OUT_OF_BAILIWICK_ADDR_MISMATCH - 'Out-of-bailiwick name server listed at parent with glue record has a mismatch between ' - . 'the glue at the parent ({parent_addresses}) and any equivalent address record found ' - . 'in authoritative zone ({zone_addresses}).', @_; - }, SOA_RNAME => sub { __x # CONSISTENCY:SOA_RNAME "Found SOA rname {rname} on following nameserver set : {ns_list}.", @_; @@ -410,44 +437,6 @@ sub _ip_disabled_message { return 0; } -=over - -=item _get_addr_rrs() - - my ( $logentry, @rrs_array ) = _get_addr_rrs( $ns, $zone_name, $query_type_string ); - -Queries a given name server for resource records of the given type. Used as an helper function for Test Case L. - -Takes a L object, a L object and a string (query type). - -Returns a L object (which could be C) and an optional list of L objects. - -=back - -=cut - -sub _get_addr_rrs { - my ( $class, $ns, $name, $qtype ) = @_; - my $p = $ns->query( $name, $qtype, { recurse => 0 } ); - if ( !$p ) { - return _emit_log( NO_RESPONSE => { ns => $ns->string } ); - } - elsif ($p->is_redirect) { - my $p = Zonemaster::Engine->recurse( $name, $qtype, q{IN} ); - if ( $p ) { - return ( undef, $p->get_records_for_name( $qtype, $name, 'answer' ) ); - } else { - return ( undef ); - } - } - elsif ( $p->aa and $p->rcode eq 'NOERROR' ) { - return ( undef, $p->get_records_for_name( $qtype, $name, 'answer' ) ); - } - elsif (not ($p->aa and $p->rcode eq 'NXDOMAIN')) { - return _emit_log( CHILD_NS_FAILED => { ns => $ns->string } ); - } - return ( undef ); -} =head1 TESTS @@ -823,140 +812,266 @@ sub consistency05 { local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Consistency05'; push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ); - my %strict_glue; - my %extended_glue; - - # We need to work on Methods... - # This part of code is supposed to replace method4 call - my @child_nsnames; - my @nsnames; - my $ns_aref = $zone->parent->query_all( $zone->name, q{NS} ); - my %parent_glues; - foreach my $p ( @{$ns_aref} ) { - next if not $p; - push @nsnames, $p->get_records_for_name( q{NS}, $zone->name ); + + # Steps 1–3 + my @parent_ns = @{ Zonemaster::Engine::TestMethodsV2->get_parent_ns_names_and_ips( $zone ) // [] }; + my @parent_ns_ips = @{ Zonemaster::Engine::TestMethodsV2->get_parent_ns_ips( $zone ) // [] }; + + my @child_ns_ips = uniq + @{ Zonemaster::Engine::TestMethodsV2->get_del_ns_ips( $zone ) // [] }, + @{ Zonemaster::Engine::TestMethodsV2->get_zone_ns_ips( $zone ) // [] }; + + my %delegation; + my %child_zone_ns; + my $delegation_id_ns = Zonemaster::Engine::NameserverSet->new(); + my $delegation_ood_ns = Zonemaster::Engine::NameserverSet->new(); + my %missing_glue; + my $auth_addr_records_in_child = Zonemaster::Engine::NameserverSet->new(); + my %extra_address_child; + + # Step 4 + foreach my $parent_ns ( @parent_ns_ips ) { + # Do SOA query over TCP preferably, but fall back to UDP if name + # server does not respond over TCP. + my $p = $parent_ns->query( $zone->name(), 'SOA', { usevc => 1 } ); + $p //= $parent_ns->query( $zone->name(), 'SOA' ); + + next unless ( defined $p and $p->is_redirect ); + + my %authority = + map { lc name( $_->nsdname() ) => 1 } + $p->get_records_for_name( 'NS', $zone, 'authority' ); + + + my @additional = + map { + my $name = name( $_->owner() ); + + # Filters out A and AAAA records with invalid IP addresses + my $addr = Net::IP::XS->new( $_->address ); + if ( defined $addr ) { + Zonemaster::Engine::Nameserver->new( { name => $name, address => $addr } ) + } + else { + () + } + } + grep { + my $name = lc name( $_->owner() ); + exists $authority{$name} and ( $_->type eq 'A' or $_->type eq 'AAAA' ) + } + $p->additional; + + my $set = Zonemaster::Engine::NameserverSet->new( keys %authority, @additional ); + + $delegation{$parent_ns} = [ $set->sorted_items() ]; } - @child_nsnames = uniq map { name( lc( $_->nsdname ) ) } @nsnames; - foreach my $nsname ( @child_nsnames ) { - my $a_aref = $zone->parent->query_all( $nsname, q{A} ); - my $aaaa_aref = $zone->parent->query_all( $nsname, q{AAAA} ); - foreach my $p ( @{$a_aref} ) { - next if not $p; - foreach my $rr ( $p->get_records_for_name( q{A}, $nsname ) ) { - $parent_glues{ lc( $rr->owner ) . q{/} . $rr->address } = $nsname; + + + # Step 5 + foreach my $parent_ns_ip ( sort keys %delegation ) { + foreach my $delegation_ns ( @{ $delegation{$parent_ns_ip} } ) { + my $delegation_ns_name = do { + if ( $delegation_ns->isa('Zonemaster::Engine::Nameserver') ) { + $delegation_ns->name() + } + else { + $delegation_ns; + } + }; + + if ( $zone->name()->is_in_bailiwick( $delegation_ns_name ) ) { + if ( $delegation_ns->isa('Zonemaster::Engine::DNSName') ) { + push @{$missing_glue{ lc $delegation_ns }}, $parent_ns_ip; + } + $delegation_id_ns->push($delegation_ns); } - } - foreach my $p ( @{$aaaa_aref} ) { - next if not $p; - foreach my $rr ( $p->get_records_for_name( q{AAAA}, $nsname ) ) { - $parent_glues{ lc( $rr->owner ) . q{/} . $rr->address } = $nsname; + else { + $delegation_ood_ns->push($delegation_ns); } } } - for my $ns_string ( keys %parent_glues ) { - if ( $zone->name->is_in_bailiwick( $parent_glues{$ns_string} ) ) { - $strict_glue{ $ns_string } = 1; + # Step 6 + { + my %delegation_sets; + for my $d ( keys %delegation ) { + my $s = join(";", @{$delegation{$d}}); + push @{$delegation_sets{$s}}, $d; } - else { - push @{ $extended_glue{ $parent_glues{$ns_string} } }, $ns_string; + if ( scalar %delegation_sets > 1 ) { + push @results, _emit_log( CS05_INCONSISTENT_DELEGATION => {} ); + while ( my ($ns_deleg_list, $ns_list) = each %delegation_sets ) { + push @results, _emit_log( CS05_DELEGATION => { + ns_deleg_list => $ns_deleg_list, + ns_list => join( ';', sort @$ns_list ) + } ); + } } } - my @ib_nsnames = - grep { $zone->name->is_in_bailiwick( $_ ) } @{ Zonemaster::Engine::TestMethods->method2and3( $zone ) }; - my @ib_nss = grep { Zonemaster::Engine::Util::ipversion_ok( $_->address->version ) } - @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) }; - my %child_ib_strings; - for my $ib_nsname ( @ib_nsnames ) { - my $is_lame = 1; - for my $ns ( @ib_nss ) { - my ( $msg_a, @rrs_a ) = $class->_get_addr_rrs( $ns, $ib_nsname, q{A} ); - my ( $msg_aaaa, @rrs_aaaa ) = $class->_get_addr_rrs( $ns, $ib_nsname, q{AAAA} ); - - if ( defined $msg_a ) { - push @results, $msg_a; - } - if ( defined $msg_aaaa ) { - push @results, $msg_aaaa; + # Step 7 + if ( $zone->name() ne '.' ) { + foreach my $fake_name ( Zonemaster::Engine::Recursor->get_fake_names( $zone->name() ) ) { + $fake_name = name( $fake_name ); + my @fake_ns = map { + Zonemaster::Engine::Nameserver->new( { name => $fake_name, address => $_ } ) + } Zonemaster::Engine::Recursor->get_fake_addresses( $zone->name(), $fake_name ); + + if ( $zone->name()->is_in_bailiwick( $fake_name ) ) { + if ( scalar @fake_ns == 0 ) { + push @results, _emit_log( CS05_MISSING_GLUE_FOR_NS_UNDEL => { nsname => $fake_name } ); + } + else { + $delegation_id_ns->push( @fake_ns ); + } } - if ( !defined $msg_a || !defined $msg_aaaa ) { - $is_lame = 0; + else { + if ( scalar @fake_ns == 0 ) { + $delegation_ood_ns->push( $fake_name ); + } + else { + $delegation_ood_ns->push( @fake_ns ); + } } + } + } - for my $rr ( @rrs_a, @rrs_aaaa ) { - $child_ib_strings{ lc( $rr->name ) . q{/} . $rr->address } = 1; + # Step 8 + if ( $zone->name() eq '.' ) { + my @hint_ns = @{ Zonemaster::Engine::TestMethodsV2->get_del_ns_names_and_ips( $zone ) }; + + foreach my $item ( @hint_ns ) { + if ( $item->isa( 'Zonemaster::Engine::DNSName') ) { + push @results, _emit_log( CS05_MISSING_GLUE_FOR_ROOT_NS => { nsname => $item } ); + } + else { + $delegation_id_ns->push( $item ); } } + } - if ( $is_lame ) { - push @results, _emit_log( CHILD_ZONE_LAME => {} ); - return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) ); - } - } ## end for my $ib_nsname ( @ib_nsnames) + # Step 9 + while ( my ($ns, $ns_list) = each %missing_glue ) { + push @results, _emit_log( CS05_MISSING_GLUE_FOR_NS => { + nsname => $ns, + ns_list => join(';', @$ns_list) + } ); + } - my @ib_match = grep { exists $child_ib_strings{$_} } keys %strict_glue; - my @ib_mismatch = grep { !exists $child_ib_strings{$_} } keys %strict_glue; - my @ib_extra_child = grep { !exists $strict_glue{$_} } keys %child_ib_strings; + # Step 10 + if ( scalar @child_ns_ips == 0 ) { + push @results, _emit_log( CS05_NO_NS_ADDR_CHILD => {} ); + goto out; + } - if ( scalar @ib_mismatch ) { - push @results, - _emit_log( - IN_BAILIWICK_ADDR_MISMATCH => { - parent_addresses => join( q{;}, sort keys %strict_glue ), - zone_addresses => join( q{;}, sort keys %child_ib_strings ), + # Step 11 + foreach my $ns ( @child_ns_ips ) { + my $p = $ns->query( $zone->name(), 'NS' ); + next unless defined $p and $p->rcode eq 'NOERROR' and $p->aa; + + my @ns_nsnames = uniq map { name( lc $_->nsdname() ) } $p->get_records_for_name( 'NS', $zone ); + $child_zone_ns{ $ns->address()->short() } = \@ns_nsnames if scalar @ns_nsnames > 0; + + my @id_ns = uniq ( + grep( { $zone->name()->is_in_bailiwick(name($_)) } @ns_nsnames ), + $delegation_id_ns->names() + ); + + foreach my $s ( @id_ns ) { + foreach my $qtype ( qw(A AAAA) ) { + my $p = Zonemaster::Engine::Recursor->recurse( $s, $qtype, 'IN', [ $ns ] ); + next unless defined $p and $p->rcode eq 'NOERROR' and $p->aa; + + $auth_addr_records_in_child->push( + map { + Zonemaster::Engine::Nameserver->new( { name => $s, address => $_->address() } ) + } $p->get_records_for_name( $qtype, $s, 'answer' ) + ); } - ); + } } - if ( scalar @ib_extra_child ) { - push @results, - _emit_log( - EXTRA_ADDRESS_CHILD => { - ns_ip_list => join( q{;}, sort @ib_extra_child ), - } - ); + + # Step 12 + if ( scalar %child_zone_ns == 0 ) { + push @results, _emit_log( CS05_CHILD_ZONE_LAME => { ns_list => join(';', @child_ns_ips) } ); + goto out; } - my @oob_match; - my @oob_mismatch; - for my $glue_name ( keys %extended_glue ) { - my @glue_strings = @{ $extended_glue{$glue_name} }; + # Step 13 + foreach my $n ( $delegation_id_ns->names() ) { + my $parent_glue = Zonemaster::Engine::NameserverSet->new( + grep { $_->isa('Zonemaster::Engine::Nameserver') } $delegation_id_ns->get($n) + ); + next if $parent_glue->is_empty(); - my %child_oob_strings; + my $child_auth = Zonemaster::Engine::NameserverSet->new(); + $child_auth->push( $auth_addr_records_in_child->get($n) ); + my ( $only_in_parent, $only_in_child ) = $parent_glue->difference( $child_auth ); - my $p_a = Zonemaster::Engine->recurse( $glue_name, q{A}, q{IN} ); - if ( $p_a ) { - for my $rr ( $p_a->get_records_for_name( q{A}, $glue_name, q{answer} ) ) { - $child_oob_strings{ lc( $rr->owner ) . q{/} . $rr->address } = 1; - } + if ( $child_auth->is_empty() ) { + push @results, _emit_log( CS05_ID_ADDR_MISSING => { nsname => $n } ); } - - my $p_aaaa = Zonemaster::Engine->recurse( $glue_name, q{AAAA}, q{IN} ); - if ( $p_aaaa ) { - for my $rr ( $p_aaaa->get_records_for_name( q{AAAA}, $glue_name, q{answer} ) ) { - $child_oob_strings{ lc( $rr->owner ) . q{/} . $rr->address } = 1; - } + elsif ( not $only_in_parent->is_empty() ) { + push @results, _emit_log( CS05_ID_ADDR_MISMATCH => { + nsname => $n, + ns_ip_list_glue => join( ';', map { $_->address()->short() } $only_in_parent->sorted_items() ), + ns_ip_list_zone => join( ';', map { $_->address()->short() } $child_auth->sorted_items() ) + } ); } + elsif ( not $only_in_child->is_empty() ) { + $extra_address_child{ $_->address()->short() } = 1 foreach $only_in_child->items(); + } + } - push @oob_match, grep { exists $child_oob_strings{$_} } @glue_strings; - push @oob_mismatch, grep { !exists $child_oob_strings{$_} } @glue_strings; - if ( grep { !exists $child_oob_strings{$_} } @glue_strings ) { - push @results, - _emit_log( - OUT_OF_BAILIWICK_ADDR_MISMATCH => { - parent_addresses => join( q{;}, sort @glue_strings ), - zone_addresses => join( q{;}, sort keys %child_oob_strings ), + # Step 14 + if ( scalar %extra_address_child ) { + push @results, _emit_log( CS05_EXTRA_ADDR_CHILD => { + ns_list => join(';', sort keys %extra_address_child ) + } ); + } + + # Step 15 + foreach my $n ( $delegation_ood_ns->names() ) { + my $set = Zonemaster::Engine::NameserverSet->new( + grep { $_->isa('Zonemaster::Engine::Nameserver') } $delegation_ood_ns->get($n) + ); + next if $set->is_empty(); + + my $lookup = Zonemaster::Engine::NameserverSet->new( + map { + Zonemaster::Engine::Nameserver->new( { name => $_->owner(), address => $_->address() } ) + } + map { + my $p = Zonemaster::Engine::Recursor->recurse( $n, $_ ); + if ( defined $p and $p->rcode eq 'NOERROR' ) { + $p->get_records_for_name( $_, $n, 'answer' ); } - ); + else { + (); + } + } ( qw(A AAAA) ) + ); + + if ( not $lookup->is_empty() ) { + if ( not $set->equals( $lookup ) ) { + push @results, _emit_log( CS05_OOD_ADDR_MISMATCH => { + nsname => $n, + ns_ip_list_ref => join( ";", map { $_->address() } $set->sorted_items() ), + ns_ip_list_lookup => join( ";", map { $_->address() } $lookup->sorted_items() ) + } ); + } } - } ## end for my $glue_name ( keys...) + } - if ( !@ib_extra_child && !@ib_mismatch && !@oob_mismatch ) { - push @results, _emit_log( ADDRESSES_MATCH => {} ); + # Step 16 + if ( not grep /^CS05_/, map { $_->tag() } @results ) { + push @results, _emit_log( CS05_NO_MISMATCH_GLUE_ZONE => {} ); } + out: return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) ); -} ## end sub consistency05 +} ## end sub consistency05 =over diff --git a/lib/Zonemaster/Engine/TestMethodsV2.pm b/lib/Zonemaster/Engine/TestMethodsV2.pm index 3a2498648..4d320fb00 100644 --- a/lib/Zonemaster/Engine/TestMethodsV2.pm +++ b/lib/Zonemaster/Engine/TestMethodsV2.pm @@ -558,7 +558,7 @@ Glue Records are address records for In-Bailiwick name server names, if any. Als Takes a L object. -Returns an arrayref of strings, or C if no parent zone was found. +Returns an arrayref of L objects, or C if no parent zone was found. =back @@ -567,11 +567,23 @@ Returns an arrayref of strings, or C if no parent zone was found. sub get_del_ns_ips { my ( $class, $zone ) = @_; - my $ns_ref = $class->get_del_ns_names_and_ips( $zone ); + # FIXME: We really should just be outputting name server IPs here, as the + # specification says. Instead we output name server objects (but filtered + # on unique IP addresses) because these objects are required to perform + # queries. - return undef unless defined $ns_ref; + my $nameservers = $class->get_del_ns_names_and_ips( $zone ); + + return undef unless defined $nameservers; + + my %ns_by_ip = (); + foreach my $ns ( @$nameservers ) { + next if ! $ns->isa('Zonemaster::Engine::Nameserver'); + my $ip = $ns->address->short; + $ns_by_ip{$ip} = $ns unless exists $ns_by_ip{$ip}; + } - return [ uniq sort map { $_->address->short } grep { $_->isa('Zonemaster::Engine::Nameserver') } @{ $ns_ref } ]; + return [ sort values %ns_by_ip ]; } =over @@ -769,7 +781,7 @@ This Method will obtain the IP addresses of the name servers, as extracted from Takes a L object. -Returns an arrayref of strings, or C if no parent zone was found. +Returns an arrayref of L objects, or C if no parent zone was found. =back @@ -778,16 +790,18 @@ Returns an arrayref of strings, or C if no parent zone was found. sub get_zone_ns_ips { my ( $class, $zone ) = @_; - my $ns_ref = $class->get_zone_ns_names_and_ips( $zone ); + my $nameservers = $class->get_zone_ns_names_and_ips( $zone ); - return undef unless defined $ns_ref; + return undef unless defined $nameservers; - my @ns_ips; - foreach my $ns ( @{ $ns_ref } ) { - push @ns_ips, $ns->address->short if $ns->isa('Zonemaster::Engine::Nameserver'); + my %ns_by_ip = (); + foreach my $ns ( @$nameservers ) { + next if ! $ns->isa('Zonemaster::Engine::Nameserver'); + my $ip = $ns->address->short; + $ns_by_ip{$ip} = $ns unless exists $ns_by_ip{$ip}; } - return [ uniq sort @ns_ips ]; + return [ sort values %ns_by_ip ]; } diff --git a/share/profile.json b/share/profile.json index ebf832271..d9de7da5e 100644 --- a/share/profile.json +++ b/share/profile.json @@ -125,11 +125,18 @@ "TEST_CASE_START" : "DEBUG" }, "CONSISTENCY" : { - "ADDRESSES_MATCH" : "INFO", - "CHILD_NS_FAILED" : "DEBUG", - "CHILD_ZONE_LAME" : "ERROR", - "EXTRA_ADDRESS_CHILD" : "NOTICE", - "IN_BAILIWICK_ADDR_MISMATCH" : "ERROR", + "CS05_CHILD_ZONE_LAME": "CRITICAL", + "CS05_DELEGATION": "INFO", + "CS05_EXTRA_ADDR_CHILD": "NOTICE", + "CS05_NO_MISMATCH_GLUE_ZONE": "INFO", + "CS05_ID_ADDR_MISMATCH": "ERROR", + "CS05_ID_ADDR_MISSING": "NOTICE", + "CS05_INCONSISTENT_DELEGATION": "WARNING", + "CS05_MISSING_GLUE_FOR_NS": "WARNING", + "CS05_MISSING_GLUE_FOR_NS_UNDEL": "WARNING", + "CS05_MISSING_GLUE_FOR_ROOT_NS": "WARNING", + "CS05_NO_NS_ADDR_CHILD": "CRITICAL", + "CS05_OOD_ADDR_MISMATCH": "WARNING", "IPV4_DISABLED" : "DEBUG", "IPV6_DISABLED" : "DEBUG", "MULTIPLE_NS_SET" : "NOTICE", @@ -146,7 +153,6 @@ "ONE_SOA_RNAME" : "INFO", "ONE_SOA_SERIAL" : "INFO", "ONE_SOA_TIME_PARAMETER_SET" : "INFO", - "OUT_OF_BAILIWICK_ADDR_MISMATCH" : "ERROR", "SOA_RNAME" : "INFO", "SOA_SERIAL" : "INFO", "SOA_SERIAL_VARIATION" : "NOTICE", diff --git a/t/Test-consistency.t b/t/Test-consistency.t index 7b861d2d3..90cd679c0 100644 --- a/t/Test-consistency.t +++ b/t/Test-consistency.t @@ -61,7 +61,6 @@ ok( $res{NS_SET}, q{NS set details} ); ok( $res{ONE_SOA_SERIAL}, q{One SOA serial} ); ok( $res{ONE_SOA_RNAME}, q{One SOA rname} ); ok( $res{ONE_SOA_TIME_PARAMETER_SET}, q{One SOA time parameters set} ); -ok( $res{ADDRESSES_MATCH}, q{Addresses IP match} ); if ( $ENV{ZONEMASTER_RECORD} ) { Zonemaster::Engine::Nameserver->save( $datafile ); @@ -87,54 +86,6 @@ ok( ( any { $_->tag eq 'NO_NETWORK' } @res ), 'IPv6 and IPv4 disabled' ); ok( ( none { $_->tag eq 'IPV6_DISABLED' } @res ), 'No network' ); ok( ( none { $_->tag eq 'IPV4_DISABLED' } @res ), 'No network' ); -#Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 1 ); -#Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 0 ); -#@res = Zonemaster::Engine->test_method( 'Consistency', 'consistency01', Zonemaster::Engine->zone( q{afnic.fr} ) ); -#ok( ( any { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 disabled' ); -#ok( ( none { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 not disabled' ); -#@res = Zonemaster::Engine->test_method( 'Consistency', 'consistency02', Zonemaster::Engine->zone( q{afnic.fr} ) ); -#ok( ( any { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 disabled' ); -#ok( ( none { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 not disabled' ); -#@res = Zonemaster::Engine->test_method( 'Consistency', 'consistency03', Zonemaster::Engine->zone( q{afnic.fr} ) ); -#ok( ( any { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 disabled' ); -#ok( ( none { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 not disabled' ); -#@res = Zonemaster::Engine->test_method( 'Consistency', 'consistency04', Zonemaster::Engine->zone( q{afnic.fr} ) ); -#ok( ( any { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 disabled' ); -#ok( ( none { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 not disabled' ); -# -#if ( Zonemaster::Engine::Util::supports_ipv6() ) { -# -# Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 0 ); -# Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 1 ); -# @res = Zonemaster::Engine->test_method( 'Consistency', 'consistency01', Zonemaster::Engine->zone( q{afnic.fr} ) ); -# ok( ( none { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 not disabled' ); -# ok( ( any { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 disabled' ); -# @res = Zonemaster::Engine->test_method( 'Consistency', 'consistency02', Zonemaster::Engine->zone( q{afnic.fr} ) ); -# ok( ( none { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 not disabled' ); -# ok( ( any { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 disabled' ); -# @res = Zonemaster::Engine->test_method( 'Consistency', 'consistency03', Zonemaster::Engine->zone( q{afnic.fr} ) ); -# ok( ( none { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 not disabled' ); -# ok( ( any { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 disabled' ); -# @res = Zonemaster::Engine->test_method( 'Consistency', 'consistency04', Zonemaster::Engine->zone( q{afnic.fr} ) ); -# ok( ( none { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 not disabled' ); -# ok( ( any { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 disabled' ); -# -# Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 1 ); -# Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 1 ); -# @res = Zonemaster::Engine->test_method( 'Consistency', 'consistency01', Zonemaster::Engine->zone( q{afnic.fr} ) ); -# ok( ( none { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 not disabled' ); -# ok( ( none { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 not disabled' ); -# @res = Zonemaster::Engine->test_method( 'Consistency', 'consistency02', Zonemaster::Engine->zone( q{afnic.fr} ) ); -# ok( ( none { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 not disabled' ); -# ok( ( none { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 not disabled' ); -# @res = Zonemaster::Engine->test_method( 'Consistency', 'consistency03', Zonemaster::Engine->zone( q{afnic.fr} ) ); -# ok( ( none { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 not disabled' ); -# ok( ( none { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 not disabled' ); -# @res = Zonemaster::Engine->test_method( 'Consistency', 'consistency04', Zonemaster::Engine->zone( q{afnic.fr} ) ); -# ok( ( none { $_->tag eq 'IPV6_DISABLED' } @res ), 'IPv6 not disabled' ); -# ok( ( none { $_->tag eq 'IPV4_DISABLED' } @res ), 'IPv4 not disabled' ); -# -#} Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 ); diff --git a/t/Test-consistency05.data b/t/Test-consistency05.data index cbce8f0e4..4997a100e 100644 Binary files a/t/Test-consistency05.data and b/t/Test-consistency05.data differ diff --git a/t/Test-consistency05.t b/t/Test-consistency05.t index fa87e72e6..a640e8249 100644 --- a/t/Test-consistency05.t +++ b/t/Test-consistency05.t @@ -11,119 +11,193 @@ use TestUtil::DSL; testing_test_case 'Consistency', 'consistency05'; -all_tags qw(ADDRESSES_MATCH - IN_BAILIWICK_ADDR_MISMATCH - OUT_OF_BAILIWICK_ADDR_MISMATCH - EXTRA_ADDRESS_CHILD - CHILD_ZONE_LAME - CHILD_NS_FAILED - NO_RESPONSE); - -# Common hint file (test-zone-data/COMMON/hintfile) -root_hints 'ns1' => [ qw(127.1.0.1 fda1:b2:c3::127:1:0:1) ], - 'ns2' => [ qw(127.1.0.2 fda1:b2:c3::127:1:0:2) ]; +all_tags qw( CS05_CHILD_ZONE_LAME + CS05_DELEGATION + CS05_EXTRA_ADDR_CHILD + CS05_ID_ADDR_MISMATCH + CS05_ID_ADDR_MISSING + CS05_INCONSISTENT_DELEGATION + CS05_MISSING_GLUE_FOR_NS + CS05_MISSING_GLUE_FOR_NS_UNDEL + CS05_MISSING_GLUE_FOR_ROOT_NS + CS05_NO_MISMATCH_GLUE_ZONE + CS05_NO_NS_ADDR_CHILD + CS05_OOD_ADDR_MISMATCH ); + +# General Consistency05 hintfile (test-zone-data/Consistency-TP/consistency05/hintfile.zone) +root_hints 'ns1' => [ qw(127.14.5.61 fda1:b2:c3:0:127:14:5:61) ], + 'ns2' => [ qw(127.14.5.62 fda1:b2:c3:0:127:14:5:62) ]; # Test zone scenarios zone_name_template '{SCENARIO}.{TESTCASE}.xa'; -scenario 'ADDRESSES-MATCH-{1..2}' => sub { - expect 'ADDRESSES_MATCH'; +scenario 'ADDR-MATCH-DEL-UNDEL-1' => sub { + fake_ns 'ns3.addr-match-del-undel-1.consistency05.xa' => '127.14.5.33', 'fda1:b2:c3:0:127:14:5:33'; + fake_ns 'ns4.addr-match-del-undel-1.consistency05.xa' => '127.14.5.34', 'fda1:b2:c3:0:127:14:5:34'; + + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; forbid_others; }; -scenario 'ADDRESSES-MATCH-{3..4}' => sub { - expect qw(ADDRESSES_MATCH CHILD_NS_FAILED); +scenario 'ADDR-MATCH-DEL-UNDEL-2' => sub { + fake_ns 'ns3.addr-match-del-undel-2.consistency05.xb'; + fake_ns 'ns4.addr-match-del-undel-2.consistency05.xb'; + + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; forbid_others; }; -scenario 'ADDRESSES-MATCH-5' => sub { - expect qw(ADDRESSES_MATCH NO_RESPONSE); +scenario 'ADDR-MATCH-NO-DEL-UNDEL-1' => sub { + fake_ns 'ns1.addr-match-no-del-undel-1.consistency05.xa' => '127.14.5.31', 'fda1:b2:c3:0:127:14:5:31'; + fake_ns 'ns2.addr-match-no-del-undel-1.consistency05.xa' => '127.14.5.32', 'fda1:b2:c3:0:127:14:5:32'; + + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; + forbid_others; +}; + +scenario 'ADDR-MATCH-NO-DEL-UNDEL-2' => sub { + fake_ns 'ns3.addr-match-no-del-undel-2.consistency05.xb'; + fake_ns 'ns4.addr-match-no-del-undel-2.consistency05.xb'; + + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; + forbid_others; +}; + +scenario 'ADDRESSES-MATCH-{1..5}' => sub { + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; forbid_others; }; scenario 'ADDRESSES-MATCH-6' => sub { zone 'child.{SCENARIO}.{TESTCASE}.xa'; - expect 'ADDRESSES_MATCH'; + + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; forbid_others; }; scenario 'ADDRESSES-MATCH-7' => sub { - expect 'ADDRESSES_MATCH'; + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; forbid_others; }; -scenario 'ADDR-MATCH-DEL-UNDEL-1' => sub { - fake_ns 'ns3.addr-match-del-undel-1.consistency05.xa' => '127.14.5.33', 'fda1:b2:c3:0:127:14:5:33'; - fake_ns 'ns4.addr-match-del-undel-1.consistency05.xa' => '127.14.5.34', 'fda1:b2:c3:0:127:14:5:34'; - expect 'ADDRESSES_MATCH'; +scenario 'ADDRESSES-MATCH-8' => sub { + zone 'child.a.b.{SCENARIO}.{TESTCASE}.xa'; + + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; forbid_others; }; -scenario 'ADDR-MATCH-DEL-UNDEL-2' => sub { - fake_ns 'ns3.addr-match-del-undel-2.consistency05.xb'; - fake_ns 'ns4.addr-match-del-undel-2.consistency05.xb'; - expect 'ADDRESSES_MATCH'; +scenario 'ADDRESSES-MATCH-9' => sub { + zone 'child.{SCENARIO}.{TESTCASE}.xa'; + + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; forbid_others; }; -scenario 'ADDR-MATCH-NO-DEL-UNDEL-1' => sub { - fake_ns 'ns1.addr-match-no-del-undel-1.consistency05.xa' => '127.14.5.31', 'fda1:b2:c3:0:127:14:5:31'; - fake_ns 'ns2.addr-match-no-del-undel-1.consistency05.xa' => '127.14.5.32', 'fda1:b2:c3:0:127:14:5:32'; - expect 'ADDRESSES_MATCH'; +scenario 'CHILD-ZONE-LAME-{1..3}' => sub { + expect 'CS05_CHILD_ZONE_LAME'; forbid_others; }; -scenario 'ADDR-MATCH-NO-DEL-UNDEL-2' => sub { - fake_ns 'ns3.addr-match-no-del-undel-2.consistency05.xb'; - fake_ns 'ns4.addr-match-no-del-undel-2.consistency05.xb'; - expect 'ADDRESSES_MATCH'; +scenario 'EXTRA-ADDRESS-CHILD' => sub { + expect 'CS05_EXTRA_ADDR_CHILD'; forbid_others; }; -scenario 'CHILD-ZONE-LAME-1' => sub { - todo 'see https://github.com/zonemaster/zonemaster-engine/issues/1301'; +scenario 'ID-ADDR-MISMATCH-1' => sub { + expect 'CS05_ID_ADDR_MISMATCH'; + forbid_others; +}; - expect qw(CHILD_ZONE_LAME NO_RESPONSE); +scenario 'ID-ADDR-MISSING-{1..2}' => sub { + expect 'CS05_ID_ADDR_MISSING'; forbid_others; }; -scenario 'CHILD-ZONE-LAME-2' => sub { - expect qw(CHILD_ZONE_LAME CHILD_NS_FAILED); +scenario 'INCONSISTENT-DELEGATION-{1..2}' => sub { + zone 'child.{SCENARIO}.{TESTCASE}.xa'; + + expect 'CS05_INCONSISTENT_DELEGATION'; + expect 'CS05_DELEGATION'; forbid_others; }; -scenario 'IB-ADDR-MISMATCH-1' => sub { - expect qw(IN_BAILIWICK_ADDR_MISMATCH EXTRA_ADDRESS_CHILD); +scenario 'MISSING-GLUE-FOR-NS-1' => sub { + zone 'child.{SCENARIO}.{TESTCASE}.xa'; + + expect 'CS05_MISSING_GLUE_FOR_NS'; forbid_others; }; -scenario 'IB-ADDR-MISMATCH-2' => sub { - expect 'IN_BAILIWICK_ADDR_MISMATCH'; +scenario 'MISSING-GLUE-FOR-NS-2' => sub { + fake_ns 'ns1.missing-glue-for-ns-2.consistency05.xa'; + fake_ns 'ns2.missing-glue-for-ns-2.consistency05.xa' => '127.14.5.32', 'fda1:b2:c3:0:127:14:5:32'; + + expect 'CS05_MISSING_GLUE_FOR_NS_UNDEL'; forbid_others; }; -scenario 'IB-ADDR-MISMATCH-3' => sub { - todo 'see https://github.com/zonemaster/zonemaster-engine/issues/1301'; +scenario 'NO-NS-ADDR-CHILD-1' => sub { + zone 'child.{SCENARIO}.{TESTCASE}.xa'; - expect qw(IN_BAILIWICK_ADDR_MISMATCH NO_RESPONSE); + expect 'CS05_NO_NS_ADDR_CHILD'; + expect 'CS05_MISSING_GLUE_FOR_NS'; forbid_others; }; -scenario 'IB-ADDR-MISMATCH-4' => sub { - todo 'see https://github.com/zonemaster/zonemaster-engine/issues/1349'; +scenario 'NO-NS-ADDR-CHILD-2' => sub { + zone 'child.{SCENARIO}.{TESTCASE}.xa'; - expect 'IN_BAILIWICK_ADDR_MISMATCH'; + expect 'CS05_NO_NS_ADDR_CHILD'; forbid_others; }; -scenario 'OOB-ADDR-MISMATCH' => sub { +scenario 'OOD-ADDR-MISMATCH' => sub { zone 'child.{SCENARIO}.{TESTCASE}.xa'; - expect 'OUT_OF_BAILIWICK_ADDR_MISMATCH'; + + expect 'CS05_OOD_ADDR_MISMATCH'; forbid_others; }; -scenario 'EXTRA-ADDRESS-CHILD' => sub { - expect 'EXTRA_ADDRESS_CHILD'; +scenario 'ROOT-MATCH-1' => sub { + zone '.'; + + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; + forbid_others; +}; + +scenario 'ROOT-MISSING-GLUE-UNDEL-1' => sub { + zone '.'; + + fake_ns 'ns1'; + fake_ns 'ns2' => '127.14.5.66', 'fda1:b2:c3:0:127:14:5:66'; + + expect 'CS05_MISSING_GLUE_FOR_ROOT_NS'; + forbid_others; +}; + +# Specific hintfile (test-zone-data/Consistency-TP/consistency05/Z-ROOT-MATCH-1-hintfile.zone) +root_hints 'ns1' => [ qw(127.14.5.63 fda1:b2:c3:0:127:14:5:63) ], + 'ns2' => [ qw(127.14.5.64 fda1:b2:c3:0:127:14:5:64) ]; + +scenario 'Z-ROOT-MATCH-1' => sub { + zone '.'; + + fake_ns 'ns1' => '127.14.5.61', 'fda1:b2:c3:0:127:14:5:61'; + fake_ns 'ns2' => '127.14.5.62', 'fda1:b2:c3:0:127:14:5:62'; + + expect 'CS05_NO_MISMATCH_GLUE_ZONE'; + forbid_others; +}; + +# Specific hintfile (test-zone-data/Consistency-TP/consistency05/Z-ROOT-INCOMPLETE-HINT-hintfile.zone) +root_hints 'ns1' => [ ], + 'ns2' => [ qw(127.14.5.62 fda1:b2:c3:0:127:14:5:62) ]; + +scenario 'Z-ROOT-INCOMPLETE-HINT' => sub { + zone '.'; + + expect 'CS05_MISSING_GLUE_FOR_ROOT_NS'; forbid_others; }; diff --git a/t/Test.data b/t/Test.data index 0bd8b92b0..73c9461f2 100644 Binary files a/t/Test.data and b/t/Test.data differ diff --git a/t/TestUtil.pm b/t/TestUtil.pm index fb05a5f3b..a66864a60 100644 --- a/t/TestUtil.pm +++ b/t/TestUtil.pm @@ -388,11 +388,13 @@ sub perform_methodsv2_testing { if ( defined $expected_res ) { ok( defined $res, "Result is defined" ) or diag "Unexpected undefined result"; foreach my $expected_ip ( @{ $expected_res } ) { - ok( grep( /^$expected_ip$/, @{ $res } ), "Name server IP '$expected_ip' is present" ) + ok( grep( { $_->address()->short eq $expected_ip } @{ $res } ), + "Name server IP '$expected_ip' is present" ) or diag "Expected but missing: $expected_ip"; } foreach my $ip ( @{ $res } ) { - ok( grep( /^$ip$/, @{ $expected_res } ), "Name server IP '$ip' is expected" ) + ok( grep( { $_ eq $ip->address()->short } @{ $expected_res } ), + "Name server IP '$ip' is expected" ) or diag "Present but not expected: $ip"; } ok( scalar @{ $res } == scalar @{ $expected_res }, "Number of name server IPs in both arrays match" ) diff --git a/t/TestUtil/DSL/Compiler.pm b/t/TestUtil/DSL/Compiler.pm index 1fe20ed81..4580c3ef6 100644 --- a/t/TestUtil/DSL/Compiler.pm +++ b/t/TestUtil/DSL/Compiler.pm @@ -310,6 +310,9 @@ sub _compile_fake_ns { my $undel_ns = $scenario_declaration->{body}{fake_ns}; return sub { my ( $zone_name ) = @_; + if ( $zone_name eq '.' ) { + Zonemaster::Engine::Recursor->remove_fake_addresses( '.' ); + } # Use default value of "fill_in_empty_oob_glue". Zonemaster::Engine->add_fake_delegation( $zone_name => $undel_ns, fill_in_empty_oob_glue => 1 ); diff --git a/t/profiles/Test-consistency-all.json b/t/profiles/Test-consistency-all.json index 34a27fce4..503478b0a 100644 --- a/t/profiles/Test-consistency-all.json +++ b/t/profiles/Test-consistency-all.json @@ -4,7 +4,6 @@ "consistency02", "consistency03", "consistency04", - "consistency05", "consistency06" ] }