- Clone all 5 Zonemaster component repos (LDNS, Engine, CLI, Backend, GUI) - Dockerfile.backend: 8-stage multi-stage build LDNS→Engine→CLI→Backend - Dockerfile.gui: Astro static build served via nginx - docker-compose.yml: backend (internal) + frontend (port 5353) - nginx.conf: root redirects to /es/, /api/ proxied to backend - zonemaster-gui/config.ts: defaultLanguage set to 'es' (Spanish) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1809 lines
58 KiB
Perl
1809 lines
58 KiB
Perl
package Zonemaster::Engine::Test::Nameserver;
|
|
|
|
use v5.16.0;
|
|
use warnings;
|
|
|
|
use version; our $VERSION = version->declare( "v1.1.0" );
|
|
|
|
use List::MoreUtils qw[uniq none];
|
|
use Locale::TextDomain qw[Zonemaster-Engine];
|
|
use Readonly;
|
|
use JSON::PP;
|
|
use Net::IP::XS;
|
|
|
|
use Zonemaster::Engine::Profile;
|
|
use Zonemaster::Engine::Constants qw[:ip];
|
|
use Zonemaster::Engine::Test::Address;
|
|
use Zonemaster::Engine::Util;
|
|
use Zonemaster::Engine::TestMethods;
|
|
|
|
=head1 NAME
|
|
|
|
Zonemaster::Engine::Test::Nameserver - Module implementing tests focused on the properties of a name server
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
my @results = Zonemaster::Engine::Test::Nameserver->all( $zone );
|
|
|
|
=cut
|
|
|
|
Readonly my @NONEXISTENT_NAMES => qw{
|
|
xn--nameservertest.iis.se
|
|
xn--nameservertest.icann.org
|
|
xn--nameservertest.ripe.net
|
|
};
|
|
|
|
=head1 METHODS
|
|
|
|
=over
|
|
|
|
=item all()
|
|
|
|
my @logentry_array = all( $zone );
|
|
|
|
Runs the default set of tests for that module, i.e. L<fourteen tests|/TESTS>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub all {
|
|
my ( $class, $zone ) = @_;
|
|
my @results;
|
|
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver01} ) ) {
|
|
push @results, $class->nameserver01( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver02} ) ) {
|
|
push @results, $class->nameserver02( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver03} ) ) {
|
|
push @results, $class->nameserver03( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver04} ) ) {
|
|
push @results, $class->nameserver04( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver05} ) ) {
|
|
push @results, $class->nameserver05( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver06} ) ) {
|
|
push @results, $class->nameserver06( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver07} ) ) {
|
|
push @results, $class->nameserver07( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver08} ) ) {
|
|
push @results, $class->nameserver08( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver09} ) ) {
|
|
push @results, $class->nameserver09( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver10} ) ) {
|
|
push @results, $class->nameserver10( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver11} ) ) {
|
|
push @results, $class->nameserver11( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver12} ) ) {
|
|
push @results, $class->nameserver12( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver13} ) ) {
|
|
push @results, $class->nameserver13( $zone );
|
|
}
|
|
if ( Zonemaster::Engine::Util::should_run_test( q{nameserver15} ) ) {
|
|
push @results, $class->nameserver15( $zone );
|
|
}
|
|
|
|
return @results;
|
|
} ## end sub all
|
|
|
|
=over
|
|
|
|
=item metadata()
|
|
|
|
my $hash_ref = metadata();
|
|
|
|
Returns a reference to a hash, the keys of which are the names of all Test Cases in the module, and the corresponding values are references to
|
|
an array containing all the message tags that the Test Case can use in L<log entries|Zonemaster::Engine::Logger::Entry>.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub metadata {
|
|
my ( $class ) = @_;
|
|
|
|
return {
|
|
nameserver01 => [
|
|
qw(
|
|
IS_A_RECURSOR
|
|
NO_RECURSOR
|
|
NO_RESPONSE
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver02 => [
|
|
qw(
|
|
BREAKS_ON_EDNS
|
|
EDNS_RESPONSE_WITHOUT_EDNS
|
|
EDNS_VERSION_ERROR
|
|
EDNS0_SUPPORT
|
|
NO_EDNS_SUPPORT
|
|
NO_RESPONSE
|
|
NS_ERROR
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver03 => [
|
|
qw(
|
|
AXFR_FAILURE
|
|
AXFR_AVAILABLE
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver04 => [
|
|
qw(
|
|
DIFFERENT_SOURCE_IP
|
|
SAME_SOURCE_IP
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver05 => [
|
|
qw(
|
|
AAAA_BAD_RDATA
|
|
AAAA_QUERY_DROPPED
|
|
AAAA_UNEXPECTED_RCODE
|
|
AAAA_WELL_PROCESSED
|
|
A_UNEXPECTED_RCODE
|
|
NO_RESPONSE
|
|
IPV4_DISABLED
|
|
IPV6_DISABLED
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver06 => [
|
|
qw(
|
|
CAN_NOT_BE_RESOLVED
|
|
CAN_BE_RESOLVED
|
|
NO_RESOLUTION
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver07 => [
|
|
qw(
|
|
UPWARD_REFERRAL_IRRELEVANT
|
|
UPWARD_REFERRAL
|
|
NO_UPWARD_REFERRAL
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver08 => [
|
|
qw(
|
|
QNAME_CASE_INSENSITIVE
|
|
QNAME_CASE_SENSITIVE
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver09 => [
|
|
qw(
|
|
CASE_QUERY_SAME_ANSWER
|
|
CASE_QUERY_DIFFERENT_ANSWER
|
|
CASE_QUERY_SAME_RC
|
|
CASE_QUERY_DIFFERENT_RC
|
|
CASE_QUERY_NO_ANSWER
|
|
CASE_QUERIES_RESULTS_OK
|
|
CASE_QUERIES_RESULTS_DIFFER
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver10 => [
|
|
qw(
|
|
N10_NO_RESPONSE_EDNS1_QUERY
|
|
N10_UNEXPECTED_RCODE
|
|
N10_EDNS_RESPONSE_ERROR
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver11 => [
|
|
qw(
|
|
N11_NO_EDNS
|
|
N11_NO_RESPONSE
|
|
N11_RETURNS_UNKNOWN_OPTION_CODE
|
|
N11_UNEXPECTED_ANSWER_SECTION
|
|
N11_UNEXPECTED_RCODE
|
|
N11_UNSET_AA
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver12 => [
|
|
qw(
|
|
NO_RESPONSE
|
|
NO_EDNS_SUPPORT
|
|
Z_FLAGS_NOTCLEAR
|
|
NS_ERROR
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver13 => [
|
|
qw(
|
|
NO_RESPONSE
|
|
NO_EDNS_SUPPORT
|
|
NS_ERROR
|
|
MISSING_OPT_IN_TRUNCATED
|
|
TEST_CASE_END
|
|
TEST_CASE_START
|
|
)
|
|
],
|
|
nameserver15 => [
|
|
qw(
|
|
N15_ERROR_ON_VERSION_QUERY
|
|
N15_NO_VERSION_REVEALED
|
|
N15_SOFTWARE_VERSION
|
|
N15_WRONG_CLASS
|
|
)
|
|
],
|
|
};
|
|
} ## end sub metadata
|
|
|
|
Readonly my %TAG_DESCRIPTIONS => (
|
|
NAMESERVER01 => sub {
|
|
__x # NAMESERVER:NAMESERVER01
|
|
'A name server should not be a recursor';
|
|
},
|
|
NAMESERVER02 => sub {
|
|
__x # NAMESERVER:NAMESERVER02
|
|
'Test of EDNS0 support';
|
|
},
|
|
NAMESERVER03 => sub {
|
|
__x # NAMESERVER:NAMESERVER03
|
|
'Test availability of zone transfer (AXFR)';
|
|
},
|
|
NAMESERVER04 => sub {
|
|
__x # NAMESERVER:NAMESERVER04
|
|
'Same source address';
|
|
},
|
|
NAMESERVER05 => sub {
|
|
__x # NAMESERVER:NAMESERVER05
|
|
'Behaviour against AAAA query';
|
|
},
|
|
NAMESERVER06 => sub {
|
|
__x # NAMESERVER:NAMESERVER06
|
|
'NS can be resolved';
|
|
},
|
|
NAMESERVER07 => sub {
|
|
__x # NAMESERVER:NAMESERVER07
|
|
'To check whether authoritative name servers return an upward referral';
|
|
},
|
|
NAMESERVER08 => sub {
|
|
__x # NAMESERVER:NAMESERVER08
|
|
'Testing QNAME case insensitivity';
|
|
},
|
|
NAMESERVER09 => sub {
|
|
__x # NAMESERVER:NAMESERVER09
|
|
'Testing QNAME case sensitivity';
|
|
},
|
|
NAMESERVER10 => sub {
|
|
__x # NAMESERVER:NAMESERVER10
|
|
'Test for undefined EDNS version';
|
|
},
|
|
NAMESERVER11 => sub {
|
|
__x # NAMESERVER:NAMESERVER11
|
|
'Test for unknown EDNS OPTION-CODE';
|
|
},
|
|
NAMESERVER12 => sub {
|
|
__x # NAMESERVER:NAMESERVER12
|
|
'Test for unknown EDNS flags';
|
|
},
|
|
NAMESERVER13 => sub {
|
|
__x # NAMESERVER:NAMESERVER13
|
|
'Test for truncated response on EDNS query';
|
|
},
|
|
NAMESERVER15 => sub {
|
|
__x # NAMESERVER:NAMESERVER15
|
|
'Checking for revealed software version';
|
|
},
|
|
AAAA_BAD_RDATA => sub {
|
|
__x # NAMESERVER:AAAA_BAD_RDATA
|
|
'Nameserver {ns} answered AAAA query with an unexpected RDATA length ({length} instead of 16).', @_;
|
|
},
|
|
AAAA_QUERY_DROPPED => sub {
|
|
__x # NAMESERVER:AAAA_QUERY_DROPPED
|
|
'Nameserver {ns} dropped AAAA query.', @_;
|
|
},
|
|
AAAA_UNEXPECTED_RCODE => sub {
|
|
__x # NAMESERVER:AAAA_UNEXPECTED_RCODE
|
|
'Nameserver {ns} answered AAAA query with an unexpected rcode ({rcode}).', @_;
|
|
},
|
|
AAAA_WELL_PROCESSED => sub {
|
|
__x # NAMESERVER:AAAA_WELL_PROCESSED
|
|
'The following nameservers answer AAAA queries without problems : {ns_list}.', @_;
|
|
},
|
|
A_UNEXPECTED_RCODE => sub {
|
|
__x # NAMESERVER:A_UNEXPECTED_RCODE
|
|
'Nameserver {ns} answered A query with an unexpected rcode ({rcode}).', @_;
|
|
},
|
|
AXFR_AVAILABLE => sub {
|
|
__x # NAMESERVER:AXFR_AVAILABLE
|
|
'Nameserver {ns} allow zone transfer using AXFR.', @_;
|
|
},
|
|
AXFR_FAILURE => sub {
|
|
__x # NAMESERVER:AXFR_FAILURE
|
|
'AXFR not available on nameserver {ns}.', @_;
|
|
},
|
|
BREAKS_ON_EDNS => sub {
|
|
__x # NAMESERVER:BREAKS_ON_EDNS
|
|
'No response from {ns} when EDNS is used in query asking for {domain}.', @_;
|
|
},
|
|
CAN_BE_RESOLVED => sub {
|
|
__x # NAMESERVER:CAN_BE_RESOLVED
|
|
'All nameservers succeeded to resolve to an IP address.', @_;
|
|
},
|
|
CAN_NOT_BE_RESOLVED => sub {
|
|
__x # NAMESERVER:CAN_NOT_BE_RESOLVED
|
|
'The following nameservers failed to resolve to an IP address : {nsname_list}.', @_;
|
|
},
|
|
CASE_QUERIES_RESULTS_DIFFER => sub {
|
|
__x # NAMESERVER:CASE_QUERIES_RESULTS_DIFFER
|
|
'When asked for {type} records on "{domain}" with different cases, all servers do not reply consistently.', @_;
|
|
},
|
|
CASE_QUERIES_RESULTS_OK => sub {
|
|
__x # NAMESERVER:CASE_QUERIES_RESULTS_OK
|
|
'When asked for {type} records on "{domain}" with different cases, all servers reply consistently.', @_;
|
|
},
|
|
CASE_QUERY_DIFFERENT_ANSWER => sub {
|
|
__x # NAMESERVER:CASE_QUERY_DIFFERENT_ANSWER
|
|
'When asked for {type} records on "{query1}" and "{query2}", '
|
|
. 'nameserver {ns} returns different answers.',
|
|
@_;
|
|
},
|
|
CASE_QUERY_DIFFERENT_RC => sub {
|
|
__x # NAMESERVER:CASE_QUERY_DIFFERENT_RC
|
|
'When asked for {type} records on "{query1}" and "{query2}", '
|
|
. 'nameserver {ns} returns different RCODE ("{rcode1}" vs "{rcode2}").',
|
|
@_;
|
|
},
|
|
CASE_QUERY_NO_ANSWER => sub {
|
|
__x # NAMESERVER:CASE_QUERY_NO_ANSWER
|
|
'When asked for {type} records on "{domain}", nameserver {ns} returns nothing.', @_;
|
|
},
|
|
CASE_QUERY_SAME_ANSWER => sub {
|
|
__x # NAMESERVER:CASE_QUERY_SAME_ANSWER
|
|
'When asked for {type} records on "{query1}" and "{query2}", nameserver {ns} returns same answers.',
|
|
@_;
|
|
},
|
|
CASE_QUERY_SAME_RC => sub {
|
|
__x # NAMESERVER:CASE_QUERY_SAME_RC
|
|
'When asked for {type} records on "{query1}" and "{query2}", '
|
|
. 'nameserver {ns} returns same RCODE "{rcode}".',
|
|
@_;
|
|
},
|
|
DIFFERENT_SOURCE_IP => sub {
|
|
__x # NAMESERVER:DIFFERENT_SOURCE_IP
|
|
'Nameserver {ns} replies on a SOA query with a different source address ({source}).', @_;
|
|
},
|
|
EDNS_RESPONSE_WITHOUT_EDNS => sub {
|
|
__x # NAMESERVER:EDNS_RESPONSE_WITHOUT_EDNS
|
|
'Response without EDNS from {ns} on query with EDNS0 asking for {domain}.', @_;
|
|
},
|
|
EDNS_VERSION_ERROR => sub {
|
|
__x # NAMESERVER:EDNS_VERSION_ERROR
|
|
'Incorrect version of EDNS (expected 0) in response from {ns} '
|
|
. 'on query with EDNS (version 0) asking for {domain}.',
|
|
@_;
|
|
},
|
|
EDNS0_SUPPORT => sub {
|
|
__x # NAMESERVER:EDNS0_SUPPORT
|
|
'The following nameservers support EDNS0 : {ns_list}.', @_;
|
|
},
|
|
IPV4_DISABLED => sub {
|
|
__x # NAMESERVER:IPV4_DISABLED
|
|
'IPv4 is disabled, not sending "{rrtype}" query to {ns}.', @_;
|
|
},
|
|
IPV6_DISABLED => sub {
|
|
__x # NAMESERVER:IPV6_DISABLED
|
|
'IPv6 is disabled, not sending "{rrtype}" query to {ns}.', @_;
|
|
},
|
|
IS_A_RECURSOR => sub {
|
|
__x # NAMESERVER:IS_A_RECURSOR
|
|
'Nameserver {ns} is a recursor.', @_;
|
|
},
|
|
MISSING_OPT_IN_TRUNCATED => sub {
|
|
__x # NAMESERVER:MISSING_OPT_IN_TRUNCATED
|
|
'Nameserver {ns} replies on an EDNS query with a truncated response without EDNS.', @_;
|
|
},
|
|
NO_EDNS_SUPPORT => sub {
|
|
__x # NAMESERVER:NO_EDNS_SUPPORT
|
|
'Nameserver {ns} does not support EDNS0 (replies with FORMERR).', @_;
|
|
},
|
|
NO_RECURSOR => sub {
|
|
__x # NAMESERVER:NO_RECURSOR
|
|
'Nameserver {ns} is not a recursor.', @_;
|
|
},
|
|
NO_RESOLUTION => sub {
|
|
__x # NAMESERVER:NO_RESOLUTION
|
|
"No nameserver was successfully resolved to an IP address.", @_;
|
|
},
|
|
NO_RESPONSE => sub {
|
|
__x # NAMESERVER:NO_RESPONSE
|
|
'No response from {ns} asking for {domain}.', @_;
|
|
},
|
|
NO_UPWARD_REFERRAL => sub {
|
|
__x # NAMESERVER:NO_UPWARD_REFERRAL
|
|
'None of the following nameservers returns an upward referral : {nsname_list}.', @_;
|
|
},
|
|
NS_ERROR => sub {
|
|
__x # NAMESERVER:NS_ERROR
|
|
'Erroneous response from nameserver {ns}.', @_;
|
|
},
|
|
N10_EDNS_RESPONSE_ERROR => sub {
|
|
__x # NAMESERVER:N10_EDNS_RESPONSE_ERROR
|
|
'Expected RCODE but received erroneous response to an EDNS version 1 query. Fetched from the nameservers with IP addresses {ns_ip_list}', @_;
|
|
},
|
|
N10_NO_RESPONSE_EDNS1_QUERY => sub {
|
|
__x # NAMESERVER:N10_NO_RESPONSE_EDNS1_QUERY
|
|
'No response to an EDNS version 1 query. Fetched from the nameservers with IP addresses {ns_ip_list}', @_;
|
|
},
|
|
N10_UNEXPECTED_RCODE => sub {
|
|
__x # NAMESERVER:N10_UNEXPECTED_RCODE
|
|
'Erroneous RCODE ("{rcode}") in response to an EDNS version 1 query. Fetched from the nameservers with IP addresses {ns_ip_list}', @_;
|
|
},
|
|
N11_NO_EDNS => sub {
|
|
__x # NAMESERVER:N11_NO_EDNS
|
|
'The DNS response, on query with unknown EDNS option-code, does not contain any EDNS from name servers "{ns_ip_list}".', @_;
|
|
},
|
|
N11_NO_RESPONSE => sub {
|
|
__x # NAMESERVER:N11_NO_RESPONSE
|
|
'There is no response on query with unknown EDNS option-code from name servers "{ns_ip_list}".', @_;
|
|
},
|
|
N11_RETURNS_UNKNOWN_OPTION_CODE => sub {
|
|
__x # NAMESERVER:N11_RETURNS_UNKNOWN_OPTION_CODE
|
|
'The DNS response contains an unknown EDNS option-code. Returned from name servers "{ns_ip_list}".', @_;
|
|
},
|
|
N11_UNEXPECTED_ANSWER_SECTION => sub {
|
|
__x # NAMESERVER:N11_UNEXPECTED_ANSWER_SECTION
|
|
'The DNS response, on query with unknown EDNS option-code, does not contain the expected SOA record in the answer section from name servers "{ns_ip_list}".', @_;
|
|
},
|
|
N11_UNEXPECTED_RCODE => sub {
|
|
__x # NAMESERVER:N11_UNEXPECTED_RCODE
|
|
'The DNS response, on query with unknown EDNS option-code, has unexpected RCODE name "{rcode}" from name servers "{ns_ip_list}".', @_;
|
|
},
|
|
N11_UNSET_AA => sub {
|
|
__x # NAMESERVER:N11_UNSET_AA
|
|
'The DNS response, on query with unknown EDNS option-code, is unexpectedly not authoritative from name servers "{ns_ip_list}".', @_;
|
|
},
|
|
N15_ERROR_ON_VERSION_QUERY => sub {
|
|
__x # NAMESERVER:N15_ERROR_ON_VERSION_QUERY
|
|
'The following name server(s) do not respond or respond with SERVFAIL to software version query "{query_name}". Returned from name servers: "{ns_list}"', @_;
|
|
},
|
|
N15_NO_VERSION_REVEALED => sub {
|
|
__x # NAMESERVER:N15_NO_VERSION_REVEALED
|
|
'The following name server(s) do not reveal the software version. Returned from name servers: "{ns_list}"', @_;
|
|
},
|
|
N15_SOFTWARE_VERSION => sub {
|
|
__x # NAMESERVER:N15_SOFTWARE_VERSION
|
|
'The following name server(s) respond to software version query "{query_name}" with string "{string}". Returned from name servers: "{ns_list}"', @_;
|
|
},
|
|
N15_WRONG_CLASS => sub {
|
|
__x # NAMESERVER:N15_WRONG_CLASS
|
|
'The following name server(s) do not return CH class record(s) on CH class query. Returned from name servers: "{ns_list}"', @_;
|
|
},
|
|
QNAME_CASE_INSENSITIVE => sub {
|
|
__x # NAMESERVER:QNAME_CASE_INSENSITIVE
|
|
'Nameserver {ns} does not preserve original case of the queried name ({domain}).', @_;
|
|
},
|
|
QNAME_CASE_SENSITIVE => sub {
|
|
__x # NAMESERVER:QNAME_CASE_SENSITIVE
|
|
"Nameserver {ns} preserves original case of queried names ({domain}).", @_;
|
|
},
|
|
SAME_SOURCE_IP => sub {
|
|
__x # NAMESERVER:SAME_SOURCE_IP
|
|
'All nameservers reply with same IP used to query them.', @_;
|
|
},
|
|
TEST_CASE_END => sub {
|
|
__x # NAMESERVER:TEST_CASE_END
|
|
'TEST_CASE_END {testcase}.', @_;
|
|
},
|
|
TEST_CASE_START => sub {
|
|
__x # NAMESERVER:TEST_CASE_START
|
|
'TEST_CASE_START {testcase}.', @_;
|
|
},
|
|
UPWARD_REFERRAL => sub {
|
|
__x # NAMESERVER:UPWARD_REFERRAL
|
|
'Nameserver {ns} returns an upward referral.', @_;
|
|
},
|
|
UPWARD_REFERRAL_IRRELEVANT => sub {
|
|
__x # NAMESERVER:UPWARD_REFERRAL_IRRELEVANT
|
|
'Upward referral tests skipped for root zone.', @_;
|
|
},
|
|
Z_FLAGS_NOTCLEAR => sub {
|
|
__x # NAMESERVER:Z_FLAGS_NOTCLEAR
|
|
'Nameserver {ns} has one or more unknown EDNS Z flag bits set.', @_;
|
|
},
|
|
);
|
|
|
|
=over
|
|
|
|
=item tag_descriptions()
|
|
|
|
my $hash_ref = tag_descriptions();
|
|
|
|
Used by the L<built-in translation system|Zonemaster::Engine::Translator>.
|
|
|
|
Returns a reference to a hash, the keys of which are the message tags and the corresponding values are strings (message IDs).
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub tag_descriptions {
|
|
return \%TAG_DESCRIPTIONS;
|
|
}
|
|
|
|
=over
|
|
|
|
=item version()
|
|
|
|
my $version_string = version();
|
|
|
|
Returns a string containing the version of the current module.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub version {
|
|
return "$Zonemaster::Engine::Test::Nameserver::VERSION";
|
|
}
|
|
|
|
=head1 INTERNAL METHODS
|
|
|
|
=over
|
|
|
|
=item _emit_log()
|
|
|
|
my $log_entry = _emit_log( $message_tag_string, $hash_ref );
|
|
|
|
Adds a message to the L<logger|Zonemaster::Engine::Logger> for this module.
|
|
See L<Zonemaster::Engine::Logger::Entry/add($tag, $argref, $module, $testcase)> for more details.
|
|
|
|
Takes a string (message tag) and a reference to a hash (arguments).
|
|
|
|
Returns a L<Zonemaster::Engine::Logger::Entry> object.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub _emit_log { my ( $tag, $argref ) = @_; return Zonemaster::Engine->logger->add( $tag, $argref, 'Nameserver' ); }
|
|
|
|
=over
|
|
|
|
=item _ip_disabled_message()
|
|
|
|
my $bool = _ip_disabled_message( $logentry_array_ref, $ns, @query_type_array );
|
|
|
|
Checks if the IP version of a given name server is allowed to be queried. If not, it adds a logging message and returns true. Else, it returns false.
|
|
|
|
Takes a reference to an array of L<Zonemaster::Engine::Logger::Entry> objects, a L<Zonemaster::Engine::Nameserver> object and an array of strings (query type).
|
|
|
|
Returns a boolean.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub _ip_disabled_message {
|
|
my ( $results_array, $ns, @rrtypes ) = @_;
|
|
|
|
if ( not Zonemaster::Engine::Profile->effective->get(q{net.ipv6}) and $ns->address->version == $IP_VERSION_6 ) {
|
|
push @$results_array, map {
|
|
_emit_log(
|
|
IPV6_DISABLED => {
|
|
ns => $ns->string,
|
|
rrtype => $_
|
|
}
|
|
)
|
|
} @rrtypes;
|
|
return 1;
|
|
}
|
|
|
|
if ( not Zonemaster::Engine::Profile->effective->get(q{net.ipv4}) and $ns->address->version == $IP_VERSION_4 ) {
|
|
push @$results_array, map {
|
|
_emit_log(
|
|
IPV4_DISABLED => {
|
|
ns => $ns->string,
|
|
rrtype => $_,
|
|
}
|
|
)
|
|
} @rrtypes;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
=head1 TESTS
|
|
|
|
=over
|
|
|
|
=item nameserver01()
|
|
|
|
my @logentry_array = nameserver01( $zone );
|
|
|
|
Runs the L<Nameserver01 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver01.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver01 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver01';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
for my $ns ( @nss ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $ns, q{A} ) );
|
|
|
|
my $response_count = 0;
|
|
my $nxdomain_count = 0;
|
|
my $is_no_recursor = 1;
|
|
my $has_seen_ra = 0;
|
|
for my $nonexistent_name ( @NONEXISTENT_NAMES ) {
|
|
|
|
my $p = $ns->query( $nonexistent_name, q{A} );
|
|
if ( !$p ) {
|
|
push @results,
|
|
_emit_log(
|
|
NO_RESPONSE => {
|
|
ns => $ns->string,
|
|
domain => $nonexistent_name,
|
|
}
|
|
);
|
|
$is_no_recursor = 0;
|
|
}
|
|
else {
|
|
$response_count++;
|
|
|
|
if ( $p->ra ) {
|
|
$has_seen_ra = 1;
|
|
}
|
|
|
|
if ( $p->rcode eq q{NXDOMAIN} ) {
|
|
$nxdomain_count++;
|
|
}
|
|
}
|
|
} ## end for my $nonexistent_name...
|
|
|
|
if ( $has_seen_ra || ( $response_count > 0 && $nxdomain_count == $response_count ) ) {
|
|
push @results, _emit_log( IS_A_RECURSOR => { ns => $ns->string } );
|
|
$is_no_recursor = 0;
|
|
}
|
|
|
|
if ( $is_no_recursor ) {
|
|
push @results, _emit_log( NO_RECURSOR => { ns => $ns->string } );
|
|
}
|
|
} ## end for my $ns ( @nss )
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver01
|
|
|
|
=over
|
|
|
|
=item nameserver02()
|
|
|
|
my @logentry_array = nameserver02( $zone );
|
|
|
|
Runs the L<Nameserver02 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver02.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver02 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver02';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
my %nsnames_and_ip;
|
|
my $n_error = 0;
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
foreach my $local_ns ( @nss ) {
|
|
next if ( _ip_disabled_message( \@results, $local_ns, q{SOA} ) );
|
|
|
|
next if $nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short };
|
|
|
|
my $p = $local_ns->query( $zone->name, q{SOA}, { edns_details => { version => 0 } } );
|
|
if ( $p ) {
|
|
if ( $p->rcode eq q{FORMERR} and not $p->has_edns) {
|
|
push @results, _emit_log( NO_EDNS_SUPPORT => { ns => $local_ns->string } );
|
|
$n_error++;
|
|
}
|
|
elsif ( $p->rcode eq q{NOERROR} and not $p->edns_rcode and $p->get_records( q{SOA}, q{answer} ) and $p->edns_version == 0 ) {
|
|
$nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short }++;
|
|
next;
|
|
}
|
|
elsif ( $p->rcode eq q{NOERROR} and not $p->has_edns ) {
|
|
push @results,
|
|
_emit_log(
|
|
EDNS_RESPONSE_WITHOUT_EDNS => {
|
|
ns => $local_ns->string,
|
|
domain => $zone->name,
|
|
}
|
|
);
|
|
$n_error++;
|
|
}
|
|
elsif ( $p->rcode eq q{NOERROR} and $p->has_edns and $p->edns_version != 0 ) {
|
|
push @results,
|
|
_emit_log(
|
|
EDNS_VERSION_ERROR => {
|
|
ns => $local_ns->string,
|
|
domain => $zone->name,
|
|
}
|
|
);
|
|
$n_error++;
|
|
}
|
|
else {
|
|
push @results, _emit_log( NS_ERROR => { ns => $local_ns->string } );
|
|
$n_error++;
|
|
}
|
|
}
|
|
else {
|
|
my $p2 = $local_ns->query( $zone->name, q{SOA} );
|
|
if ( $p2 ) {
|
|
push @results,
|
|
_emit_log(
|
|
BREAKS_ON_EDNS => {
|
|
ns => $local_ns->string,
|
|
domain => $zone->name,
|
|
}
|
|
);
|
|
$n_error++;
|
|
}
|
|
else {
|
|
push @results,
|
|
_emit_log(
|
|
NO_RESPONSE => {
|
|
ns => $local_ns->string,
|
|
domain => $zone->name,
|
|
}
|
|
);
|
|
$n_error++;
|
|
}
|
|
}
|
|
|
|
$nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short }++;
|
|
} ## end foreach my $local_ns ( @{ Zonemaster::Engine::TestMethods...})
|
|
|
|
if ( scalar keys %nsnames_and_ip and not $n_error ) {
|
|
push @results,
|
|
_emit_log(
|
|
EDNS0_SUPPORT => {
|
|
ns_list => join( q{;}, keys %nsnames_and_ip ),
|
|
}
|
|
);
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver02
|
|
|
|
=over
|
|
|
|
=item nameserver03()
|
|
|
|
my @logentry_array = nameserver03( $zone );
|
|
|
|
Runs the L<Nameserver03 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver03.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver03 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver03';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
my %nsnames_and_ip;
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
foreach my $local_ns ( @nss ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $local_ns, q{AXFR} ) );
|
|
|
|
next if $nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short };
|
|
|
|
my $first_rr;
|
|
eval {
|
|
$local_ns->axfr( $zone->name, sub { ( $first_rr ) = @_; return 0; } );
|
|
1;
|
|
} or do {
|
|
push @results, _emit_log( AXFR_FAILURE => { ns => $local_ns->string } );
|
|
};
|
|
|
|
if ( $first_rr and $first_rr->type eq q{SOA} ) {
|
|
push @results, _emit_log( AXFR_AVAILABLE => { ns => $local_ns->string } );
|
|
}
|
|
|
|
$nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short }++;
|
|
} ## end foreach my $local_ns ( @{ Zonemaster::Engine::TestMethods...})
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver03
|
|
|
|
=over
|
|
|
|
=item nameserver04()
|
|
|
|
my @logentry_array = nameserver04( $zone );
|
|
|
|
Runs the L<Nameserver04 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver04.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver04 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver04';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
my %nsnames_and_ip;
|
|
my $n_error = 0;
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
foreach my $local_ns ( @nss ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $local_ns, q{SOA} ) );
|
|
|
|
next if $nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short };
|
|
|
|
my $p = $local_ns->query( $zone->name, q{SOA} );
|
|
if ( $p ) {
|
|
if ( $p->answerfrom and ( $local_ns->address->short ne Net::IP::XS->new( $p->answerfrom )->short ) ) {
|
|
push @results,
|
|
_emit_log(
|
|
DIFFERENT_SOURCE_IP => {
|
|
ns => $local_ns->string,
|
|
source => $p->answerfrom,
|
|
}
|
|
);
|
|
$n_error++;
|
|
}
|
|
}
|
|
$nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short }++;
|
|
} ## end foreach my $local_ns ( @{ Zonemaster::Engine::TestMethods...})
|
|
|
|
if ( scalar keys %nsnames_and_ip and not $n_error) {
|
|
push @results,
|
|
_emit_log(
|
|
SAME_SOURCE_IP => {
|
|
names => join( q{,}, keys %nsnames_and_ip ),
|
|
}
|
|
);
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver04
|
|
|
|
=over
|
|
|
|
=item nameserver05()
|
|
|
|
my @logentry_array = nameserver05( $zone );
|
|
|
|
Runs the L<Nameserver05 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver05.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver05 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver05';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
my %nsnames_and_ip;
|
|
my $aaaa_issue = 0;
|
|
my @aaaa_ok;
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
foreach my $ns ( @nss ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $ns, q{A} ) );
|
|
|
|
next if $nsnames_and_ip{ $ns->name->string . q{/} . $ns->address->short };
|
|
|
|
$nsnames_and_ip{ $ns->name->string . q{/} . $ns->address->short }++;
|
|
|
|
my $p = $ns->query( $zone->name, q{A}, { usevc => 0 } );
|
|
|
|
if ( not $p ) {
|
|
push @results,
|
|
_emit_log(
|
|
NO_RESPONSE => {
|
|
ns => $ns->string,
|
|
domain => $zone->name,
|
|
}
|
|
);
|
|
}
|
|
elsif ( $p->rcode ne q{NOERROR} ) {
|
|
push @results,
|
|
_emit_log(
|
|
A_UNEXPECTED_RCODE => {
|
|
ns => $ns->string,
|
|
rcode => $p->rcode,
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
$p = $ns->query( $zone->name, q{AAAA}, { usevc => 0 } );
|
|
|
|
if ( not $p ) {
|
|
push @results,
|
|
_emit_log( AAAA_QUERY_DROPPED => { ns => $ns->string } );
|
|
$aaaa_issue++;
|
|
}
|
|
elsif ( $p->rcode ne q{NOERROR} ) {
|
|
push @results,
|
|
_emit_log(
|
|
AAAA_UNEXPECTED_RCODE => {
|
|
ns => $ns->string,
|
|
rcode => $p->rcode,
|
|
}
|
|
);
|
|
$aaaa_issue++;
|
|
}
|
|
else {
|
|
foreach my $rr ( $p->get_records( q{AAAA}, q{answer} ) ) {
|
|
if ( length($rr->rdf(0)) != 16 ) {
|
|
push @results,
|
|
_emit_log(
|
|
AAAA_BAD_RDATA => {
|
|
ns => $ns->string,
|
|
length => length( $rr->rdf( 0 ) ),
|
|
}
|
|
);
|
|
$aaaa_issue++;
|
|
}
|
|
else {
|
|
push @aaaa_ok, $rr->address;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( scalar @aaaa_ok and not $aaaa_issue ) {
|
|
push @results,
|
|
_emit_log(
|
|
AAAA_WELL_PROCESSED => {
|
|
ns_list => join( q{;}, keys %nsnames_and_ip ),
|
|
}
|
|
);
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver05
|
|
|
|
=over
|
|
|
|
=item nameserver06()
|
|
|
|
my @logentry_array = nameserver06( $zone );
|
|
|
|
Runs the L<Nameserver06 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver06.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver06 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver06';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
my @all_nsnames = uniq map { lc( $_->string ) } @{ Zonemaster::Engine::TestMethods->method2( $zone ) },
|
|
@{ Zonemaster::Engine::TestMethods->method3( $zone ) };
|
|
my @all_nsnames_with_ip = uniq map { lc( $_->name->string ) } @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
my @all_nsnames_without_ip;
|
|
my %diff;
|
|
|
|
@diff{@all_nsnames} = undef;
|
|
delete @diff{@all_nsnames_with_ip};
|
|
|
|
@all_nsnames_without_ip = keys %diff;
|
|
if ( scalar @all_nsnames_without_ip and scalar @all_nsnames_with_ip ) {
|
|
push @results,
|
|
_emit_log(
|
|
CAN_NOT_BE_RESOLVED => {
|
|
nsname_list => join( q{;}, @all_nsnames_without_ip ),
|
|
}
|
|
);
|
|
}
|
|
elsif ( not scalar @all_nsnames_with_ip ) {
|
|
push @results,
|
|
_emit_log(
|
|
NO_RESOLUTION => {
|
|
names => join( q{,}, @all_nsnames_without_ip ),
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
push @results, _emit_log( CAN_BE_RESOLVED => {} );
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver06
|
|
|
|
=over
|
|
|
|
=item nameserver07()
|
|
|
|
my @logentry_array = nameserver07( $zone );
|
|
|
|
Runs the L<Nameserver07 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver07.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver07 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver07';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
my %nsnames_and_ip;
|
|
my %nsnames;
|
|
my $n_error = 0;
|
|
|
|
if ( $zone->name eq q{.} ) {
|
|
push @results, _emit_log( UPWARD_REFERRAL_IRRELEVANT => {} );
|
|
}
|
|
else {
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
foreach my $local_ns ( @nss ) {
|
|
next if ( _ip_disabled_message( \@results, $local_ns, q{NS} ) );
|
|
|
|
next if $nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short };
|
|
|
|
my $p = $local_ns->query( q{.}, q{NS} );
|
|
if ( $p ) {
|
|
my @ns = $p->get_records( q{NS}, q{authority} );
|
|
|
|
if ( @ns ) {
|
|
push @results, _emit_log( UPWARD_REFERRAL => { ns => $local_ns->string } );
|
|
$n_error++;
|
|
}
|
|
}
|
|
$nsnames{ $local_ns->name }++;
|
|
$nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short }++;
|
|
} ## end foreach my $local_ns ( @{ Zonemaster::Engine::TestMethods...})
|
|
|
|
if ( scalar keys %nsnames_and_ip and not $n_error ) {
|
|
push @results,
|
|
_emit_log(
|
|
NO_UPWARD_REFERRAL => {
|
|
nsname_list => join( q{;}, sort keys %nsnames ),
|
|
}
|
|
);
|
|
}
|
|
} ## end else [ if ( $zone->name eq q{.})]
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver07
|
|
|
|
=over
|
|
|
|
=item nameserver08()
|
|
|
|
my @logentry_array = nameserver08( $zone );
|
|
|
|
Runs the L<Nameserver08 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver08.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver08 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver08';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
my %nsnames_and_ip;
|
|
my $original_name = q{www.} . $zone->name->string;
|
|
my $randomized_uc_name;
|
|
|
|
$original_name =~ s/[.]+\z//smgx;
|
|
|
|
do {
|
|
$randomized_uc_name = scramble_case $original_name;
|
|
} while ( $randomized_uc_name eq $original_name );
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
foreach my $local_ns ( @nss ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $local_ns, q{SOA} ) );
|
|
|
|
next if $nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short };
|
|
|
|
my $p = $local_ns->query( $randomized_uc_name, q{SOA} );
|
|
|
|
if ( $p and my ( $qrr ) = $p->question() ) {
|
|
my $qrr_name = $qrr->name();
|
|
$qrr_name =~ s/\.\z//smgx;
|
|
if ( $qrr_name eq $randomized_uc_name ) {
|
|
push @results,
|
|
_emit_log(
|
|
QNAME_CASE_SENSITIVE => {
|
|
ns => $local_ns->string,
|
|
domain => $randomized_uc_name,
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
push @results,
|
|
_emit_log(
|
|
QNAME_CASE_INSENSITIVE => {
|
|
ns => $local_ns->string,
|
|
domain => $randomized_uc_name,
|
|
}
|
|
);
|
|
}
|
|
} ## end if ( $p and my ( $qrr ...))
|
|
$nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short }++;
|
|
} ## end foreach my $local_ns ( @{ Zonemaster::Engine::TestMethods...})
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver08
|
|
|
|
=over
|
|
|
|
=item nameserver09()
|
|
|
|
my @logentry_array = nameserver09( $zone );
|
|
|
|
Runs the L<Nameserver09 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver09.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver09 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver09';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
my %nsnames_and_ip;
|
|
my $original_name = q{www.} . $zone->name->string;
|
|
my $record_type = q{SOA};
|
|
my $randomized_uc_name1;
|
|
my $randomized_uc_name2;
|
|
my $all_results_match = 1;
|
|
|
|
$original_name =~ s/[.]+\z//smgx;
|
|
|
|
do {
|
|
$randomized_uc_name1 = scramble_case $original_name;
|
|
} while ( $randomized_uc_name1 eq $original_name );
|
|
|
|
do {
|
|
$randomized_uc_name2 = scramble_case $original_name;
|
|
} while ( $randomized_uc_name2 eq $original_name or $randomized_uc_name2 eq $randomized_uc_name1 );
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
foreach my $local_ns ( @nss ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $local_ns, $record_type ) );
|
|
|
|
next if $nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short };
|
|
|
|
my $p1 = $local_ns->query( $randomized_uc_name1, $record_type );
|
|
my $p2 = $local_ns->query( $randomized_uc_name2, $record_type );
|
|
|
|
my $answer1_string = q{};
|
|
my $answer2_string = q{};
|
|
my $json = JSON::PP->new->canonical->pretty;
|
|
if ( $p1 and scalar $p1->answer ) {
|
|
|
|
my @answer1 = map { lc $_->string } sort $p1->answer;
|
|
$answer1_string = $json->encode( \@answer1 );
|
|
|
|
if ( $p2 and scalar $p2->answer ) {
|
|
|
|
my @answer2 = map { lc $_->string } sort $p2->answer;
|
|
$answer2_string = $json->encode( \@answer2 );
|
|
}
|
|
|
|
if ( $answer1_string eq $answer2_string ) {
|
|
push @results,
|
|
_emit_log(
|
|
CASE_QUERY_SAME_ANSWER => {
|
|
ns => $local_ns->string,
|
|
type => $record_type,
|
|
query1 => $randomized_uc_name1,
|
|
query2 => $randomized_uc_name2,
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
$all_results_match = 0;
|
|
push @results,
|
|
_emit_log(
|
|
CASE_QUERY_DIFFERENT_ANSWER => {
|
|
ns => $local_ns->string,
|
|
type => $record_type,
|
|
query1 => $randomized_uc_name1,
|
|
query2 => $randomized_uc_name2,
|
|
}
|
|
);
|
|
}
|
|
|
|
} ## end if ( $p1 and scalar $p1...)
|
|
elsif ( $p1 and $p2 ) {
|
|
|
|
if ( $p1->rcode eq $p2->rcode ) {
|
|
push @results,
|
|
_emit_log(
|
|
CASE_QUERY_SAME_RC => {
|
|
ns => $local_ns->string,
|
|
type => $record_type,
|
|
query1 => $randomized_uc_name1,
|
|
query2 => $randomized_uc_name2,
|
|
rcode => $p1->rcode,
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
$all_results_match = 0;
|
|
push @results,
|
|
_emit_log(
|
|
CASE_QUERY_DIFFERENT_RC => {
|
|
ns => $local_ns->string,
|
|
type => $record_type,
|
|
query1 => $randomized_uc_name1,
|
|
query2 => $randomized_uc_name2,
|
|
rcode1 => $p1->rcode,
|
|
rcode2 => $p2->rcode,
|
|
}
|
|
);
|
|
}
|
|
|
|
} ## end elsif ( $p1 and $p2 )
|
|
elsif ( $p1 or $p2 ) {
|
|
$all_results_match = 0;
|
|
push @results,
|
|
_emit_log(
|
|
CASE_QUERY_NO_ANSWER => {
|
|
ns => $local_ns->string,
|
|
type => $record_type,
|
|
domain => $p1 ? $randomized_uc_name1 : $randomized_uc_name2,
|
|
}
|
|
);
|
|
}
|
|
|
|
$nsnames_and_ip{ $local_ns->name->string . q{/} . $local_ns->address->short }++;
|
|
} ## end foreach my $local_ns ( @{ Zonemaster::Engine::TestMethods...})
|
|
|
|
if ( $all_results_match ) {
|
|
push @results,
|
|
_emit_log(
|
|
CASE_QUERIES_RESULTS_OK => {
|
|
type => $record_type,
|
|
domain => $original_name,
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
push @results,
|
|
_emit_log(
|
|
CASE_QUERIES_RESULTS_DIFFER => {
|
|
type => $record_type,
|
|
domain => $original_name,
|
|
}
|
|
);
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver09
|
|
|
|
=over
|
|
|
|
=item nameserver10()
|
|
|
|
my @logentry_array = nameserver10( $zone );
|
|
|
|
Runs the L<Nameserver10 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver10.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver10 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver10';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
|
|
my @no_response_edns1;
|
|
my %unexpected_rcode;
|
|
my @edns_response_error;
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
for my $ns ( @nss ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $ns, q{SOA} ) );
|
|
|
|
my $p = $ns->query( $zone->name, q{SOA}, { edns_details => { version => 0 } } );
|
|
|
|
if ( $p and $p->rcode eq q{NOERROR} ){
|
|
my $p2 = $ns->query( $zone->name, q{SOA}, { edns_details => { version => 1 } } );
|
|
|
|
if ( $p2 ) {
|
|
if ( ($p2->rcode ne q{NOERROR} and $p2->edns_rcode != 1) ) {
|
|
push @{ $unexpected_rcode{$p->rcode} }, $ns->address->short;
|
|
}
|
|
elsif ( ($p2->rcode eq q{NOERROR} and $p2->edns_rcode == 1) and $p2->edns_version == 0 and not scalar $p2->answer){
|
|
next;
|
|
}
|
|
else {
|
|
push @edns_response_error, $ns->address->short;
|
|
}
|
|
}
|
|
else {
|
|
push @no_response_edns1, $ns->address->short;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( scalar @no_response_edns1 ){
|
|
push @results,
|
|
_emit_log(
|
|
N10_NO_RESPONSE_EDNS1_QUERY => {
|
|
ns_ip_list => join ( q{;}, uniq sort @no_response_edns1 )
|
|
}
|
|
);
|
|
}
|
|
|
|
if ( scalar keys %unexpected_rcode ){
|
|
push @results, map {
|
|
_emit_log(
|
|
N10_UNEXPECTED_RCODE => {
|
|
rcode => $_,
|
|
ns_ip_list => join( q{;}, uniq sort @{ $unexpected_rcode{$_} } )
|
|
}
|
|
)
|
|
} keys %unexpected_rcode;
|
|
}
|
|
|
|
if ( scalar @edns_response_error ){
|
|
push @results,
|
|
_emit_log(
|
|
N10_EDNS_RESPONSE_ERROR => {
|
|
ns_ip_list => join ( q{;}, uniq sort @edns_response_error )
|
|
}
|
|
);
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver10
|
|
|
|
=over
|
|
|
|
=item nameserver11()
|
|
|
|
my @logentry_array = nameserver11( $zone );
|
|
|
|
Runs the L<Nameserver11 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver11.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver11 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver11';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
|
|
my @no_response;
|
|
my %unexpected_rcode;
|
|
my @no_edns;
|
|
my @unexpected_answer;
|
|
my @unset_aa;
|
|
my @unknown_opt_code;
|
|
|
|
# Choose an unassigned EDNS0 Option Codes
|
|
# values 15-26945 are Unassigned. Let's say we use 137 ???
|
|
my $opt_code = 137;
|
|
my $opt_data = q{};
|
|
my $opt_length = length($opt_data);
|
|
my $rdata = $opt_code*65536 + $opt_length;
|
|
|
|
foreach my $ns ( @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) } ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $ns, q{SOA} ) );
|
|
|
|
my $p = $ns->query( $zone->name, q{SOA}, { edns_details => { version => 0 } } );
|
|
|
|
if ( not $p or not $p->has_edns or $p->rcode ne q{NOERROR} or not $p->aa or not $p->get_records_for_name(q{SOA}, $zone->name, q{answer}) ) {
|
|
next;
|
|
}
|
|
|
|
$p = $ns->query( $zone->name, q{SOA}, { edns_details => { version => 0, data => $rdata } } );
|
|
|
|
if ( $p ) {
|
|
if ( $p->rcode ne q{NOERROR} ) {
|
|
push @{ $unexpected_rcode{$p->rcode} }, $ns->address->short;
|
|
}
|
|
|
|
elsif ( not $p->has_edns ) {
|
|
push @no_edns, $ns->address->short;
|
|
}
|
|
|
|
elsif ( not $p->get_records_for_name(q{SOA}, $zone->name, q{answer}) ) {
|
|
push @unexpected_answer, $ns->address->short;
|
|
}
|
|
|
|
elsif ( not $p->aa ) {
|
|
push @unset_aa, $ns->address->short;
|
|
}
|
|
|
|
elsif ( defined $p->edns_data ) {
|
|
my $p_opt = $p->edns_data;
|
|
|
|
# Unpack the bytes string:
|
|
# - OPTION-CODE as unsigned short (16-bit) in "network" (big-endian) order, and
|
|
# - OPTION-DATA as a sequence of bytes of length specified by a prefixed unsigned short (16-bit)
|
|
# in "network" (big-endian) order (OPTION-LENGTH), and
|
|
# - Remaining data, if any (i.e., other OPTIONS)
|
|
|
|
my @unpacked_opt = eval { unpack("(n n/a)*", $p_opt) };
|
|
|
|
while ( my ( $p_opt_code, $p_opt_data, @next_data ) = @unpacked_opt ) {
|
|
if ( $p_opt_code == $opt_code ) {
|
|
push @unknown_opt_code, $ns->address->short;
|
|
last;
|
|
}
|
|
|
|
@unpacked_opt = @next_data;
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
push @no_response, $ns->address->short;
|
|
}
|
|
}
|
|
|
|
if ( scalar @no_response ) {
|
|
push @results, _emit_log( N11_NO_RESPONSE => { ns_ip_list => join( q{;}, uniq sort @no_response ) } );
|
|
}
|
|
|
|
if ( scalar keys %unexpected_rcode ) {
|
|
push @results, map {
|
|
_emit_log(
|
|
N11_UNEXPECTED_RCODE => {
|
|
rcode => $_,
|
|
ns_ip_list => join( q{;}, uniq sort @{ $unexpected_rcode{$_} } )
|
|
}
|
|
)
|
|
} keys %unexpected_rcode;
|
|
}
|
|
|
|
if ( scalar @no_edns ) {
|
|
push @results, _emit_log( N11_NO_EDNS => { ns_ip_list => join( q{;}, uniq sort @no_edns ) } );
|
|
}
|
|
|
|
if ( scalar @unexpected_answer ) {
|
|
push @results, _emit_log( N11_UNEXPECTED_ANSWER_SECTION => { ns_ip_list => join( q{;}, uniq sort @unexpected_answer ) } );
|
|
}
|
|
|
|
if ( scalar @unset_aa ) {
|
|
push @results, _emit_log( N11_UNSET_AA => { ns_ip_list => join( q{;}, uniq sort @unset_aa ) } );
|
|
}
|
|
|
|
if ( scalar @unknown_opt_code ) {
|
|
push @results, _emit_log( N11_RETURNS_UNKNOWN_OPTION_CODE => { ns_ip_list => join( q{;}, uniq sort @unknown_opt_code ) } );
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver11
|
|
|
|
=over
|
|
|
|
=item nameserver12()
|
|
|
|
my @logentry_array = nameserver12( $zone );
|
|
|
|
Runs the L<Nameserver12 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver12.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver12 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver12';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
for my $ns ( @nss ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $ns, q{SOA} ) );
|
|
|
|
my $p = $ns->query( $zone->name, q{SOA}, { edns_details => { version => 0, z => 3 } } );
|
|
|
|
if ( $p ) {
|
|
if ( $p->rcode eq q{FORMERR} and not $p->edns_rcode ) {
|
|
push @results, _emit_log( NO_EDNS_SUPPORT => { ns => $ns->string } );
|
|
}
|
|
elsif ( $p->edns_z ) {
|
|
push @results, _emit_log( Z_FLAGS_NOTCLEAR => { ns => $ns->string } );
|
|
}
|
|
elsif ( $p->rcode eq q{NOERROR} and not $p->edns_rcode and $p->edns_version == 0 and $p->edns_z == 0 and $p->get_records( q{SOA}, q{answer} ) ) {
|
|
next;
|
|
}
|
|
else {
|
|
push @results, _emit_log( NS_ERROR => { ns => $ns->string } );
|
|
}
|
|
}
|
|
else {
|
|
push @results,
|
|
_emit_log(
|
|
NO_RESPONSE => {
|
|
ns => $ns->string,
|
|
domain => $zone->name,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver12
|
|
|
|
=over
|
|
|
|
=item nameserver13()
|
|
|
|
my @logentry_array = nameserver13( $zone );
|
|
|
|
Runs the L<Nameserver13 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver13.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver13 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver13';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
|
|
my @nss = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
|
|
|
for my $ns ( @nss ) {
|
|
|
|
next if ( _ip_disabled_message( \@results, $ns, q{DNSKEY} ) );
|
|
|
|
my $p = $ns->query( $zone->name, q{DNSKEY}, { usevc => 0, fallback => 0, edns_details => { version => 0, do => 1, size => 512 } } );
|
|
|
|
if ( $p ) {
|
|
if ( $p->rcode eq q{FORMERR} and not $p->edns_rcode ) {
|
|
push @results, _emit_log( NO_EDNS_SUPPORT => { ns => $ns->string, } );
|
|
}
|
|
elsif ( $p->tc and not $p->has_edns ) {
|
|
push @results, _emit_log( MISSING_OPT_IN_TRUNCATED => { ns => $ns->string } );
|
|
}
|
|
elsif ( $p->rcode eq q{NOERROR} and not $p->edns_rcode and $p->edns_version == 0 ) {
|
|
next;
|
|
}
|
|
else {
|
|
push @results, _emit_log( NS_ERROR => { ns => $ns->string } );
|
|
}
|
|
}
|
|
else {
|
|
push @results,
|
|
_emit_log(
|
|
NO_RESPONSE => {
|
|
ns => $ns->string,
|
|
domain => $zone->name,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver13
|
|
|
|
=over
|
|
|
|
=item nameserver15()
|
|
|
|
my @logentry_array = nameserver15( $zone );
|
|
|
|
Runs the L<Nameserver15 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Nameserver-TP/nameserver15.md>.
|
|
|
|
Takes a L<Zonemaster::Engine::Zone> object.
|
|
|
|
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub nameserver15 {
|
|
my ( $class, $zone ) = @_;
|
|
|
|
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Nameserver15';
|
|
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
|
|
|
my %txt_data;
|
|
my %error_on_version_query;
|
|
my %sending_version_query;
|
|
my @wrong_record_class;
|
|
|
|
foreach my $ns ( @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) } ) {
|
|
next if ( _ip_disabled_message( \@results, $ns, q{SOA TXT} ) );
|
|
|
|
my $p_soa = $ns->query( $zone->name, q{SOA} );
|
|
|
|
next if not $p_soa;
|
|
|
|
$sending_version_query{$ns} = 1;
|
|
|
|
foreach my $query_name ( q{version.bind}, q{version.server} ) {
|
|
my $p_txt = $ns->query( $query_name, q{TXT}, { class => q{CH} } );
|
|
|
|
if ( not $p_txt or $p_txt->rcode eq q{SERVFAIL} ) {
|
|
push @{ $error_on_version_query{$query_name} }, $ns;
|
|
next;
|
|
}
|
|
|
|
my @rrs_txt = $p_txt->get_records_for_name(q{TXT}, $query_name, q{answer});
|
|
|
|
if ( scalar @rrs_txt ) {
|
|
foreach my $rr ( @rrs_txt ) {
|
|
if ( $rr->class ne q{CH} ) {
|
|
push @wrong_record_class, $ns;
|
|
}
|
|
|
|
my $string = $rr->txtdata;
|
|
$string =~ s/^\s+|\s+$//g; # Remove leading and trailing spaces
|
|
|
|
if ( $string and $string ne "") {
|
|
push @{ $txt_data{$string}{$query_name} }, $ns;
|
|
delete $sending_version_query{$ns};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( scalar keys %txt_data ) {
|
|
foreach my $string ( keys %txt_data ) {
|
|
push @results, map {
|
|
_emit_log(
|
|
N15_SOFTWARE_VERSION => {
|
|
string => $string,
|
|
query_name => $_,
|
|
ns_list => join( q{;}, sort @{ $txt_data{$string}{$_} } )
|
|
}
|
|
)
|
|
} keys %{ $txt_data{$string} };
|
|
}
|
|
}
|
|
|
|
if ( scalar keys %error_on_version_query ) {
|
|
push @results, map {
|
|
_emit_log(
|
|
N15_ERROR_ON_VERSION_QUERY => {
|
|
query_name => $_,
|
|
ns_list => join( q{;}, sort @{ $error_on_version_query{$_} } )
|
|
}
|
|
)
|
|
} keys %error_on_version_query;
|
|
}
|
|
|
|
if ( scalar keys %sending_version_query ) {
|
|
push @results,
|
|
_emit_log(
|
|
N15_NO_VERSION_REVEALED => {
|
|
ns_list => join( q{;}, sort keys %sending_version_query )
|
|
}
|
|
);
|
|
}
|
|
|
|
if ( scalar @wrong_record_class ) {
|
|
push @results,
|
|
_emit_log(
|
|
N15_WRONG_CLASS => {
|
|
ns_list => join( q{;}, sort @wrong_record_class )
|
|
}
|
|
);
|
|
}
|
|
|
|
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
|
} ## end sub nameserver15
|
|
|
|
1;
|