feat: add full Zonemaster stack with Docker and Spanish UI
- 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>
This commit is contained in:
2
zonemaster-engine/.gitattributes
vendored
Normal file
2
zonemaster-engine/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# While technically text, diffs are meaningless on test data files.
|
||||
t/*.data -diff
|
||||
15
zonemaster-engine/.github/pull_request_template.md
vendored
Normal file
15
zonemaster-engine/.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
## Purpose
|
||||
|
||||
This PR...
|
||||
|
||||
## Context
|
||||
|
||||
(e.g. Fixes #9999, Follow-up to #9999, etc.)
|
||||
|
||||
## Changes
|
||||
|
||||
...
|
||||
|
||||
## How to test this PR
|
||||
|
||||
...
|
||||
99
zonemaster-engine/.github/workflows/ci.yml
vendored
Normal file
99
zonemaster-engine/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- master
|
||||
- 'release/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- master
|
||||
- 'release/**'
|
||||
|
||||
jobs:
|
||||
run-tests:
|
||||
strategy:
|
||||
matrix:
|
||||
compatibility:
|
||||
- develop
|
||||
#- latest
|
||||
perl:
|
||||
- '5.40'
|
||||
- '5.36'
|
||||
- '5.26'
|
||||
runner:
|
||||
- ubuntu-22.04
|
||||
|
||||
runs-on: ${{ matrix.runner }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: shogo82148/actions-setup-perl@v1
|
||||
with:
|
||||
perl-version: ${{ matrix.perl }}
|
||||
|
||||
- name: Binary dependencies
|
||||
run: |
|
||||
# * These were taken from the installation instruction.
|
||||
# * Gettext was added so we can run cpanm . on the Engine sources.
|
||||
# * The Perl modules were left out because I couldn't get all of them
|
||||
# to work with custom Perl versions.
|
||||
# * Cpanminus was left out because actions-setup-perl installs it.
|
||||
sudo apt-get install -y \
|
||||
autoconf \
|
||||
automake \
|
||||
build-essential \
|
||||
gettext \
|
||||
libidn2-dev \
|
||||
liblog-any-perl \
|
||||
libssl-dev \
|
||||
libtool \
|
||||
m4 \
|
||||
|
||||
- name: Install Zonemaster::LDNS (latest)
|
||||
if: ${{ matrix.compatibility == 'latest' }}
|
||||
run: |
|
||||
cpanm --sudo --notest Module::Install ExtUtils::PkgConfig Zonemaster::LDNS
|
||||
|
||||
- name: Install Zonemaster::LDNS (develop)
|
||||
if: ${{ matrix.compatibility == 'develop' }}
|
||||
run: |
|
||||
cpanm --sudo --notest Devel::CheckLib Module::Install ExtUtils::PkgConfig Module::Install::XSUtil
|
||||
git clone --branch=develop --depth=1 https://github.com/zonemaster/zonemaster-ldns.git
|
||||
perl Makefile.PL # Generate MYMETA.yml to appease cpanm .
|
||||
( cd zonemaster-ldns ; cpanm --sudo --notest . )
|
||||
rm -rf zonemaster-ldns
|
||||
|
||||
# Installing Zonemaster::Engine requires root privileges, because of a
|
||||
# bug in Mail::SPF preventing normal installation with cpanm as
|
||||
# non-root user (see link below [1]).
|
||||
#
|
||||
# The alternative, if one still wishes to install Zonemaster::Engine
|
||||
# as non-root user, is to install Mail::SPF first with a command like:
|
||||
#
|
||||
# % cpanm --notest \
|
||||
# --install-args="--install_path sbin=$HOME/.local/sbin" \
|
||||
# Mail::SPF
|
||||
#
|
||||
# For the sake of consistency, other Perl packages installed from CPAN
|
||||
# are also installed as root.
|
||||
#
|
||||
# [1]: https://rt.cpan.org/Public/Bug/Display.html?id=34768
|
||||
- name: Install remaining dependencies
|
||||
run: |
|
||||
cpanm --sudo --verbose --notest --installdeps .
|
||||
|
||||
- name: Install Zonemaster::Engine
|
||||
run: |
|
||||
cpanm --sudo --verbose --notest .
|
||||
|
||||
- name: Show content of log files
|
||||
if: ${{ failure() }}
|
||||
run: cat /home/runner/.cpanm/work/*/build.log
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
prove -lv t
|
||||
30
zonemaster-engine/.gitignore
vendored
Normal file
30
zonemaster-engine/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Temporary files created in repository root during build process
|
||||
/Makefile
|
||||
/Makefile.old
|
||||
/Build
|
||||
/Build.bat
|
||||
/META.*
|
||||
/MYMETA.*
|
||||
|
||||
*.mo
|
||||
.build/
|
||||
_build/
|
||||
cover_db/
|
||||
blib/
|
||||
inc/
|
||||
.lwpcookies
|
||||
.last_cover_stats
|
||||
nytprof.out
|
||||
pod2htm*.tmp
|
||||
pm_to_blib
|
||||
Zonemaster-*
|
||||
Zonemaster-*.tar.gz
|
||||
|
||||
# Backup files from editor, by unknown and Emacs, respectively
|
||||
*.bak
|
||||
*~
|
||||
.*.swp
|
||||
|
||||
# Unknown
|
||||
dev.pl
|
||||
bug.pl
|
||||
164
zonemaster-engine/.perlcriticrc
Normal file
164
zonemaster-engine/.perlcriticrc
Normal file
@@ -0,0 +1,164 @@
|
||||
# Set default values
|
||||
severity=4
|
||||
verbose = %f: %m at line %l, column %c. %e. (Severity: %s, %p)\n
|
||||
|
||||
# severity 5 rules
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitReturnSort]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitSleepViaSelect]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::RequireGlobFunction]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::ClassHierarchies::ProhibitOneArgBless]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitStringyEval]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitInteractiveTest]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitEvilModules]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitExplicitReturnUndef]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitNestedSubs]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitSubroutinePrototypes]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::ProhibitNoWarnings]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::ProhibitProlongedStrictureOverride]
|
||||
severity=5
|
||||
statements = 15
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::RequireUseWarnings]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitConditionalDeclarations]
|
||||
severity=5
|
||||
|
||||
#[Perl::Critic::Policy::Variables::ProhibitEvilVariables]
|
||||
#severity=5
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireLexicalLoopIterators]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage]
|
||||
severity=5
|
||||
|
||||
[Perl::Critic::Policy::CodeLayout::RequireTidyCode]
|
||||
severity=3
|
||||
|
||||
# severity 4 rules
|
||||
[Perl::Critic::Policy::Miscellanea::ProhibitUnrestrictedNoCritic]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireExplicitInclusion]
|
||||
severity=4
|
||||
|
||||
[-Perl::Critic::Policy::Subroutines::ProhibitCallsToUndeclaredSubs]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitCallsToUnexportedSubs]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitExportingUndeclaredSubs]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitQualifiedSubDeclarations]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitExplicitStdin]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitOneArgSelect]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitReadlineInForLoop]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::RequireBriefOpen]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitAutomaticExportation]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitMultiplePackages]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireExplicitPackage]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Objects::ProhibitIndirectSyntax]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitBuiltinHomonyms]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::RequireArgUnpacking]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::RequireFinalReturn]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitCommaSeparatedStatements]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitConstantPragma]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitMatchVars]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireLocalizedPunctuationVars]
|
||||
severity=4
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireNegativeIndices]
|
||||
severity=4
|
||||
|
||||
# intentionally disabled rules
|
||||
[-Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalCan]
|
||||
[-Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalIsa]
|
||||
[-Perl::Critic::Policy::ControlStructures::ProhibitPostfixControls]
|
||||
17
zonemaster-engine/.perltidyrc
Normal file
17
zonemaster-engine/.perltidyrc
Normal file
@@ -0,0 +1,17 @@
|
||||
-ole=unix
|
||||
-se
|
||||
-bext=~
|
||||
-l=120
|
||||
-i=4
|
||||
-ci=2
|
||||
-nsbl
|
||||
-cti=0
|
||||
-pt=0
|
||||
-sbt=1
|
||||
-bt=1
|
||||
-bbt=0
|
||||
-sfs
|
||||
-tso
|
||||
-csc
|
||||
-csci=20
|
||||
-nbbc
|
||||
30
zonemaster-engine/CONTRIBUTING.md
Normal file
30
zonemaster-engine/CONTRIBUTING.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Contributing to Zonemaster::Engine
|
||||
|
||||
Contribution to this repository is welcome. Contribution can be either an issue
|
||||
report or a code or a documentation update. Also see the information in the
|
||||
[main README][Zonemaster/Zonemaster README] in the main Zonemaster repository.
|
||||
|
||||
## Issue
|
||||
|
||||
First search for a similar issue in the [issues list]. If a relevant issue is
|
||||
found, add your information as a comment. If no relevant issue is found, create
|
||||
[a new issue][create issue]. Give as many details as you have and describe, if
|
||||
possible, how the issue can be reproduced.
|
||||
|
||||
## Pull request
|
||||
|
||||
If you would like to contribute an update, first please look for issues and open
|
||||
[pull requests] that are about the same thing. If nothing relevant is found or
|
||||
you have a different solution, create [a new pull request][create pull request].
|
||||
Creating a pull request assumes that you have your proposal in a fork repository.
|
||||
|
||||
When you create a pull request, please always start with the `develop` branch
|
||||
and create the pull request against the same branch.
|
||||
|
||||
|
||||
[issues list]: https://github.com/zonemaster/zonemaster-engine/issues
|
||||
[create issue]: https://github.com/zonemaster/zonemaster-engine/issues/new
|
||||
[pull requests]: https://github.com/zonemaster/zonemaster-engine/pulls
|
||||
[create pull request]: https://github.com/zonemaster/zonemaster-engine/compare
|
||||
[Zonemaster/Zonemaster README]: https://github.com/zonemaster/zonemaster#readme
|
||||
|
||||
898
zonemaster-engine/Changes
Normal file
898
zonemaster-engine/Changes
Normal file
@@ -0,0 +1,898 @@
|
||||
Release history for Zonemaster component Zonemaster-Engine
|
||||
|
||||
v8.1.1 2026-03-04 (part of Zonemaster v2025.2.1 release)
|
||||
|
||||
[Fixes]
|
||||
- Corrects Nameserver13 implementation (#1505)
|
||||
- Fixes inactive upward referral guard (#1494)
|
||||
- Updates root hints after B-root renumbering (#1490)
|
||||
- Fixes missing value in Zone11 message tag (#1491)
|
||||
- Updates Swedish translation (#1509)
|
||||
|
||||
|
||||
v8.1.0 2025-12-17 (part of Zonemaster v2025.2 release)
|
||||
|
||||
[Deprecation]
|
||||
- Ignores and deprecates some profile properties. To be removed in
|
||||
release v2026.1 (#1472)
|
||||
|
||||
[Features]
|
||||
- Updates test case implementations for Zone11, DNSSEC07, DNSSEC01, DNSSEC05,
|
||||
and Address01 (#1477, #1476, #1474, #1473, #1453)
|
||||
- Implements new MethodV2 method to give more information
|
||||
in relevant messages (#1475)
|
||||
|
||||
[Fixes]
|
||||
- Fixes error in MethodsV2 (#1470)
|
||||
- Updates French translation (#1464, #1482)
|
||||
|
||||
|
||||
v8.0.0 2025-06-26 (part of Zonemaster v2025.1 release)
|
||||
|
||||
[Breaking changes]
|
||||
- Changes the string representation of IPv6 addresses in "NS_CREATED"
|
||||
messages #1420
|
||||
- Separates functions to trim whitespace and to normalize domain
|
||||
name, respectively #1316
|
||||
|
||||
[Features]
|
||||
- Updates local copies of IANA special IP registries #1456
|
||||
- Lowers all WARNING to NOTICE for test case Zone01 #1455
|
||||
- Downgrades ERROR to WARNING in test case DNSSEC03 #1452
|
||||
- Improves performance by optimizing critical code sections #1420
|
||||
|
||||
[Fixes]
|
||||
- Updates translations (Danish, Norwegian) #1418, #1449
|
||||
- Updates Dockerfile for release 2025.1 #1460
|
||||
- Fixes test case DNSSEC10 for name servers sharing the same IPs #1457
|
||||
- Adds blacklisting log message #1434
|
||||
- Fixes alias (CNAME) handling in Address03 test case #1432
|
||||
|
||||
|
||||
v7.1.0 2025-03-04 (part of Zonemaster v2024.2.1 release)
|
||||
|
||||
[Release information]
|
||||
- Translations have not been fully updated in this release. They will
|
||||
be updated in an upcoming extra release.
|
||||
|
||||
[Features]
|
||||
- Adds translation to Slovenian language (#1385, #1435)
|
||||
|
||||
[Fixes]
|
||||
- Updates translations for Swedish, Spanish and French (#1416, #1417, #1430)
|
||||
- Corrects how EDNS buffer size is set through 'edns_details' (#1429)
|
||||
- Corrects how Test Cases use returned values from some TestMethodsV2
|
||||
methods (#1427)
|
||||
- Updates implementation of test case DNSSEC10 (#1415)
|
||||
- Updates the name server blacklisting mechanism (#1423)
|
||||
- Fixes an infinite recursion bug when NS record points to CNAME (#1422)
|
||||
|
||||
|
||||
v7.0.0 2024-12-09 (part of Zonemaster v2024.2 release)
|
||||
|
||||
[Release information]
|
||||
- Translations have not been fully updated in this release. They will
|
||||
be updated in an upcoming extra release.
|
||||
|
||||
[Breaking change]
|
||||
- Refactors ASNLookup code and documentation (#1257)
|
||||
|
||||
[Features]
|
||||
- Changes default settings of queries (#1397)
|
||||
- Updates DNSSEC10 implementation (#1396)
|
||||
- Updates global cache and makes the feature being supported and not
|
||||
experimental (#1394)
|
||||
- Lowers the levels of ASN related messages from test case Connectivity03
|
||||
(#1388)
|
||||
- Updates MethodsV2 method to match new algorithm in test
|
||||
case Basic01 (#1373)
|
||||
- Adds CNAME followage in recursive lookups (#1288)
|
||||
- Updates profile function (#1356)
|
||||
|
||||
[Fixes]
|
||||
- Fixes abort condition for undelegated tests in the Basic module (#1401)
|
||||
- Adds Try::Tiny in zonemaster-cli runtime Docker container (#1399)
|
||||
- Fixes CDS and CDNSKEY RRsets comparison in test case DNSSEC15 (#1383)
|
||||
- Removes test case Nameserver14 (never implemented) (#1390)
|
||||
- Updates Connectivity04 implementation (#1393)
|
||||
- Updates test case Delegation05 to include QNAME and QTYPE in diagnostics for
|
||||
failed queries (#1392)
|
||||
- Removes obsolete document (#1359)
|
||||
- Resolves IP addresses directly from Engine for out-of-bailiwick names in fake
|
||||
delegations (#1389)
|
||||
- Fixes spelling errors (external contribution from @emollier) (#1378)
|
||||
- Fixes barewords (external contribution from @emollier) (#1380)
|
||||
- Fixes misspellings (external contribution from @jsoref) (#1366)
|
||||
|
||||
|
||||
v6.0.0 2024-07-01 (part of Zonemaster v2024.1 release)
|
||||
|
||||
[Release information]
|
||||
- Translations have not been fully updated in this release. They will
|
||||
be updated in an upcoming release.
|
||||
|
||||
[Breaking change]
|
||||
- Removes planned deprecated 'resolver.source' profile property.
|
||||
Breaks custom profiles using this property. Use 'resolver.source4'
|
||||
and 'resolver.source6' instead. (#1343)
|
||||
- Using 'profile.json' that comes with the Zonemaster::Engine
|
||||
installation as a template for custom profile is no longer supported.
|
||||
For the new supported way of extracting the default profile, see
|
||||
https://github.com/zonemaster/zonemaster/blob/master/docs/public/configuration/profiles.md#default-profile (#1339)
|
||||
|
||||
[Features]
|
||||
- Makes it possible to run single testcase via test_zone() (#1312)
|
||||
- Updates Basic01 implementation (#1357)
|
||||
|
||||
[Fixes]
|
||||
- Makes syntax04 take zone name instead of NS name (#1322, #1369)
|
||||
- Removes the code dependency on Moose (#1319)
|
||||
- Fixes Zonemaster::Engine::Translator’s instance() method (#1347)
|
||||
- Fixes polymorphism in Translator (#1346)
|
||||
- Adds minor changes to Zone11 msgids (#1348)
|
||||
- Update minimum Perl version to 5.16.0 and add missing 'warnings' (#1342)
|
||||
- Fixes undef eq comparison (#1363)
|
||||
|
||||
|
||||
v5.0.0 2024-03-18 (public release version)
|
||||
|
||||
[Release information]
|
||||
- Translations have not been fully updated in this release. They will
|
||||
be updated in an upcoming fix release.
|
||||
|
||||
[Breaking change]
|
||||
- Removes deprecated features (#1309)
|
||||
|
||||
[Features]
|
||||
- Adds global cache based on Redis (experimental) (#1201, #1327)
|
||||
- Adds implementation of test case Zone11 (SPF test) (#1287)
|
||||
- Removes implementation of Basic00 (#1308)
|
||||
|
||||
[Fixes]
|
||||
- Updates implementation of test case Syntax06 (#1245)
|
||||
- Refactors code (#1256, #1294, #1293, #1296, #1297, #1302)
|
||||
- Improves documentation in Test modules (#1277)
|
||||
- Updates implementation of test case Delegation01 (#1290)
|
||||
- Fixes message tag log level for test case Zone08 (#1295)
|
||||
- Updates unit tests for test cases Consistency05 and Consistency06
|
||||
(#1303)
|
||||
- Updates implementation of test case DNSSEC03 (#1304)
|
||||
- Updates implementation of test case Nameserver15 (#1306)
|
||||
- Documents and updates method '_emit_log()' in all Test modules #1310)
|
||||
- Corrects ASN lookup sources (#1311)
|
||||
- Adds Mail::SPF in 2nd stage in Dockerfile (#1317)
|
||||
- Updates translations (#1324)
|
||||
|
||||
|
||||
v4.7.3 2023-09-08 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Fixes a bug in test case BASIC01 that made tests of zones
|
||||
fail if, while walking the chain of delegations from the root to the
|
||||
zone under test, a name server returns a referral pointing to name
|
||||
servers for an intermediate zone which are all out-of-bailiwick and
|
||||
lacking glue. The bug was introduced in version v4.7.0 in release
|
||||
v2023.1. (#1282)
|
||||
- Disable blacklisting for queries by NAMESERVER15 to prevent false
|
||||
errors and warnings (#1285)
|
||||
|
||||
|
||||
v4.7.2 2023-08-07 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Fixes bug (regression) in test case BASIC01 that made all tests of
|
||||
zone names that start with the same letters as the whole first label
|
||||
of the parent zone fail, e.g. "NOrid.NO" and "FRance.FR". The bug
|
||||
was introduced in version v4.7.1 in release v2023.1.1. (#1270)
|
||||
|
||||
|
||||
v4.7.1 2023-07-24 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Fixes typo in the B01_CHILD_IS_ALIAS msgid for test case BASIC01
|
||||
(#1240, #1262)
|
||||
- Updates the translation to Norwegian (#1246)
|
||||
- Fixes bug in test case BASIC01 (#1249)
|
||||
|
||||
|
||||
v4.7.0 2023-06-21 (public release version)
|
||||
|
||||
[Features]
|
||||
- Updates implementation of test case BASIC01 (#1212)
|
||||
- Adds implementation of test case CONNECTIVITY04 and cleans-up
|
||||
implementation of test case CONNECTIVITY03 (#1219)
|
||||
- Adds implementation of test case NAMESERVER15 (#1218)
|
||||
- Adds implementation of MethodsV2 (#1050)
|
||||
- Updates implementation of test case BASIC02 (#1197)
|
||||
- Updates to accept YAML input for profile (#1209)
|
||||
- Adds new "resolver.source4" and "resolver.source6"
|
||||
properties to profile (#1203)
|
||||
|
||||
[Fixes]
|
||||
- Updates translations (#1238, #1239, #1237, #1226, #1234, #1231)
|
||||
- Removes documentation moved to the zonemaster/zonemaster
|
||||
repository (#1235)
|
||||
- Docker: install Locale::PO through CPAN (#1232)
|
||||
- Lowers problematic tag in DNSSEC10 to DEBUG (#1225)
|
||||
- Updates for the usage of Zonemaster::Engine::Translator (#1221)
|
||||
- Updates implementation of NAMESERVER15 (#1220)
|
||||
- Updates unit tests for test case DNSSEC16 (#1216)
|
||||
- Updates unit tests for test case ZONE09 (#1215)
|
||||
- Disables default testing in installation instructions (#1217)
|
||||
- Fixes EDNS behavior for queries (#1147)
|
||||
- Removes Basic04 completely (#1179)
|
||||
- Adds method to get name server names from undelegated data
|
||||
cache (#1214)
|
||||
- Updates to avoid unnecessary calls to Profile::get in
|
||||
logger (#1200)
|
||||
- Updates implementation of ZONE09 (#1211)
|
||||
- Fixes RIPE ASN lookup results parsing (#1207)
|
||||
- Adds check PO file function (#1195)
|
||||
|
||||
|
||||
v4.6.2 2023-03-01 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Updates translations to Spanish and Norwegian (#1199, #1196)
|
||||
|
||||
|
||||
v4.6.1 2023-01-31 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Updates translations (#1180, #1167)
|
||||
- Fixes a bug that made implementation of ADDRESS01 crash (#1181)
|
||||
- Fixes a bug that made implementation of NAMESERVER11 create false error
|
||||
when server returned legitimate EDNS OPTION-CODE (#1173, #1177)
|
||||
- Removed unneeded binary dependency from installation instruction for
|
||||
Ubuntu and Debian (#1176)
|
||||
- Fixes a bug that made implementation of ZONE01 crash when more than
|
||||
one SOA Serial was returned from the zone's name servers (#1175, #1178)
|
||||
- Removes unused message tag (#1174)
|
||||
|
||||
|
||||
v4.6.0 2022-12-19 (public release version)
|
||||
|
||||
[Features]
|
||||
- Updates implementation of test case Zone09 (#1109, #1163, #1103,
|
||||
#1140, #1139)
|
||||
- Updates implementation of test case Zone01 (#1035, #1161)
|
||||
- Updates implementation of test case DNSSEC02 (#1158)
|
||||
- Adds new implementation of test case Nameserver11 (#1034)
|
||||
- Adds implementation of normalization specification replacing test
|
||||
case Basic00 (#1040, #1157)
|
||||
- Adds method for accessing translatable test case description (#1144)
|
||||
- Rewrites implementation of test cases Connectivity01 and
|
||||
Connectivity02 (#1143, #1136, #1137)
|
||||
- Removes test case Basic04, replaced by updated test cases Connectivity01
|
||||
and Connectivity02 (#1143)
|
||||
- Makes root hints configurable (#1134, #850)
|
||||
- Removes special treatment of SHA-1 in test case DNSSEC01 (#1116, #1115)
|
||||
- Makes IPV4_DISABLED/IPV6_DISABLED tags being consistently outputted in
|
||||
DEBUG level (#1102, #1117)
|
||||
|
||||
[Fixes]
|
||||
- Fixes typo in message and removes zombie messages for Basic04 (#1168)
|
||||
- Updates installation instructions (#1162, #1130)
|
||||
- Cleanup system messages (#1142, #1164)
|
||||
- Fixes Nameserver10 EDNS query (#1160)
|
||||
- Fixes queries and response packets content for undelegated tests (#1150)
|
||||
- Corrects the license statement in Engine.pm (#1152)
|
||||
- Exposes the init methods (#1151)
|
||||
- Updates logentry arguments (#1138, #1128, #1135, #1126)
|
||||
- Removes need for double quotes for ASN Lookup (#1141)
|
||||
- Limits old profile_example.json to only properties not used in
|
||||
default profile and rename it (#1120)
|
||||
- Fixes missing update of ns args in SYSTEM messages (#1097)
|
||||
- Remove dependency on Net::IP, and use Net::IP::XS everywhere (#1119,
|
||||
#1107, #1159)
|
||||
- Use lowercase fragments to refer to internal headings in markdown
|
||||
documents (#1127)
|
||||
- Removes the use of a public resolver for test case Syntax06 (#1063)
|
||||
- Adds check for undelegated test in DNSSEC11 (#1101, #1099)
|
||||
- Refactors to avoid code duplication (#1098)
|
||||
- Adds editorial update of msgid for test case DNSSEC01 (#1072)
|
||||
|
||||
|
||||
v4.5.1 2022-07-08 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Fixes a bug from previous release that prevented out-of-bailiwick nameservers
|
||||
to be resolved to IP address (no IP address was provided) in undelegated
|
||||
tests (#1090, #1089)
|
||||
- Updates Norwegian, Finnish and Danish translations (#1083, #1086, #1092)
|
||||
|
||||
|
||||
v4.5.0 2022-06-09 (public release version)
|
||||
|
||||
[Features]
|
||||
- Use pre-built packages for ubuntu (#1079)
|
||||
- Updates implementation of test case Nameserver10 (#1061, #1060)
|
||||
- Updates implementation of test case DNSSEC02 (#1051, #1049, #1036)
|
||||
- Updates implementation of test case DNSSEC01 (#1059, #1057)
|
||||
- Makes query timeout configurable in profile (#1069)
|
||||
- Makes SOA values configurable in profile (#1032, #945)
|
||||
|
||||
[Fixes]
|
||||
- Updates installation procedure (#1084, #1080)
|
||||
- Updates translation (#1073, #1075, #1081, #1077, #1082, #1038)
|
||||
- Updates to Docker image (#1066)
|
||||
- Makes installation test true network independent (#1045, #888, #1068)
|
||||
- Updates message argument document (#1064)
|
||||
- Improves caching of test results (#1044, #1043, #1042)
|
||||
- Updates to use libidn2 instead of libidn (#1056 )
|
||||
- Updates messages (#1010, #1047)
|
||||
- Fixes test case DNSSEC10 case when DNSKEY is empty (#1037, #1036)
|
||||
- Fixes test case order in profile.json (#1027)
|
||||
- Cleans up dependencies and imports (#1041, #1054)
|
||||
|
||||
|
||||
v4.4.0 2021-12-20 (public fix version)
|
||||
[Features]
|
||||
- Adds translation to Spanish language (#994)
|
||||
|
||||
[Fixes]
|
||||
- Updates Danish translation (#1025, #1005)
|
||||
- Updates Norwegian translation (#1026, #1006)
|
||||
- Fixes bug where TC flag was checked on non-response (#1029, #1028)
|
||||
- Updates Swedish translation (#1011, #1007)
|
||||
|
||||
|
||||
v4.3.0 2021-12-03 (public release version)
|
||||
|
||||
[Features]
|
||||
- Deb packages are available for Debian (#1021)
|
||||
- Adds support for Docker (#1002, #1012, #991)
|
||||
- Replaces CentOS with Rocky Linux (#996)
|
||||
- Updates test case DNSSEC10 from updated specification (#995, #992, #772)
|
||||
- Adds test case DNSSEC18 (#990, #987)
|
||||
- Updates test cases DNSSEC15, DNSSEC16 and DNSSEC17 from update
|
||||
specification (#989, #988, 907, #896)
|
||||
- Updates test case DNSSEC09 from updated specification (#986, #985, #983, #980)
|
||||
- Updates test case DNSSEC02 from updated specification (#984, #982)
|
||||
- Updates test case DNSSEC08 from updated specification (#981, #978)
|
||||
- Updates test case DNSSEC11 from updated specification (#979, #977)
|
||||
- Updates test case DNSSEC13 from updated specification (#976, #975)
|
||||
|
||||
[Fixes]
|
||||
- Updates French translation (#1015, #1004)
|
||||
- Cleanup in documentation (#1018, #1009)
|
||||
- Adds missing test case description (#1020, #1001)
|
||||
- Updates documentation for developers and translators (#997)
|
||||
- Updates dependency list (#999)
|
||||
- Updates msgid in Zone09 (#968, #967)
|
||||
- Improves code performance (#566)
|
||||
- Improves output in CLI (#971, #970)
|
||||
- Corrects the handling of "delete" CDS and CDNSKEY in test case
|
||||
DNSSEC15 (#969, #964)
|
||||
|
||||
v4.2.3 2021-09-17 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Fixes fatal error in output for Basic04 test. (#952)
|
||||
- Fixes bug that prevents installation tests to pass. The bug does not
|
||||
affect live tests, only tests with recorded data. (#958, #956)
|
||||
|
||||
|
||||
v4.2.2 2021-07-23 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Updates data for unit tests. The error prevented normal installation,
|
||||
but did not affect normal operation. (#943)
|
||||
|
||||
|
||||
v4.2.1 2021-06-04 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Updates Finnish translations (#931, #911)
|
||||
|
||||
|
||||
v4.2.0 2021-05-28 (public release version)
|
||||
|
||||
[Features]
|
||||
- Updates profile to reduce repeated messages on no response (#923)
|
||||
- Adds test case implementation of DNSSEC17 (#906, #898)
|
||||
- Adds test case implementation of DNSSEC16 (#900, #897)
|
||||
- Adds test case implementation of DNSSEC15 (#896, #894)
|
||||
- Adds test case implementation of Basci04 (#892, #763)
|
||||
- Adds Finnish translation (PO file) (#880, #879)
|
||||
|
||||
[Fixes]
|
||||
- Updates translations (#921, #917, #916, #914, #925, #910, #927, #913,
|
||||
#925, #910)
|
||||
- Updates instructions (#918, #919, #889, #926)
|
||||
- Corrects code (#915)
|
||||
- Updates share/Makefile to be FreeBSD compatible (#893, #702)
|
||||
|
||||
|
||||
v4.1.1 2021-03-16 (public fix release version)
|
||||
|
||||
[Fixes]
|
||||
- Updates unit test data to make it possible to install (#882, #884)
|
||||
|
||||
|
||||
v4.1.0 2021-02-10 (public release version)
|
||||
|
||||
[Features]
|
||||
- Adds configurable timer settings for RRSIG in test case DNSSEC04
|
||||
(#860, #200)
|
||||
- Updates test case Syntax06 according to updated specification
|
||||
(#803, 610)
|
||||
- Updates messages arguments to be consistent (#854, #853, #60, #713)
|
||||
|
||||
[Fixes]
|
||||
- Updates translation (#874, #873, #872)
|
||||
- Corrects test case messages (#870)
|
||||
- Cleans up profile file (#868, #867, #866, 843)
|
||||
- Updates installation instructions (#865, #861)
|
||||
- Makes code accept key ID 0 (#863, #864)
|
||||
- Corrects DNSSEC unit test (#859, #864, #860)
|
||||
- Corrects typos in test case Delegation05 (#858, #820, #846, #822)
|
||||
- Corrects sorting for test case Connectivity03 (#852, #851)
|
||||
- Updates root hint data (#849)
|
||||
|
||||
|
||||
v4.0.3 2020-11-18 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Fixed bug that prevented installation due to false error
|
||||
in unit test (#845, #844)
|
||||
|
||||
|
||||
v4.0.2 2020-11-12 (public fix version)
|
||||
|
||||
[Fixes]
|
||||
- Fixed bug in ASN lookup using RIPE riswhois that prevented it
|
||||
from working (#833, #834)
|
||||
|
||||
|
||||
v4.0.1 2020-11-09 (public release version)
|
||||
|
||||
[Fixes]
|
||||
- Fixed a version specification error in Makefile.PL.
|
||||
|
||||
|
||||
v4.0.0 2020-11-06 (public release version)
|
||||
|
||||
[Breaking changes]
|
||||
- Remove the DISABLE feature (#736, #454)
|
||||
|
||||
[Features]
|
||||
- Added Norwegian language (#806, #786, #751)
|
||||
- Updated Connectivity03:
|
||||
- Add support of RIPE Ris whois for ASN lookup (#802, #592),
|
||||
however, also see #833
|
||||
- New messages.
|
||||
- DNSSEC02 with updated logic (#619, #783)
|
||||
- Add Zonemaster::Engine::Net::IP::ip_is_ipv4 (#689, #688)
|
||||
|
||||
[Fixes]
|
||||
- Updated/corrected translations/PO files (#832, #824, #827, #823, #809,
|
||||
#811, #808, #796, #794 #789, #787, #779, #757, #776, #761, #735, #745,
|
||||
#746)
|
||||
- Updated/corrected data for unit test (#831, #830)
|
||||
- Corrected/updated Translator.pm (#826, #817, #805, #775, #766, #798,
|
||||
#776, #755, #749, #748)
|
||||
- Made po-files.t use gmake for FreeBSD (#819, #816)
|
||||
- Made FreeBSD to use gmake at installation (#815)
|
||||
- Corrections to share/Makefile (#813, #814, #807, #804, #771, #726)
|
||||
- Updated msgid and message arguments (#799, #713, #795, #788, #792, #790,
|
||||
#782, #776, #758, #760, #743)
|
||||
- Updated instructions for translators (#772)
|
||||
- Updated documentation in Profile.pm (#768)
|
||||
- Corrected system message (#784, #731)
|
||||
- Corrected Nameserver::Cache (#778, #324)
|
||||
- Removed duplicate messages from DNSSEC14 (#770, #769)
|
||||
- Added START/END messages to test cases (#764, #665)
|
||||
- Clean-up (#801, #767, #765, #752, #736, #454)
|
||||
- Corrected Consistency05 (#759, #742)
|
||||
- Corrected Delegation01 (#760, #743)
|
||||
|
||||
|
||||
v3.1.2 2020-05-22
|
||||
|
||||
[Fixes]
|
||||
- Comparison in Zone10 on SOA owner name was done in case
|
||||
sensitive manner which created false ERRORs for some zones
|
||||
and depending on the case of input zone name (#734, #737)
|
||||
|
||||
|
||||
v3.1.1 2020-05-15
|
||||
|
||||
[Fixes]
|
||||
- Resolved issue where Zonemaster crashed when testing zones which
|
||||
have RRSIG referring to absent DNSKEY (#727, #728)
|
||||
|
||||
|
||||
v3.1.0 2020-04-30
|
||||
|
||||
[Features]
|
||||
- Allow installation of new LDNS on Debian 9 (#667)
|
||||
- Add more meaningful message for unsupported algorithms in
|
||||
DNSSEC test cases (#641, #632)
|
||||
- Improved the handling of "fuzzy" PO files (#640, #598, #600, #596)
|
||||
- Implementation of revised version of DNSSEC10 (#633, #618, #632)
|
||||
- Implementation of revised version of DNSSEC01 (#627, #616, #308)
|
||||
- Implementation of revised version of DELEGATION05 (#628, #617, #236)
|
||||
- Added test case DNSSEC13 (#611, #620)
|
||||
- Update implementation of Nameserver05 (#612, #615)
|
||||
- Added new test case Zone10 (#595, #606)
|
||||
- Update implementation of Delegation01 (#569, #603, #520)
|
||||
- Update implementation of DNSSEC05 (#570, #602)
|
||||
- Implement DNSSEC14 (#584, #586)
|
||||
- Updated gettext handling of translations (#573, #588, #191, #631, #625)
|
||||
- Make test module auto-detection less dynamic (#580)
|
||||
- Use Net::IP::XS when available (#565, #567)
|
||||
- Install all runtime dependencies from binary packages (#547, #451)
|
||||
- A util that prints recorded data in dig format (#555)
|
||||
|
||||
[Fixes]
|
||||
- Update installation instructions on algo 15 support and other
|
||||
issues (#677, #678, #683, #690)
|
||||
- Update Danish translation da.po (#718, #706)
|
||||
- Updated Swedish translation (sv.po) (#710, #705, #716)
|
||||
- Add DS treatment in case of SHA-1 in DNSSEC01 (#715, #712)
|
||||
- Updated and expanded Translation document (#701, #708)
|
||||
- Remove useless dependency (#700, #699)
|
||||
- French translations updated (#698, #696)
|
||||
- Fixes erroneous duplicated message in dnssec01 test (#695, #694)
|
||||
- Fixes DNSSEC unexpected hash reference in messages (#693, #692)
|
||||
- Editorial changes to some DNSSEC message strings (#691)
|
||||
- Corrected link to default profile (#687)
|
||||
- Add test case for listing IPv6 addresses in IPv6 message (#686)
|
||||
- Fixed Engine fails to install in CentOS 8 due to unit test problem
|
||||
(#680, #681)
|
||||
- Fixed false error when IPv6 was disabled (#674, #676)
|
||||
- Fixed incorrect handling of CNAME (#672, #673)
|
||||
- Fixed code and test data for broken DNSSEC test (#670, #671)
|
||||
- Update management of MO file (#664)
|
||||
- Fixed case sensitivity in Consistency05 (#659, #658)
|
||||
- Translation to Danish (#636, #626, #624, #604, #600)
|
||||
- Fixed Consistency test cases messages typos (#653, #643, #647)
|
||||
- Fixed DELEGATION01 messages (#652, #648, #644)
|
||||
- Fixed DNSSEC error message typo (#651, #642)
|
||||
- Fixed some DNSSEC10 and DNSSEC14 messages (#650, #646, #644)
|
||||
- Cleaned up obsolete whitelist mechanism (#649, #597)
|
||||
- Fixed French translation (#644, #599, #551, #607)
|
||||
- Fixed DNSSEC14 missing messages (#645)
|
||||
- Remove non-determinism in file generation (#639, #638)
|
||||
- Update of en.po (#637, #609)
|
||||
- Updated and corrected sv.po (#623, #605, #598)
|
||||
- Fixed missing msgid in Zone.pm (#613, #614)
|
||||
- Fixed that Consistency05 not reported extra addresses at child
|
||||
(#577, #593)
|
||||
- Fixed that Consistency05 not reported that glue address is
|
||||
different from authoritative data (#582, #593)
|
||||
- Fixed argument should only be name, not name/addr (#102, #608)
|
||||
- Expected MX CNAME Error (#561, #589)
|
||||
- Fixed that some DNSSECxx do not respect "no IPv6" (#543, #587)
|
||||
- Fixed missing messages in Delegation.pm (#558, #590)
|
||||
- Fixed broken Test-dnssec.t (#419, #583)
|
||||
- Fixed incorrect message from the Delegation module (#545, #552)
|
||||
- Partial fix for translation on FreeBSD 11 (#562, #546)
|
||||
|
||||
|
||||
v3.0.3 2019-05-22 (public release version)
|
||||
|
||||
[Status]
|
||||
- This a public release fully tested before release. This version
|
||||
will be available on CPAN.
|
||||
|
||||
[Fixes]
|
||||
- Added instructions for preparation of packages for FreeBSD (#544)
|
||||
- Removed example entry from default profile (#536)
|
||||
- Corrected incorrect consistency verification for IP addresses between
|
||||
the child and the parent (#532, #535)
|
||||
- Corrected message (#523, #533)
|
||||
- Corrected truncated msgid (#528)
|
||||
- Added missing DNSSEC messages to test results (#521, #527)
|
||||
- Updated dependencies for Debian and FreeBSD (#525)
|
||||
- Dropped support for Ubuntu 14.04 (#519)
|
||||
- Split message BROKEN_EDNS_SUPPORT in Nameserver02 (#516, #517)
|
||||
- Made message IN_BAILIWICK_ADDR_MISMATCH more explicit (#467, #515)
|
||||
- Resolve issues around unwanted blacklisting (#504, #511)
|
||||
|
||||
|
||||
v3.0.2 2019-03-15 (pre-release version)
|
||||
|
||||
[Status]
|
||||
- This is a pre-release version not fully tested on all supported
|
||||
OS's and Perl versions. This version will not be available on
|
||||
CPAN.
|
||||
|
||||
[Fixes]
|
||||
- Never serialize numeric profile properties as JSON strings (#505)
|
||||
- Add a forgotten dependency to the installation instruction. (#490)
|
||||
|
||||
|
||||
v3.0.1 2019-01-31 (pre-release version)
|
||||
|
||||
- Status
|
||||
- This is a pre-release version not fully tested on all supported
|
||||
OS's and Perl versions. This version will not be available on
|
||||
CPAN.
|
||||
- Fixes
|
||||
- Fixed nameserver02 algorithm to match specification (#493)
|
||||
|
||||
|
||||
v3.0.0 2019-01-25 (pre-release version)
|
||||
|
||||
- Status
|
||||
- This is a pre-release version not fully tested on all supported
|
||||
OS's and Perl versions. This version will not be available on
|
||||
CPAN.
|
||||
- API change
|
||||
- Replaced separate config and policy with a unified profile
|
||||
(see below)
|
||||
- Features
|
||||
- Updated implementation of Delegation01 (#396, #410)
|
||||
- Updated implementation of Consistency06 (#408)
|
||||
- Updated implementation of Nameserver01 (#399, #435)
|
||||
- Updated implementation of DNSSEC05 (#409)
|
||||
- Updated translation tooling #394
|
||||
- Updated implementation of Syntax06 (#421)
|
||||
- Updated implementation of Delegation03 (#425)
|
||||
- Updated implementation of Consistency05 (#427, #429, #470)
|
||||
- Updated implementation of Consistency01 (#433)
|
||||
- Updated translation of messages (#436)
|
||||
- Replaced separate config and policy with a unified profile
|
||||
- Updated profile.pm (#386)
|
||||
- Profiles updates #441
|
||||
- Profile instead of config and policy #446
|
||||
- Profiles tweaks #447
|
||||
- Add script to convert Config/Policy to Profile files #446
|
||||
- Fixed flags issue #459
|
||||
- Add more details in case of crash for profile properties #471
|
||||
- Add true/false processing for Booleans #472
|
||||
- Deleted iana-profile.json #465
|
||||
- Updated profile.json files; removed unused messages and added
|
||||
missing messages #464
|
||||
- Update resolver.source default value to match
|
||||
documentation #473
|
||||
- Align tests and impl for boolean properties with
|
||||
documentation #474
|
||||
- Various #478
|
||||
- Added support to test EDNS features
|
||||
- Support for Edns and test case implementation (Nameserver10,
|
||||
..11, ..12 and ..13; ..14 excluded) #460
|
||||
- Test against develop version of Zonemaster LDNS with EDNS
|
||||
support #462
|
||||
- Various #481, #485
|
||||
- Fixes
|
||||
- Updated formatting of msgid tables #402
|
||||
- Fixed broken tests #412
|
||||
- All link references on GitHub now to zonemaster/zonemaster instead
|
||||
of old dotse/zonemaster #406
|
||||
- Updated manifest #422
|
||||
- Updated travis conf to match supported perl versions (#426, #463)
|
||||
- Removed List::Util::all (only supported in Perl 5.20 and higher) #432
|
||||
- Added missing message in Delegation.pm #415
|
||||
- Added missing message in DNSSEC.pm #416
|
||||
- Added missing message in Nameserver.pm #417
|
||||
- Removed unused message and added used message in Basic.pm #413
|
||||
- Various #487
|
||||
|
||||
|
||||
v2.0.7 2018-06-25
|
||||
|
||||
- Fixed
|
||||
- Update Installation.md. Add installation of Test::More from
|
||||
CPAN to get the newest version. (#371)
|
||||
- Minor updates (#374, #386, #375, #377)
|
||||
- Restore unit tests (#378)
|
||||
- Better report when Cymru reports no ASN (#272, #385)
|
||||
- More checks in get_iana_address_spaces_infos.pl (#300, #387)
|
||||
- Fixed bug in Nameserver.pm (#384, #389)
|
||||
- Update install instructions for debian and centos (#393)
|
||||
- Updated installation instructions for FreeBSD. Now using
|
||||
cpanm instead of cpan. (#403)
|
||||
|
||||
|
||||
v2.0.6 2018-01-12
|
||||
|
||||
- Natural Language support
|
||||
- Added support for Danish language (#351, #354)
|
||||
|
||||
- Patches
|
||||
- Fixed installation instruction (#342)
|
||||
- Update distribution description (#339)
|
||||
- Updated according to updated Delegation01 (#296, #346)
|
||||
- Fixes: Missing French translation (#343, #347)
|
||||
- Fixes: NAMESERVER:RECURSIVITY_UNDEF does not log host (#275, #311)
|
||||
- Fixes: Fake delegation with explicit IP address is ignored (#295, #355)
|
||||
- Fixes: Engine fails to verify NS in zone (#356, #357)
|
||||
- Fixes: When testing a zone with 2 NS that point to the same IP (#140, #360)
|
||||
- Update sv.po to match updates of en.po (#271, #359)
|
||||
- Editorial updates of po files (#362)
|
||||
|
||||
- Other changes
|
||||
- Unneeded translation file removed (#345)
|
||||
- Add (and corrected) MANIFEST to repo (#364, #366)
|
||||
|
||||
v2.0.2 - v2.0.5 never released.
|
||||
|
||||
v2.0.1 2017-11-02
|
||||
|
||||
- Fixed
|
||||
- Fixed licensing discrepancy (#336)
|
||||
- Updated dependency version (#334)
|
||||
- Updated links in documentation (#330)
|
||||
- Updated installation instructions (#332, #333, #335)
|
||||
|
||||
- Changed in v2.0.0
|
||||
- Renamed distribution from Zonemaster to Zonemaster-Engine (#303)
|
||||
|
||||
v2.0.0
|
||||
|
||||
- Switch version scheme to Semantic Versioning.
|
||||
- Breaking changes
|
||||
- Renamed module from Zonemaster to Zonemaster::Engine (#303)
|
||||
- Patches
|
||||
- Updated implementation of ADDRESS01/Name server address must be globally routable (#264)
|
||||
- Updated implementation of BASIC01/The domain must have a parent domain (#260)
|
||||
- Updated translations (#127, #196, #216, #248, #249, #291)
|
||||
- Incremented VERSION of internal packages (98868cb)
|
||||
- Other changes
|
||||
- Fixed Commonmark rendering for GitHub (cc7a28a)
|
||||
- Switch msgfmt implementation to pure Perl (#223, #224)
|
||||
- New Unit tests infrastructure. 896 tests kept, 2 removed, will be added back later (#310)
|
||||
|
||||
v1.1.0 2017-04-04 Public pre-release
|
||||
|
||||
- This release will not be published on CPAN since it contains updates that have
|
||||
not been fully tested. Do not update production systems without verification.
|
||||
- Use JSON::PP instead of JSON (#262)
|
||||
- Changed versioning (#263)
|
||||
- Fixes Use of uninitialized value in Nameserver.pm (#267)
|
||||
- Solved couldn't find pod for Zonemaster::Net::IP (#222)
|
||||
- Solved Tags without translations in en.po (#253)
|
||||
- Solved Duplicate tag in en.po (#252)
|
||||
- Solved Update documentation for logfilters (#221)
|
||||
- add Ed25519 (15) and Ed448 (16) algorithms (#290)
|
||||
- Solved Nonexistent name server doesn't cause a failure (#278)
|
||||
|
||||
v1.0.16 2016-12-12
|
||||
|
||||
- Updated and added Swedish translations (#249, #248, #216, #196, partially #127)
|
||||
- Corrected installation instructions for Debian/Ubuntu (#240)
|
||||
- If one nameserver is "lame" then the Basic test could halt on that and not
|
||||
continue with next nameserver. Fixed in #239.
|
||||
- Added test for NSEC3 OPT-OUT (#226).
|
||||
- Using different IP address in unit test to avoid false fail (#219).
|
||||
- Remove dependency on Net::IP::XS (#174).
|
||||
- Updated the algorithm for discovering open resolvers to be more correct (#171).
|
||||
|
||||
v1.0.15 2016-10-14
|
||||
|
||||
- DNSSEC:EXTRA_PROCESSING_BROKEN when not authoritative #210
|
||||
- Remove dependency on Net::IP::XS, use Net::IP (#174)
|
||||
- remove-JSON-XS-from-installation (#208)
|
||||
- Add filters to hostnames / messages (#206)
|
||||
- Bug in comparison between parent and child (dotse/zonemaster#439)
|
||||
- Fixing can't call method "rcode" on an undefined value at (dotse/zonemaster#463)
|
||||
- Fixing french FAQ last version (dotse/zonemaster#210)
|
||||
- Modify the "Notice" information when DNSSEC not signed (#193)
|
||||
- Fixing CONNECTIVITY:NAMESERVER_NO_UDP_53 (and TCP) when ns not authoritative (#209)
|
||||
- Fix perl warning about unquoted 'refs' (#229)
|
||||
- Fixing issue while running "make test" in "CentOS Linux (#230)
|
||||
- Add missing dependency in dotse/zonemaster-engine/docs/installation.md
|
||||
|
||||
|
||||
v1.0.14 2016-06-15
|
||||
|
||||
- Make it possible to run tests without network (pull request #166)
|
||||
- Error in status message (Zone category) issue #175
|
||||
- Fixed Swedish translation, issue #176
|
||||
- no public symbols defined for Zonemaster::NSArray issue #446
|
||||
- uninitialized value $tld (issue #179)
|
||||
- Would be nice to add a count of name servers #453
|
||||
- Partly resolved "JSON/JSON::XS usage and requirement", see issue #165
|
||||
- nameserver03 test should not perform network requests, issue #168
|
||||
- Locale files are neither generated nor installed #173
|
||||
- ASN test should only look at authoritative nameservers at the child, issue #441
|
||||
- Modify the "Notice" information when DNSSEC not signed, issue #193
|
||||
- Implement updated test case dnssec02, issue #187
|
||||
- Log issues (pull request #203)
|
||||
- Delegation::ARE_AUTHORITATIVE display too many name servers + translation, issue #186
|
||||
- False positive on open recursive name server, issue #171
|
||||
|
||||
v1.0.13 2016-01-27
|
||||
- Updates to MANIFEST.skip
|
||||
|
||||
v1.0.12 2015-12-22
|
||||
- Fixed pod testing
|
||||
- Fixed links in pods
|
||||
- Update CPAN dependencies for centos
|
||||
|
||||
v1.0.11 2015-12-17
|
||||
- Added IANA profile
|
||||
- Fixed a spelling error
|
||||
|
||||
v1.0.10 2015-11-18
|
||||
- Proper Makefile regex in MANIFEST.SKIP
|
||||
|
||||
v1.0.9 2015-11-17
|
||||
- Removed files from distribution, added to MANIFEST.SKIP, #153 #154
|
||||
|
||||
v1.0.8 2015-11-16
|
||||
- Make test should work much better, #149 #137 #139 #121
|
||||
- Translation fixes for French and Swedish #144
|
||||
- Better output for ASN debug messages #138
|
||||
- Fixes for Upper and lower case queries, #128
|
||||
- Fixes in output of arrays in log messages
|
||||
- Increase EDNS0_BAD_QUERY message severity level
|
||||
|
||||
v1.0.7 2015-10-01
|
||||
- ASN in logs are now arrays
|
||||
- Make test without IPv6 connectivity works, fixes #121
|
||||
- Now requires Net::Socket::IP
|
||||
- Fixes malformed log messages
|
||||
|
||||
v1.0.6 2015-09-16
|
||||
- Fix for removal of Text::Capitalize
|
||||
|
||||
v1.0.5 2015-09-10
|
||||
- Removes dependency on Text::Capitalize (issue #110 and #109)
|
||||
- Split the ns and address args fields in the log (continuing work), including RECURSE_QUERY
|
||||
- Added get_max_level() to Zonemaster::Logger
|
||||
- Added blacklisting functionality for non-responding name servers (issue #96)
|
||||
- Removed GOST functionality
|
||||
- Improved test coverage
|
||||
- Fixed the test nameserver08 (issue #93 and issue #94)
|
||||
|
||||
v1.0.4 2015-06-24
|
||||
- Fixed NSEC3 iterations evaluation, issue #77
|
||||
- Key size info on DNSKEY records, issue #79
|
||||
- Signature expiration info, issue #13
|
||||
- Added new test case, nameserver08, QNAME case insensitivity
|
||||
- Fixed English log entry on open recursors, issue #76
|
||||
- Added new test case, nameserver07, upward referrals
|
||||
- Fixed nameserver response times, issue cli #20
|
||||
- Fix empty key list crash, issue zonemaster #320
|
||||
- Logging improvements, work on issue #60
|
||||
- Improvements on JSON output
|
||||
- Fixed locale problems
|
||||
|
||||
v1.0.3 2015-04-07
|
||||
- Add message stating clearly that a name is not a domain.
|
||||
- Check more thoroughly that answers are answers when building zone NS
|
||||
list.
|
||||
- Source address settable by nameserver object with a global default.
|
||||
- Documentation updates.
|
||||
- Log information on how and when a test was started.
|
||||
- Fix capitalization bug in consistency04.
|
||||
- Cache was not properly cleared.
|
||||
- Fix for crash bug.
|
||||
- Updated DNSSEC policy
|
||||
- Refer to overview page from Zonemaster page.
|
||||
- Fix missed corner case in parent-finding algorithm.
|
||||
- CNAME handling accidentally left outside check for packet existence.
|
||||
- Clearer message when failing signature checks because GOST support is
|
||||
not present.
|
||||
|
||||
v1.0.2 2015-02-24
|
||||
- Run most DNSSEC tests even when there is no DS.
|
||||
- Raise Net::LDNS requirement to v0.71. Closes #17. Closes #18.
|
||||
- Document policy data.
|
||||
- Make it so the policy can decide which test cases should be run by
|
||||
default.
|
||||
- Make sure dnssec11 emits one and only one message.
|
||||
- [BugFix] Classless in-addr.arpa #14
|
||||
|
||||
v1.0.1 2014-12-30
|
||||
- Raise version numbers for changed modules.
|
||||
- Change level of open AXFR message to NOTICE.
|
||||
- CNAME was not handled correctly when looking up addresses for names.
|
||||
- Faked DS response packets should have AA set.
|
||||
- Add and/or adjust Swedish translations to match en.po.
|
||||
- Fix NSEC3PARAM issue reported by Jakob Schlyter.
|
||||
|
||||
v1.0.0 2014-12-11 Public beta release.
|
||||
|
||||
v0.1.0 2014-11-17 Alpha test release.
|
||||
|
||||
v0.0.8 2014-11-04 Developer release.
|
||||
|
||||
v0.0.7 2014-10-30 Developer release.
|
||||
|
||||
v0.0.2 2014-05-25 Initial developer release.
|
||||
|
||||
76
zonemaster-engine/Dockerfile
Normal file
76
zonemaster-engine/Dockerfile
Normal file
@@ -0,0 +1,76 @@
|
||||
FROM zonemaster/ldns:local AS build
|
||||
|
||||
RUN apk add --no-cache \
|
||||
# Only needed for CPAN deps
|
||||
gcc \
|
||||
make \
|
||||
musl-dev \
|
||||
perl-dev \
|
||||
# Transitive deps included to improve build speed
|
||||
perl-mailtools \
|
||||
perl-module-build-tiny \
|
||||
# Compile-time dependencies
|
||||
perl-app-cpanminus \
|
||||
perl-class-accessor \
|
||||
perl-clone \
|
||||
perl-file-sharedir \
|
||||
perl-file-slurp \
|
||||
perl-io-socket-inet6 \
|
||||
perl-list-moreutils \
|
||||
perl-locale-msgfmt \
|
||||
perl-log-any \
|
||||
perl-lwp-protocol-https \
|
||||
perl-mail-spf \
|
||||
perl-module-install \
|
||||
perl-pod-coverage \
|
||||
perl-readonly \
|
||||
perl-sub-override \
|
||||
perl-test-differences \
|
||||
perl-test-exception \
|
||||
perl-test-fatal \
|
||||
perl-test-nowarnings \
|
||||
perl-test-pod \
|
||||
perl-text-csv \
|
||||
perl-yaml \
|
||||
perl-yaml-libyaml \
|
||||
&& cpanm --no-wget --from=https://cpan.metacpan.org/ \
|
||||
Email::Valid \
|
||||
List::Compare \
|
||||
Locale::PO \
|
||||
Locale::TextDomain \
|
||||
Module::Find \
|
||||
Net::IP::XS
|
||||
|
||||
ARG version
|
||||
|
||||
COPY ./Zonemaster-Engine-${version}.tar.gz ./Zonemaster-Engine-${version}.tar.gz
|
||||
|
||||
RUN cpanm --notest --no-wget \
|
||||
./Zonemaster-Engine-${version}.tar.gz
|
||||
|
||||
FROM zonemaster/ldns:local
|
||||
|
||||
# Include all the Perl modules we built
|
||||
COPY --from=build /usr/local/lib/perl5/site_perl /usr/local/lib/perl5/site_perl
|
||||
COPY --from=build /usr/local/share/perl5/site_perl /usr/local/share/perl5/site_perl
|
||||
|
||||
RUN apk add --no-cache \
|
||||
# All the locales we need and more
|
||||
musl-locales \
|
||||
# Run-time dependencies
|
||||
perl-class-accessor \
|
||||
perl-clone \
|
||||
perl-file-sharedir \
|
||||
perl-file-slurp \
|
||||
perl-io-socket-inet6 \
|
||||
perl-list-moreutils \
|
||||
perl-locale-msgfmt \
|
||||
perl-log-any \
|
||||
perl-mail-spf \
|
||||
perl-mailtools \
|
||||
perl-module-install \
|
||||
perl-net-ip \
|
||||
perl-readonly \
|
||||
perl-text-csv \
|
||||
perl-try-tiny \
|
||||
perl-yaml-libyaml
|
||||
12
zonemaster-engine/KNOWN_ISSUES
Normal file
12
zonemaster-engine/KNOWN_ISSUES
Normal file
@@ -0,0 +1,12 @@
|
||||
We currently have the following known issues. They are all considered of low
|
||||
importance, and while we intend to fix them we make no promises about when.
|
||||
|
||||
* When testing a zone using GOST-family algorithms in DNSSEC and having an
|
||||
underlying OpenSSL library without GOST support, the Zonemaster DNSSEC test
|
||||
results may be misleading or unpredictable. The Zonemaster::LDNS module will warn at
|
||||
build time if OpenSSL lacks GOST support.
|
||||
|
||||
* Testing alternative root zones is not possible. Testing the usual root zone
|
||||
is possible, but not all results make sense.
|
||||
|
||||
* Testing IDN names (U-label) does not always work.
|
||||
42
zonemaster-engine/LICENSE
Normal file
42
zonemaster-engine/LICENSE
Normal file
@@ -0,0 +1,42 @@
|
||||
### Code license
|
||||
|
||||
Copyright (c) The Swedish Internet Foundation (<https://internetstiftelsen.se/en/>)
|
||||
Copyright (c) AFNIC (<https://www.afnic.fr/en/>)
|
||||
All rights reserved.
|
||||
|
||||
Copyright belongs to external contributor where applicable.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
### Documentation license
|
||||
|
||||
Copyright (c) The Swedish Internet Foundation (<https://internetstiftelsen.se/en/>)
|
||||
Copyright (c) AFNIC (<https://www.afnic.fr/en/>)
|
||||
All rights reserved.
|
||||
|
||||
Copyright belongs to external contributor where applicable.
|
||||
|
||||
Creative Commons Attribution 4.0 International License
|
||||
|
||||
You should have received a copy of the license along with this
|
||||
work. If not, see <https://creativecommons.org/licenses/by/4.0/>.
|
||||
262
zonemaster-engine/MANIFEST
Normal file
262
zonemaster-engine/MANIFEST
Normal file
@@ -0,0 +1,262 @@
|
||||
Changes
|
||||
CONTRIBUTING.md
|
||||
docs/Implementing_Tests.pod
|
||||
docs/Translation.pod
|
||||
inc/Module/Install/Base.pm
|
||||
inc/Module/Install/Can.pm
|
||||
inc/Module/Install/External.pm
|
||||
inc/Module/Install/Fetch.pm
|
||||
inc/Module/Install/Makefile.pm
|
||||
inc/Module/Install/Metadata.pm
|
||||
inc/Module/Install.pm
|
||||
inc/Module/Install/Share.pm
|
||||
inc/Module/Install/Win32.pm
|
||||
inc/Module/Install/WriteAll.pm
|
||||
KNOWN_ISSUES
|
||||
lib/Zonemaster/Engine/ASNLookup.pm
|
||||
lib/Zonemaster/Engine/Constants.pm
|
||||
lib/Zonemaster/Engine/DNSName.pm
|
||||
lib/Zonemaster/Engine/Exception.pm
|
||||
lib/Zonemaster/Engine/Logger/Entry.pm
|
||||
lib/Zonemaster/Engine/Logger.pm
|
||||
lib/Zonemaster/Engine/Nameserver/Cache/LocalCache.pm
|
||||
lib/Zonemaster/Engine/Nameserver/Cache.pm
|
||||
lib/Zonemaster/Engine/Nameserver/Cache/RedisCache.pm
|
||||
lib/Zonemaster/Engine/Nameserver.pm
|
||||
lib/Zonemaster/Engine/Normalization/Error.pm
|
||||
lib/Zonemaster/Engine/Normalization.pm
|
||||
lib/Zonemaster/Engine/NSArray.pm
|
||||
lib/Zonemaster/Engine/Overview.pod
|
||||
lib/Zonemaster/Engine/Packet.pm
|
||||
lib/Zonemaster/Engine.pm
|
||||
lib/Zonemaster/Engine/Profile.pm
|
||||
lib/Zonemaster/Engine/Recursor.pm
|
||||
lib/Zonemaster/Engine/Test/Address.pm
|
||||
lib/Zonemaster/Engine/Test/Basic.pm
|
||||
lib/Zonemaster/Engine/Test/Connectivity.pm
|
||||
lib/Zonemaster/Engine/Test/Consistency.pm
|
||||
lib/Zonemaster/Engine/Test/Delegation.pm
|
||||
lib/Zonemaster/Engine/Test/DNSSEC.pm
|
||||
lib/Zonemaster/Engine/TestMethods.pm
|
||||
lib/Zonemaster/Engine/TestMethodsV2.pm
|
||||
lib/Zonemaster/Engine/Test/Nameserver.pm
|
||||
lib/Zonemaster/Engine/Test.pm
|
||||
lib/Zonemaster/Engine/Test/Syntax.pm
|
||||
lib/Zonemaster/Engine/Test/Zone.pm
|
||||
lib/Zonemaster/Engine/Translator.pm
|
||||
lib/Zonemaster/Engine/Util.pm
|
||||
lib/Zonemaster/Engine/Validation.pm
|
||||
lib/Zonemaster/Engine/Zone.pm
|
||||
LICENSE
|
||||
Makefile.PL
|
||||
MANIFEST This list of files
|
||||
META.yml
|
||||
README.md
|
||||
share/GNUmakefile
|
||||
share/iana-ipv4-special-registry.csv
|
||||
share/iana-ipv6-special-registry.csv
|
||||
share/json2yaml.pl
|
||||
share/locale/da/LC_MESSAGES/Zonemaster-Engine.mo
|
||||
share/locale/es/LC_MESSAGES/Zonemaster-Engine.mo
|
||||
share/locale/fi/LC_MESSAGES/Zonemaster-Engine.mo
|
||||
share/locale/fr/LC_MESSAGES/Zonemaster-Engine.mo
|
||||
share/locale/nb/LC_MESSAGES/Zonemaster-Engine.mo
|
||||
share/locale/sl/LC_MESSAGES/Zonemaster-Engine.mo
|
||||
share/locale/sv/LC_MESSAGES/Zonemaster-Engine.mo
|
||||
share/Makefile
|
||||
share/modules.txt
|
||||
share/named.root
|
||||
share/profile_additional_properties.json
|
||||
share/profile.json
|
||||
share/profile.yaml
|
||||
t/00-load.t
|
||||
t/asn.data
|
||||
t/asn.t
|
||||
t/dnsname.t
|
||||
t/logger.t
|
||||
t/methodsv2.data
|
||||
t/methodsv2.t
|
||||
t/nameserver-axfr.data
|
||||
t/nameserver-axfr.t
|
||||
t/nameserver.data
|
||||
t/nameserver.t
|
||||
t/normalization.t
|
||||
t/packet.t
|
||||
t/pod-coverage.t
|
||||
t/pod.t
|
||||
t/profiles/policy.json
|
||||
t/profiles/profile.json
|
||||
t/profiles.t
|
||||
t/profiles/Test-address-all.json
|
||||
t/profiles/Test-all.json
|
||||
t/profiles/Test-all-levels.json
|
||||
t/profiles/Test-basic-all.json
|
||||
t/profiles/Test-connectivity01-only.json
|
||||
t/profiles/Test-connectivity02-only.json
|
||||
t/profiles/Test-connectivity03-only.json
|
||||
t/profiles/Test-connectivity04-only.json
|
||||
t/profiles/Test-connectivity-all.json
|
||||
t/profiles/Test-consistency01-only.json
|
||||
t/profiles/Test-consistency02-only.json
|
||||
t/profiles/Test-consistency03-only.json
|
||||
t/profiles/Test-consistency04-only.json
|
||||
t/profiles/Test-consistency-all.json
|
||||
t/profiles/Test-delegation-all.json
|
||||
t/profiles/Test-dnssec01-only.json
|
||||
t/profiles/Test-dnssec02-only.json
|
||||
t/profiles/Test-dnssec03-only.json
|
||||
t/profiles/Test-dnssec04-only.json
|
||||
t/profiles/Test-dnssec05-only.json
|
||||
t/profiles/Test-dnssec06-only.json
|
||||
t/profiles/Test-dnssec07-only.json
|
||||
t/profiles/Test-dnssec08-only.json
|
||||
t/profiles/Test-dnssec09-only.json
|
||||
t/profiles/Test-dnssec10-only.json
|
||||
t/profiles/Test-dnssec11-only.json
|
||||
t/profiles/Test-dnssec13-only.json
|
||||
t/profiles/Test-dnssec14-only.json
|
||||
t/profiles/Test-dnssec15-only.json
|
||||
t/profiles/Test-dnssec17-only.json
|
||||
t/profiles/Test-dnssec18-only.json
|
||||
t/profiles/Test-dnssec-all.json
|
||||
t/profiles/Test-dnssec-more-all.json
|
||||
t/profiles/Test-nameserver01-only.json
|
||||
t/profiles/Test-nameserver02-only.json
|
||||
t/profiles/Test-nameserver03-only.json
|
||||
t/profiles/Test-nameserver04-only.json
|
||||
t/profiles/Test-nameserver05-only.json
|
||||
t/profiles/Test-nameserver06-only.json
|
||||
t/profiles/Test-nameserver07-only.json
|
||||
t/profiles/Test-nameserver08-only.json
|
||||
t/profiles/Test-nameserver09-only.json
|
||||
t/profiles/Test-nameserver-all.json
|
||||
t/profiles/Test-syntax01-only.json
|
||||
t/profiles/Test-syntax02-only.json
|
||||
t/profiles/Test-syntax03-only.json
|
||||
t/profiles/Test-syntax04-only.json
|
||||
t/profiles/Test-syntax05-only.json
|
||||
t/profiles/Test-syntax06-only.json
|
||||
t/profiles/Test-syntax07-only.json
|
||||
t/profiles/Test-syntax08-only.json
|
||||
t/profiles/Test-syntax-all.json
|
||||
t/profiles/Test-zone-all.json
|
||||
t/recursor-A.data
|
||||
t/recursor-A.t
|
||||
t/recursor.data
|
||||
t/recursor.t
|
||||
t/Test-address01.data
|
||||
t/Test-address01.t
|
||||
t/Test-address03.data
|
||||
t/Test-address03.t
|
||||
t/Test-address.data
|
||||
t/Test-address.t
|
||||
t/Test-basic01.data
|
||||
t/Test-basic01.t
|
||||
t/Test-basic02.data
|
||||
t/Test-basic02.t
|
||||
t/Test-basic.data
|
||||
t/Test-basic.t
|
||||
t/Test-connectivity03.data
|
||||
t/Test-connectivity03.t
|
||||
t/Test-connectivity04.data
|
||||
t/Test-connectivity04.t
|
||||
t/Test-connectivity.data
|
||||
t/Test-connectivity.t
|
||||
t/Test-consistency05.data
|
||||
t/Test-consistency05.t
|
||||
t/Test-consistency06.data
|
||||
t/Test-consistency06.t
|
||||
t/Test-consistency.data
|
||||
t/Test-consistency.t
|
||||
t/Test.data
|
||||
t/Test-delegation01.data
|
||||
t/Test-delegation01.t
|
||||
t/Test-delegation02.data
|
||||
t/Test-delegation02.t
|
||||
t/Test-delegation03.data
|
||||
t/Test-delegation03.t
|
||||
t/Test-delegation.data
|
||||
t/Test-delegation.t
|
||||
t/Test-dnssec01.data
|
||||
t/Test-dnssec01.t
|
||||
t/Test-dnssec03.data
|
||||
t/Test-dnssec03.t
|
||||
t/Test-dnssec05.data
|
||||
t/Test-dnssec05.t
|
||||
t/Test-dnssec07.data
|
||||
t/Test-dnssec07.t
|
||||
t/Test-dnssec10.data
|
||||
t/Test-dnssec10.t
|
||||
t/Test-dnssec16.data
|
||||
t/Test-dnssec16.t
|
||||
t/Test-dnssec.data
|
||||
t/Test-dnssec-more.data
|
||||
t/Test-dnssec-more.t
|
||||
t/Test-dnssec.t
|
||||
t/Test-nameserver01-A.data
|
||||
t/Test-nameserver01-A.t
|
||||
t/Test-nameserver01-B.data
|
||||
t/Test-nameserver01-B.t
|
||||
t/Test-nameserver01-C.data
|
||||
t/Test-nameserver01-C.t
|
||||
t/Test-nameserver01-D.data
|
||||
t/Test-nameserver01-D.t
|
||||
t/Test-nameserver15.data
|
||||
t/Test-nameserver15.t
|
||||
t/Test-nameserver.data
|
||||
t/Test-nameserver.t
|
||||
t/Test-syntax06-A.data
|
||||
t/Test-syntax06-A.t
|
||||
t/Test-syntax06-B.data
|
||||
t/Test-syntax06-B.t
|
||||
t/Test-syntax06-C.data
|
||||
t/Test-syntax06-C.t
|
||||
t/Test-syntax06-D.data
|
||||
t/Test-syntax06-D.t
|
||||
t/Test-syntax06-E.data
|
||||
t/Test-syntax06-E.t
|
||||
t/Test-syntax06-F.data
|
||||
t/Test-syntax06-F.t
|
||||
t/Test-syntax06-G.data
|
||||
t/Test-syntax06-G.t
|
||||
t/Test-syntax06-I.data
|
||||
t/Test-syntax06-I.t
|
||||
t/Test-syntax06-J.data
|
||||
t/Test-syntax06-J.t
|
||||
t/Test-syntax06-K.data
|
||||
t/Test-syntax06-K.t
|
||||
t/Test-syntax06-L.data
|
||||
t/Test-syntax06-L.t
|
||||
t/Test-syntax.data
|
||||
t/Test-syntax.t
|
||||
t/Test.t
|
||||
t/TestUtil/DSL/Compiler.pm
|
||||
t/TestUtil/DSL.pm
|
||||
t/TestUtil.pm
|
||||
t/Test-zone01-A.data
|
||||
t/Test-zone01-A.t
|
||||
t/Test-zone01-B.data
|
||||
t/Test-zone01-B.t
|
||||
t/Test-zone09-1.data
|
||||
t/Test-zone09-1.t
|
||||
t/Test-zone09.data
|
||||
t/Test-zone09.t
|
||||
t/Test-zone11-1.data
|
||||
t/Test-zone11-1.t
|
||||
t/Test-zone11-2.data
|
||||
t/Test-zone11-2.t
|
||||
t/Test-zone11-3.data
|
||||
t/Test-zone11-3.t
|
||||
t/Test-zone11.data
|
||||
t/Test-zone11.t
|
||||
t/Test-zone.data
|
||||
t/Test-zone.t
|
||||
t/translator.t
|
||||
t/undelegated.data
|
||||
t/undelegated.t
|
||||
t/util.t
|
||||
t/validation.t
|
||||
t/zone.data
|
||||
t/zonemaster.data
|
||||
t/zonemaster.t
|
||||
t/zone.t
|
||||
85
zonemaster-engine/MANIFEST.SKIP
Normal file
85
zonemaster-engine/MANIFEST.SKIP
Normal file
@@ -0,0 +1,85 @@
|
||||
\.po$
|
||||
^share/[^/]*\.mo$
|
||||
\.tar\.gz$
|
||||
\bMANIFEST\.SKIP$
|
||||
^\.perlcriticrc$
|
||||
^\.perltidyrc$
|
||||
^\.travis\.yml$
|
||||
^util/
|
||||
^share/update-po$ # PO files are excluded from dist, which makes this script meaningless in dist
|
||||
^share/Zonemaster-Engine.pot$
|
||||
^t/po-files.t$ # PO files are excluded from dist, so we cannot test them
|
||||
^Dockerfile$
|
||||
|
||||
#!start included /usr/share/perl/5.20/ExtUtils/MANIFEST.SKIP
|
||||
# Avoid version control files.
|
||||
\bRCS\b
|
||||
\bCVS\b
|
||||
\bSCCS\b
|
||||
,v$
|
||||
\B\.svn\b
|
||||
\B\.git\b
|
||||
\B\.gitignore\b
|
||||
\b_darcs\b
|
||||
\B\.cvsignore$
|
||||
|
||||
# Avoid VMS specific MakeMaker generated files
|
||||
\bDescrip.MMS$
|
||||
\bDESCRIP.MMS$
|
||||
\bdescrip.mms$
|
||||
|
||||
# Avoid Makemaker generated and utility files.
|
||||
\bMANIFEST\.bak
|
||||
^Makefile$
|
||||
\bblib/
|
||||
\bMakeMaker-\d
|
||||
\bpm_to_blib\.ts$
|
||||
\bpm_to_blib$
|
||||
\bblibdirs\.ts$ # 6.18 through 6.25 generated this
|
||||
|
||||
# Avoid Module::Build generated and utility files.
|
||||
\bBuild$
|
||||
\b_build/
|
||||
\bBuild.bat$
|
||||
\bBuild.COM$
|
||||
\bBUILD.COM$
|
||||
\bbuild.com$
|
||||
|
||||
# Avoid temp and backup files.
|
||||
~$
|
||||
\.old$
|
||||
\#$
|
||||
\b\.#
|
||||
\.bak$
|
||||
\.tmp$
|
||||
\.#
|
||||
\.rej$
|
||||
|
||||
# Avoid OS-specific files/dirs
|
||||
# Mac OSX metadata
|
||||
\B\.DS_Store
|
||||
# Mac OSX SMB mount metadata files
|
||||
\B\._
|
||||
# Development mode aid for File::ShareDir
|
||||
^lib/auto/share/dist/Zonemaster-Engine
|
||||
|
||||
# Avoid Devel::Cover and Devel::CoverX::Covered files.
|
||||
\bcover_db\b
|
||||
\bcovered\b
|
||||
|
||||
# Avoid MYMETA files
|
||||
^MYMETA\.
|
||||
#!end included /usr/share/perl/5.20/ExtUtils/MANIFEST.SKIP
|
||||
|
||||
# More Git configuration files
|
||||
\B\.gitattributes\b
|
||||
|
||||
# For GitHub action
|
||||
^\.github/
|
||||
|
||||
# Debian packaging
|
||||
^debian/
|
||||
^.pc/
|
||||
|
||||
# Avoid MANIFEST test
|
||||
t/manifest.t
|
||||
84
zonemaster-engine/Makefile.PL
Normal file
84
zonemaster-engine/Makefile.PL
Normal file
@@ -0,0 +1,84 @@
|
||||
use inc::Module::Install;
|
||||
use Module::Install::Share;
|
||||
|
||||
name 'Zonemaster-Engine';
|
||||
# "name" must be the same as the equivalent string in the following files:
|
||||
# lib/Zonemaster/Engine/Constants.pm
|
||||
# lib/Zonemaster/Engine/Config.pm
|
||||
# lib/Zonemaster/Engine/Translator.pm
|
||||
# share/Makefile
|
||||
|
||||
|
||||
repository 'https://github.com/zonemaster/zonemaster-engine';
|
||||
bugtracker 'https://github.com/zonemaster/zonemaster-engine/issues';
|
||||
|
||||
all_from 'lib/Zonemaster/Engine.pm';
|
||||
|
||||
# "2.1.0" could be declared as "2.001" but not as "2.1"
|
||||
# (see Zonemaster::LDNS below)
|
||||
|
||||
requires 'Class::Accessor' => 0;
|
||||
requires 'Clone' => 0;
|
||||
requires 'Email::Valid' => 0;
|
||||
requires 'File::ShareDir' => 1.00;
|
||||
requires 'File::Slurp' => 0;
|
||||
requires 'IO::Socket::INET6' => 2.69;
|
||||
requires 'List::Compare' => 0;
|
||||
requires 'List::MoreUtils' => 0;
|
||||
requires 'Locale::TextDomain' => 1.20;
|
||||
requires 'Log::Any' => 0;
|
||||
requires 'Mail::SPF' => 0;
|
||||
requires 'Module::Find' => 0.10;
|
||||
requires 'Net::DNS' => 0;
|
||||
requires 'Net::IP::XS' => 0.21;
|
||||
requires 'Readonly' => 0;
|
||||
requires 'Text::CSV' => 0;
|
||||
requires 'YAML::XS' => 0;
|
||||
requires 'Zonemaster::LDNS' => 5.000002; # For v5.0.2
|
||||
|
||||
test_requires 'Locale::PO' => 0;
|
||||
test_requires 'Pod::Coverage' => 0;
|
||||
test_requires 'Sub::Override' => 0;
|
||||
test_requires 'Test::Differences' => 0;
|
||||
test_requires 'Test::Exception' => 0;
|
||||
test_requires 'Test::Fatal' => 0;
|
||||
test_requires 'Test::NoWarnings' => 0;
|
||||
test_requires 'Test::Pod' => 1.22;
|
||||
|
||||
# Make all platforms include inc/Module/Install/External.pm
|
||||
requires_external_bin 'find';
|
||||
if ($^O eq "freebsd") {
|
||||
requires_external_bin 'gmake';
|
||||
};
|
||||
|
||||
sub MY::postamble {
|
||||
my $pure_all;
|
||||
if ($^O eq "freebsd") {
|
||||
# Make FreeBSD use gmake for share/Makefile
|
||||
$pure_all = 'GMAKE ?= "gmake"' . "\n"
|
||||
. 'pure_all :: share/Makefile' . "\n"
|
||||
. "\t" . 'cd share && $(GMAKE) all' . "\n";
|
||||
} else {
|
||||
$pure_all = 'pure_all :: share/Makefile' . "\n"
|
||||
. "\t" . 'cd share && $(MAKE) all' . "\n";
|
||||
}
|
||||
|
||||
my $docker = <<'END_DOCKER';
|
||||
|
||||
docker-build:
|
||||
docker build --tag zonemaster/engine:local --build-arg version=$(VERSION) .
|
||||
|
||||
docker-tag-version:
|
||||
docker tag zonemaster/engine:local zonemaster/engine:$(VERSION)
|
||||
|
||||
docker-tag-latest:
|
||||
docker tag zonemaster/engine:local zonemaster/engine:latest
|
||||
|
||||
END_DOCKER
|
||||
|
||||
return $pure_all . $docker;
|
||||
};
|
||||
|
||||
install_share;
|
||||
|
||||
WriteAll;
|
||||
66
zonemaster-engine/README.md
Normal file
66
zonemaster-engine/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Zonemaster Engine
|
||||
|
||||
## Purpose
|
||||
|
||||
This repository holds one of the components of the Zonemaster product. For an
|
||||
overview of the Zonemaster software, please see the
|
||||
[Zonemaster main repository].
|
||||
|
||||
This Git repository contains the *Zonemaster Engine testing framework*,
|
||||
and contains all code needed to perform the full suite of Zonemaster
|
||||
tests.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
For supported processor architectures, operating systems and Perl versions see
|
||||
[Zonemaster/README.md].
|
||||
|
||||
## Installation
|
||||
|
||||
Installation instructions for the Engine is provided in the
|
||||
[installation] document.
|
||||
|
||||
## Configuration
|
||||
|
||||
This repository does not need any specific configuration.
|
||||
|
||||
## Docker
|
||||
|
||||
Zonemaster-CLI is available on [Docker Hub], and can be conveniently downloaded
|
||||
and run without any installation. See [USING] Zonemaster-CLI for how to run
|
||||
Zonemaster-CLI on Docker.
|
||||
|
||||
To build your own Docker image, see the [Docker Image Creation] documentation.
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
There is full POD coverage of the Perl code. The documentation can be
|
||||
read on the [CPAN site].
|
||||
|
||||
Documentation on Zonemaster-Engine is also found under the [docs] directory.
|
||||
|
||||
|
||||
## Participation, Contact and Bug reporting
|
||||
|
||||
For participation, contact and bug reporting, please see
|
||||
[Zonemaster/README.md](https://github.com/zonemaster/zonemaster/blob/master/README.md).
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This is free software under a 2-clause BSD license. The full text of the license can
|
||||
be found in the [LICENSE](LICENSE) file included in this repository.
|
||||
|
||||
|
||||
[CPAN site]: https://metacpan.org/pod/Zonemaster::Engine
|
||||
[Docker Hub]: https://hub.docker.com/u/zonemaster
|
||||
[Docker Image Creation]: https://github.com/zonemaster/zonemaster/blob/master/docs/internal/maintenance/ReleaseProcess-create-docker-image.md
|
||||
[Docs]: docs/
|
||||
[Installation]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/installation/zonemaster-engine.md
|
||||
[USING]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/using/cli.md
|
||||
[Zonemaster main repository]: https://github.com/zonemaster/zonemaster
|
||||
[Zonemaster/README.md]: https://github.com/zonemaster/zonemaster/blob/master/README.md
|
||||
|
||||
|
||||
|
||||
71
zonemaster-engine/docs/Implementing_Tests.pod
Normal file
71
zonemaster-engine/docs/Implementing_Tests.pod
Normal file
@@ -0,0 +1,71 @@
|
||||
=head1 INTRODUCTION
|
||||
|
||||
The Zonemaster system intends to provide a framework for implementing DNS tests. In order to do this, it exposes APIs to the test code for examining a
|
||||
DNS zone, to emit examination results and to access configuration information on how the tests should be performed.
|
||||
|
||||
=head1 RULES AND RECOMMENDATIONS FOR TEST CODE
|
||||
|
||||
=over
|
||||
|
||||
=item Only access the outside world via the framework APIs.
|
||||
|
||||
The framework is built to make test results traceable, repeatable and understandable. If the test code itself goes around the framework and accesses
|
||||
things on its own, all those attributes are lost. If the test you're writing needs something the framework doesn't provide, request that it be added
|
||||
to the framework.
|
||||
|
||||
=item Make as few assumptions as possible.
|
||||
|
||||
When the entry method in a test module is called, it can only rely on a handful of things about the testing process so far:
|
||||
|
||||
=over
|
||||
|
||||
=item
|
||||
|
||||
The zone object it's given has a parent.
|
||||
|
||||
=item
|
||||
|
||||
The zone object it's given has glue records.
|
||||
|
||||
=item
|
||||
|
||||
The zone object it's given has nameservers.
|
||||
|
||||
=back
|
||||
|
||||
=item Favor code clarity over performance.
|
||||
|
||||
Strive to make the test code as easily understandable as possible. Ideally, it should be possible for a person with only a basic understanding of Perl to
|
||||
read the code and verify that it correctly implements the corresponding Test Case specifications.
|
||||
|
||||
=back
|
||||
|
||||
=head1 TEST MODULE STRUCTURE
|
||||
|
||||
The Zonemaster framework will attempt to load and call all modules in the namespace C<Zonemaster::Engine::Test>. These modules must have three class methods
|
||||
defined. One to run all tests it provides, and two to expose metadata about the module. Apart from that (and the rules above) the module can be
|
||||
implemented as desired.
|
||||
|
||||
=head2 Metadata methods
|
||||
|
||||
=over
|
||||
|
||||
=item C<version()>
|
||||
|
||||
This method should return a string representing the current version of the test module. The version information will be logged in a C<SYSTEM>
|
||||
message, but otherwise not used.
|
||||
|
||||
=item C<metadata()>
|
||||
|
||||
This method should return a reference to a hash. The keys of this hash should be the names of the individual test methods in the module, and the
|
||||
values for each key should be a reference to an array listing all the possible messages that test methods can emit.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Test-running method(s)
|
||||
|
||||
The framework expects to be able to run all tests in a module by calling a class method L<all()|Zonemaster::Engine::Test/all()> with a
|
||||
L<Zonemaster::Engine::Zone> object as the argument.
|
||||
The return value from this method is expected to be a list of L<Zonemaster::Engine::Logger::Entry> objects describing the results of the tests run.
|
||||
|
||||
=cut
|
||||
63
zonemaster-engine/docs/Translation.pod
Normal file
63
zonemaster-engine/docs/Translation.pod
Normal file
@@ -0,0 +1,63 @@
|
||||
=head1 TRANSLATION
|
||||
|
||||
=head2 Introduction
|
||||
|
||||
The translation system in Zonemaster is a two-step process, where internal
|
||||
message tags are first replaced by English strings with argument
|
||||
placeholders, and a second step where GNU gettext is used to translate the
|
||||
strings to other languages and fill in the placeholders based on provided data.
|
||||
|
||||
All translation files live in the F<share> directory in the
|
||||
L<Zonemaster::Engine> source directory and all commands described here are
|
||||
executed from that directory.
|
||||
|
||||
=head2 For translators
|
||||
|
||||
Instructions for translators can be found at
|
||||
L<https://github.com/zonemaster/zonemaster/blob/develop/docs/internal/maintenance/Instructions-for-translators.md>
|
||||
|
||||
=head2 For developers of Zonemaster test modules
|
||||
|
||||
The test module code should produce log messages with message tags, as documented
|
||||
elsewhere. These tags will be used for translation to human language, for
|
||||
determining the severity of the event logged and to make the events easily used
|
||||
by other software.
|
||||
|
||||
Each test module must also have a method named C<tag_descriptions()>.
|
||||
This method must return a reference to a hash whose entries are expected to look
|
||||
like this, where C<MESSAGE_TAG> is a message, C<TEST_MODULE> is the name of a
|
||||
test module tag and C<"Hello, {name}!"> is a message id:
|
||||
|
||||
MESSAGE_TAG => sub {
|
||||
__x # TEST_MODULE:MESSAGE_TAG
|
||||
"Hello, {name}!", @_;
|
||||
},
|
||||
|
||||
A number of things are important here.
|
||||
Keys in the hashref are message tags and values are coderefs.
|
||||
The coderef calls Locale::TextDomain::__x() with a Perl brace format string
|
||||
(a.k.a. message id) and passes along its own @_).
|
||||
The coderef propagates the return value of Locale::TextDomain::__x().
|
||||
The line immediately before the format string contains a comment consisting of
|
||||
the module name, a colon and the message tag.
|
||||
|
||||
The format strings themselves, the comments and the line numbers of the __x
|
||||
calls are used by the gettext tooling when updating the PO files with new
|
||||
message ids and old message strings.
|
||||
|
||||
=head2 For Zonemaster package maintainers
|
||||
|
||||
In order to make a new translation usable, it must be compiled to C<mo> format
|
||||
and installed. The first step needs the C<msgfmt> program from the GNU gettext
|
||||
package to be installed and available in the shell path. As long as it is, it
|
||||
should be enough to go to the F<share> directory and run C<make>.
|
||||
This is automatically done when following the release instructions.
|
||||
|
||||
For the new translation to actually be installed, the C<mo> file must be added
|
||||
to the F<MANIFEST> file. At the end of the C<make> run, it should have printed
|
||||
a list of all the paths that has to be there. Just open F<MANIFEST> in a text
|
||||
editor, check that all the lines are in there and add any that are missing (if
|
||||
you just added a new translation, that will be missing, for example).
|
||||
|
||||
Once the new translation is compiled and added to F<MANIFEST>, the normal Perl
|
||||
C<make install> process will install it.
|
||||
432
zonemaster-engine/lib/Zonemaster/Engine.pm
Normal file
432
zonemaster-engine/lib/Zonemaster/Engine.pm
Normal file
@@ -0,0 +1,432 @@
|
||||
package Zonemaster::Engine;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v8.1.1");
|
||||
|
||||
BEGIN {
|
||||
# Locale::TextDomain (<= 1.20) doesn't know about File::ShareDir so give a helping hand.
|
||||
# This is a hugely simplified version of the reference implementation located here:
|
||||
# https://metacpan.org/source/GUIDO/libintl-perl-1.21/lib/Locale/TextDomain.pm
|
||||
require File::ShareDir;
|
||||
require Locale::TextDomain;
|
||||
my $share = File::ShareDir::dist_dir( 'Zonemaster-Engine' );
|
||||
Locale::TextDomain->import( 'Zonemaster-Engine', "$share/locale" );
|
||||
}
|
||||
|
||||
use Class::Accessor "antlers";
|
||||
use Carp;
|
||||
|
||||
use Zonemaster::Engine::Nameserver;
|
||||
use Zonemaster::Engine::Logger;
|
||||
use Zonemaster::Engine::Profile;
|
||||
use Zonemaster::Engine::Zone;
|
||||
use Zonemaster::Engine::Test;
|
||||
use Zonemaster::Engine::Recursor;
|
||||
use Zonemaster::Engine::ASNLookup;
|
||||
|
||||
INIT {
|
||||
init_engine();
|
||||
}
|
||||
|
||||
our $logger;
|
||||
our $recursor = Zonemaster::Engine::Recursor->new;
|
||||
|
||||
my $init_done = 0;
|
||||
|
||||
sub init_engine {
|
||||
return if $init_done++;
|
||||
Zonemaster::Engine::Recursor::init_recursor();
|
||||
}
|
||||
|
||||
sub logger {
|
||||
return $logger //= Zonemaster::Engine::Logger->new;
|
||||
}
|
||||
|
||||
sub profile {
|
||||
return Zonemaster::Engine::Profile->effective;
|
||||
}
|
||||
|
||||
sub ns {
|
||||
my ( $class, $name, $address ) = @_;
|
||||
|
||||
return Zonemaster::Engine::Nameserver->new( { name => $name, address => $address } );
|
||||
}
|
||||
|
||||
sub zone {
|
||||
my ( $class, $name ) = @_;
|
||||
|
||||
return Zonemaster::Engine::Zone->new( { name => Zonemaster::Engine::DNSName->new( $name ) } );
|
||||
}
|
||||
|
||||
sub test_zone {
|
||||
my ( $class, $zname ) = @_;
|
||||
|
||||
return Zonemaster::Engine::Test->run_all_for( $class->zone( $zname ) );
|
||||
}
|
||||
|
||||
sub test_module {
|
||||
my ( $class, $module, $zname ) = @_;
|
||||
|
||||
return Zonemaster::Engine::Test->run_module( $module, $class->zone( $zname ) );
|
||||
}
|
||||
|
||||
sub test_method {
|
||||
my ( $class, $module, $method, $zname ) = @_;
|
||||
|
||||
return Zonemaster::Engine::Test->run_one( $module, $method, $class->zone( $zname ) );
|
||||
}
|
||||
|
||||
sub all_tags {
|
||||
my ( $class ) = @_;
|
||||
my @res;
|
||||
|
||||
foreach my $module ( sort { $a cmp $b } Zonemaster::Engine::Test->modules ) {
|
||||
my $full = "Zonemaster::Engine::Test::$module";
|
||||
my $ref = $full->metadata;
|
||||
foreach my $list ( values %{$ref} ) {
|
||||
push @res, map { uc( $module ) . q{:} . $_ } sort { $a cmp $b } @{$list};
|
||||
}
|
||||
}
|
||||
|
||||
return @res;
|
||||
}
|
||||
|
||||
sub all_methods {
|
||||
my ( $class ) = @_;
|
||||
my %res;
|
||||
|
||||
foreach my $module ( Zonemaster::Engine::Test->modules ) {
|
||||
my $full = "Zonemaster::Engine::Test::$module";
|
||||
my $ref = $full->metadata;
|
||||
foreach my $method ( sort { $a cmp $b } keys %{$ref} ) {
|
||||
push @{ $res{$module} }, $method;
|
||||
}
|
||||
}
|
||||
|
||||
return %res;
|
||||
}
|
||||
|
||||
sub recurse {
|
||||
my ( $class, $qname, $qtype, $qclass ) = @_;
|
||||
$qtype //= 'A';
|
||||
$qclass //= 'IN';
|
||||
|
||||
return $recursor->recurse( $qname, $qtype, $qclass );
|
||||
}
|
||||
|
||||
sub add_fake_delegation {
|
||||
my ( $class, $domain, $href, %flags ) = @_;
|
||||
my $fill_in_empty_oob_glue = exists $flags{fill_in_empty_oob_glue} ? delete $flags{fill_in_empty_oob_glue} : 1;
|
||||
croak 'Unrecognized flags: ' . join( ', ', keys %flags )
|
||||
if %flags;
|
||||
undef %flags;
|
||||
|
||||
# Validate arguments
|
||||
$domain =~ /[^.]$|^\.$/
|
||||
or croak 'Argument $domain must omit the trailing dot, or it must be a single dot';
|
||||
foreach my $name ( keys %{$href} ) {
|
||||
$name =~ /[^.]$|^\.$/
|
||||
or croak 'Each key of argument $href must omit the trailing dot, or it must be a single dot';
|
||||
( !defined $href->{$name} or ref $href->{$name} eq 'ARRAY' )
|
||||
or croak 'Each value of argument $href must be an arrayref or undef';
|
||||
$href->{$name} //= []; # normalize undef to empty arrayref
|
||||
}
|
||||
|
||||
# Check fake delegation
|
||||
my $incomplete_delegation;
|
||||
if ( $fill_in_empty_oob_glue ) {
|
||||
foreach my $name ( keys %{$href} ) {
|
||||
if ( !@{ $href->{$name} }
|
||||
&& !$class->zone( $domain )->is_in_zone( $name ) )
|
||||
{
|
||||
my @ips = map { $_->ip } Zonemaster::Engine::Recursor->get_addresses_for( $name );
|
||||
push @{ $href->{$name} }, @ips;
|
||||
if ( !@ips ) {
|
||||
$incomplete_delegation = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach my $name ( keys %{$href} ) {
|
||||
if ( not @{ $href->{$name} } ) {
|
||||
if ( $class->zone( $domain )->is_in_zone( $name ) ) {
|
||||
Zonemaster::Engine->logger->add( #
|
||||
FAKE_DELEGATION_IN_ZONE_NO_IP => { domain => $domain, nsname => $name }
|
||||
);
|
||||
}
|
||||
else {
|
||||
Zonemaster::Engine->logger->add( #
|
||||
FAKE_DELEGATION_NO_IP => { domain => $domain, nsname => $name }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$recursor->add_fake_addresses( $domain, $href );
|
||||
my $parent = $class->zone( $recursor->parent( $domain ) );
|
||||
foreach my $ns ( @{ $parent->ns } ) {
|
||||
$ns->add_fake_delegation( $domain => $href );
|
||||
}
|
||||
|
||||
if ( $incomplete_delegation ) {
|
||||
return;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub add_fake_ds {
|
||||
my ( $class, $domain, $aref ) = @_;
|
||||
|
||||
my $parent = $class->zone( scalar( $recursor->parent( $domain ) ) );
|
||||
if ( not $parent ) {
|
||||
die "Failed to find parent for $domain";
|
||||
}
|
||||
|
||||
foreach my $ns ( @{ $parent->ns } ) {
|
||||
$ns->add_fake_ds( $domain => $aref );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub can_continue {
|
||||
my ( $class ) = @_;
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
sub save_cache {
|
||||
my ( $class, $filename ) = @_;
|
||||
|
||||
return Zonemaster::Engine::Nameserver->save( $filename );
|
||||
}
|
||||
|
||||
sub preload_cache {
|
||||
my ( $class, $filename ) = @_;
|
||||
|
||||
return Zonemaster::Engine::Nameserver->restore( $filename );
|
||||
}
|
||||
|
||||
sub asn_lookup {
|
||||
my ( undef, $ip ) = @_;
|
||||
|
||||
return Zonemaster::Engine::ASNLookup->get( $ip );
|
||||
}
|
||||
|
||||
sub modules {
|
||||
return Zonemaster::Engine::Test->modules;
|
||||
}
|
||||
|
||||
sub start_time_now {
|
||||
Zonemaster::Engine::Logger->start_time_now();
|
||||
return;
|
||||
}
|
||||
|
||||
sub reset {
|
||||
Zonemaster::Engine::Logger->start_time_now();
|
||||
Zonemaster::Engine::Logger->reset_config();
|
||||
Zonemaster::Engine::Nameserver->empty_cache();
|
||||
$logger->clear_history() if $logger;
|
||||
Zonemaster::Engine::Recursor->clear_cache();
|
||||
Zonemaster::Engine::TestMethodsV2->clear_cache();
|
||||
return;
|
||||
}
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine - A tool to check the quality of a DNS zone
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my @results = Zonemaster::Engine->test_zone('iis.se')
|
||||
|
||||
=head1 INTRODUCTION
|
||||
|
||||
This manual describes the main L<Zonemaster::Engine> module. If what you're after is documentation on the Zonemaster test engine as a whole, see L<Zonemaster::Engine::Overview>.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item init_engine()
|
||||
|
||||
Run the initialization tasks if they have not been run already. This method is called automatically in INIT block.
|
||||
|
||||
=item test_zone($name)
|
||||
|
||||
Runs all available tests and returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=item test_module($module, $name)
|
||||
|
||||
Runs all available tests for the zone with the given name in the specified module.
|
||||
|
||||
=item test_method($module, $method, $name)
|
||||
|
||||
Run one particular test method in one particular module for one particular zone.
|
||||
The requested module must be in the list of currently enabled modules (that is,
|
||||
not a module disabled by the current profile), and the method must be listed in
|
||||
the metadata of the module exports.
|
||||
If those requirements are fulfilled, the method will be called with the provided
|
||||
arguments.
|
||||
|
||||
=item zone($name)
|
||||
|
||||
Returns a L<Zonemaster::Engine::Zone> object for the given name.
|
||||
|
||||
=item ns($name, $address)
|
||||
|
||||
Returns a L<Zonemaster::Engine::Nameserver> object for the given name and address.
|
||||
|
||||
=item profile()
|
||||
|
||||
Returns the effective profile (L<Zonemaster::Engine::Profile> object).
|
||||
|
||||
=item logger()
|
||||
|
||||
Returns the global L<Zonemaster::Engine::Logger> object.
|
||||
|
||||
=item all_tags()
|
||||
|
||||
Returns a list of all the tags that can be logged for all available test modules.
|
||||
|
||||
=item all_methods()
|
||||
|
||||
Returns a hash, where the keys are test module names and the values are lists with the names of the test methods in that module.
|
||||
|
||||
=item recurse($name, $type, $class)
|
||||
|
||||
Does a recursive lookup for the given name, type and class, and returns the resulting packet (if any). Simply calls
|
||||
L<Zonemaster::Engine::Recursor/recurse> on a globally stored object.
|
||||
|
||||
=item can_continue()
|
||||
|
||||
In case of critical condition that prevents tool to process tests, add test here and return False.
|
||||
|
||||
=item save_cache($filename)
|
||||
|
||||
After running the tests, save the accumulated cache to a file with the given name.
|
||||
|
||||
=item preload_cache($filename)
|
||||
|
||||
Before running the tests, load the cache with information from a file with the given name. This file must have the same format as is produced by
|
||||
L</save_cache()>.
|
||||
|
||||
=item asn_lookup($ip)
|
||||
|
||||
Takes a single IP address (string or L<Net::IP::XS> object) and returns a list of AS numbers, if any.
|
||||
|
||||
=item modules()
|
||||
|
||||
Returns a list of the loaded test modules. Exactly the same as L<Zonemaster::Engine::Test/modules>.
|
||||
|
||||
=item add_fake_delegation($domain, $data, %flags)
|
||||
|
||||
This method adds some fake delegation information to the system.
|
||||
|
||||
The arguments are a domain name, and a hashref with delegation information.
|
||||
The keys in the hash are nameserver names, and the values are arrayrefs of IP
|
||||
addresses for their corresponding nameserver.
|
||||
Alternatively the IP addresses may be specified as an `undef` which is handled
|
||||
the same as an empty arrayref.
|
||||
|
||||
For each provided nameserver with an empty list of addresses, either a
|
||||
C<FAKE_DELEGATION_NO_IP> or a C<FAKE_DELEGATION_IN_ZONE_NO_IP> message is
|
||||
emitted.
|
||||
|
||||
The only recognized flag is C<fill_in_empty_oob_glue>.
|
||||
This flag is boolean and defaults to true.
|
||||
If this flag is true, this method updates the given C<$data> by looking up and
|
||||
filling in some glue addresses.
|
||||
Specifically the glue addresses for any nameserver name that are
|
||||
out-of-bailiwick of the given C<$domain> and that comes with an empty list of
|
||||
addresses.
|
||||
|
||||
Returns `1` if all name servers in C<$data> have non-empty lists of
|
||||
glue (after they've been filled in) or if `fill_in_empty_oob_glue` is false.
|
||||
Otherwise it returns `undef`.
|
||||
|
||||
Examples:
|
||||
|
||||
Zonemaster::Engine->add_fake_delegation(
|
||||
'lysator.liu.se' => {
|
||||
'ns1.nic.fr' => [ ],
|
||||
'ns.nic.se' => [ '212.247.7.228', '2a00:801:f0:53::53' ],
|
||||
'i.ns.se' => [ '194.146.106.22', '2001:67c:1010:5::53' ],
|
||||
'ns3.nic.se' => [ '212.247.8.152', '2a00:801:f0:211::152' ]
|
||||
},
|
||||
);
|
||||
|
||||
returns 1.
|
||||
|
||||
Zonemaster::Engine->add_fake_delegation(
|
||||
'lysator.liu.se' => {
|
||||
'ns1.lysator.liu.se' => [ ],
|
||||
'ns.nic.se' => [ '212.247.7.228', '2a00:801:f0:53::53' ],
|
||||
'i.ns.se' => [ '194.146.106.22', '2001:67c:1010:5::53' ],
|
||||
'ns3.nic.se' => [ '212.247.8.152', '2a00:801:f0:211::152' ]
|
||||
}
|
||||
);
|
||||
|
||||
returns C<undef> (signalling that fake delegation with empty glue was added to
|
||||
the system).
|
||||
|
||||
Zonemaster::Engine->add_fake_delegation(
|
||||
'lysator.liu.se' => {
|
||||
'ns1.nic.fr' => [ ],
|
||||
'ns.nic.se' => [ '212.247.7.228', '2a00:801:f0:53::53' ],
|
||||
'i.ns.se' => [ '194.146.106.22', '2001:67c:1010:5::53' ],
|
||||
'ns3.nic.se' => [ '212.247.8.152', '2a00:801:f0:211::152' ]
|
||||
},
|
||||
fill_in_empty_oob_glue => 0,
|
||||
);
|
||||
|
||||
returns 1. It does not even attempt to fill in glue for ns1.nic.fr.
|
||||
|
||||
=item add_fake_ds($domain, $data)
|
||||
|
||||
This method adds fake DS records to the system. The arguments are a domain
|
||||
name, and a reference to a list of references to hashes. The hashes in turn
|
||||
must have the keys C<keytag>, C<algorithm>, C<type> and C<digest>, with the
|
||||
values holding the corresponding data. The digest data should be a single
|
||||
unbroken string of hexadecimal digits.
|
||||
|
||||
Example:
|
||||
|
||||
Zonemaster::Engine->add_fake_ds(
|
||||
'nic.se' => [
|
||||
{ keytag => 16696, algorithm => 5, type => 2, digest => '40079DDF8D09E7F10BB248A69B6630478A28EF969DDE399F95BC3B39F8CBACD7' },
|
||||
{ keytag => 16696, algorithm => 5, type => 1, digest => 'EF5D421412A5EAF1230071AFFD4F585E3B2B1A60' },
|
||||
]
|
||||
);
|
||||
|
||||
=item start_time_now()
|
||||
|
||||
Set the logger's start time to the current time.
|
||||
|
||||
=item reset()
|
||||
|
||||
Reset logger start time to current time, empty the list of log messages, clear
|
||||
nameserver object cache, clear recursor cache and clear all cached results of
|
||||
MethodsV2.
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Vincent Levigneron <vincent.levigneron at nic.fr>
|
||||
- Current maintainer
|
||||
|
||||
Calle Dybedahl <calle at init.se>
|
||||
- Original author
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This is free software under a 2-clause BSD license. The full text of the license can
|
||||
be found in the F<LICENSE> file included with this distribution.
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
238
zonemaster-engine/lib/Zonemaster/Engine/ASNLookup.pm
Normal file
238
zonemaster-engine/lib/Zonemaster/Engine/ASNLookup.pm
Normal file
@@ -0,0 +1,238 @@
|
||||
package Zonemaster::Engine::ASNLookup;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare( "v1.0.11" );
|
||||
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Engine::Util qw( name );
|
||||
use Zonemaster::Engine::Nameserver;
|
||||
use Zonemaster::Engine::Profile;
|
||||
|
||||
use IO::Socket;
|
||||
use IO::Socket::INET;
|
||||
use Net::IP::XS;
|
||||
use Scalar::Util qw( looks_like_number );
|
||||
|
||||
our @db_sources;
|
||||
our $db_style;
|
||||
|
||||
sub get_with_prefix {
|
||||
my ( $class, $ip ) = @_;
|
||||
|
||||
if ( not @db_sources ) {
|
||||
$db_style = Zonemaster::Engine::Profile->effective->get( q{asn_db.style} );
|
||||
my %db_sources = %{ Zonemaster::Engine::Profile->effective->get( q{asn_db.sources} ) };
|
||||
@db_sources = map { name( $_ ) } @{ $db_sources{ $db_style } };
|
||||
}
|
||||
|
||||
if ( not ref( $ip ) or not $ip->isa( 'Net::IP::XS' ) ) {
|
||||
$ip = Net::IP::XS->new( $ip );
|
||||
}
|
||||
|
||||
if ( not @db_sources ) {
|
||||
die "ASN database sources undefined";
|
||||
}
|
||||
|
||||
my ( $asnref, $prefix, $raw, $ret_code );
|
||||
|
||||
if ( $db_style eq q{cymru} ) {
|
||||
( $asnref, $prefix, $raw, $ret_code ) = _cymru_asn_lookup($ip);
|
||||
}
|
||||
elsif ( $db_style eq q{ripe} ) {
|
||||
( $asnref, $prefix, $raw, $ret_code ) = _ripe_asn_lookup($ip);
|
||||
}
|
||||
else {
|
||||
if ( not $db_style ) {
|
||||
die "ASN database style undefined";
|
||||
}
|
||||
else {
|
||||
die "ASN database style value '$db_style' is illegal";
|
||||
}
|
||||
}
|
||||
|
||||
map { looks_like_number( $_ ) || die "ASN lookup value isn't numeric: '$_'" } @$asnref;
|
||||
|
||||
return ( $asnref, $prefix, $raw, $ret_code );
|
||||
|
||||
} ## end sub get_with_prefix
|
||||
|
||||
sub _cymru_asn_lookup {
|
||||
my $ip = shift;
|
||||
my @asns = ();
|
||||
|
||||
my $db_source_nb = 0;
|
||||
foreach my $db_source ( @db_sources ) {
|
||||
Zonemaster::Engine->logger->add( ASN_LOOKUP_SOURCE => { name => $db_source } );
|
||||
my $reverse = $ip->reverse_ip;
|
||||
my $domain = $db_source->string;
|
||||
my $pair = {
|
||||
'in-addr.arpa.' => "origin.$domain",
|
||||
'ip6.arpa.' => "origin6.$domain",
|
||||
};
|
||||
$db_source_nb++;
|
||||
|
||||
foreach my $root ( keys %{$pair} ) {
|
||||
if ( $reverse =~ s/$root/$pair->{$root}/ix ) {
|
||||
my $p = Zonemaster::Engine->recurse( $reverse, 'TXT' );
|
||||
|
||||
if ( $p ) {
|
||||
if ( $p->rcode eq q{NXDOMAIN} ) {
|
||||
if ( $p->get_records( 'SOA', 'authority' ) and scalar $p->get_records( 'SOA', 'authority' ) == 1 and ($p->get_records( 'SOA', 'authority' ))[0]->owner eq name( $db_source ) ) {
|
||||
return \@asns, undef, q{}, q{EMPTY_ASN_SET};
|
||||
}
|
||||
}
|
||||
elsif ( $p->rcode eq q{NOERROR} ) {
|
||||
if ( $p->answer ) {
|
||||
my @rr = $p->get_records( 'TXT', 'answer' );
|
||||
|
||||
if ( @rr ) {
|
||||
my $max_length = 0;
|
||||
my @fields;
|
||||
my $str;
|
||||
|
||||
foreach my $rr ( @rr ) {
|
||||
my $_str = $rr->txtdata;
|
||||
my @_fields = split( /[ ][|][ ]?/x, $_str );
|
||||
|
||||
next if scalar @_fields <= 1;
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE} unless Net::IP::XS->new( $_fields[1] )->overlaps( $ip );
|
||||
|
||||
my @_asns = split( /\s+/x, $_fields[0] );
|
||||
my $_prefix_length = ($_fields[1] =~ m!^.*[/](.*)!x)[0];
|
||||
if ( $_prefix_length > $max_length ) {
|
||||
$str = $_str;
|
||||
@asns = @_asns;
|
||||
@fields = @_fields;
|
||||
$max_length = $_prefix_length;
|
||||
}
|
||||
}
|
||||
|
||||
if ( @fields ) {
|
||||
if ( Net::IP::XS->new( $fields[1] )->overlaps( $ip ) ) {
|
||||
return \@asns, Net::IP::XS->new( $fields[1] ), $str, q{AS_FOUND}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return \@asns, undef, q{}, q{EMPTY_ASN_SET};
|
||||
}
|
||||
}
|
||||
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE};
|
||||
}
|
||||
|
||||
return \@asns, undef, q{}, q{EMPTY_ASN_SET};
|
||||
}
|
||||
}
|
||||
|
||||
if ( $db_source_nb == scalar @db_sources ) {
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE};
|
||||
}
|
||||
|
||||
last;
|
||||
}
|
||||
}
|
||||
} ## end foreach my $db_source ( @db_sources )
|
||||
return;
|
||||
}
|
||||
|
||||
sub _ripe_asn_lookup {
|
||||
my $ip = shift;
|
||||
my @asns = ();
|
||||
|
||||
my $db_source_nb = 0;
|
||||
foreach my $db_source ( @db_sources ) {
|
||||
$db_source_nb++;
|
||||
my $socket = IO::Socket::INET->new( PeerAddr => $db_source->string,
|
||||
PeerPort => q{43},
|
||||
Proto => q{tcp} );
|
||||
unless ( $socket ) {
|
||||
if ( $db_source_nb == scalar @db_sources ) {
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE};
|
||||
}
|
||||
else {
|
||||
next;
|
||||
}
|
||||
};
|
||||
|
||||
printf $socket "-F -M %s\n", $ip->short();
|
||||
|
||||
my $data;
|
||||
my $str;
|
||||
my $has_answer = 0;
|
||||
while ( defined ($data = <$socket>) ) {
|
||||
$has_answer = 1;
|
||||
chop $data;
|
||||
if ( $data !~ /^%/x and $data !~ /^\s*$/x ) {
|
||||
$str = $data;
|
||||
last;
|
||||
}
|
||||
}
|
||||
$socket->close();
|
||||
if ( not $has_answer ) {
|
||||
if ( $db_source_nb == scalar @db_sources ) {
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE};
|
||||
}
|
||||
else {
|
||||
next;
|
||||
}
|
||||
}
|
||||
elsif ( $str ) {
|
||||
my @fields = split( /\s+/x, $str );
|
||||
my @asns = split( '/', $fields[0] );
|
||||
return \@asns, Net::IP::XS->new( $fields[1] ), $str, q{AS_FOUND};
|
||||
}
|
||||
else {
|
||||
return \@asns, undef, q{}, q{EMPTY_ASN_SET};
|
||||
}
|
||||
} ## end foreach my $db_source ( @
|
||||
return;
|
||||
}
|
||||
|
||||
sub get {
|
||||
my ( $class, $ip ) = @_;
|
||||
|
||||
my ( $asnref, $prefix, $raw, $ret_code ) = $class->get_with_prefix( $ip );
|
||||
|
||||
if ( $asnref ) {
|
||||
return @{$asnref};
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::ASNLookup - do lookups of ASNs for IP addresses
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my ( $asnref, $prefix, $raw, $ret_code ) = Zonemaster::Engine::ASNLookup->get_with_prefix( '8.8.4.4' );
|
||||
my $asnref = Zonemaster::Engine::ASNLookup->get( '192.168.0.1' );
|
||||
|
||||
=head1 FUNCTION
|
||||
|
||||
=over
|
||||
|
||||
=item get($addr)
|
||||
|
||||
As L<get_with_prefix()>, except it returns only the list of AS numbers
|
||||
for the address, if any.
|
||||
|
||||
=item get_with_prefix($addr)
|
||||
|
||||
Takes a string (or a L<Net::IP::XS> object) with a single IP address, and
|
||||
does a lookup in either: a) Cymru-style DNS zone or b) RIPE whois server,
|
||||
depending on L<Zonemaster::Engine::Profile> setting "asn_db{style}".
|
||||
|
||||
Returns a list of a reference to a list of AS numbers, a Net::IP::XS object
|
||||
of the covering prefix for that AS, a string of the raw query, and a string
|
||||
of the return code for that query.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
292
zonemaster-engine/lib/Zonemaster/Engine/Constants.pm
Normal file
292
zonemaster-engine/lib/Zonemaster/Engine/Constants.pm
Normal file
@@ -0,0 +1,292 @@
|
||||
package Zonemaster::Engine::Constants;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.2.5");
|
||||
|
||||
use Carp;
|
||||
use English qw( -no_match_vars ) ;
|
||||
use parent 'Exporter';
|
||||
use Net::IP::XS;
|
||||
use Text::CSV;
|
||||
use File::ShareDir qw[dist_dir dist_file];
|
||||
|
||||
use Readonly;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Constants - module holding constants used in Test modules
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Zonemaster::Engine::Constants ':all';
|
||||
|
||||
=head1 EXPORTED GROUPS
|
||||
|
||||
=over
|
||||
|
||||
=item all
|
||||
|
||||
All exportable names.
|
||||
|
||||
=item algo
|
||||
|
||||
DNSSEC algorithms.
|
||||
|
||||
=item cname
|
||||
|
||||
CNAME records.
|
||||
|
||||
=item name
|
||||
|
||||
Label and name lengths.
|
||||
|
||||
=item ip
|
||||
|
||||
IP version constants.
|
||||
|
||||
=item soa
|
||||
|
||||
SOA values limits.
|
||||
|
||||
=item misc
|
||||
|
||||
Other, uncategorized export names, e.g. UDP payload limit and minimum number of name servers per zone.
|
||||
|
||||
=item addresses
|
||||
|
||||
Address classes for IPv4 and IPv6.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
our @EXPORT_OK = qw[
|
||||
$ALGO_STATUS_DEPRECATED
|
||||
$ALGO_STATUS_PRIVATE
|
||||
$ALGO_STATUS_RESERVED
|
||||
$ALGO_STATUS_UNASSIGNED
|
||||
$ALGO_STATUS_OTHER
|
||||
$ALGO_STATUS_NOT_RECOMMENDED
|
||||
$ALGO_STATUS_NOT_ZONE_SIGN
|
||||
$BLACKLISTING_ENABLED
|
||||
$CNAME_MAX_CHAIN_LENGTH
|
||||
$CNAME_MAX_RECORDS
|
||||
$DURATION_5_MINUTES_IN_SECONDS
|
||||
$DURATION_1_HOUR_IN_SECONDS
|
||||
$DURATION_4_HOURS_IN_SECONDS
|
||||
$DURATION_12_HOURS_IN_SECONDS
|
||||
$DURATION_1_DAY_IN_SECONDS
|
||||
$DURATION_1_WEEK_IN_SECONDS
|
||||
$DURATION_180_DAYS_IN_SECONDS
|
||||
$FQDN_MAX_LENGTH
|
||||
$IP_VERSION_4
|
||||
$IP_VERSION_6
|
||||
$LABEL_MAX_LENGTH
|
||||
$SERIAL_BITS
|
||||
$SERIAL_MAX_VARIATION
|
||||
$MINIMUM_NUMBER_OF_NAMESERVERS
|
||||
$UDP_PAYLOAD_LIMIT
|
||||
$EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT
|
||||
$EDNS_UDP_PAYLOAD_DEFAULT
|
||||
$EDNS_UDP_PAYLOAD_COMMON_LIMIT
|
||||
@IPV4_SPECIAL_ADDRESSES
|
||||
@IPV6_SPECIAL_ADDRESSES
|
||||
];
|
||||
|
||||
our %EXPORT_TAGS = (
|
||||
all => \@EXPORT_OK,
|
||||
algo => [
|
||||
qw($ALGO_STATUS_DEPRECATED $ALGO_STATUS_PRIVATE $ALGO_STATUS_RESERVED $ALGO_STATUS_UNASSIGNED $ALGO_STATUS_OTHER $ALGO_STATUS_NOT_ZONE_SIGN $ALGO_STATUS_NOT_RECOMMENDED)
|
||||
],
|
||||
cname => [ qw($CNAME_MAX_CHAIN_LENGTH $CNAME_MAX_RECORDS) ],
|
||||
name => [qw($FQDN_MAX_LENGTH $LABEL_MAX_LENGTH)],
|
||||
ip => [qw($IP_VERSION_4 $IP_VERSION_6)],
|
||||
soa => [
|
||||
qw($DURATION_5_MINUTES_IN_SECONDS $DURATION_1_HOUR_IN_SECONDS $DURATION_4_HOURS_IN_SECONDS $DURATION_12_HOURS_IN_SECONDS $DURATION_1_DAY_IN_SECONDS $DURATION_1_WEEK_IN_SECONDS $DURATION_180_DAYS_IN_SECONDS $SERIAL_BITS $SERIAL_MAX_VARIATION)
|
||||
],
|
||||
misc => [qw($UDP_PAYLOAD_LIMIT $EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT $EDNS_UDP_PAYLOAD_DEFAULT $EDNS_UDP_PAYLOAD_COMMON_LIMIT $MINIMUM_NUMBER_OF_NAMESERVERS $BLACKLISTING_ENABLED)]
|
||||
, # everything in %EXPORT_OK that isn't included in any of the other tags
|
||||
addresses => [qw(@IPV4_SPECIAL_ADDRESSES @IPV6_SPECIAL_ADDRESSES)],
|
||||
);
|
||||
|
||||
=head1 EXPORTED NAMES
|
||||
|
||||
=over
|
||||
|
||||
=item * C<$ALGO_STATUS_DEPRECATED>
|
||||
|
||||
=item * C<$ALGO_STATUS_PRIVATE>
|
||||
|
||||
=item * C<$ALGO_STATUS_RESERVED>
|
||||
|
||||
=item * C<$ALGO_STATUS_UNASSIGNED>
|
||||
|
||||
=item * C<$ALGO_STATUS_OTHER>
|
||||
|
||||
=item * C<$ALGO_STATUS_NOT_RECOMMENDED>
|
||||
|
||||
=item * C<$ALGO_STATUS_NOT_ZONE_SIGN>
|
||||
|
||||
=item * C<$BLACKLISTING_ENABLED>
|
||||
|
||||
A boolean, used to enable the name server blacklisting mechanism.
|
||||
|
||||
=item * C<$CNAME_MAX_CHAIN_LENGTH>
|
||||
|
||||
An integer, used to define the maximum length of a CNAME chain when doing consecutive recursive lookups.
|
||||
|
||||
=item * C<$CNAME_MAX_RECORDS>
|
||||
|
||||
An integer, used to define the maximum number of CNAME records in a response.
|
||||
|
||||
=item * C<$DURATION_5_MINUTES_IN_SECONDS>
|
||||
|
||||
=item * C<$DURATION_1_HOUR_IN_SECONDS>
|
||||
|
||||
=item * C<$DURATION_4_HOURS_IN_SECONDS>
|
||||
|
||||
=item * C<$DURATION_12_HOURS_IN_SECONDS>
|
||||
|
||||
=item * C<$DURATION_1_DAY_IN_SECONDS>
|
||||
|
||||
=item * C<$DURATION_1_WEEK_IN_SECONDS>
|
||||
|
||||
=item * C<$DURATION_180_DAYS_IN_SECONDS>
|
||||
|
||||
=item * C<$FQDN_MAX_LENGTH>
|
||||
|
||||
=item * C<$LABEL_MAX_LENGTH>
|
||||
|
||||
=item * C<$IP_VERSION_4>
|
||||
|
||||
=item * C<$IP_VERSION_6>
|
||||
|
||||
=item * C<$SERIAL_BITS>
|
||||
|
||||
An integer, used to define the size of the serial number space, as defined in RFC1982, section 2.
|
||||
|
||||
=item * C<$SERIAL_MAX_VARIATION>
|
||||
|
||||
=item * C<$MINIMUM_NUMBER_OF_NAMESERVERS>
|
||||
|
||||
=item * C<$UDP_PAYLOAD_LIMIT>
|
||||
|
||||
=item * C<$EDNS_UDP_PAYLOAD_DEFAULT>
|
||||
|
||||
An integer, used to define the EDNS0 UDP packet size in non-DNSSEC EDNS queries.
|
||||
|
||||
=item * C<$EDNS_UDP_PAYLOAD_COMMON_LIMIT>
|
||||
|
||||
=item * C<$EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT>
|
||||
|
||||
An integer, used to define the EDNS0 UDP packet size in DNSSEC queries.
|
||||
|
||||
=item * C<@IPV4_SPECIAL_ADDRESSES>
|
||||
|
||||
=item * C<@IPV6_SPECIAL_ADDRESSES>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
Readonly our $ALGO_STATUS_DEPRECATED => 1;
|
||||
Readonly our $ALGO_STATUS_PRIVATE => 4;
|
||||
Readonly our $ALGO_STATUS_RESERVED => 2;
|
||||
Readonly our $ALGO_STATUS_UNASSIGNED => 3;
|
||||
Readonly our $ALGO_STATUS_OTHER => 5;
|
||||
Readonly our $ALGO_STATUS_NOT_ZONE_SIGN => 8;
|
||||
Readonly our $ALGO_STATUS_NOT_RECOMMENDED => 9;
|
||||
|
||||
Readonly our $BLACKLISTING_ENABLED => 1;
|
||||
|
||||
Readonly our $CNAME_MAX_CHAIN_LENGTH => 10;
|
||||
Readonly our $CNAME_MAX_RECORDS => 9;
|
||||
|
||||
Readonly our $DURATION_5_MINUTES_IN_SECONDS => 5 * 60;
|
||||
Readonly our $DURATION_1_HOUR_IN_SECONDS => 60 * 60;
|
||||
Readonly our $DURATION_4_HOURS_IN_SECONDS => 4 * 60 * 60;
|
||||
Readonly our $DURATION_12_HOURS_IN_SECONDS => 12 * 60 * 60;
|
||||
Readonly our $DURATION_1_DAY_IN_SECONDS => 24 * 60 * 60;
|
||||
Readonly our $DURATION_1_WEEK_IN_SECONDS => 7 * 24 * 60 * 60;
|
||||
Readonly our $DURATION_180_DAYS_IN_SECONDS => 180 * 24 * 60 * 60;
|
||||
|
||||
# Maximum length of ASCII version of a domain name, with trailing dot.
|
||||
Readonly our $FQDN_MAX_LENGTH => 254;
|
||||
Readonly our $LABEL_MAX_LENGTH => 63;
|
||||
|
||||
Readonly our $IP_VERSION_4 => 4;
|
||||
Readonly our $IP_VERSION_6 => 6;
|
||||
|
||||
Readonly our $MINIMUM_NUMBER_OF_NAMESERVERS => 2;
|
||||
|
||||
Readonly our $SERIAL_BITS => 32;
|
||||
Readonly our $SERIAL_MAX_VARIATION => 0;
|
||||
|
||||
Readonly our $UDP_PAYLOAD_LIMIT => 512;
|
||||
Readonly our $EDNS_UDP_PAYLOAD_DEFAULT => 512;
|
||||
Readonly our $EDNS_UDP_PAYLOAD_COMMON_LIMIT => 4096;
|
||||
Readonly our $EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT => 1232;
|
||||
|
||||
Readonly::Array our @IPV4_SPECIAL_ADDRESSES => _extract_iana_ip_blocks($IP_VERSION_4);
|
||||
Readonly::Array our @IPV6_SPECIAL_ADDRESSES => _extract_iana_ip_blocks($IP_VERSION_6);
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item _extract_iana_ip_blocks()
|
||||
|
||||
my @array = _extract_iana_ip_blocks( $ip_version );
|
||||
|
||||
Internal method that is used to extract IP blocks details from IANA files for a given IP version (i.e. 4 or 6).
|
||||
|
||||
Takes an integer (IP version).
|
||||
|
||||
Returns a list of hashes - the keys of which are C<ip> (L<Net::IP::XS> object), C<name> (string), C<reference> (string)
|
||||
and C<globally_reachable> (string).
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub _extract_iana_ip_blocks {
|
||||
my $ip_version = shift;
|
||||
my @list = ();
|
||||
|
||||
my $csv = Text::CSV->new ({
|
||||
binary => 1,
|
||||
auto_diag => 1,
|
||||
sep_char => q{,}
|
||||
});
|
||||
my @files_details = (
|
||||
{ name => q{iana-ipv4-special-registry.csv}, ip_version => $IP_VERSION_4 },
|
||||
{ name => q{iana-ipv6-special-registry.csv}, ip_version => $IP_VERSION_6 },
|
||||
);
|
||||
|
||||
foreach my $file_details ( @files_details ) {
|
||||
my $first_line = 1;
|
||||
next if ${$file_details}{ip_version} != $ip_version;
|
||||
my $makefile_name = 'Zonemaster-Engine'; # This must be the same name as "name" in Makefile.PL
|
||||
my $data_location = dist_file($makefile_name, ${$file_details}{name});
|
||||
open(my $data, '<:encoding(utf8)', $data_location) or croak "Cannot open '${data_location}' : ${OS_ERROR}";
|
||||
while (my $fields = $csv->getline( $data )) {
|
||||
if ( $first_line ) {
|
||||
$first_line = 0;
|
||||
next;
|
||||
}
|
||||
my $address_data = $fields->[0];
|
||||
$address_data =~ s/[ ]+//smx;
|
||||
foreach my $address_item ( split /,/smx, $address_data ) {
|
||||
$address_item =~ s/(\A.+\/\d+).*\z/$1/smx;
|
||||
push @list, { ip => Net::IP::XS->new( $address_item ), name => $fields->[1], reference => $fields->[2], globally_reachable => $fields->[8] };
|
||||
}
|
||||
}
|
||||
close $data or croak "Cannot close '${data_location}' : ${OS_ERROR}";
|
||||
}
|
||||
|
||||
return @list;
|
||||
} ## end sub _extract_iana_ip_blocks
|
||||
|
||||
1;
|
||||
249
zonemaster-engine/lib/Zonemaster/Engine/DNSName.pm
Normal file
249
zonemaster-engine/lib/Zonemaster/Engine/DNSName.pm
Normal file
@@ -0,0 +1,249 @@
|
||||
package Zonemaster::Engine::DNSName;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.3");
|
||||
|
||||
use Carp;
|
||||
use Scalar::Util qw( blessed );
|
||||
|
||||
use Class::Accessor "antlers";
|
||||
|
||||
use overload
|
||||
'""' => \&string,
|
||||
'cmp' => \&str_cmp;
|
||||
|
||||
has 'labels' => ( is => 'ro' );
|
||||
|
||||
sub from_string {
|
||||
my ( $class, $domain ) = @_;
|
||||
|
||||
confess 'Argument must be a string: $domain'
|
||||
if !defined $domain || ref $domain ne '';
|
||||
|
||||
my $obj = Class::Accessor::new( $class, { labels => [ split( /[.]/x, $domain ) ] } );
|
||||
|
||||
# We have the raw string, so we can precompute the string representation
|
||||
# easily and cheaply so it can be immediately returned by the string()
|
||||
# method instead of recomputing it from the labels list. The only thing we
|
||||
# need to do is to remove any trailing dot except if it’s the only
|
||||
# character.
|
||||
$obj->{_string} = ( $domain =~ s/.\K [.] \z//rx );
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub new {
|
||||
my ( $class, $input ) = @_;
|
||||
|
||||
my $attrs = {};
|
||||
if ( !defined $input ) {
|
||||
$attrs->{labels} = [];
|
||||
}
|
||||
elsif ( blessed $input && $input->isa( 'Zonemaster::Engine::DNSName' ) ) {
|
||||
$attrs->{labels} = \@{ $input->labels };
|
||||
}
|
||||
elsif ( blessed $input && $input->isa( 'Zonemaster::Engine::Zone' ) ) {
|
||||
$attrs->{labels} = \@{ $input->name->labels };
|
||||
}
|
||||
elsif ( ref $input eq '' ) {
|
||||
$attrs->{labels} = [ split( /[.]/x, $input ) ];
|
||||
}
|
||||
elsif ( ref $input eq 'HASH' ) {
|
||||
confess "Attribute \(labels\) is required"
|
||||
if !exists $input->{labels};
|
||||
|
||||
confess "Argument must be an ARRAYREF: labels"
|
||||
if exists $input->{labels}
|
||||
&& ref $input->{labels} ne 'ARRAY';
|
||||
|
||||
$attrs->{labels} = $input->{labels};
|
||||
}
|
||||
else {
|
||||
my $what =
|
||||
( blessed $input )
|
||||
? "blessed(" . blessed $input . ")"
|
||||
: "ref(" . ref $input . ")";
|
||||
confess "Unrecognized argument: " . $what;
|
||||
}
|
||||
|
||||
return Class::Accessor::new( $class, $attrs );
|
||||
}
|
||||
|
||||
sub string {
|
||||
my $self = shift;
|
||||
|
||||
if ( not exists $self->{_string} ) {
|
||||
my $string = join( '.', @{ $self->labels } );
|
||||
$string = '.' if $string eq q{};
|
||||
|
||||
$self->{_string} = $string;
|
||||
}
|
||||
|
||||
return $self->{_string};
|
||||
}
|
||||
|
||||
sub fqdn {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return join( '.', @{ $self->labels } ) . '.';
|
||||
}
|
||||
|
||||
sub str_cmp {
|
||||
# For performance reasons, we do not unpack @_.
|
||||
# As a reminder, the calling convention is my ( $self, $other, $swap ) = @_.
|
||||
|
||||
my $me = uc ( $_[0]->{_string} // $_[0]->string );
|
||||
|
||||
# Treat undefined value as root
|
||||
my $other = $_[1] // q{};
|
||||
|
||||
if ( blessed $other and $other->isa( 'Zonemaster::Engine::DNSName' ) ) {
|
||||
return $me cmp uc( $other->{_string} // $other->string() );
|
||||
}
|
||||
else {
|
||||
# Assume $other is a string; remove trailing dot except if only character
|
||||
return $me cmp uc( $other =~ s/.\K [.] \z//xr );
|
||||
}
|
||||
}
|
||||
|
||||
sub next_higher {
|
||||
my $self = shift;
|
||||
my @l = @{ $self->labels };
|
||||
if ( @l ) {
|
||||
shift @l;
|
||||
return Zonemaster::Engine::DNSName->new({ labels => \@l });
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub common {
|
||||
my ( $self, $other ) = @_;
|
||||
|
||||
my @me = reverse @{ $self->labels };
|
||||
my @them = reverse @{ $other->labels };
|
||||
|
||||
my $count = 0;
|
||||
while ( @me and @them ) {
|
||||
my $m = shift @me;
|
||||
my $t = shift @them;
|
||||
if ( uc( $m ) eq uc( $t ) ) {
|
||||
$count += 1;
|
||||
next;
|
||||
}
|
||||
else {
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
} ## end sub common
|
||||
|
||||
sub is_in_bailiwick {
|
||||
my ( $self, $other ) = @_;
|
||||
|
||||
return scalar( @{ $self->labels } ) == $self->common( $other );
|
||||
}
|
||||
|
||||
sub prepend {
|
||||
my ( $self, $label ) = @_;
|
||||
my @labels = ( $label, @{ $self->labels } );
|
||||
|
||||
return $self->new( { labels => \@labels } );
|
||||
}
|
||||
|
||||
sub TO_JSON {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return $self->string;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::DNSName - class representing DNS names
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $name1 = Zonemaster::Name->new('www.example.org');
|
||||
my $name2 = Zonemaster::Name->new('ns.example.org');
|
||||
say "Yay!" if $name1->common($name2) == 2;
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item labels
|
||||
|
||||
A reference to a list of strings, being the labels the DNS name is made up from.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item new($input) _or_ new({ labels => \@labellist })
|
||||
|
||||
The constructor can be called with either a single argument or with a reference
|
||||
to a hash as in the example above.
|
||||
|
||||
If there is a single argument, it must be either a non-reference, a
|
||||
L<Zonemaster::Engine::DNSName> object or a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
If it's a non-reference, it will be split at period characters (possibly after
|
||||
stringification) and the resulting list used as the name's labels.
|
||||
|
||||
If it's a L<Zonemaster::Engine::DNSName> object it will simply be returned.
|
||||
|
||||
If it's a L<Zonemaster::Engine::Zone> object, the value of its C<name> attribute will
|
||||
be returned.
|
||||
|
||||
=item from_string($domain)
|
||||
|
||||
A specialized constructor that must be called with a string.
|
||||
|
||||
=item string()
|
||||
|
||||
Returns a string representation of the name. The string representation is created by joining the labels with dots. If there are no labels, a
|
||||
single dot is returned. The names created this way do not have a trailing dot.
|
||||
|
||||
The stringification operator is overloaded to this function, so it should rarely be necessary to call it directly.
|
||||
|
||||
=item fqdn()
|
||||
|
||||
Returns the name as a string complete with a trailing dot.
|
||||
|
||||
=item str_cmp($other)
|
||||
|
||||
Overloads string comparison. Comparison is made after converting the names to upper case, and ignores any trailing dot on the other name.
|
||||
|
||||
=item next_higher()
|
||||
|
||||
Returns a new L<Zonemaster::Engine::DNSName> object, representing the name of the called one with the leftmost label removed.
|
||||
|
||||
=item common($other)
|
||||
|
||||
Returns the number of labels from the rightmost going left that are the same in both names. Used by the recursor to check for redirections going
|
||||
up the DNS tree.
|
||||
|
||||
=item is_in_bailiwick($other)
|
||||
|
||||
Returns true if $other is in-bailiwick of $self, and false otherwise.
|
||||
See also L<https://tools.ietf.org/html/rfc7719#section-6>.
|
||||
|
||||
=item prepend($label)
|
||||
|
||||
Returns a new L<Zonemaster::Engine::DNSName> object, representing the called one with the given label prepended.
|
||||
|
||||
=item TO_JSON
|
||||
|
||||
Helper method for JSON encoding.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
51
zonemaster-engine/lib/Zonemaster/Engine/Exception.pm
Normal file
51
zonemaster-engine/lib/Zonemaster/Engine/Exception.pm
Normal file
@@ -0,0 +1,51 @@
|
||||
package Zonemaster::Engine::Exception;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.3");
|
||||
|
||||
use Class::Accessor "antlers";
|
||||
|
||||
use overload '""' => \&string;
|
||||
|
||||
has 'message' => ( is => 'ro', isa => 'Str', required => 1 );
|
||||
|
||||
sub string {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return $self->message;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Exception -- base class for Zonemaster::Engine exceptions
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
die Zonemaster::Engine::Exception->new({ message => "This is an exception" });
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item message
|
||||
|
||||
A string attribute holding a message for possible human consumption.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item string()
|
||||
|
||||
Method that stringifies the object by returning the C<message> attribute.
|
||||
Stringification is overloaded to this.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
277
zonemaster-engine/lib/Zonemaster/Engine/Logger.pm
Normal file
277
zonemaster-engine/lib/Zonemaster/Engine/Logger.pm
Normal file
@@ -0,0 +1,277 @@
|
||||
package Zonemaster::Engine::Logger;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.8");
|
||||
|
||||
use Class::Accessor "antlers";
|
||||
|
||||
use Carp qw( confess );
|
||||
use Data::Dumper;
|
||||
use JSON::PP;
|
||||
use List::MoreUtils qw[none any];
|
||||
use Scalar::Util qw[blessed];
|
||||
use Zonemaster::Engine::Profile;
|
||||
use Zonemaster::Engine::Logger::Entry;
|
||||
|
||||
our $TEST_CASE_NAME = 'Unspecified';
|
||||
our $MODULE_NAME = 'System';
|
||||
|
||||
has 'entries' => (
|
||||
is => 'ro',
|
||||
isa => 'ArrayRef[Zonemaster::Engine::Logger::Entry]',
|
||||
);
|
||||
has 'callback' => (
|
||||
is => 'rw',
|
||||
isa => 'CodeRef',
|
||||
);
|
||||
|
||||
my $logfilter;
|
||||
|
||||
sub new {
|
||||
my $proto = shift;
|
||||
confess "must be called without arguments"
|
||||
if scalar( @_ ) != 0;
|
||||
|
||||
my $class = ref $proto || $proto;
|
||||
return Class::Accessor::new( $class, { entries => [] } );
|
||||
}
|
||||
|
||||
sub add {
|
||||
my ( $self, $tag, $argref, $module, $testcase ) = @_;
|
||||
|
||||
$module //= $MODULE_NAME;
|
||||
$testcase //= $TEST_CASE_NAME;
|
||||
|
||||
my $new =
|
||||
Zonemaster::Engine::Logger::Entry->new( { tag => uc( $tag ), args => $argref, testcase => $testcase, module => $module } );
|
||||
$self->_check_filter( $new );
|
||||
push @{ $self->entries }, $new;
|
||||
|
||||
if ( $self->callback and ref( $self->callback ) eq 'CODE' ) {
|
||||
eval { $self->callback->( $new ) };
|
||||
if ( $@ ) {
|
||||
my $err = $@;
|
||||
if ( blessed( $err ) and $err->isa( "Zonemaster::Engine::Exception" ) ) {
|
||||
die $err;
|
||||
}
|
||||
else {
|
||||
$self->callback( undef );
|
||||
$self->add( LOGGER_CALLBACK_ERROR => { exception => $err } );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $new;
|
||||
} ## end sub add
|
||||
|
||||
sub _check_filter {
|
||||
my ( $self, $entry ) = @_;
|
||||
|
||||
if ( ! defined $logfilter ) {
|
||||
$logfilter = Zonemaster::Engine::Profile->effective->get(q{logfilter});
|
||||
}
|
||||
|
||||
if ( $logfilter ) {
|
||||
if ( $logfilter->{ uc $entry->module } ) {
|
||||
my $match = 0;
|
||||
foreach my $rule ( @{$logfilter->{ uc $entry->module }{ $entry->tag }} ) {
|
||||
foreach my $key ( keys %{ $rule->{when} } ) {
|
||||
my $cond = $rule->{when}{$key};
|
||||
if ( ref( $cond ) and ref( $cond ) eq 'ARRAY' ) {
|
||||
if ( any { $_ eq $entry->args->{$key} } @$cond ) {
|
||||
$match = 1;
|
||||
} else {
|
||||
$match = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( $cond eq $entry->args->{$key} ) {
|
||||
$match = 1;
|
||||
} else {
|
||||
$match = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $match ) {
|
||||
$entry->_set_level( $rule->{set} );
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
} ## end sub _check_filter
|
||||
|
||||
sub start_time_now {
|
||||
Zonemaster::Engine::Logger::Entry->start_time_now();
|
||||
return;
|
||||
}
|
||||
|
||||
sub reset_config {
|
||||
$logfilter = undef;
|
||||
Zonemaster::Engine::Logger::Entry->reset_config();
|
||||
return;
|
||||
}
|
||||
|
||||
sub clear_history {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my $r = $self->entries;
|
||||
splice @$r, 0, scalar( @$r );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# get the max level from a log, return as a string
|
||||
sub get_max_level {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my %levels = reverse Zonemaster::Engine::Logger::Entry->levels();
|
||||
my $level = 0;
|
||||
|
||||
foreach ( @{ $self->entries } ) {
|
||||
$level = $_->numeric_level if $_->numeric_level > $level;
|
||||
}
|
||||
|
||||
return $levels{$level};
|
||||
}
|
||||
|
||||
sub json {
|
||||
my ( $self, $min_level ) = @_;
|
||||
my $json = JSON::PP->new->allow_blessed->convert_blessed->canonical;
|
||||
my %numeric = Zonemaster::Engine::Logger::Entry->levels();
|
||||
|
||||
my @msg = @{ $self->entries };
|
||||
|
||||
if ( $min_level and defined $numeric{ uc( $min_level ) } ) {
|
||||
@msg = grep { $_->numeric_level >= $numeric{ uc( $min_level ) } } @msg;
|
||||
}
|
||||
|
||||
my @out;
|
||||
foreach my $m ( @msg ) {
|
||||
my %r;
|
||||
$r{timestamp} = $m->timestamp;
|
||||
$r{module} = $m->module;
|
||||
$r{testcase} = $m->testcase;
|
||||
$r{tag} = $m->tag;
|
||||
$r{level} = $m->level;
|
||||
$r{args} = $m->args if $m->args;
|
||||
|
||||
push @out, \%r;
|
||||
}
|
||||
|
||||
return $json->encode( \@out );
|
||||
} ## end sub json
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Logger - class that holds L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $logger = Zonemaster::Engine::Logger->new;
|
||||
$logger->add( TAG => {some => 'arguments'});
|
||||
|
||||
=head1 CONSTRUCTORS
|
||||
|
||||
=over
|
||||
|
||||
=item new
|
||||
|
||||
Construct a new object.
|
||||
|
||||
my $logger = Zonemaster::Engine::Logger->new;
|
||||
|
||||
=back
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item entries
|
||||
|
||||
A reference to an array holding L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=item callback($coderef)
|
||||
|
||||
If this attribute is set, the given code reference will be called every time a
|
||||
log entry is added. The referenced code will be called with the newly created
|
||||
entry as its single argument. The return value of the called code is ignored.
|
||||
|
||||
If the called code throws an exception, and the exception is not an object of
|
||||
class L<Zonemaster::Engine::Exception> (or a subclass of it), the exception will be
|
||||
logged as a system message at default level C<CRITICAL> and the callback
|
||||
attribute will be cleared.
|
||||
|
||||
If an exception that is of (sub)class L<Zonemaster::Engine::Exception> is called, the
|
||||
exception will simply be rethrown until it reaches the code that started the
|
||||
test run that logged the message.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item add($tag, $argref, $module, $testcase)
|
||||
|
||||
Adds an entry with the given tag and arguments to the logger object.
|
||||
|
||||
C<$module> is optional and will default to
|
||||
C<$Zonemaster::Engine::Logger::MODULE_NAME> if not set.
|
||||
|
||||
C<$testcase> is optional and will default to
|
||||
C<$Zonemaster::Engine::Logger::TEST_CASE_NAME> if not set.
|
||||
|
||||
The variables C<$Zonemaster::Engine::Logger::MODULE_NAME> and
|
||||
C<$Zonemaster::Engine::Logger::TEST_CASE_NAME> can be dynamically set to
|
||||
change the default module ("System") or test case name ("Unspecified").
|
||||
|
||||
=item json([$level])
|
||||
|
||||
Returns a JSON-formatted string with all the stored log entries. If an argument
|
||||
is given and is a known severity level, only messages with at least that level
|
||||
will be included.
|
||||
|
||||
=item get_max_level
|
||||
|
||||
Returns the maximum log level from the entire log as the level string.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item start_time_now()
|
||||
|
||||
Set the logger's start time to the current time.
|
||||
|
||||
=item clear_history()
|
||||
|
||||
Remove all known log entries.
|
||||
|
||||
=item reset_config()
|
||||
|
||||
Clear the test level cached configuration.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item _check_filter($entry)
|
||||
|
||||
Apply the C<logfilter> defined rules to the entry. See
|
||||
L<Zonemaster::Engine::Profile/"logfilter">.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
263
zonemaster-engine/lib/Zonemaster/Engine/Logger/Entry.pm
Normal file
263
zonemaster-engine/lib/Zonemaster/Engine/Logger/Entry.pm
Normal file
@@ -0,0 +1,263 @@
|
||||
package Zonemaster::Engine::Logger::Entry;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.1.8");
|
||||
|
||||
use Carp qw( confess );
|
||||
use Time::HiRes qw[time];
|
||||
use JSON::PP;
|
||||
use Class::Accessor;
|
||||
|
||||
use Zonemaster::Engine::Profile;
|
||||
|
||||
use base qw(Class::Accessor);
|
||||
|
||||
use overload '""' => \&string;
|
||||
|
||||
our %numeric = (
|
||||
DEBUG3 => -2,
|
||||
DEBUG2 => -1,
|
||||
DEBUG => 0,
|
||||
INFO => 1,
|
||||
NOTICE => 2,
|
||||
WARNING => 3,
|
||||
ERROR => 4,
|
||||
CRITICAL => 5,
|
||||
);
|
||||
|
||||
our $start_time = time();
|
||||
|
||||
my $json = JSON::PP->new->allow_blessed->convert_blessed->canonical;
|
||||
my $test_levels_config;
|
||||
|
||||
__PACKAGE__->mk_ro_accessors(qw(tag args timestamp testcase module));
|
||||
|
||||
|
||||
sub new {
|
||||
my ( $proto, $attrs ) = @_;
|
||||
# tag, testcase and module required, args optional, other built
|
||||
|
||||
confess "Attribute \(tag\) is required"
|
||||
if !exists $attrs->{tag};
|
||||
|
||||
confess "Attribute \(testcase\) is required"
|
||||
if !exists $attrs->{testcase};
|
||||
|
||||
confess "Attribute \(module\) is required"
|
||||
if !exists $attrs->{module};
|
||||
|
||||
confess "Argument must be a HASHREF: args"
|
||||
if exists $attrs->{args}
|
||||
&& ref $attrs->{args} ne 'HASH';
|
||||
|
||||
my $time = time() - $start_time;
|
||||
$time =~ s/,/\./;
|
||||
$attrs->{timestamp} = $time;
|
||||
|
||||
# lazy attributes
|
||||
$attrs->{_level} = delete $attrs->{level} if exists $attrs->{level};
|
||||
|
||||
my $class = ref $proto || $proto;
|
||||
return Class::Accessor::new( $class, $attrs );
|
||||
}
|
||||
|
||||
sub level {
|
||||
my $self = shift;
|
||||
|
||||
# Lazy default value
|
||||
if ( !exists $self->{_level} ) {
|
||||
$self->{_level} = $self->_build_level();
|
||||
}
|
||||
|
||||
return $self->{_level}
|
||||
}
|
||||
|
||||
sub _build_level {
|
||||
my ( $self ) = @_;
|
||||
my $string;
|
||||
|
||||
if ( !defined $test_levels_config ) {
|
||||
$test_levels_config = Zonemaster::Engine::Profile->effective->get( q{test_levels} );
|
||||
}
|
||||
|
||||
if ( exists $test_levels_config->{ uc $self->module }{ $self->tag } ) {
|
||||
$string = uc $test_levels_config->{ uc $self->module }{ $self->tag };
|
||||
}
|
||||
else {
|
||||
$string = 'DEBUG';
|
||||
}
|
||||
|
||||
if ( defined $numeric{$string} ) {
|
||||
return $string;
|
||||
}
|
||||
else {
|
||||
die "Unknown level string: $string";
|
||||
}
|
||||
}
|
||||
|
||||
sub _set_level {
|
||||
my ( $self, $level ) = @_;
|
||||
|
||||
$self->{_level} = $level
|
||||
}
|
||||
|
||||
|
||||
sub numeric_level {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return $numeric{ $self->level };
|
||||
}
|
||||
|
||||
sub levels {
|
||||
return %numeric;
|
||||
}
|
||||
|
||||
sub string {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return sprintf( '%s%s:%s %s', $self->module, $self->testcase ? q{:} . $self->testcase : q{}, $self->tag, $self->argstr );
|
||||
}
|
||||
|
||||
sub argstr {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my $argstr = q{};
|
||||
## no critic (TestingAndDebugging::ProhibitNoWarnings)
|
||||
no warnings 'uninitialized';
|
||||
|
||||
if ( $self->args ) {
|
||||
my $p_args = $self->printable_args;
|
||||
$argstr = join( q{; },
|
||||
map { $_ . q{=} . ( ref( $p_args->{$_} ) ? $json->encode( $p_args->{$_} ) : $p_args->{$_} ) }
|
||||
sort keys %{$p_args} );
|
||||
}
|
||||
|
||||
return $argstr;
|
||||
}
|
||||
|
||||
sub printable_args {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( $self->args ) {
|
||||
my %p_args;
|
||||
foreach my $key_arg ( keys %{ $self->args } ) {
|
||||
if ( not ref( $self->args->{$key_arg} ) ) {
|
||||
$p_args{$key_arg} = $self->args->{$key_arg};
|
||||
}
|
||||
elsif ( $key_arg eq q{asn} and ref( $self->args->{$key_arg} ) eq q{ARRAY} ) {
|
||||
$p_args{q{asn}} = join( q{,}, @{ $self->args->{$key_arg} } );
|
||||
}
|
||||
else {
|
||||
$p_args{$key_arg} = $self->args->{$key_arg};
|
||||
}
|
||||
}
|
||||
return \%p_args;
|
||||
}
|
||||
|
||||
return;
|
||||
} ## end sub printable_args
|
||||
|
||||
###
|
||||
### Class method
|
||||
###
|
||||
|
||||
sub start_time_now {
|
||||
$start_time = time();
|
||||
return;
|
||||
}
|
||||
|
||||
sub reset_config {
|
||||
undef $test_levels_config;
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Logger::Entry - module for single log entries
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Zonemaster::Engine->logger->add( TAG => { some => 'arguments' });
|
||||
|
||||
There should never be a need to create a log entry object in isolation. They should always be associated with and created via a logger object.
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item new
|
||||
|
||||
Construct a new object.
|
||||
|
||||
=item levels
|
||||
|
||||
Returns a hash where the keys are log levels as strings and the corresponding values their numeric value.
|
||||
|
||||
=item start_time_now()
|
||||
|
||||
Set the logger's start time to the current time.
|
||||
|
||||
=item reset_config()
|
||||
|
||||
Clear the test level cached configuration.
|
||||
|
||||
=back
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item module
|
||||
|
||||
The name of the module associated to the entry, or "System".
|
||||
|
||||
=item testcase
|
||||
|
||||
The name of the test case which generated the entry, or "Unspecified".
|
||||
|
||||
=item tag
|
||||
|
||||
The tag that was set when the entry was created.
|
||||
|
||||
=item args
|
||||
|
||||
The argument hash reference that was provided when the entry was created.
|
||||
|
||||
=item timestamp
|
||||
|
||||
The time after the current program started running when this entry was created. This is a floating-point value with the precision provided by
|
||||
L<Time::HiRes>.
|
||||
|
||||
=item level
|
||||
|
||||
The log level associated to this log entry.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item string
|
||||
|
||||
Simple method to generate a string representation of the log entry. Overloaded to the stringification operator.
|
||||
|
||||
=item argstr
|
||||
|
||||
Returns the string representation of the message arguments.
|
||||
|
||||
=item printable_args
|
||||
|
||||
Used to transform data from an internal/JSON representation to a "user friendly" representation one.
|
||||
|
||||
=item numeric_level
|
||||
|
||||
Returns the log level of the entry in numeric form.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
196
zonemaster-engine/lib/Zonemaster/Engine/NSArray.pm
Normal file
196
zonemaster-engine/lib/Zonemaster/Engine/NSArray.pm
Normal file
@@ -0,0 +1,196 @@
|
||||
package Zonemaster::Engine::NSArray;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.3");
|
||||
|
||||
use Carp qw( confess croak );
|
||||
use Zonemaster::Engine::Recursor;
|
||||
use Zonemaster::Engine::Nameserver;
|
||||
|
||||
use Class::Accessor 'antlers';
|
||||
|
||||
has 'names' => ( is => 'ro', isa => 'ArrayRef', required => 1 );
|
||||
has 'ary' => ( is => 'ro', isa => 'ArrayRef', default => sub { [] } );
|
||||
|
||||
sub TIEARRAY {
|
||||
my ( $class, @names ) = @_;
|
||||
|
||||
return $class->new(
|
||||
{
|
||||
ary => [],
|
||||
names => [ sort { $a cmp $b } @names ]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub STORE {
|
||||
my ( $self, $index, $value ) = @_;
|
||||
|
||||
croak "STORE forbidden for this type of array.";
|
||||
}
|
||||
|
||||
sub STORESIZE {
|
||||
my ( $self, $index, $value ) = @_;
|
||||
|
||||
croak "STORESIZE forbidden for this type of array.";
|
||||
}
|
||||
|
||||
sub FETCH {
|
||||
my ( $self, $index ) = @_;
|
||||
|
||||
if ( exists $self->ary->[$index] ) {
|
||||
return $self->ary->[$index];
|
||||
}
|
||||
elsif ( scalar( @{ $self->names } ) == 0 ) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$self->_load_name( shift @{ $self->names } );
|
||||
return $self->FETCH( $index );
|
||||
}
|
||||
}
|
||||
|
||||
sub FETCHSIZE {
|
||||
my ( $self ) = @_;
|
||||
|
||||
while ( my $name = shift @{ $self->names } ) {
|
||||
$self->_load_name( $name );
|
||||
}
|
||||
|
||||
return scalar( @{ $self->ary } );
|
||||
}
|
||||
|
||||
sub EXISTS {
|
||||
my ( $self, $index ) = @_;
|
||||
|
||||
if ( $self->FETCH( $index ) ) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub DELETE {
|
||||
my ( $self, $index ) = @_;
|
||||
|
||||
croak "DELETE forbidden for this type of array.";
|
||||
}
|
||||
|
||||
sub CLEAR {
|
||||
my ( $self ) = @_;
|
||||
|
||||
croak "CLEAR forbidden for this type of array.";
|
||||
}
|
||||
|
||||
sub PUSH {
|
||||
my ( $self, @values ) = @_;
|
||||
|
||||
croak "PUSH forbidden for this type of array.";
|
||||
}
|
||||
|
||||
sub UNSHIFT {
|
||||
my ( $self, @values ) = @_;
|
||||
|
||||
croak "UNSHIFT forbidden for this type of array.";
|
||||
}
|
||||
|
||||
sub POP {
|
||||
my ( $self ) = @_;
|
||||
|
||||
croak "POP forbidden for this type of array.";
|
||||
}
|
||||
|
||||
sub SHIFT {
|
||||
my ( $self ) = @_;
|
||||
|
||||
croak "SHIFT forbidden for this type of array.";
|
||||
}
|
||||
|
||||
sub SPLICE {
|
||||
my ( $self, $offset, $length, @values ) = @_;
|
||||
|
||||
croak "SPLICE forbidden for this type of array.";
|
||||
}
|
||||
|
||||
sub UNTIE {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub _load_name {
|
||||
my ( $self, $name ) = @_;
|
||||
my @addrs = Zonemaster::Engine::Recursor->get_addresses_for( $name );
|
||||
foreach my $addr ( sort { $a->ip cmp $b->ip } @addrs ) {
|
||||
my $ns = Zonemaster::Engine::Nameserver->new( { name => $name, address => $addr } );
|
||||
if ( not grep { "$ns" eq "$_" } @{ $self->ary } ) {
|
||||
push @{ $self->ary }, $ns;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::NSArray - Class implementing arrays that lazily looks up name server addresses from their names
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
tie @ary, 'Zonemaster::Engine::NSArray', @ns_names
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This class is used for the C<glue> and C<ns> attributes of the
|
||||
L<Zonemaster::Engine::Zone> class. It is initially seeded with a list of
|
||||
names, which will be expanded into proper L<Zonemaster::Engine::Nameserver>
|
||||
objects on demand. Be careful with using Perl functions that act on
|
||||
whole arrays (particularly C<foreach>), since they will usually force
|
||||
the entire array to expand, negating the use of the lazy-loading.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
These are all methods implementing the Perl tie interface. They have no independent use.
|
||||
|
||||
=over
|
||||
|
||||
=item TIEARRAY
|
||||
|
||||
=item STORE
|
||||
|
||||
=item STORESIZE
|
||||
|
||||
=item FETCH
|
||||
|
||||
=item FETCHSIZE
|
||||
|
||||
=item EXISTS
|
||||
|
||||
=item DELETE
|
||||
|
||||
=item CLEAR
|
||||
|
||||
=item PUSH
|
||||
|
||||
=item UNSHIFT
|
||||
|
||||
=item POP
|
||||
|
||||
=item SHIFT
|
||||
|
||||
=item SPLICE
|
||||
|
||||
=item UNTIE
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Calle Dybedahl, C<< <calle at init.se> >>
|
||||
|
||||
=cut
|
||||
919
zonemaster-engine/lib/Zonemaster/Engine/Nameserver.pm
Normal file
919
zonemaster-engine/lib/Zonemaster/Engine/Nameserver.pm
Normal file
@@ -0,0 +1,919 @@
|
||||
package Zonemaster::Engine::Nameserver;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.1.16");
|
||||
|
||||
use Class::Accessor qw[ antlers ];
|
||||
|
||||
use Zonemaster::Engine::DNSName;
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Engine::Packet;
|
||||
use Zonemaster::Engine::Nameserver::Cache;
|
||||
use Zonemaster::Engine::Recursor;
|
||||
use Zonemaster::Engine::Constants qw( :ip :misc );
|
||||
use Zonemaster::LDNS;
|
||||
|
||||
use Net::IP::XS;
|
||||
use Time::HiRes qw[time];
|
||||
use JSON::PP;
|
||||
use MIME::Base64;
|
||||
use Module::Find qw[useall];
|
||||
use Carp qw( confess croak );
|
||||
use List::Util qw[max min sum];
|
||||
use Digest::MD5;
|
||||
use POSIX ();
|
||||
use Scalar::Util qw[ blessed ];
|
||||
|
||||
our @ISA = qw (Class::Accessor);
|
||||
|
||||
use overload
|
||||
'""' => \&string,
|
||||
'cmp' => \&compare;
|
||||
|
||||
has 'name' => ( is => 'ro' );
|
||||
has 'address' => ( is => 'ro' );
|
||||
|
||||
has 'dns' => ( is => 'ro' );
|
||||
has 'cache' => ( is => 'ro' );
|
||||
has 'times' => ( is => 'ro' );
|
||||
|
||||
has 'fake_delegations' => ( is => 'ro' );
|
||||
has 'fake_ds' => ( is => 'ro' );
|
||||
|
||||
has 'blacklisted' => ( is => 'rw' );
|
||||
|
||||
###
|
||||
### Variables
|
||||
###
|
||||
|
||||
our %object_cache;
|
||||
our %address_object_cache;
|
||||
our %address_repr_cache;
|
||||
|
||||
###
|
||||
### Build methods for attributes
|
||||
###
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $attrs = shift;
|
||||
|
||||
my %lazy_attrs;
|
||||
$lazy_attrs{dns} = delete $attrs->{dns} if exists $attrs->{dns};
|
||||
$lazy_attrs{cache} = delete $attrs->{cache} if exists $attrs->{cache};
|
||||
|
||||
# Required arguments
|
||||
confess "Attribute \(address\) is required"
|
||||
if !defined $attrs->{address};
|
||||
|
||||
# Type coercions
|
||||
$attrs->{name} = Zonemaster::Engine::DNSName->from_string( $attrs->{name} )
|
||||
if !blessed $attrs->{name} || !$attrs->{name}->isa( 'Zonemaster::Engine::DNSName' );
|
||||
|
||||
my $name = lc( q{} . $attrs->{name} );
|
||||
$name = '$$$NONAME' if $name eq q{};
|
||||
|
||||
my $address;
|
||||
|
||||
# Use a object cache for IP type coercion (don't parse IP unless it is needed)
|
||||
if (!blessed $attrs->{address} || !$attrs->{address}->isa( 'Net::IP::XS' )) {
|
||||
if (!exists $address_object_cache{$attrs->{address}}) {
|
||||
$address_object_cache{$attrs->{address}} = Net::IP::XS->new($attrs->{address});
|
||||
$address_repr_cache{$attrs->{address}} = $address_object_cache{$attrs->{address}}->short;
|
||||
}
|
||||
# Fetch IP object from the address cache (avoid object creation and method call)
|
||||
$address = $address_repr_cache{$attrs->{address}};
|
||||
$attrs->{address} = $address_object_cache{$attrs->{address}};
|
||||
} else {
|
||||
$address = $attrs->{address}->short;
|
||||
}
|
||||
|
||||
# Return Nameserver object as soon as possible
|
||||
if ( exists $object_cache{$name}{$address} ) {
|
||||
return $object_cache{$name}{$address};
|
||||
}
|
||||
|
||||
# Type constraints
|
||||
confess "Argument must be coercible into a Zonemaster::Engine::DNSName: name"
|
||||
if !$attrs->{name}->isa( 'Zonemaster::Engine::DNSName' );
|
||||
confess "Argument must be coercible into a Net::IP::XS: address"
|
||||
if exists $attrs->{address}
|
||||
&& !$attrs->{address}->isa( 'Net::IP::XS' );
|
||||
confess "Argument must be an ARRAYREF: times"
|
||||
if exists $attrs->{times}
|
||||
&& ref $attrs->{times} ne 'ARRAY';
|
||||
confess "Argument must be a HASHREF: fake_delegations"
|
||||
if exists $attrs->{fake_delegations}
|
||||
&& ref $attrs->{fake_delegations} ne 'HASH';
|
||||
confess "Argument must be a HASHREF: fake_ds"
|
||||
if exists $attrs->{fake_ds}
|
||||
&& ref $attrs->{fake_ds} ne 'HASH';
|
||||
confess "Argument must be a HASHREF: blacklisted"
|
||||
if exists $attrs->{blacklisted}
|
||||
&& ref $attrs->{blacklisted} ne 'HASH';
|
||||
confess "Argument must be a Zonemaster::LDNS: dns"
|
||||
if exists $lazy_attrs{dns}
|
||||
&& ( !blessed $lazy_attrs{dns} || !$lazy_attrs{dns}->isa( 'Zonemaster::LDNS' ) );
|
||||
confess "Argument must be a Zonemaster::Engine::Nameserver::Cache: cache"
|
||||
if exists $lazy_attrs{cache}
|
||||
&& ( !blessed $lazy_attrs{cache} || !$lazy_attrs{cache}->isa( 'Zonemaster::Engine::Nameserver::Cache' ) );
|
||||
|
||||
# Default values
|
||||
$attrs->{blacklisted} //= {};
|
||||
$attrs->{fake_delegations} //= {};
|
||||
$attrs->{fake_ds} //= {};
|
||||
$attrs->{times} //= [];
|
||||
|
||||
my $obj = Class::Accessor::new( $class, $attrs );
|
||||
$obj->{_dns} = $lazy_attrs{dns} if exists $lazy_attrs{dns};
|
||||
$obj->{_cache} = $lazy_attrs{cache} if exists $lazy_attrs{cache};
|
||||
|
||||
$obj->{_string} = $name . q{/} . $address;
|
||||
|
||||
Zonemaster::Engine->logger->add( NS_CREATED => { name => $name, ip => $address } );
|
||||
$object_cache{$name}{$address} = $obj;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub dns {
|
||||
my $self = shift;
|
||||
|
||||
# Lazy default value
|
||||
if ( !exists $self->{_dns} ) {
|
||||
$self->{_dns} = $self->_build_dns();
|
||||
}
|
||||
|
||||
return $self->{_dns};
|
||||
}
|
||||
|
||||
sub cache {
|
||||
my $self = shift;
|
||||
|
||||
# Lazy default value
|
||||
if ( !exists $self->{_cache} ) {
|
||||
$self->{_cache} = $self->_build_cache();
|
||||
}
|
||||
|
||||
return $self->{_cache};
|
||||
}
|
||||
|
||||
sub _build_dns {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my $res = Zonemaster::LDNS->new( $self->address->ip );
|
||||
|
||||
$res->recurse( 0 );
|
||||
$res->dnssec( 0 );
|
||||
$res->edns_size( 0 );
|
||||
|
||||
$res->retry( Zonemaster::Engine::Profile->effective->get( q{resolver.defaults.retry} ) );
|
||||
$res->retrans( Zonemaster::Engine::Profile->effective->get( q{resolver.defaults.retrans} ) );
|
||||
$res->debug( Zonemaster::Engine::Profile->effective->get( q{resolver.defaults.debug} ) );
|
||||
$res->timeout( Zonemaster::Engine::Profile->effective->get( q{resolver.defaults.timeout} ) );
|
||||
|
||||
my $src_address = $self->source_address();
|
||||
if ( defined( $src_address ) ) {
|
||||
$res->source( $src_address );
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub _build_cache {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my $cache_type = Zonemaster::Engine::Nameserver::Cache->get_cache_type( Zonemaster::Engine::Profile->effective );
|
||||
my $cache_class = Zonemaster::Engine::Nameserver::Cache->get_cache_class( $cache_type );
|
||||
|
||||
$cache_class->new( { address => $self->address } );
|
||||
}
|
||||
|
||||
###
|
||||
### Public Methods (and helpers)
|
||||
###
|
||||
|
||||
sub query {
|
||||
my ( $self, $name, $type, $href ) = @_;
|
||||
$type //= 'A';
|
||||
|
||||
my $address = $self->address;
|
||||
my $profile = Zonemaster::Engine::Profile->effective;
|
||||
|
||||
if ( $address->version == 4 and not $profile->get( q{net.ipv4} ) ) {
|
||||
Zonemaster::Engine->logger->add( IPV4_BLOCKED => { ns => $self->string } );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $address->version == 6 and not $profile->get( q{net.ipv6} ) ) {
|
||||
Zonemaster::Engine->logger->add( IPV6_BLOCKED => { ns => $self->string } );
|
||||
return;
|
||||
}
|
||||
|
||||
Zonemaster::Engine->logger->add(
|
||||
'QUERY',
|
||||
{
|
||||
name => "$name",
|
||||
type => $type,
|
||||
flags => $href,
|
||||
ip => $address->short
|
||||
}
|
||||
);
|
||||
|
||||
my $class = $href->{class} // 'IN';
|
||||
my $dnssec = $href->{dnssec} // 0;
|
||||
my $usevc = $href->{usevc} // 0;
|
||||
my $recurse = $href->{recurse} // 0;
|
||||
|
||||
if ( exists $href->{edns_details} and exists $href->{edns_details}{do} ) {
|
||||
$dnssec = $href->{edns_details}{do};
|
||||
}
|
||||
|
||||
my $edns_size = $href->{edns_size} // ( $dnssec ? $EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT : 0 );
|
||||
|
||||
# Fake a DS answer
|
||||
if ( $type eq 'DS' and $class eq 'IN' and $self->fake_ds->{ lc( $name ) } ) {
|
||||
my $p = Zonemaster::LDNS::Packet->new( $name, $type, $class );
|
||||
|
||||
$p->qr( 1 );
|
||||
$p->aa( 1 );
|
||||
$p->do( $dnssec );
|
||||
$p->rd( $recurse );
|
||||
|
||||
foreach my $rr ( @{ $self->fake_ds->{ lc( $name ) } } ) {
|
||||
$p->unique_push( 'answer', $rr );
|
||||
}
|
||||
|
||||
my $res = Zonemaster::Engine::Packet->new( { packet => $p } );
|
||||
Zonemaster::Engine->logger->add( FAKE_DS_RETURNED => { name => "$name", type => $type, class => $class, from => "$self" } );
|
||||
Zonemaster::Engine->logger->add( FAKE_PACKET_RETURNED => { packet => $res->string } );
|
||||
return $res;
|
||||
}
|
||||
|
||||
# Fake a delegation
|
||||
foreach my $fname ( sort keys %{ $self->fake_delegations } ) {
|
||||
if ( $name =~ m/([.]|\A)\Q$fname\E\z/xi ) {
|
||||
my $p = Zonemaster::LDNS::Packet->new( $name, $type, $class );
|
||||
|
||||
if ( lc( $name ) eq lc( $fname ) and $type eq 'NS' ) {
|
||||
my $name = $self->fake_delegations->{$fname}{authority};
|
||||
my $addr = $self->fake_delegations->{$fname}{additional};
|
||||
$p->unique_push( 'answer', $_ ) for @{$name};
|
||||
$p->unique_push( 'additional', $_ ) for @{$addr};
|
||||
}
|
||||
elsif ( $type eq 'DS' ) {
|
||||
$p->aa( 1 );
|
||||
}
|
||||
else {
|
||||
while ( my ( $section, $aref ) = each %{ $self->fake_delegations->{$fname} } ) {
|
||||
$p->unique_push( $section, $_ ) for @{$aref};
|
||||
}
|
||||
}
|
||||
|
||||
$p->aa( 0 ) unless ( $type eq 'DS' );
|
||||
$p->qr( 1 );
|
||||
$p->do( $dnssec );
|
||||
$p->rd( $recurse );
|
||||
$p->answerfrom( $address->ip );
|
||||
|
||||
Zonemaster::Engine->logger->add( FAKE_DELEGATION_RETURNED => { name => "$name", type => $type, class => $class, from => "$self" } );
|
||||
|
||||
my $res = Zonemaster::Engine::Packet->new( { packet => $p } );
|
||||
Zonemaster::Engine->logger->add( FAKE_PACKET_RETURNED => { packet => $res->string } );
|
||||
return $res;
|
||||
} ## end if ( $name =~ m/([.]|\A)\Q$fname\E\z/xi)
|
||||
} ## end foreach my $fname ( sort keys...)
|
||||
|
||||
my $md5 = Digest::MD5->new;
|
||||
|
||||
$md5->add( q{NAME} , $name,
|
||||
q{TYPE} , "\U$type",
|
||||
q{CLASS} , "\U$class",
|
||||
q{DNSSEC} , $dnssec,
|
||||
q{USEVC} , $usevc,
|
||||
q{RECURSE} , $recurse );
|
||||
|
||||
if ( exists $href->{edns_details} ) {
|
||||
$md5->add( q{EDNS_VERSION} , $href->{edns_details}{version} // 0,
|
||||
q{EDNS_Z} , $href->{edns_details}{z} // 0,
|
||||
q{EDNS_EXTENDED_RCODE} , $href->{edns_details}{rcode} // 0,
|
||||
q{EDNS_DATA} , $href->{edns_details}{data} // q{} );
|
||||
$edns_size = $href->{edns_details}{size} // ( $href->{edns_size} // ( $dnssec ? $EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT : $EDNS_UDP_PAYLOAD_DEFAULT ) );
|
||||
}
|
||||
|
||||
croak "edns_size (or edns_details->size) parameter must be a value between 0 and 65535" if $edns_size > 65535 or $edns_size < 0;
|
||||
|
||||
$md5->add( q{EDNS_UDP_SIZE} , $edns_size );
|
||||
|
||||
my $idx = $md5->b64digest();
|
||||
|
||||
my ( $in_cache, $p ) = $self->cache->get_key( $idx );
|
||||
if ( not $in_cache ) {
|
||||
$p = $self->_query( $name, $type, $href );
|
||||
$self->cache->set_key( $idx, $p );
|
||||
}
|
||||
|
||||
Zonemaster::Engine->logger->add( CACHED_RETURN => { packet => ( $p ? $p->string : 'undef' ) } );
|
||||
|
||||
return $p;
|
||||
} ## end sub query
|
||||
|
||||
sub add_fake_delegation {
|
||||
my ( $self, $domain, $href ) = @_;
|
||||
my %delegation;
|
||||
|
||||
$domain = q{} . Zonemaster::Engine::DNSName->new( $domain );
|
||||
foreach my $name ( keys %{$href} ) {
|
||||
push @{ $delegation{authority} }, Zonemaster::LDNS::RR->new( sprintf( '%s IN NS %s', $domain, $name ) );
|
||||
foreach my $ip ( @{ $href->{$name} } ) {
|
||||
if ( Net::IP::XS->new( $ip )->ip eq $self->address->ip ) {
|
||||
Zonemaster::Engine->logger->add( FAKE_DELEGATION_TO_SELF => { ns => "$self", domain => $domain, data => $href } );
|
||||
return;
|
||||
}
|
||||
|
||||
push @{ $delegation{additional} },
|
||||
Zonemaster::LDNS::RR->new( sprintf( '%s IN %s %s', $name, ( Net::IP::XS::ip_is_ipv6( $ip ) ? 'AAAA' : 'A' ), $ip ) );
|
||||
}
|
||||
}
|
||||
|
||||
$self->fake_delegations->{$domain} = \%delegation;
|
||||
Zonemaster::Engine->logger->add( FAKE_DELEGATION_ADDED => { ns => "$self", domain => $domain, data => $href } );
|
||||
|
||||
# We're changing the world, so the cache can't be trusted
|
||||
Zonemaster::Engine::Recursor->clear_cache;
|
||||
|
||||
return;
|
||||
} ## end sub add_fake_delegation
|
||||
|
||||
sub add_fake_ds {
|
||||
my ( $self, $domain, $aref ) = @_;
|
||||
my @ds;
|
||||
|
||||
if ( not ref $domain ) {
|
||||
$domain = Zonemaster::Engine::DNSName->new( $domain );
|
||||
}
|
||||
|
||||
foreach my $href ( @{$aref} ) {
|
||||
push @ds,
|
||||
Zonemaster::LDNS::RR->new(
|
||||
sprintf(
|
||||
'%s IN DS %d %d %d %s',
|
||||
"$domain", $href->{keytag}, $href->{algorithm}, $href->{type}, $href->{digest}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$self->fake_ds->{ lc( "$domain" ) } = \@ds;
|
||||
Zonemaster::Engine->logger->add( FAKE_DS_ADDED => { domain => lc( "$domain" ), data => $aref, ns => "$self" } );
|
||||
|
||||
# We're changing the world, so the cache can't be trusted
|
||||
Zonemaster::Engine::Recursor->clear_cache;
|
||||
|
||||
return;
|
||||
} ## end sub add_fake_ds
|
||||
|
||||
sub _query {
|
||||
my ( $self, $name, $type, $href ) = @_;
|
||||
my %flags;
|
||||
|
||||
$type //= 'A';
|
||||
$href->{class} //= 'IN';
|
||||
|
||||
if ( Zonemaster::Engine::Profile->effective->get( q{no_network} ) ) {
|
||||
croak sprintf
|
||||
"External query for %s, %s attempted to %s while running with no_network",
|
||||
$name, $type, $self->string;
|
||||
}
|
||||
|
||||
Zonemaster::Engine->logger->add(
|
||||
'external_query',
|
||||
{
|
||||
name => "$name",
|
||||
type => $type,
|
||||
flags => $href,
|
||||
ip => $self->address->short
|
||||
}
|
||||
);
|
||||
|
||||
# Make sure we have a value for each flag
|
||||
$flags{q{retry}} = $href->{q{retry}} // Zonemaster::Engine::Profile->effective->get( q{resolver.defaults.retry} );
|
||||
$flags{q{retrans}} = $href->{q{retrans}}
|
||||
// Zonemaster::Engine::Profile->effective->get( q{resolver.defaults.retrans} );
|
||||
$flags{q{dnssec}} = $href->{q{dnssec}} // 0;
|
||||
$flags{q{usevc}} = $href->{q{usevc}} // 0;
|
||||
$flags{q{igntc}} = $href->{q{igntc}} // 0;
|
||||
$flags{q{fallback}} = $href->{q{fallback}}
|
||||
// Zonemaster::Engine::Profile->effective->get( q{resolver.defaults.fallback} );
|
||||
$flags{q{recurse}} = $href->{q{recurse}} // 0;
|
||||
$flags{q{timeout}} = $href->{q{timeout}}
|
||||
// Zonemaster::Engine::Profile->effective->get( q{resolver.defaults.timeout} );
|
||||
|
||||
if ( exists $href->{edns_details} ) {
|
||||
$flags{q{dnssec}} = $href->{edns_details}{do} // $flags{q{dnssec}};
|
||||
$flags{q{edns_size}} = $href->{edns_details}{size} // ( $href->{q{edns_size}} // ( $flags{q{dnssec}} ? $EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT : $EDNS_UDP_PAYLOAD_DEFAULT ) );
|
||||
}
|
||||
else {
|
||||
$flags{q{edns_size}} = $href->{q{edns_size}} // ( $flags{q{dnssec}} ? $EDNS_UDP_PAYLOAD_DNSSEC_DEFAULT : 0 );
|
||||
}
|
||||
|
||||
# Set flags for this query
|
||||
foreach my $flag ( keys %flags ) {
|
||||
$self->dns->$flag( $flags{$flag} );
|
||||
}
|
||||
|
||||
my $before = time();
|
||||
my $res;
|
||||
if ( $BLACKLISTING_ENABLED and $self->blacklisted->{ $flags{usevc} } ) {
|
||||
Zonemaster::Engine->logger->add(
|
||||
IS_BLACKLISTED => {
|
||||
message => "Server transport has been blacklisted due to previous failure",
|
||||
ns => "$self",
|
||||
name => "$name",
|
||||
type => $type,
|
||||
class => $href->{class},
|
||||
proto => $flags{usevc} ? q{TCP} : q{UDP},
|
||||
dnssec => $flags{dnssec},
|
||||
edns_size => $flags{q{edns_size}}
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
if ( exists $href->{edns_details} ) {
|
||||
my $pkt = Zonemaster::LDNS::Packet->new("$name", $type, $href->{class} );
|
||||
$pkt->set_edns_present();
|
||||
|
||||
$pkt->do($flags{q{dnssec}});
|
||||
$pkt->edns_size($flags{q{edns_size}});
|
||||
|
||||
if ( exists $href->{edns_details}{version} ) {
|
||||
$pkt->edns_version($href->{edns_details}{version});
|
||||
}
|
||||
if ( exists $href->{edns_details}{z} ) {
|
||||
$pkt->edns_z($href->{edns_details}{z});
|
||||
}
|
||||
if ( exists $href->{edns_details}{rcode} ) {
|
||||
$pkt->edns_rcode($href->{edns_details}{rcode});
|
||||
}
|
||||
if ( exists $href->{edns_details}{data} ) {
|
||||
$pkt->edns_data($href->{edns_details}{data});
|
||||
}
|
||||
|
||||
$res = eval { $self->dns->query_with_pkt( $pkt ) };
|
||||
}
|
||||
else {
|
||||
$res = eval { $self->dns->query( "$name", $type, $href->{class} ) };
|
||||
}
|
||||
|
||||
if ( $@ ) {
|
||||
my $msg = "$@";
|
||||
my $trailing_info = " at ".__FILE__;
|
||||
|
||||
chomp( $msg );
|
||||
$msg =~ s/$trailing_info.*/\./;
|
||||
|
||||
Zonemaster::Engine->logger->add( LOOKUP_ERROR =>
|
||||
{ message => $msg, ns => "$self", domain => "$name", type => $type, class => $href->{class} } );
|
||||
|
||||
if ( not $href->{q{blacklisting_disabled}} and $type eq q{SOA} and $flags{q{edns_size}} == 0 ) {
|
||||
$self->blacklisted->{ $flags{usevc} } = 1;
|
||||
Zonemaster::Engine->logger->add( BLACKLISTING =>
|
||||
{ ns => "$self", proto => $flags{usevc} ? q{TCP} : q{UDP} } );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
push @{ $self->times }, ( time() - $before );
|
||||
|
||||
if ( $res ) {
|
||||
my $p = Zonemaster::Engine::Packet->new( { packet => $res } );
|
||||
my $size = length( $p->data );
|
||||
if ( $size > $EDNS_UDP_PAYLOAD_COMMON_LIMIT ) {
|
||||
my $command = sprintf q{dig @%s %s%s %s}, $self->address->short, $flags{dnssec} ? q{+dnssec } : q{},
|
||||
"$name", $type;
|
||||
Zonemaster::Engine->logger->add(
|
||||
PACKET_BIG => { size => $size, command => $command } );
|
||||
}
|
||||
Zonemaster::Engine->logger->add( EXTERNAL_RESPONSE => { packet => $p->string } );
|
||||
return $p;
|
||||
}
|
||||
else {
|
||||
Zonemaster::Engine->logger->add( EMPTY_RETURN => {} );
|
||||
return;
|
||||
}
|
||||
} ## end sub _query
|
||||
|
||||
sub string {
|
||||
return $_[0]->{_string};
|
||||
}
|
||||
|
||||
sub compare {
|
||||
my ( $self, $other, $reverse ) = @_;
|
||||
|
||||
return $self->string cmp $other->string;
|
||||
}
|
||||
|
||||
sub save {
|
||||
my ( $class, $filename ) = @_;
|
||||
|
||||
my $old = POSIX::setlocale( POSIX::LC_ALL, 'C' );
|
||||
my $json = JSON::PP->new->allow_blessed->convert_blessed;
|
||||
$json = $json->canonical( 1 );
|
||||
|
||||
open my $fh, '>', $filename or die "Cache save failed: $!";
|
||||
foreach my $name ( sort keys %object_cache ) {
|
||||
foreach my $addr ( sort keys %{ $object_cache{$name} } ) {
|
||||
say $fh "$name $addr " . $json->encode( $object_cache{$name}{$addr}->cache->data );
|
||||
}
|
||||
}
|
||||
|
||||
close $fh or die $!;
|
||||
|
||||
Zonemaster::Engine->logger->add( SAVED_NS_CACHE => { file => $filename } );
|
||||
|
||||
POSIX::setlocale( POSIX::LC_ALL, $old );
|
||||
return;
|
||||
}
|
||||
|
||||
sub restore {
|
||||
my ( $class, $filename ) = @_;
|
||||
|
||||
useall 'Zonemaster::LDNS::RR';
|
||||
my $decode = JSON::PP->new->filter_json_single_key_object(
|
||||
'Zonemaster::LDNS::Packet' => sub {
|
||||
my ( $ref ) = @_;
|
||||
## no critic (Modules::RequireExplicitInclusion)
|
||||
my $obj = Zonemaster::LDNS::Packet->new_from_wireformat( decode_base64( $ref->{data} ) );
|
||||
$obj->answerfrom( $ref->{answerfrom} );
|
||||
$obj->timestamp( $ref->{timestamp} );
|
||||
|
||||
return $obj;
|
||||
}
|
||||
)->filter_json_single_key_object(
|
||||
'Zonemaster::Engine::Packet' => sub {
|
||||
my ( $ref ) = @_;
|
||||
|
||||
return Zonemaster::Engine::Packet->new( { packet => $ref } );
|
||||
}
|
||||
);
|
||||
|
||||
my $cache_type = Zonemaster::Engine::Nameserver::Cache->get_cache_type( Zonemaster::Engine::Profile->effective );
|
||||
my $cache_class = Zonemaster::Engine::Nameserver::Cache->get_cache_class( $cache_type );
|
||||
|
||||
open my $fh, '<', $filename or die "Failed to open restore data file: $!\n";
|
||||
while ( my $line = <$fh> ) {
|
||||
my ( $name, $addr, $data ) = split( / /, $line, 3 );
|
||||
my $ref = $decode->decode( $data );
|
||||
my $ns = Zonemaster::Engine::Nameserver->new(
|
||||
{
|
||||
name => $name,
|
||||
address => Net::IP::XS->new($addr),
|
||||
cache => $cache_class->new( { data => $ref, address => Net::IP::XS->new( $addr ) } )
|
||||
}
|
||||
);
|
||||
}
|
||||
close $fh;
|
||||
|
||||
Zonemaster::Engine->logger->add( RESTORED_NS_CACHE => { file => $filename } );
|
||||
|
||||
return;
|
||||
} ## end sub restore
|
||||
|
||||
sub max_time {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return max( @{ $self->times } ) // 0;
|
||||
}
|
||||
|
||||
sub min_time {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return min( @{ $self->times } ) // 0;
|
||||
}
|
||||
|
||||
sub sum_time {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return sum( @{ $self->times } ) // 0;
|
||||
}
|
||||
|
||||
sub average_time {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return 0 if @{ $self->times } == 0;
|
||||
|
||||
return ( $self->sum_time / scalar( @{ $self->times } ) );
|
||||
}
|
||||
|
||||
sub median_time {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my @t = sort { $a <=> $b } @{ $self->times };
|
||||
my $c = scalar( @t );
|
||||
if ( $c == 0 ) {
|
||||
return 0;
|
||||
}
|
||||
elsif ( $c % 2 == 0 ) {
|
||||
return ( $t[ $c / 2 ] + $t[ ( $c / 2 ) - 1 ] ) / 2;
|
||||
}
|
||||
else {
|
||||
return $t[ int( $c / 2 ) ];
|
||||
}
|
||||
}
|
||||
|
||||
sub stddev_time {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my $avg = $self->average_time;
|
||||
my $c = scalar( @{ $self->times } );
|
||||
|
||||
return 0 if $c == 0;
|
||||
|
||||
return sqrt( sum( map { ( $_ - $avg )**2 } @{ $self->times } ) / $c );
|
||||
}
|
||||
|
||||
sub all_known_nameservers {
|
||||
my @res;
|
||||
|
||||
foreach my $n ( values %object_cache ) {
|
||||
push @res, values %{$n};
|
||||
}
|
||||
|
||||
return @res;
|
||||
}
|
||||
|
||||
sub axfr {
|
||||
my ( $self, $domain, $callback, $class ) = @_;
|
||||
$class //= 'IN';
|
||||
|
||||
if ( Zonemaster::Engine::Profile->effective->get( q{no_network} ) ) {
|
||||
croak sprintf
|
||||
"External AXFR query for %s attempted to %s while running with no_network",
|
||||
$domain, $self->string;
|
||||
}
|
||||
|
||||
if ( $self->address->version == 4 and not Zonemaster::Engine::Profile->effective->get( q{net.ipv4} ) ) {
|
||||
Zonemaster::Engine->logger->add( IPV4_BLOCKED => { ns => $self->string } );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $self->address->version == 6 and not Zonemaster::Engine::Profile->effective->get( q{net.ipv6} ) ) {
|
||||
Zonemaster::Engine->logger->add( IPV6_BLOCKED => { ns => $self->string } );
|
||||
return;
|
||||
}
|
||||
|
||||
return $self->dns->axfr( $domain, $callback, $class );
|
||||
} ## end sub axfr
|
||||
|
||||
sub source_address {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my $src_address = Zonemaster::Engine::Profile->effective->get( "resolver.source" . Net::IP::XS::ip_get_version( $self->address->ip ) );
|
||||
|
||||
return $src_address eq '' ? undef : $src_address;
|
||||
}
|
||||
|
||||
sub empty_cache {
|
||||
%object_cache = ();
|
||||
%address_object_cache = ();
|
||||
%address_repr_cache = ();
|
||||
|
||||
Zonemaster::Engine::Nameserver::Cache::empty_cache();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Nameserver - object representing a DNS nameserver
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $ns = Zonemaster::Engine::Nameserver->new({ name => 'ns.nic.se', address => '212.247.7.228' });
|
||||
my $p = $ns->query('www.iis.se', 'AAAA');
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is a very central object in the L<Zonemaster::Engine> framework. All DNS
|
||||
communications with the outside world pass through here, so we can do
|
||||
things like synthesizing and recording traffic. All the objects are
|
||||
also unique per name/IP pair, and creating a new one with an already
|
||||
existing pair will return the existing object instead of creating a
|
||||
new one. Queries and their responses are cached by IP address, so that
|
||||
a specific query will only be sent once to each address (even if there
|
||||
are multiple objects for that address with different names).
|
||||
|
||||
Class methods on this class allows saving and loading cache contents.
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item name
|
||||
|
||||
A L<Zonemaster::Engine::DNSName> object holding the nameserver's name.
|
||||
|
||||
=item address
|
||||
|
||||
A L<Net::IP::XS> object holding the nameserver's address.
|
||||
|
||||
=item dns
|
||||
|
||||
The L<Zonemaster::LDNS> object used to actually send and receive DNS queries.
|
||||
|
||||
=item cache
|
||||
|
||||
A reference to a L<Zonemaster::Engine::Nameserver::Cache> object holding the cache of sent queries. Not meant for external use.
|
||||
|
||||
=item times
|
||||
|
||||
A reference to a list with elapsed time values for the queries made through this nameserver.
|
||||
|
||||
=item blacklisted
|
||||
|
||||
A reference to a hash used to prevent sending subsequent queries to the name server after specific queries have failed.
|
||||
|
||||
The mechanism will only trigger on no response from non-EDNS SOA queries and is protocol dependent (i.e. TCP/UDP). It can be disabled
|
||||
on a per query basis with L<blacklisting_disabled>, or globally with L<Zonemaster::Engine::Constants/$BLACKLISTING_ENABLED>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item new
|
||||
|
||||
Construct a new object.
|
||||
|
||||
=item save($filename)
|
||||
|
||||
Save the entire object cache to the given filename, using the
|
||||
byte-order-independent Storable format.
|
||||
|
||||
=item restore($filename)
|
||||
|
||||
Replace the entire object cache with the contents of the named file.
|
||||
|
||||
=item all_known_nameservers()
|
||||
|
||||
Class method that returns a list of all nameserver objects in the global cache.
|
||||
|
||||
=item empty_cache()
|
||||
|
||||
Remove all cached nameserver objects and queries.
|
||||
|
||||
=back
|
||||
|
||||
=head1 INSTANCE METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item query($name, $type, $flagref)
|
||||
|
||||
Send a DNS query to the nameserver the object represents. C<$name> and C<$type> are the name and type that will be queried for (C<$type> defaults
|
||||
to 'A' if it's left undefined). C<$flagref> is a reference to a hash, the keys of which are flags and the values are their corresponding values.
|
||||
The available flags are as follows. All but 'class' and 'edns_details' directly correspond to methods in the L<Zonemaster::LDNS> object.
|
||||
|
||||
=over
|
||||
|
||||
=item class
|
||||
|
||||
Defaults to 'IN' if not set.
|
||||
|
||||
=item usevc
|
||||
|
||||
Send the query via TCP (only).
|
||||
|
||||
=item retrans
|
||||
|
||||
The retransmission interval.
|
||||
|
||||
=item dnssec
|
||||
|
||||
Set the DO flag in the query. Defaults to false.
|
||||
|
||||
If set to true, it becomes an EDNS query.
|
||||
Value overridden by C<edns_details{do}> (if also given). More details in L<edns_details> below.
|
||||
|
||||
=item debug
|
||||
|
||||
Set the debug flag in the resolver, producing output on STDERR as the query process proceeds.
|
||||
|
||||
=item recurse
|
||||
|
||||
Set the RD flag in the query.
|
||||
|
||||
=item timeout
|
||||
|
||||
Set the timeout for the outgoing sockets. May or may not be observed by the underlying network stack.
|
||||
|
||||
=item retry
|
||||
|
||||
Set the number of times the query is tried.
|
||||
|
||||
=item igntc
|
||||
|
||||
If set to true, incoming response packets with the TC flag set are not automatically retried over TCP.
|
||||
|
||||
=item fallback
|
||||
|
||||
If set to true, incoming response packets with the TC flag set fall back to EDNS and/or TCP.
|
||||
|
||||
=item blacklisting_disabled
|
||||
|
||||
If set to true, prevents a name server from being blacklisted.
|
||||
|
||||
=item edns_size
|
||||
|
||||
Set the EDNS0 UDP maximum size. The value must be comprised between 0 and 65535.
|
||||
Defaults to 0, or 512 if the query is a non-DNSSEC EDNS query, or 1232 if the query is a DNSSEC query.
|
||||
|
||||
Setting a value other than 0 will also implicitly enable EDNS for the query.
|
||||
Value overridden by C<edns_details-E<gt>{size}> (if also given). More details in L<edns_details> below.
|
||||
|
||||
=item edns_details
|
||||
|
||||
A hash. An empty hash or a hash with any keys below will enable EDNS for the query.
|
||||
|
||||
The currently supported keys are 'version', 'z', 'do', 'rcode', 'size' and 'data'.
|
||||
See L<Zonemaster::LDNS::Packet> for more details (key names prefixed with 'edns_').
|
||||
|
||||
Note that flag L<edns_size> also exists (see above) and has the same effect as C<edns_details-E<gt>{size}>, although the value of the
|
||||
latter will take precedence if both are given.
|
||||
|
||||
Similarly, note that flag L<dnssec> also exists (see above) and has the same effect as C<edns_details-E<gt>{do}>, although the value of the
|
||||
latter will take precedence if both are given.
|
||||
|
||||
=back
|
||||
|
||||
=item string()
|
||||
|
||||
Returns a string representation of the object. Normally this is just the name and IP address separated by a slash.
|
||||
|
||||
=item compare($other)
|
||||
|
||||
Used for overloading comparison operators.
|
||||
|
||||
=item sum_time()
|
||||
|
||||
Returns the total time spent sending queries and waiting for responses.
|
||||
|
||||
=item min_time()
|
||||
|
||||
Returns the shortest time spent on a query.
|
||||
|
||||
=item max_time()
|
||||
|
||||
Returns the longest time spent on a query.
|
||||
|
||||
=item average_time()
|
||||
|
||||
Returns the average time spent on queries.
|
||||
|
||||
=item median_time()
|
||||
|
||||
Returns the median query time.
|
||||
|
||||
=item stddev_time()
|
||||
|
||||
Returns the standard deviation for the whole set of query times.
|
||||
|
||||
=item add_fake_delegation($domain,$data)
|
||||
|
||||
Adds fake delegation information to this specific nameserver object. Takes the
|
||||
same arguments as the similarly named method in L<Zonemaster::Engine>. This is
|
||||
primarily used for internal information, and using it directly will likely give
|
||||
confusing results (but may be useful to model certain kinds of
|
||||
misconfigurations).
|
||||
|
||||
=item add_fake_ds($domain, $data)
|
||||
|
||||
Adds fake DS information to this nameserver object. Takes the same arguments as
|
||||
the similarly named method in L<Zonemaster::Engine>.
|
||||
|
||||
=item axfr( $domain, $callback, $class )
|
||||
|
||||
Does an AXFR for the requested domain from the nameserver. The callback
|
||||
function will be called once for each received RR, with that RR as its only
|
||||
argument. To continue getting more RRs, the callback must return a true value.
|
||||
If it returns a true value, the AXFR will be aborted. See L<Zonemaster::LDNS::axfr>
|
||||
for more details.
|
||||
|
||||
=item source_address()
|
||||
|
||||
my $src_address = source_address();
|
||||
|
||||
Returns the configured IPv4 or IPv6 source address to be used by the underlying DNS resolver for sending queries,
|
||||
or C<undef> if the source address is the empty string.
|
||||
|
||||
=item empty_cache()
|
||||
|
||||
Clears the caches of Zonemaster::Engine::Nameserver (name server names and IP addresses) and Zonemaster::Engine::Nameserver::Cache (query and response packets) objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
99
zonemaster-engine/lib/Zonemaster/Engine/Nameserver/Cache.pm
Normal file
99
zonemaster-engine/lib/Zonemaster/Engine/Nameserver/Cache.pm
Normal file
@@ -0,0 +1,99 @@
|
||||
package Zonemaster::Engine::Nameserver::Cache;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.4");
|
||||
|
||||
use Class::Accessor "antlers";
|
||||
|
||||
our %object_cache;
|
||||
|
||||
has 'data' => ( is => 'ro' );
|
||||
has 'address' => ( is => 'ro' );
|
||||
|
||||
sub get_cache_type {
|
||||
my ( $class, $profile ) = @_;
|
||||
my $cache_type = 'LocalCache';
|
||||
|
||||
my %cache_config = %{ $profile->get( 'cache' ) };
|
||||
|
||||
if ( exists $cache_config{'redis'} ) {
|
||||
$cache_type = 'RedisCache';
|
||||
}
|
||||
|
||||
return $cache_type;
|
||||
}
|
||||
|
||||
sub get_cache_class {
|
||||
my ( $class, $cache_type ) = @_;
|
||||
|
||||
my $cache_class = "Zonemaster::Engine::Nameserver::Cache::$cache_type";
|
||||
|
||||
require ( "$cache_class.pm" =~ s{::}{/}gr );
|
||||
$cache_class->import();
|
||||
|
||||
return $cache_class;
|
||||
}
|
||||
|
||||
sub empty_cache {
|
||||
%object_cache = ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Nameserver::Cache - shared caches for nameserver objects
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This class should not be used directly.
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item address
|
||||
|
||||
A L<Net::IP::XS> object holding the nameserver's address.
|
||||
|
||||
=item data
|
||||
|
||||
A reference to a hash holding the cache of sent queries. Not meant for external use.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item get_cache_type()
|
||||
|
||||
my $cache_type = get_cache_type( Zonemaster::Engine::Profile->effective );
|
||||
|
||||
Get the cache type value from the profile, i.e. the name of the cache module to use.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Profile> object.
|
||||
|
||||
Returns a string.
|
||||
|
||||
=item get_cache_class()
|
||||
|
||||
my $cache_class = get_cache_class( 'LocalCache' );
|
||||
|
||||
Get the cache adapter class for the given database type.
|
||||
|
||||
Takes a string (cache database type).
|
||||
|
||||
Returns a string, or throws an exception if the cache adapter class cannot be loaded.
|
||||
|
||||
=item empty_cache()
|
||||
|
||||
Clear the cache.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -0,0 +1,100 @@
|
||||
package Zonemaster::Engine::Nameserver::Cache::LocalCache;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.4");
|
||||
|
||||
use Carp qw( confess );
|
||||
use Scalar::Util qw( blessed );
|
||||
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Engine::Nameserver::Cache;
|
||||
|
||||
use base qw( Zonemaster::Engine::Nameserver::Cache );
|
||||
|
||||
our $object_cache = \%Zonemaster::Engine::Nameserver::Cache::object_cache;
|
||||
|
||||
sub new {
|
||||
my $proto = shift;
|
||||
my $class = ref $proto || $proto;
|
||||
my $attrs = shift;
|
||||
|
||||
confess "Attribute \(address\) is required"
|
||||
if !exists $attrs->{address};
|
||||
|
||||
# Type coercions
|
||||
$attrs->{address} = Net::IP::XS->new( $attrs->{address} )
|
||||
if !blessed $attrs->{address} || !$attrs->{address}->isa( 'Net::IP::XS' );
|
||||
|
||||
# Type constraint
|
||||
confess "Argument must be coercible into a Net::IP::XS: address"
|
||||
if !$attrs->{address}->isa( 'Net::IP::XS' );
|
||||
confess "Argument must be a HASHREF: data"
|
||||
if exists $attrs->{data} && ref $attrs->{data} ne 'HASH';
|
||||
|
||||
# Default value
|
||||
$attrs->{data} //= {};
|
||||
|
||||
my $ip = $attrs->{address}->ip;
|
||||
if ( exists $object_cache->{ $ip } ) {
|
||||
Zonemaster::Engine->logger->add( CACHE_FETCHED => { ip => $ip } );
|
||||
return $object_cache->{ $ip };
|
||||
}
|
||||
|
||||
my $obj = Class::Accessor::new( $class, $attrs );
|
||||
|
||||
Zonemaster::Engine->logger->add( CACHE_CREATED => { ip => $ip } );
|
||||
$object_cache->{ $ip } = $obj;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub set_key {
|
||||
my ( $self, $idx, $packet ) = @_;
|
||||
$self->data->{$idx} = $packet;
|
||||
}
|
||||
|
||||
sub get_key {
|
||||
my ( $self, $idx ) = @_;
|
||||
|
||||
if ( exists $self->data->{$idx} ) {
|
||||
# cache hit
|
||||
return ( 1, $self->data->{$idx} );
|
||||
}
|
||||
return ( 0, undef );
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Nameserver::LocalCache - local shared caches for nameserver objects
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This class should not be used directly.
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
Subclass of L<Zonemaster::Engine::Nameserver::Cache>.
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item new
|
||||
|
||||
Construct a new Cache object.
|
||||
|
||||
=item set_key($idx, $packet)
|
||||
|
||||
Store C<$packet> (data) with key C<$idx>.
|
||||
|
||||
=item get_key($idx)
|
||||
|
||||
Retrieve C<$packet> (data) at key C<$idx>.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -0,0 +1,182 @@
|
||||
package Zonemaster::Engine::Nameserver::Cache::RedisCache;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.0");
|
||||
|
||||
use Class::Accessor "antlers";
|
||||
use Time::HiRes qw[gettimeofday tv_interval];
|
||||
use List::Util qw( min );
|
||||
|
||||
use Zonemaster::LDNS::Packet;
|
||||
use Zonemaster::Engine::Packet;
|
||||
use Zonemaster::Engine::Profile;
|
||||
|
||||
use base qw( Zonemaster::Engine::Nameserver::Cache );
|
||||
|
||||
eval {
|
||||
use Data::MessagePack;
|
||||
use Redis;
|
||||
};
|
||||
|
||||
if ( $@ ) {
|
||||
die "Can't use the Redis cache. Make sure the Data::MessagePack and Redis modules are installed.\n";
|
||||
}
|
||||
|
||||
my $redis;
|
||||
my $config;
|
||||
our $object_cache = \%Zonemaster::Engine::Nameserver::Cache::object_cache;
|
||||
|
||||
my $REDIS_EXPIRE_DEFAULT = 300; # seconds
|
||||
|
||||
has 'redis' => ( is => 'ro' );
|
||||
has 'config' => ( is => 'ro' );
|
||||
|
||||
my $mp = Data::MessagePack->new();
|
||||
|
||||
sub new {
|
||||
my $proto = shift;
|
||||
my $params = shift;
|
||||
$params->{address} = $params->{address}->ip;
|
||||
if ( exists $object_cache->{ $params->{address} } ) {
|
||||
Zonemaster::Engine->logger->add( CACHE_FETCHED => { ip => $params->{address} } );
|
||||
return $object_cache->{ $params->{address} };
|
||||
} else {
|
||||
if (! defined $redis) {
|
||||
my $redis_config = Zonemaster::Engine::Profile->effective->get( q{cache} )->{'redis'};
|
||||
$redis = Redis->new(server => $redis_config->{server});
|
||||
$config = $redis_config;
|
||||
}
|
||||
$params->{redis} //= $redis;
|
||||
$params->{data} //= {};
|
||||
$params->{config} //= $config;
|
||||
$config->{expire} //= $REDIS_EXPIRE_DEFAULT;
|
||||
my $class = ref $proto || $proto;
|
||||
my $obj = Class::Accessor::new( $class, $params );
|
||||
|
||||
Zonemaster::Engine->logger->add( CACHE_CREATED => { ip => $params->{address} } );
|
||||
$object_cache->{ $params->{address} } = $obj;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
||||
sub set_key {
|
||||
my ( $self, $hash, $packet ) = @_;
|
||||
my $key = "ns:" . $self->address . ":" . $hash;
|
||||
|
||||
# Never cache with answer, NXDOMAIN or NODATA longer than this.
|
||||
my $redis_expire = $self->{config}->{expire};
|
||||
|
||||
# If no response or response without answer or SOA in authority,
|
||||
# cache this many seconds (e.g. SERVFAIL or REFUSED).
|
||||
my $ttl_no_response = 1200;
|
||||
|
||||
my $ttl;
|
||||
|
||||
$self->data->{$hash} = $packet;
|
||||
if ( defined $packet ) {
|
||||
my $msg = $mp->pack({
|
||||
data => $packet->data,
|
||||
answerfrom => $packet->answerfrom,
|
||||
timestamp => $packet->timestamp,
|
||||
querytime => $packet->querytime,
|
||||
});
|
||||
if ( $packet->answer ) {
|
||||
my @rr = $packet->answer;
|
||||
$ttl = min( map { $_->ttl } @rr );
|
||||
}
|
||||
elsif ( $packet->authority ) {
|
||||
my @rr = $packet->authority;
|
||||
foreach my $r (@rr) {
|
||||
if ( $r->type eq 'SOA' ) {
|
||||
$ttl = $r->ttl;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( defined( $ttl ) ) {
|
||||
$ttl = $ttl < $redis_expire ? $ttl : $redis_expire;
|
||||
} else {
|
||||
$ttl = $ttl_no_response;
|
||||
}
|
||||
|
||||
# Redis requires cache time to be greater than 0 to be stored.
|
||||
return if $ttl == 0;
|
||||
$self->redis->set( $key, $msg, 'EX', $ttl );
|
||||
} else {
|
||||
$self->redis->set( $key, '', 'EX', $ttl_no_response );
|
||||
}
|
||||
}
|
||||
|
||||
sub get_key {
|
||||
my ( $self, $hash ) = @_;
|
||||
my $key = "ns:" . $self->address . ":" . $hash;
|
||||
|
||||
if ( exists $self->data->{$hash} ) {
|
||||
Zonemaster::Engine->logger->add( MEMORY_CACHE_HIT => { hash => $hash } );
|
||||
return ( 1, $self->data->{$hash} );
|
||||
} elsif ( $self->redis->exists($key) ) {
|
||||
my $fetch_start_time = [ gettimeofday ];
|
||||
my $data = $self->redis->get( $key );
|
||||
Zonemaster::Engine->logger->add( REDIS_CACHE_HIT => { key => $key } );
|
||||
if ( not length($data) ) {
|
||||
$self->data->{$hash} = undef;
|
||||
} else {
|
||||
my $msg = $mp->unpack( $data );
|
||||
my $packet = Zonemaster::Engine::Packet->new({ packet => Zonemaster::LDNS::Packet->new_from_wireformat($msg->{data}) });
|
||||
$packet->answerfrom( $msg->{answerfrom} );
|
||||
$packet->timestamp( $msg->{timestamp} );
|
||||
$packet->querytime( $msg->{querytime} );
|
||||
|
||||
$self->data->{$hash} = $packet;
|
||||
}
|
||||
return ( 1, $self->data->{$hash} );
|
||||
}
|
||||
Zonemaster::Engine->logger->add( CACHE_MISS => { key => $key } );
|
||||
return ( 0, undef )
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Nameserver::Cache::RedisCache - global shared caches for nameserver objects
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This is a global caching layer.
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
Subclass of L<Zonemaster::Engine::Nameserver::Cache>.
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item new
|
||||
|
||||
Construct a new Cache object.
|
||||
|
||||
=item set_key($idx, $packet)
|
||||
|
||||
Store C<$packet> with key C<$idx>.
|
||||
|
||||
=item get_key($idx)
|
||||
|
||||
Retrieve C<$packet> (data) at key C<$idx>.
|
||||
|
||||
=back
|
||||
|
||||
Cache time is the shortest time of TTL in the DNS packet
|
||||
and cache.redis.expire in the profile. Default value of
|
||||
cache.redis.expire is 300 seconds.
|
||||
|
||||
If there is no TTL value to be used in the DNS packet
|
||||
(e.g. SERVFAIL or no response), then cache time is fixed
|
||||
to 1200 seconds instead.
|
||||
|
||||
=cut
|
||||
225
zonemaster-engine/lib/Zonemaster/Engine/Normalization.pm
Normal file
225
zonemaster-engine/lib/Zonemaster/Engine/Normalization.pm
Normal file
@@ -0,0 +1,225 @@
|
||||
package Zonemaster::Engine::Normalization;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use parent 'Exporter';
|
||||
|
||||
use utf8;
|
||||
use Carp;
|
||||
use Encode;
|
||||
use Readonly;
|
||||
use Try::Tiny;
|
||||
use Zonemaster::LDNS;
|
||||
|
||||
use Zonemaster::Engine::Normalization::Error;
|
||||
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Normalization - utility functions for names normalization
|
||||
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Zonemaster::Engine::Normalization;
|
||||
|
||||
my ($errors, $final_domain) = normalize_name($domain);
|
||||
|
||||
=head1 EXPORTED FUNCTIONS
|
||||
|
||||
=over
|
||||
=cut
|
||||
|
||||
|
||||
our @EXPORT = qw[ normalize_name ];
|
||||
our @EXPORT_OK = qw[ normalize_name normalize_label trim_space ];
|
||||
|
||||
Readonly my $ASCII => qr/^[[:ascii:]]+$/;
|
||||
Readonly my $VALID_ASCII => qr(^[A-Za-z0-9/_-]+$);
|
||||
|
||||
Readonly my $ASCII_FULL_STOP => "\x{002E}";
|
||||
Readonly my $ASCII_FULL_STOP_RE => qr/\x{002E}/;
|
||||
Readonly my %FULL_STOPS => (
|
||||
FULLWIDTH_FULL_STOP => q/\x{FF0E}/,
|
||||
IDEOGRAPHIC_FULL_STOP => q/\x{3002}/,
|
||||
HALFWIDTH_IDEOGRAPHIC_FULL_STOP => q/\x{FF61}/
|
||||
);
|
||||
Readonly my $FULL_STOPS_RE => (sub {
|
||||
my $re = '[' . (join '', values %FULL_STOPS) . ']';
|
||||
return qr/$re/;
|
||||
})->();
|
||||
Readonly my %WHITE_SPACES => (
|
||||
SPACE => q/\x{0020}/,
|
||||
CHARACTER_TABULATION => q/\x{0009}/,
|
||||
NO_BREAK_SPACE => q/\x{00A0}/,
|
||||
EN_QUAD => q/\x{2000}/,
|
||||
EM_QUAD => q/\x{2001}/,
|
||||
EN_SPACE => q/\x{2002}/,
|
||||
EM_SPACE => q/\x{2003}/,
|
||||
THREE_PER_EM_SPACE => q/\x{2004}/,
|
||||
FOUR_PER_EM_SPACE => q/\x{2005}/,
|
||||
SIX_PER_EM_SPACE => q/\x{2006}/,
|
||||
FIGURE_SPACE => q/\x{2007}/,
|
||||
PUNCTUATION_SPACE => q/\x{2008}/,
|
||||
THIN_SPACE => q/\x{2009}/,
|
||||
HAIR_SPACE => q/\x{200A}/,
|
||||
MEDIUM_MATHEMATICAL_SPACE => q/\x{205F}/,
|
||||
IDEOGRAPHIC_SPACE => q/\x{3000}/,
|
||||
OGHAM_SPACE_MARK => q/\x{1680}/,
|
||||
);
|
||||
Readonly my $WHITE_SPACES_RE => (sub {
|
||||
my $re = '[' . (join '', values %WHITE_SPACES) . ']';
|
||||
return qr/$re/;
|
||||
})->();
|
||||
Readonly my %AMBIGUOUS_CHARACTERS => (
|
||||
"LATIN CAPITAL LETTER I WITH DOT ABOVE" => q/\x{0130}/,
|
||||
);
|
||||
|
||||
|
||||
|
||||
=item normalize_label($label)
|
||||
|
||||
Normalize a single label from a domain name.
|
||||
|
||||
If the label is ASCII only, it is down cased, else it is converted according
|
||||
to IDNA2008.
|
||||
|
||||
Downcasing of upper case non-ASCII characters, normalization to the Unicode
|
||||
NFC format and conversion from U-label to A-label is performed by libidn2
|
||||
using L<Zonemaster::LDNS/to_idn($name, ...)>.
|
||||
|
||||
Returns a tuple C<($errors: ArrayRef[Zonemaster::Engine::Normalization::Error], $alabel: String)>.
|
||||
|
||||
In case of errors, the returned label will be undefined. If the method
|
||||
succeeded an empty error array is returned.
|
||||
|
||||
=cut
|
||||
|
||||
sub normalize_label {
|
||||
my ( $label ) = @_;
|
||||
my @messages;
|
||||
|
||||
my $alabel = "";
|
||||
|
||||
if ( $label =~ $VALID_ASCII ) {
|
||||
$alabel = lc $label;
|
||||
} elsif ( $label =~ $ASCII ) {
|
||||
push @messages, Zonemaster::Engine::Normalization::Error->new(INVALID_ASCII => {label => $label});
|
||||
|
||||
return \@messages, undef;
|
||||
} elsif ( Zonemaster::LDNS::has_idn ) {
|
||||
try {
|
||||
$alabel = Zonemaster::LDNS::to_idn($label);
|
||||
} catch {
|
||||
push @messages, Zonemaster::Engine::Normalization::Error->new(INVALID_U_LABEL => {label => $label});
|
||||
|
||||
return \@messages, undef;
|
||||
}
|
||||
} else {
|
||||
croak 'The domain name contains at least one non-ASCII character and this installation of Zonemaster has no support for IDNA.';
|
||||
}
|
||||
|
||||
if ( length($alabel) > 63 ) {
|
||||
push @messages, Zonemaster::Engine::Normalization::Error->new(LABEL_TOO_LONG => {label => $label});
|
||||
return \@messages, undef;
|
||||
}
|
||||
|
||||
return \@messages, $alabel;
|
||||
}
|
||||
|
||||
=item trim_space($str)
|
||||
|
||||
Trim leading and trailing whitespace.
|
||||
|
||||
Implements the space trimming part of L<normalization document|https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/RequirementsAndNormalizationOfDomainNames.md>.
|
||||
|
||||
Returns a string.
|
||||
|
||||
=cut
|
||||
|
||||
sub trim_space {
|
||||
my ( $str ) = @_;
|
||||
|
||||
return $str =~ s/^${$WHITE_SPACES_RE}+|${WHITE_SPACES_RE}+$//gr;
|
||||
}
|
||||
|
||||
=item normalize_name($name)
|
||||
|
||||
Normalize a domain name.
|
||||
|
||||
Implements the normalization process, except the space trimming part, described
|
||||
in L<normalization document|https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/RequirementsAndNormalizationOfDomainNames.md>.
|
||||
|
||||
Returns a tuple C<($errors: ArrayRef[Zonemaster::Engine::Normalization::Error], $name: String)>.
|
||||
|
||||
In case of errors, the returned name will be undefined. If the method succeeded
|
||||
an empty error array is returned.
|
||||
|
||||
=cut
|
||||
|
||||
sub normalize_name {
|
||||
my ( $uname ) = @_;
|
||||
my @messages;
|
||||
|
||||
if ( length($uname) == 0 ) {
|
||||
push @messages, Zonemaster::Engine::Normalization::Error->new(EMPTY_DOMAIN_NAME => {});
|
||||
return \@messages, undef;
|
||||
}
|
||||
|
||||
foreach my $char_name ( keys %AMBIGUOUS_CHARACTERS ) {
|
||||
my $char = $AMBIGUOUS_CHARACTERS{$char_name};
|
||||
if ( $uname =~ m/${char}/) {
|
||||
push @messages, Zonemaster::Engine::Normalization::Error->new(AMBIGUOUS_DOWNCASING => { unicode_name => $char_name });
|
||||
}
|
||||
}
|
||||
|
||||
if ( @messages ) {
|
||||
return \@messages, undef;
|
||||
}
|
||||
|
||||
$uname =~ s/${FULL_STOPS_RE}/${ASCII_FULL_STOP}/g;
|
||||
|
||||
if ( $uname eq $ASCII_FULL_STOP ) {
|
||||
return \@messages, $uname;
|
||||
}
|
||||
|
||||
if ( $uname =~ m/^${ASCII_FULL_STOP_RE}/ ) {
|
||||
push @messages, Zonemaster::Engine::Normalization::Error->new(INITIAL_DOT => {});
|
||||
return \@messages, undef;
|
||||
}
|
||||
|
||||
if ( $uname =~ m/${ASCII_FULL_STOP_RE}{2,}/ ) {
|
||||
push @messages, Zonemaster::Engine::Normalization::Error->new(REPEATED_DOTS => {});
|
||||
return \@messages, undef;
|
||||
}
|
||||
|
||||
$uname =~ s/${ASCII_FULL_STOP_RE}$//g;
|
||||
|
||||
my @labels = split $ASCII_FULL_STOP_RE, $uname;
|
||||
my @label_results = map { [ normalize_label($_) ] } @labels;
|
||||
my @label_errors = map { @{$_->[0]} } @label_results;
|
||||
|
||||
push @messages, @label_errors;
|
||||
|
||||
if ( @messages ) {
|
||||
return \@messages, undef;
|
||||
}
|
||||
|
||||
my @label_ok = map { $_->[1] } @label_results;
|
||||
|
||||
my $final_name = join '.', @label_ok;
|
||||
|
||||
if ( length($final_name) > 253 ) {
|
||||
push @messages, Zonemaster::Engine::Normalization::Error->new(DOMAIN_NAME_TOO_LONG => {});
|
||||
return \@messages, undef;
|
||||
}
|
||||
|
||||
return \@messages, $final_name;
|
||||
}
|
||||
|
||||
|
||||
=back
|
||||
=cut
|
||||
|
||||
1;
|
||||
147
zonemaster-engine/lib/Zonemaster/Engine/Normalization/Error.pm
Normal file
147
zonemaster-engine/lib/Zonemaster/Engine/Normalization/Error.pm
Normal file
@@ -0,0 +1,147 @@
|
||||
package Zonemaster::Engine::Normalization::Error;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use Carp;
|
||||
use Readonly;
|
||||
use Locale::TextDomain qw[Zonemaster-Engine];
|
||||
|
||||
use overload '""' => \&string;
|
||||
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Normalization::Error - normalization error class
|
||||
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Zonemaster::Engine::Normalization::Error;
|
||||
|
||||
my $error = Zonemaster::Engine::Normalization::Error->new(LABEL_TOO_LONG => {label => $label});
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
Readonly my %ERRORS => (
|
||||
AMBIGUOUS_DOWNCASING => {
|
||||
message => N__ 'Ambiguous downcasing of character "{unicode_name}" in the domain name. Use all lower case instead.',
|
||||
arguments => [ 'unicode_name' ]
|
||||
},
|
||||
DOMAIN_NAME_TOO_LONG => {
|
||||
message => N__ 'Domain name is too long (more than 253 characters with no final dot).',
|
||||
},
|
||||
EMPTY_DOMAIN_NAME => {
|
||||
message => N__ 'Domain name is empty.'
|
||||
},
|
||||
INITIAL_DOT => {
|
||||
message => N__ 'Domain name starts with dot.'
|
||||
},
|
||||
INVALID_ASCII => {
|
||||
message => N__ 'Domain name has an ASCII label ("{label}") with a character not permitted.',
|
||||
arguments => [ 'label' ]
|
||||
},
|
||||
INVALID_U_LABEL => {
|
||||
message => N__ 'Domain name has a non-ASCII label ("{label}") which is not a valid U-label.',
|
||||
arguments => [ 'label' ]
|
||||
},
|
||||
REPEATED_DOTS => {
|
||||
message => N__ 'Domain name has repeated dots.'
|
||||
},
|
||||
LABEL_TOO_LONG => {
|
||||
message => N__ 'Domain name has a label that is too long (more than 63 characters), "{label}".',
|
||||
arguments => [ 'label' ]
|
||||
},
|
||||
);
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item tag
|
||||
|
||||
The message tag associated to the error.
|
||||
|
||||
=item params
|
||||
|
||||
The error message parameters to use in the message string.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item new($tag, $params)
|
||||
|
||||
Creates and returns a new error object.
|
||||
This function will croak if there is a missing parameter for the given tag.
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my ( $proto, $tag, $params ) = @_;
|
||||
my $class = ref $proto || $proto;
|
||||
|
||||
if ( !exists $ERRORS{$tag} ) {
|
||||
croak 'Unknown error tag.';
|
||||
}
|
||||
|
||||
my $obj = { tag => $tag, params => {} };
|
||||
|
||||
if ( exists $ERRORS{$tag}->{arguments} ) {
|
||||
foreach my $arg ( @{$ERRORS{$tag}->{arguments}} ) {
|
||||
if ( !exists $params->{$arg} ) {
|
||||
croak "Missing arguments $arg.";
|
||||
}
|
||||
$obj->{params}->{$arg} = $params->{$arg};
|
||||
}
|
||||
}
|
||||
|
||||
return bless $obj, $class;
|
||||
}
|
||||
|
||||
|
||||
=item message
|
||||
|
||||
Returns the translated error message using the parameters given when creating the object.
|
||||
|
||||
=cut
|
||||
|
||||
sub message {
|
||||
my ( $self ) = @_;
|
||||
return __x $ERRORS{$self->{tag}}->{message}, %{$self->{params}};
|
||||
}
|
||||
|
||||
|
||||
=item tag
|
||||
|
||||
Returns the message tag associated to the error.
|
||||
|
||||
=cut
|
||||
|
||||
sub tag {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return $self->{tag};
|
||||
}
|
||||
|
||||
=item string
|
||||
|
||||
Returns a string representation of the error object. Equivalent to message().
|
||||
|
||||
=cut
|
||||
|
||||
sub string {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return $self->message;
|
||||
}
|
||||
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
322
zonemaster-engine/lib/Zonemaster/Engine/Overview.pod
Normal file
322
zonemaster-engine/lib/Zonemaster/Engine/Overview.pod
Normal file
@@ -0,0 +1,322 @@
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Overview - The Zonemaster Test Engine
|
||||
|
||||
=head1 INTRODUCTION
|
||||
|
||||
The Zonemaster system is a quality control tool for DNS zones, produced in cooperation between AFNIC and IIS (the top-level registries for respectively France and Sweden). It is a successor both to AFNIC's tool Zonecheck and IIS's tool DNSCheck, and is intended to be an improvement over both.
|
||||
|
||||
The system as a whole consists of the test engine and, as distributed by the project, two different front ends. One is a command-line interface intended for use by experienced technicians, and one is a web interface meant for use by anyone. This document only talks about the test engine.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
=head2 Brief overview
|
||||
|
||||
Conceptually, the test engine consists of a number of test implementation modules surrounded by a support framework. Anyone wanting to use Zonemaster to perform tests communicates with the framework from the "outside", and all modules implementing tests see the world entirely through the framework. Doing things this way lets us have features like the ability to test domains before they are published, to record entire test runs for later analysis and to make sure that test results are (as far as reality allows) predictable and repeatable.
|
||||
|
||||
=head2 For users of Zonemaster
|
||||
|
||||
If all you want to do is run tests, you need to care about four or five modules. L<Zonemaster::Engine> is the main access point to the framework, and it is via its methods that you set the configuration (if needed), request that tests be started and access the logger. The logger is where the test results end up, so that's pretty important. On top of those, you may want to use the L<Zonemaster::Engine::Translator> to turn the results into human-readable messages.
|
||||
|
||||
There are two ways that you can get the results of a test you've requested: the simple-but-inflexible way and the flexible-but-complex way.
|
||||
|
||||
The simple-but-inflexible way is that all the methods in L<Zonemaster::Engine> that run tests return lists of L<Zonemaster::Engine::Logger::Entry> objects. Those lists include all the results that the writer of the test module(s) considered important enough to return by default. The advantage of this method is that it is extremely easy to use. The following is a functional (if not very useful) way to run a full test and print the results from a command-line prompt:
|
||||
|
||||
perl -MZonemaster::Engine -E 'say "$_" for Zonemaster::Engine->new->test_zone("example.org")'
|
||||
|
||||
The main drawbacks of this method are that there is no choice about what
|
||||
messages to see, and it's entirely synchronous.
|
||||
The code that started the test does not get a chance to do anything at
|
||||
all until the whole test suite has finished, which may be several minutes later.
|
||||
|
||||
To get around those drawbacks there is the flexible-but-complex way,
|
||||
which consists of installing a callback that gets executed every time
|
||||
a message is logged.
|
||||
It's not that much more complicated, code-wise.
|
||||
The following example does roughly the same thing as the one above:
|
||||
|
||||
perl -MZonemaster::Engine -E 'Zonemaster::Engine->logger->callback(sub {say "$_[0]"}); Zonemaster::Engine->new->test_zone("example.org");'
|
||||
|
||||
If you try running those, you'll notice two main differences. First, the second variant prints results as they are generated. Second, it generates a B<lot> more output. On my machine right now, the first example gives me 94 lines of output. The second example gives me 17684.
|
||||
|
||||
You can do pretty much whatever you want with the message objects in the callback (including modifying them, although we don't promise that behavior will stay around). If the callback code throws an exception, and that exception is not a subclass of L<Zonemaster::Engine::Exception>, the callback will be removed. Note also that while the callback is running, the test engine itself is not. So think twice before you do potentially time-consuming tasks (like sticking the message in a database) in the callback. After waiting for responses from remote name servers (which usually stands for more than 90% of the time used), the result logging is the single most time-consuming task in a Zonemaster test run.
|
||||
|
||||
From here, you probably want to look at the documentation for L<Zonemaster::Engine>, L<Zonemaster::Engine::Logger>, L<Zonemaster::Engine::Logger::Entry>, L<Zonemaster::Engine::Profile> and L<Zonemaster::Engine::Translator>.
|
||||
|
||||
=head2 For developers of Zonemaster Test Modules
|
||||
|
||||
If you want to develop a test module of your own, the standard set of modules serve as examples.
|
||||
|
||||
As an entry point to the "inside" of the Zonemaster framework, you want to read L<Zonemaster::Engine::Zone> and follow references from there. Of particular interest after the L<Zone|Zonemaster::Engine::Zone> class should be the L<Zonemaster::Engine::Nameserver> and possibly L<Zonemaster::Engine::Recursor> classes.
|
||||
|
||||
If you do write your own test module, I would very much appreciate feedback on which parts were tricky to figure out, because I'm honestly not sure what I need to explain here. So please, if there's something that you think really needs to be written about, create an issue at L<https://github.com/zonemaster/zonemaster-engine/issues>.
|
||||
|
||||
=head2 For developers of the Zonemaster Test Framework
|
||||
|
||||
Random recommendations and advice. May be replaced with more coherent developer documentation in the future.
|
||||
|
||||
=over
|
||||
|
||||
=item
|
||||
|
||||
Stability, predictability and reliability are more important than performance.
|
||||
|
||||
=item
|
||||
|
||||
Don't forget that starting with Perl version 5.18, the order in which you get the keys out of a hash will be different every time the script is run. Get used to always writing C<sort keys %hash>.
|
||||
|
||||
=item
|
||||
|
||||
If two (or more) test modules implement the same (or very similar) thing, it should probably be extracted into the framework.
|
||||
|
||||
=item
|
||||
|
||||
The unit tests run against pre-recorded data, unless the environment variable C<ZONEMASTER_RECORD> is set to a (perl-)true value. In that case, it runs against the live DNS world and records all results for future use. Unfortunately this sometime means that some tests fail, when we were relying on seeing certain problems in certain domains, and those no longer look the same.
|
||||
|
||||
=item
|
||||
|
||||
The translation strings returned from a test module are used as keys in the GNU gettext system, so if you change anything in them don't forget to also change the translation C<.po> files in F<share>.
|
||||
|
||||
=item
|
||||
|
||||
Adding a new message tag is more work than it first looks, since it needs to be added to the test module metadata, the default profile and the translation system in order to be fully functional.
|
||||
|
||||
=back
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over
|
||||
|
||||
=item L<https://github.com/zonemaster/zonemaster>
|
||||
|
||||
Main repository, holding among other things our test specifications.
|
||||
|
||||
=back
|
||||
|
||||
=head2 List of all RFCs referred to in the test specifications
|
||||
|
||||
=over
|
||||
|
||||
=item
|
||||
|
||||
L<RFC0822 "STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES"|http://www.rfc-editor.org/info/rfc822>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC0919 "Broadcasting Internet Datagrams"|http://www.rfc-editor.org/info/rfc919>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC0952 "DoD Internet host table specification"|http://www.rfc-editor.org/info/rfc952>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1033 "Domain Administrators Operations Guide"|http://www.rfc-editor.org/info/rfc1033>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1034 "Domain names - concepts and facilities"|http://www.rfc-editor.org/info/rfc1034>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1035 "Domain names - implementation and specification"|http://www.rfc-editor.org/info/rfc1035>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1112 "Host extensions for IP multicasting"|http://www.rfc-editor.org/info/rfc1112>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1122 "Requirements for Internet Hosts - Communication Layers"|http://www.rfc-editor.org/info/rfc1122>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1123 "Requirements for Internet Hosts - Application and Support"|http://www.rfc-editor.org/info/rfc1123>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1912 "Common DNS Operational and Configuration Errors"|http://www.rfc-editor.org/info/rfc1912>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1918 "Address Allocation for Private Internets"|http://www.rfc-editor.org/info/rfc1918>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1930 "Guidelines for creation, selection, and registration of an Autonomous System (AS)"|http://www.rfc-editor.org/info/rfc1930>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1982 "Serial Number Arithmetic"|http://www.rfc-editor.org/info/rfc1982>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC1996 "A Mechanism for Prompt Notification of Zone Changes (DNS NOTIFY)"|http://www.rfc-editor.org/info/rfc1996>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC2142 "Mailbox Names for Common Services, Roles and Functions"|http://www.rfc-editor.org/info/rfc2142>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC2181 "Clarifications to the DNS Specification"|http://www.rfc-editor.org/info/rfc2181>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC2182 "Selection and Operation of Secondary DNS Servers"|http://www.rfc-editor.org/info/rfc2182>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC2308 "Negative Caching of DNS Queries (DNS NCACHE)"|http://www.rfc-editor.org/info/rfc2308>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC2544 "Benchmarking Methodology for Network Interconnect Devices"|http://www.rfc-editor.org/info/rfc2544>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC2671 "Extension Mechanisms for DNS (EDNS0)"|http://www.rfc-editor.org/info/rfc2671>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC2822 "Internet Message Format"|http://www.rfc-editor.org/info/rfc2822>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC2870 "Root Name Server Operational Requirements"|http://www.rfc-editor.org/info/rfc2870>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC2928 "Initial IPv6 Sub-TLA ID Assignments"|http://www.rfc-editor.org/info/rfc2928>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC3056 "Connection of IPv6 Domains via IPv4 Clouds"|http://www.rfc-editor.org/info/rfc3056>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC3068 "An Anycast Prefix for 6to4 Relay Routers"|http://www.rfc-editor.org/info/rfc3068>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC3658 "Delegation Signer (DS) Resource Record (RR)"|http://www.rfc-editor.org/info/rfc3658>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC3696 "Application Techniques for Checking and Transformation of Names"|http://www.rfc-editor.org/info/rfc3696>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC3701 "6bone (IPv6 Testing Address Allocation) Phaseout"|http://www.rfc-editor.org/info/rfc3701>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC3849 "IPv6 Address Prefix Reserved for Documentation"|http://www.rfc-editor.org/info/rfc3849>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC3927 "Dynamic Configuration of IPv4 Link-Local Addresses"|http://www.rfc-editor.org/info/rfc3927>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC4034 "Resource Records for the DNS Security Extensions"|http://www.rfc-editor.org/info/rfc4034>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC4035 "Protocol Modifications for the DNS Security Extensions"|http://www.rfc-editor.org/info/rfc4035>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC4074 "Common Misbehavior Against DNS Queries for IPv6 Addresses"|http://www.rfc-editor.org/info/rfc4074>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC4193 "Unique Local IPv6 Unicast Addresses"|http://www.rfc-editor.org/info/rfc4193>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC4291 "IP Version 6 Addressing Architecture"|http://www.rfc-editor.org/info/rfc4291>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC4343 "Domain Name System (DNS) Case Insensitivity Clarification"|http://www.rfc-editor.org/info/rfc4343>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC4380 "Teredo: Tunneling IPv6 over UDP through Network Address Translations (NATs)"|http://www.rfc-editor.org/info/rfc4380>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC4843 "An IPv6 Prefix for Overlay Routable Cryptographic Hash Identifiers (ORCHID)"|http://www.rfc-editor.org/info/rfc4843>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC5155 "DNS Security (DNSSEC) Hashed Authenticated Denial of Existence"|http://www.rfc-editor.org/info/rfc5155>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC5156 "Special-Use IPv6 Addresses"|http://www.rfc-editor.org/info/rfc5156>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC5180 "IPv6 Benchmarking Methodology for Network Interconnect Devices"|http://www.rfc-editor.org/info/rfc5180>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC5321 "Simple Mail Transfer Protocol"|http://www.rfc-editor.org/info/rfc5321>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC5358 "Preventing Use of Recursive Nameservers in Reflector Attacks"|http://www.rfc-editor.org/info/rfc5358>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC5737 "IPv4 Address Blocks Reserved for Documentation"|http://www.rfc-editor.org/info/rfc5737>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC5771 "IANA Guidelines for IPv4 Multicast Address Assignments"|http://www.rfc-editor.org/info/rfc5771>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC5892 "The Unicode Code Points and Internationalized Domain Names for Applications (IDNA)"|http://www.rfc-editor.org/info/rfc5892>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC5936 "DNS Zone Transfer Protocol (AXFR)"|http://www.rfc-editor.org/info/rfc5936>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC6052 "IPv6 Addressing of IPv4/IPv6 Translators"|http://www.rfc-editor.org/info/rfc6052>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC6333 "Dual-Stack Lite Broadband Deployments Following IPv4 Exhaustion"|http://www.rfc-editor.org/info/rfc6333>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC6598 "IANA-Reserved IPv4 Prefix for Shared Address Space"|http://www.rfc-editor.org/info/rfc6598>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC6666 "A Discard Prefix for IPv6"|http://www.rfc-editor.org/info/rfc6666>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC6781 "DNSSEC Operational Practices, Version 2"|http://www.rfc-editor.org/info/rfc6781>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC6890 "Special-Purpose IP Address Registries"|http://www.rfc-editor.org/info/rfc6890>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC6891 "Extension Mechanisms for DNS (EDNS(0))"|http://www.rfc-editor.org/info/rfc6891>
|
||||
|
||||
=item
|
||||
|
||||
L<RFC7050 "Discovery of the IPv6 Prefix Used for IPv6 Address Synthesis"|http://www.rfc-editor.org/info/rfc7050>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
293
zonemaster-engine/lib/Zonemaster/Engine/Packet.pm
Normal file
293
zonemaster-engine/lib/Zonemaster/Engine/Packet.pm
Normal file
@@ -0,0 +1,293 @@
|
||||
package Zonemaster::Engine::Packet;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.5");
|
||||
|
||||
use Class::Accessor 'antlers';
|
||||
use Carp qw( confess );
|
||||
use Zonemaster::Engine::Util;
|
||||
|
||||
has 'packet' => (
|
||||
is => 'ro',
|
||||
isa => 'Zonemaster::LDNS::Packet',
|
||||
);
|
||||
|
||||
sub new {
|
||||
my $proto = shift;
|
||||
my $class = ref $proto || $proto;
|
||||
my $attrs = shift;
|
||||
|
||||
my $packet = delete $attrs->{packet};
|
||||
if ( %$attrs ) {
|
||||
confess "unexpected arguments: " . join ', ', sort keys %$attrs;
|
||||
}
|
||||
|
||||
return Class::Accessor::new( $class, { packet => $packet } );
|
||||
}
|
||||
|
||||
sub timestamp { my ( $self, $time ) = @_; return $self->packet->timestamp( $time // () ); }
|
||||
sub querytime { my ( $self, $value ) = @_; return $self->packet->querytime( $value // () ); }
|
||||
sub id { my ( $self, $id ) = @_; return $self->packet->id( $id // () ); }
|
||||
sub opcode { my ( $self, $string ) = @_; return $self->packet->opcode( $string // () ); }
|
||||
sub rcode { my ( $self, $string ) = @_; return $self->packet->rcode( $string // () ); }
|
||||
sub edns_version { my ( $self, $version ) = @_; return $self->packet->edns_version( $version // () ); }
|
||||
|
||||
sub type { my ( $self ) = @_; return $self->packet->type; }
|
||||
sub string { my ( $self ) = @_; return $self->packet->string; }
|
||||
sub data { my ( $self ) = @_; return $self->packet->data; }
|
||||
sub aa { my ( $self ) = @_; return $self->packet->aa; }
|
||||
sub do { my ( $self ) = @_; return $self->packet->do; }
|
||||
sub ra { my ( $self ) = @_; return $self->packet->ra; }
|
||||
sub tc { my ( $self ) = @_; return $self->packet->tc; }
|
||||
sub question { my ( $self ) = @_; return $self->packet->question; }
|
||||
sub authority { my ( $self ) = @_; return $self->packet->authority; }
|
||||
sub answer { my ( $self ) = @_; return $self->packet->answer; }
|
||||
sub additional { my ( $self ) = @_; return $self->packet->additional; }
|
||||
sub edns_size { my ( $self ) = @_; return $self->packet->edns_size; }
|
||||
sub edns_rcode { my ( $self ) = @_; return $self->packet->edns_rcode; }
|
||||
sub edns_data { my ( $self ) = @_; return $self->packet->edns_data; }
|
||||
sub edns_z { my ( $self ) = @_; return $self->packet->edns_z; }
|
||||
sub has_edns { my ( $self ) = @_; return $self->packet->has_edns; }
|
||||
|
||||
sub unique_push {
|
||||
my ( $self, $section, $rr ) = @_;
|
||||
return $self->packet->unique_push( $section, $rr );
|
||||
}
|
||||
|
||||
sub no_such_record {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( $self->type eq 'nodata' ) {
|
||||
my ( $q ) = $self->question;
|
||||
Zonemaster::Engine::Util::info( NO_SUCH_RECORD => { name => Zonemaster::Engine::Util::name( $q->name ), type => $q->type } );
|
||||
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub no_such_name {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( $self->type eq 'nxdomain' ) {
|
||||
my ( $q ) = $self->question;
|
||||
info( NO_SUCH_NAME => { name => name( $q->name ), type => $q->type } );
|
||||
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub is_redirect {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( $self->type eq 'referral' ) {
|
||||
my ( $q ) = $self->question;
|
||||
my ( $a ) = $self->authority;
|
||||
Zonemaster::Engine::Util::info(
|
||||
IS_REDIRECT => {
|
||||
name => Zonemaster::Engine::DNSName->from_string( $q->name ),
|
||||
type => $q->type,
|
||||
to => Zonemaster::Engine::DNSName->from_string( $a->name )
|
||||
}
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
} ## end sub is_redirect
|
||||
|
||||
sub get_records {
|
||||
my ( $self, $type, @section ) = @_;
|
||||
@section = qw(answer authority additional) if !@section;
|
||||
my %sec = map { lc( $_ ) => 1 } @section;
|
||||
my @raw;
|
||||
$type = uc( $type );
|
||||
|
||||
if ( $sec{'answer'} ) {
|
||||
push @raw, grep { $_->type eq $type } $self->packet->answer;
|
||||
}
|
||||
|
||||
if ( $sec{'authority'} ) {
|
||||
push @raw, grep { $_->type eq $type } $self->packet->authority;
|
||||
}
|
||||
|
||||
if ( $sec{'additional'} ) {
|
||||
push @raw, grep { $_->type eq $type } $self->packet->additional;
|
||||
}
|
||||
|
||||
return @raw;
|
||||
} ## end sub get_records
|
||||
|
||||
sub get_records_for_name {
|
||||
my ( $self, $type, $name, @section ) = @_;
|
||||
|
||||
# Make sure $name is a Zonemaster::Engine::DNSName
|
||||
$name = name( $name );
|
||||
|
||||
return grep { name( $_->name ) eq $name } $self->get_records( $type, @section );
|
||||
}
|
||||
|
||||
sub has_rrs_of_type_for_name {
|
||||
my ( $self, $type, $name, @section ) = @_;
|
||||
|
||||
# Make sure $name is a Zonemaster::Engine::DNSName
|
||||
$name = name( $name );
|
||||
|
||||
return ( grep { name( $_->name ) eq $name } $self->get_records( $type, @section ) ) > 0;
|
||||
}
|
||||
|
||||
sub answerfrom {
|
||||
my ( $self, @args ) = @_;
|
||||
|
||||
if ( @args ) {
|
||||
$self->packet->answerfrom( @args );
|
||||
}
|
||||
|
||||
my $from = $self->packet->answerfrom // '<unknown>';
|
||||
|
||||
return $from;
|
||||
}
|
||||
|
||||
sub TO_JSON {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return { 'Zonemaster::Engine::Packet' => $self->packet };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Packet - wrapping object for L<Zonemaster::LDNS::Packet> objects
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $packet = $ns->query('iis.se', 'NS');
|
||||
my @rrs = $packet->get_records('ns');
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item packet
|
||||
|
||||
Holds the L<Zonemaster::LDNS::Packet> the object is wrapping.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CONSTRUCTORS
|
||||
|
||||
=over
|
||||
|
||||
=item new
|
||||
|
||||
Construct a new instance.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item no_such_record
|
||||
|
||||
Returns true if the packet represents an existing DNS node lacking any records of the requested type.
|
||||
|
||||
=item no_such_name
|
||||
|
||||
Returns true if the packet represents a nonexistent DNS node.
|
||||
|
||||
=item is_redirect
|
||||
|
||||
Returns true if the packet is a redirect to another set of nameservers.
|
||||
|
||||
=item get_records($type[, @section])
|
||||
|
||||
Returns the L<Zonemaster::LDNS::RR> objects of the requested type in the packet.
|
||||
If the optional C<@section> argument is given, and is a list of C<answer>,
|
||||
C<authority> and C<additional>, only RRs from those sections are returned.
|
||||
|
||||
=item get_records_for_name($type, $name[, @section])
|
||||
|
||||
Returns all L<Zonemaster::LDNS::RR> objects for the given name in the packet.
|
||||
If the optional C<@section> argument is given, and is a list of C<answer>,
|
||||
C<authority> and C<additional>, only RRs from those sections are returned.
|
||||
|
||||
=item has_rrs_of_type_for_name($type, $name[, @section])
|
||||
|
||||
Returns true if the packet holds any RRs of the specified type for the given name.
|
||||
If the optional C<@section> argument is given, and is a list of C<answer>,
|
||||
C<authority> and C<additional>, only RRs from those sections are returned.
|
||||
|
||||
=item answerfrom
|
||||
|
||||
Wrapper for the underlying packet method, that replaces undefined values with the string C<E<lt>unknownE<gt>>.
|
||||
|
||||
=item TO_JSON
|
||||
|
||||
Support method for L<JSON> to be able to serialize these objects.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS PASSED THROUGH
|
||||
|
||||
These methods are passed through transparently to the underlying L<Zonemaster::LDNS::Packet> object.
|
||||
|
||||
=over
|
||||
|
||||
=item data
|
||||
|
||||
=item rcode
|
||||
|
||||
=item aa
|
||||
|
||||
=item ra
|
||||
|
||||
=item tc
|
||||
|
||||
=item question
|
||||
|
||||
=item answer
|
||||
|
||||
=item authority
|
||||
|
||||
=item additional
|
||||
|
||||
=item string
|
||||
|
||||
=item unique_push
|
||||
|
||||
=item timestamp
|
||||
|
||||
=item type
|
||||
|
||||
=item edns_size
|
||||
|
||||
=item edns_rcode
|
||||
|
||||
=item edns_version
|
||||
|
||||
=item edns_z
|
||||
|
||||
=item edns_data
|
||||
|
||||
=item has_edns
|
||||
|
||||
=item id
|
||||
|
||||
=item querytime
|
||||
|
||||
=item do
|
||||
|
||||
=item opcode
|
||||
|
||||
=back
|
||||
973
zonemaster-engine/lib/Zonemaster/Engine/Profile.pm
Normal file
973
zonemaster-engine/lib/Zonemaster/Engine/Profile.pm
Normal file
@@ -0,0 +1,973 @@
|
||||
package Zonemaster::Engine::Profile;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare( "v1.2.22" );
|
||||
|
||||
use File::ShareDir qw[dist_file];
|
||||
use JSON::PP qw( encode_json decode_json );
|
||||
use Scalar::Util qw(reftype looks_like_number);
|
||||
use File::Slurp;
|
||||
use Clone qw(clone);
|
||||
use Data::Dumper;
|
||||
use Net::IP::XS;
|
||||
use Log::Any qw( $log );
|
||||
use YAML::XS qw();
|
||||
|
||||
$YAML::XS::Boolean = "JSON::PP";
|
||||
|
||||
use Zonemaster::Engine::Constants qw( $DURATION_5_MINUTES_IN_SECONDS $DURATION_1_HOUR_IN_SECONDS $DURATION_4_HOURS_IN_SECONDS $DURATION_12_HOURS_IN_SECONDS $DURATION_1_DAY_IN_SECONDS $DURATION_1_WEEK_IN_SECONDS $DURATION_180_DAYS_IN_SECONDS );
|
||||
use Zonemaster::Engine::Validation qw( validate_ipv4 validate_ipv6 );
|
||||
|
||||
my %profile_properties_details = (
|
||||
q{cache} => {
|
||||
type => q{HashRef},
|
||||
test => sub {
|
||||
my @allowed_keys = ( 'redis' );
|
||||
foreach my $cache_database ( keys %{$_[0]} ) {
|
||||
if ( not grep( /^$cache_database$/, @allowed_keys ) ) {
|
||||
die "Property cache keys have " . scalar @allowed_keys . " possible values: " . join(", ", @allowed_keys) . "\n";
|
||||
}
|
||||
|
||||
if ( not scalar keys %{ $_[0]->{$cache_database} } ) {
|
||||
die "Property cache.$cache_database has no items\n";
|
||||
}
|
||||
else {
|
||||
my @allowed_subkeys;
|
||||
if ( $cache_database eq 'redis' ) {
|
||||
@allowed_subkeys = ( 'server', 'expire' );
|
||||
}
|
||||
|
||||
foreach my $key ( keys %{ $_[0]->{$cache_database} } ) {
|
||||
if ( not grep( /^$key$/, @allowed_subkeys ) ) {
|
||||
die "Property cache.$cache_database subkeys have " . scalar @allowed_subkeys . " possible values: " . join(", ", @allowed_subkeys) . "\n";
|
||||
}
|
||||
|
||||
die "Property cache.$cache_database.$key has a NULL or empty item\n" if not $_[0]->{$cache_database}->{$key};
|
||||
die "Property cache.$cache_database.$key has a negative value\n" if ( looks_like_number( $_[0]->{$cache_database}->{$key} ) and $_[0]->{$cache_database}->{$key} < 0 ) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
default => {},
|
||||
},
|
||||
q{resolver.defaults.debug} => {
|
||||
type => q{Bool}
|
||||
},
|
||||
q{resolver.defaults.igntc} => {
|
||||
type => q{Bool}
|
||||
},
|
||||
q{resolver.defaults.fallback} => {
|
||||
type => q{Bool}
|
||||
},
|
||||
q{resolver.defaults.recurse} => {
|
||||
type => q{Bool}
|
||||
},
|
||||
q{resolver.defaults.retrans} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
max => 255
|
||||
},
|
||||
q{resolver.defaults.retry} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
max => 255
|
||||
},
|
||||
q{resolver.defaults.usevc} => {
|
||||
type => q{Bool}
|
||||
},
|
||||
q{resolver.defaults.timeout} => {
|
||||
type => q{Num}
|
||||
},
|
||||
q{resolver.source4} => {
|
||||
type => q{Str},
|
||||
test => sub {
|
||||
unless ( $_[0] eq '' or validate_ipv4( $_[0] ) ) {
|
||||
die "Property resolver.source4 must be an IPv4 address or the empty string\n";
|
||||
}
|
||||
},
|
||||
default => q{}
|
||||
},
|
||||
q{resolver.source6} => {
|
||||
type => q{Str},
|
||||
test => sub {
|
||||
unless ( $_[0] eq '' or validate_ipv6( $_[0] ) ) {
|
||||
die "Property resolver.source6 must be a valid IPv6 address or the empty string\n";
|
||||
}
|
||||
},
|
||||
default => q{}
|
||||
},
|
||||
q{net.ipv4} => {
|
||||
type => q{Bool}
|
||||
},
|
||||
q{net.ipv6} => {
|
||||
type => q{Bool}
|
||||
},
|
||||
q{no_network} => {
|
||||
type => q{Bool}
|
||||
},
|
||||
q{asn_db.style} => {
|
||||
type => q{Str},
|
||||
test => sub {
|
||||
if ( lc($_[0]) ne q{cymru} and lc($_[0]) ne q{ripe} ) {
|
||||
die "Property asn_db.style has 2 possible values : Cymru or RIPE (case-insensitive)\n";
|
||||
}
|
||||
$_[0] = lc($_[0]);
|
||||
},
|
||||
default => q{cymru}
|
||||
},
|
||||
q{asn_db.sources} => {
|
||||
type => q{HashRef},
|
||||
test => sub {
|
||||
foreach my $db_style ( keys %{$_[0]} ) {
|
||||
if ( lc($db_style) ne q{cymru} and lc($db_style) ne q{ripe} ) {
|
||||
die "Property asn_db.sources keys have 2 possible values : Cymru or RIPE (case-insensitive)\n";
|
||||
}
|
||||
if ( not scalar @{ ${$_[0]}{$db_style} } ) {
|
||||
die "Property asn_db.sources.$db_style has no items\n";
|
||||
}
|
||||
else {
|
||||
foreach my $ndd ( @{ ${$_[0]}{$db_style} } ) {
|
||||
die "Property asn_db.sources.$db_style has a NULL item\n" if not defined $ndd;
|
||||
die "Property asn_db.sources.$db_style has a non scalar item\n" if not defined ref($ndd);
|
||||
die "Property asn_db.sources.$db_style has an item too long\n" if length($ndd) > 255;
|
||||
foreach my $label ( split /[.]/, $ndd ) {
|
||||
die "Property asn_db.sources.$db_style has a non domain name item\n" if $label !~ /^[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?$/;
|
||||
}
|
||||
}
|
||||
${$_[0]}{lc($db_style)} = delete ${$_[0]}{$db_style};
|
||||
}
|
||||
}
|
||||
},
|
||||
default => { cymru => [ "asnlookup.zonemaster.net" ] },
|
||||
},
|
||||
q{logfilter} => {
|
||||
type => q{HashRef},
|
||||
default => {}
|
||||
},
|
||||
q{test_levels} => {
|
||||
type => q{HashRef}
|
||||
},
|
||||
q{test_cases} => {
|
||||
type => q{ArrayRef}
|
||||
},
|
||||
q{test_cases_vars.dnssec04.REMAINING_SHORT} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
default => $DURATION_12_HOURS_IN_SECONDS
|
||||
},
|
||||
q{test_cases_vars.dnssec04.REMAINING_LONG} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
default => $DURATION_180_DAYS_IN_SECONDS
|
||||
},
|
||||
q{test_cases_vars.dnssec04.DURATION_LONG} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
default => $DURATION_180_DAYS_IN_SECONDS
|
||||
},
|
||||
q{test_cases_vars.zone02.SOA_REFRESH_MINIMUM_VALUE} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
default => $DURATION_4_HOURS_IN_SECONDS
|
||||
},
|
||||
q{test_cases_vars.zone04.SOA_RETRY_MINIMUM_VALUE} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
default => $DURATION_1_HOUR_IN_SECONDS
|
||||
},
|
||||
q{test_cases_vars.zone05.SOA_EXPIRE_MINIMUM_VALUE} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
default => $DURATION_1_WEEK_IN_SECONDS
|
||||
},
|
||||
q{test_cases_vars.zone06.SOA_DEFAULT_TTL_MAXIMUM_VALUE} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
default => $DURATION_1_DAY_IN_SECONDS
|
||||
},
|
||||
q{test_cases_vars.zone06.SOA_DEFAULT_TTL_MINIMUM_VALUE} => {
|
||||
type => q{Num},
|
||||
min => 1,
|
||||
default => $DURATION_5_MINUTES_IN_SECONDS
|
||||
}
|
||||
);
|
||||
|
||||
_init_profile_properties_details_defaults();
|
||||
|
||||
sub _init_profile_properties_details_defaults {
|
||||
my $default_file = dist_file( 'Zonemaster-Engine', 'profile.json');
|
||||
my $json = read_file( $default_file );
|
||||
my $default_values = decode_json( $json );
|
||||
foreach my $property_name ( keys %profile_properties_details ) {
|
||||
if ( defined _get_value_from_nested_hash( $default_values, split /[.]/, $property_name ) ) {
|
||||
$profile_properties_details{$property_name}{default} = clone _get_value_from_nested_hash( $default_values, split /[.]/, $property_name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _get_profile_paths {
|
||||
my ( $paths_ref, $data, @path ) = @_;
|
||||
|
||||
foreach my $key (sort keys %$data) {
|
||||
|
||||
my $path = join '.', @path, $key;
|
||||
if (ref($data->{$key}) eq 'HASH' and not exists $profile_properties_details{$path} ) {
|
||||
_get_profile_paths($paths_ref, $data->{$key}, @path, $key);
|
||||
next;
|
||||
}
|
||||
else {
|
||||
$paths_ref->{$path} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _get_value_from_nested_hash {
|
||||
my ( $hash_ref, @path ) = @_;
|
||||
|
||||
my $key = shift @path;
|
||||
if ( exists $hash_ref->{$key} ) {
|
||||
if ( @path ) {
|
||||
my $value_type = reftype($hash_ref->{$key});
|
||||
if ( $value_type eq q{HASH} ) {
|
||||
return _get_value_from_nested_hash( $hash_ref->{$key}, @path );
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return $hash_ref->{$key};
|
||||
}
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub _set_value_to_nested_hash {
|
||||
my ( $hash_ref, $value, @path ) = @_;
|
||||
|
||||
my $key = shift @path;
|
||||
|
||||
if ( ! exists $hash_ref->{$key} ) {
|
||||
$hash_ref->{$key} = {};
|
||||
}
|
||||
if ( @path ) {
|
||||
_set_value_to_nested_hash( $hash_ref->{$key}, $value, @path );
|
||||
}
|
||||
else {
|
||||
$hash_ref->{$key} = clone $value;
|
||||
}
|
||||
}
|
||||
|
||||
our $effective = Zonemaster::Engine::Profile->default;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
$self->{q{profile}} = {};
|
||||
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub default {
|
||||
my ( $class ) = @_;
|
||||
my $new = $class->new;
|
||||
foreach my $property_name ( keys %profile_properties_details ) {
|
||||
if ( exists $profile_properties_details{$property_name}{default} ) {
|
||||
$new->set( $property_name, $profile_properties_details{$property_name}{default} );
|
||||
}
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
sub all_properties {
|
||||
my ( $class ) = @_;
|
||||
return sort keys %profile_properties_details;
|
||||
}
|
||||
|
||||
sub get {
|
||||
my ( $self, $property_name ) = @_;
|
||||
|
||||
die "Unknown property '$property_name'\n" if not exists $profile_properties_details{$property_name};
|
||||
|
||||
if ( $profile_properties_details{$property_name}->{type} eq q{ArrayRef} or $profile_properties_details{$property_name}->{type} eq q{HashRef} ) {
|
||||
return clone _get_value_from_nested_hash( $self->{q{profile}}, split /[.]/, $property_name );
|
||||
} else {
|
||||
return _get_value_from_nested_hash( $self->{q{profile}}, split /[.]/, $property_name );
|
||||
}
|
||||
}
|
||||
|
||||
sub set {
|
||||
my ( $self, $property_name, $value ) = @_;
|
||||
|
||||
$self->_set( q{DIRECT}, $property_name, $value );
|
||||
}
|
||||
|
||||
sub _set {
|
||||
my ( $self, $from, $property_name, $value ) = @_;
|
||||
my $value_type = reftype($value);
|
||||
my $data_details;
|
||||
|
||||
die "Unknown property '$property_name'\n" if not exists $profile_properties_details{$property_name};
|
||||
|
||||
$data_details = sprintf "[TYPE=%s][FROM=%s][VALUE_TYPE=%s][VALUE=%s]\n%s",
|
||||
exists $profile_properties_details{$property_name}->{type} ? $profile_properties_details{$property_name}->{type} : q{UNDEF},
|
||||
defined $from ? $from : q{UNDEF},
|
||||
defined $value_type ? $value_type : q{UNDEF},
|
||||
defined $value ? $value : q{[UNDEF]},
|
||||
Data::Dumper::Dumper($value);
|
||||
# $value is a Scalar
|
||||
if ( ! $value_type or $value_type eq q{SCALAR} ) {
|
||||
die "Property $property_name can not be undef\n" if not defined $value;
|
||||
|
||||
# Boolean
|
||||
if ( $profile_properties_details{$property_name}->{type} eq q{Bool} ) {
|
||||
if ( $from eq q{DIRECT} and !$value ) {
|
||||
$value = JSON::PP::false;
|
||||
}
|
||||
elsif ( $from eq q{DIRECT} and $value ) {
|
||||
$value = JSON::PP::true;
|
||||
}
|
||||
elsif ( $from eq q{JSON} and $value_type and $value == JSON::PP::false ) {
|
||||
$value = JSON::PP::false;
|
||||
}
|
||||
elsif ( $from eq q{JSON} and $value_type and $value == JSON::PP::true ) {
|
||||
$value = JSON::PP::true;
|
||||
}
|
||||
else {
|
||||
die "Property $property_name is of type Boolean $data_details\n";
|
||||
}
|
||||
}
|
||||
# Number. In our case, only non-negative integers
|
||||
elsif ( $profile_properties_details{$property_name}->{type} eq q{Num} ) {
|
||||
if ( $value !~ /^(\d+)$/ ) {
|
||||
die "Property $property_name is of type non-negative integer $data_details\n";
|
||||
}
|
||||
if ( exists $profile_properties_details{$property_name}->{min} and $value < $profile_properties_details{$property_name}->{min} ) {
|
||||
die "Property $property_name value is out of limit (smaller)\n";
|
||||
}
|
||||
if ( exists $profile_properties_details{$property_name}->{max} and $value > $profile_properties_details{$property_name}->{max} ) {
|
||||
die "Property $property_name value is out of limit (bigger)\n";
|
||||
}
|
||||
|
||||
$value = 0+ $value; # Make sure JSON::PP doesn't serialize it as a JSON string
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Array
|
||||
if ( $profile_properties_details{$property_name}->{type} eq q{ArrayRef} and reftype($value) ne q{ARRAY} ) {
|
||||
die "Property $property_name is not a ArrayRef $data_details\n";
|
||||
}
|
||||
# Hash
|
||||
elsif ( $profile_properties_details{$property_name}->{type} eq q{HashRef} and reftype($value) ne q{HASH} ) {
|
||||
die "Property $property_name is not a HashRef $data_details\n";
|
||||
}
|
||||
elsif ( $profile_properties_details{$property_name}->{type} eq q{Bool} or $profile_properties_details{$property_name}->{type} eq q{Num} or $profile_properties_details{$property_name}->{type} eq q{Str} ) {
|
||||
die "Property $property_name is a Scalar $data_details\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ( $profile_properties_details{$property_name}->{test} ) {
|
||||
$profile_properties_details{$property_name}->{test}->( $value );
|
||||
}
|
||||
|
||||
return _set_value_to_nested_hash( $self->{q{profile}}, $value, split /[.]/, $property_name );
|
||||
}
|
||||
|
||||
sub merge {
|
||||
my ( $self, $other_profile ) = @_;
|
||||
|
||||
die "Merge with ", __PACKAGE__, " only\n" if ref($other_profile) ne __PACKAGE__;
|
||||
|
||||
foreach my $property_name ( keys %profile_properties_details ) {
|
||||
if ( defined _get_value_from_nested_hash( $other_profile->{q{profile}}, split /[.]/, $property_name ) ) {
|
||||
$self->_set( q{JSON}, $property_name, _get_value_from_nested_hash( $other_profile->{q{profile}}, split /[.]/, $property_name ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $other_profile->{q{profile}};
|
||||
}
|
||||
|
||||
sub from_json {
|
||||
my ( $class, $json ) = @_;
|
||||
my $new = $class->new;
|
||||
my $internal = decode_json( $json );
|
||||
my %paths;
|
||||
_get_profile_paths(\%paths, $internal);
|
||||
foreach my $property_name ( keys %paths ) {
|
||||
if ( defined _get_value_from_nested_hash( $internal, split /[.]/, $property_name ) ) {
|
||||
$new->_set( q{JSON}, $property_name, _get_value_from_nested_hash( $internal, split /[.]/, $property_name ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
sub to_json {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return encode_json( $self->{q{profile}} );
|
||||
}
|
||||
|
||||
sub from_yaml {
|
||||
my ( $class, $yaml ) = @_;
|
||||
my $data = YAML::XS::Load( $yaml );
|
||||
return $class->from_json( encode_json( $data ) );
|
||||
}
|
||||
|
||||
sub to_yaml {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return YAML::XS::Dump( $self->{q{profile}} );
|
||||
}
|
||||
|
||||
sub effective {
|
||||
return $effective;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Profile - A simple system for configuring Zonemaster Engine
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module has two parts:
|
||||
|
||||
=over
|
||||
|
||||
=item * a I<profile> representation class
|
||||
|
||||
=item * a global profile object (the I<effective profile>) that configures Zonemaster Engine
|
||||
|
||||
=back
|
||||
|
||||
A I<profile> consists of a collection of named properties.
|
||||
|
||||
The properties determine the configurable behaviors of Zonemaster
|
||||
Engine with regard to what tests are to be performed, how they are to
|
||||
be performed, and how the results are to be analyzed.
|
||||
For details on available properties see the L</PROFILE PROPERTIES>
|
||||
section.
|
||||
|
||||
Here is an example for updating the effective profile with values from
|
||||
a given file and setting all properties not mentioned in the file to
|
||||
default values.
|
||||
For details on the file format see the L</REPRESENTATIONS> section.
|
||||
|
||||
use Zonemaster::Engine::Profile;
|
||||
|
||||
my $json = read_file( "/path/to/foo.profile" );
|
||||
my $foo = Zonemaster::Engine::Profile->from_json( $json );
|
||||
my $profile = Zonemaster::Engine::Profile->default;
|
||||
$profile->merge( $foo );
|
||||
Zonemaster::Engine::Profile->effective->merge( $profile );
|
||||
|
||||
Here is an example for serializing the default profile to JSON.
|
||||
|
||||
my $string = Zonemaster::Engine::Profile->default->to_json;
|
||||
|
||||
For any given profile:
|
||||
|
||||
=over
|
||||
|
||||
=item * At any moment, each property is either set or unset.
|
||||
|
||||
=item * At any moment, every set property has a valid value.
|
||||
|
||||
=item * It is possible to set the value of each unset property.
|
||||
|
||||
=item * It is possible to update the value of each set property.
|
||||
|
||||
=item * It is NOT possible to unset the value of any set property.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS ATTRIBUTES
|
||||
|
||||
=head2 effective
|
||||
|
||||
A L<Zonemaster::Engine::Profile>.
|
||||
This is the effective profile.
|
||||
It serves as the global runtime configuration for Zonemaster Engine.
|
||||
Update it to change the configuration.
|
||||
|
||||
The effective profile is initialized with the default values declared
|
||||
in the L</PROFILE PROPERTIES> section.
|
||||
|
||||
All properties in the effective profile are always set (to valid values).
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=head2 new
|
||||
|
||||
A constructor that returns a new profile with all properties unset.
|
||||
|
||||
my $profile = Zonemaster::Engine::Profile->new;
|
||||
|
||||
=head2 default
|
||||
|
||||
A constructor that returns a new profile with the default property
|
||||
values declared in the L</PROFILE PROPERTIES> section.
|
||||
|
||||
my $default = Zonemaster::Engine::Profile->default;
|
||||
|
||||
=head2 from_json
|
||||
|
||||
A constructor that returns a new profile with values parsed from a JSON string.
|
||||
|
||||
my $profile = Zonemaster::Engine::Profile->from_json( '{ "no_network": true }' );
|
||||
|
||||
The returned profile has set values for all properties specified in the
|
||||
given string.
|
||||
The remaining properties are unset.
|
||||
|
||||
Dies if the given string is illegal according to the L</JSON REPRESENTATION>
|
||||
section or if the property values are illegal according to the L</PROFILE
|
||||
PROPERTIES> section.
|
||||
|
||||
=head2 from_yaml
|
||||
|
||||
A constructor that returns a new profile with values parsed from a YAML string.
|
||||
|
||||
my $profile = Zonemaster::Engine::Profile->from_yaml( <<EOF
|
||||
no_network: true
|
||||
EOF
|
||||
);
|
||||
|
||||
The returned profile has set values for all properties specified in the
|
||||
given string.
|
||||
The remaining properties are unset.
|
||||
|
||||
Dies if the given string is illegal according to the L</YAML REPRESENTATION>
|
||||
section or if the property values are illegal according to the L</PROFILE
|
||||
PROPERTIES> section.
|
||||
|
||||
=head1 INSTANCE METHODS
|
||||
|
||||
=head2 get
|
||||
|
||||
Get the value of a property.
|
||||
|
||||
my $value = $profile1->get( 'net.ipv6' );
|
||||
|
||||
Returns value of the given property, or C<undef> if the property is unset.
|
||||
For boolean properties the returned value is either C<1> for true or C<0> for
|
||||
false.
|
||||
For properties with complex types, the returned value is a
|
||||
L<deep copy|https://en.wiktionary.org/wiki/deep_copy#Noun>.
|
||||
|
||||
Dies if the given property name is invalid.
|
||||
|
||||
=head2 set
|
||||
|
||||
Set the value of a property.
|
||||
|
||||
$profile1->set( 'net.ipv6', 0 );
|
||||
|
||||
Takes a property name and value and updates the property accordingly.
|
||||
For boolean properties any truthy value is interpreted as true and any falsy
|
||||
value except C<undef> is interpreted as false.
|
||||
|
||||
Dies if the given property name is invalid.
|
||||
|
||||
Dies if the value is C<undef> or otherwise invalid for the given property.
|
||||
|
||||
=head2 merge
|
||||
|
||||
Merge the profile data of another profile into this one.
|
||||
|
||||
$profile1->merge( $other );
|
||||
|
||||
Properties from the other profile take precedence when the same property
|
||||
name exists in both profiles.
|
||||
The other profile object remains unmodified.
|
||||
|
||||
=head2 to_json
|
||||
|
||||
Serialize the profile to the L</JSON REPRESENTATION> format.
|
||||
|
||||
my $string = $profile->to_json();
|
||||
|
||||
Returns a string.
|
||||
|
||||
=head2 to_yaml
|
||||
|
||||
Serialize the profile to the L</JSON REPRESENTATION> format.
|
||||
|
||||
my $string = $profile->to_yaml();
|
||||
|
||||
Returns a string.
|
||||
|
||||
=head2 all_properties
|
||||
|
||||
Get the names of all properties.
|
||||
|
||||
Returns a sorted list of strings.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=head2 _get_profile_paths
|
||||
|
||||
Internal method used to get all the paths of a nested hashes-of-hashes.
|
||||
It creates a hash where keys are dotted keys of the nested hashes-of-hashes
|
||||
that exist in %profile_properties_details.
|
||||
|
||||
_get_profile_paths(\%paths, $internal);
|
||||
|
||||
=head2 _get_value_from_nested_hash
|
||||
|
||||
Internal method used to get a value in a nested hashes-of-hashes.
|
||||
|
||||
_get_value_from_nested_hash( $hash_ref, @path );
|
||||
|
||||
Where $hash_ref is the hash to explore and @path are the labels of the property to get.
|
||||
|
||||
@path = split /\./, q{resolver.defaults.usevc};
|
||||
|
||||
=head2 _set_value_to_nested_hash
|
||||
|
||||
Internal method used to set a value in a nested hashes-of-hashes.
|
||||
|
||||
_set_value_from_nested_hash( $hash_ref, $value, @path );
|
||||
|
||||
Where $hash_ref is the hash to explore and @path are the labels of the property to set.
|
||||
|
||||
@path = split /\./, q{resolver.defaults.usevc};
|
||||
|
||||
=head1 PROFILE PROPERTIES
|
||||
|
||||
Each property has a name and is either set or unset.
|
||||
If it is set it has a value that is valid for that specific property.
|
||||
Here is a listing of all the properties and their respective sets of
|
||||
valid values.
|
||||
|
||||
=head2 resolver.defaults.retrans
|
||||
|
||||
An integer between 1 and 255 inclusive. The number of seconds between retries.
|
||||
Default 3.
|
||||
|
||||
=head2 resolver.defaults.retry
|
||||
|
||||
An integer between 1 and 255 inclusive.
|
||||
The number of times a query is sent before we give up. Default 2.
|
||||
|
||||
=head2 resolver.defaults.fallback
|
||||
|
||||
A boolean. If true, UDP queries that get responses with the C<TC>
|
||||
flag set will be automatically resent over TCP or using EDNS. Default
|
||||
true.
|
||||
|
||||
In ldns-1.7.0 (NLnet Labs), in case of truncated answer when UDP is used,
|
||||
the same query is resent with EDNS0 and TCP (if needed). If you
|
||||
want the original answer (with TC bit set) and avoid this kind of
|
||||
replay, set this flag to false.
|
||||
|
||||
=head2 resolver.source4
|
||||
|
||||
A string representation of an IPv4 address or the empty string.
|
||||
The source address all resolver objects should use when sending queries over IPv4.
|
||||
|
||||
If set to "" (empty string), the OS default IPv4 address is used.
|
||||
|
||||
Default: "" (empty string).
|
||||
|
||||
=head2 resolver.source6
|
||||
|
||||
A string representation of an IPv6 address or the empty string.
|
||||
The source address all resolver objects should use when sending queries over IPv6.
|
||||
|
||||
If set to "" (empty string), the OS default IPv6 address is used.
|
||||
|
||||
Default: "" (empty string).
|
||||
|
||||
=head2 resolver.defaults.igntc
|
||||
|
||||
A boolean. Default false. Ignored. Deprecated and planned for removal in v2026.1. Remove it from your profile file.
|
||||
|
||||
=head2 resolver.defaults.recurse
|
||||
|
||||
A boolean. Default false. Ignored. Deprecated and planned for removal in v2026.1. Remove it from your profile file.
|
||||
|
||||
=head2 resolver.defaults.usevc
|
||||
|
||||
A boolean. Default false. Ignored. Deprecated and planned for removal in v2026.1. Remove it from your profile file.
|
||||
|
||||
=head2 net.ipv4
|
||||
|
||||
A boolean. If true, resolver objects are allowed to send queries over
|
||||
IPv4. Default true.
|
||||
|
||||
=head2 net.ipv6
|
||||
|
||||
A boolean. If true, resolver objects are allowed to send queries over
|
||||
IPv6. Default true.
|
||||
|
||||
=head2 no_network
|
||||
|
||||
A boolean. If true, network traffic is forbidden. Default false.
|
||||
|
||||
Use when you want to be sure that any data is only taken from a preloaded
|
||||
cache.
|
||||
|
||||
=head2 asn_db.style
|
||||
|
||||
A string that is either C<"Cymru"> or C<"RIPE"> (case-insensitive).
|
||||
|
||||
Defines which service will be used for AS lookup zones.
|
||||
|
||||
Default C<"Cymru">.
|
||||
|
||||
=head2 asn_db.sources
|
||||
|
||||
A hash of arrayrefs of strings. The currently supported keys are C<"Cymru"> or C<"RIPE"> (case-insensitive).
|
||||
|
||||
For C<"Cymru">, the strings are domain names. For C<"RIPE">, they are WHOIS servers. Normally only the first
|
||||
item in the list will be used, the rest are backups in case the previous ones didn't work.
|
||||
|
||||
Default C<{Cymru: [ "asnlookup.zonemaster.net", "asn.cymru.com" ], RIPE: [ "riswhois.ripe.net" ]}>.
|
||||
|
||||
=head2 cache
|
||||
|
||||
A hash of hashes. The currently supported key is C<"redis">.
|
||||
Default C<{}>.
|
||||
|
||||
=head3 redis
|
||||
|
||||
A hashref. The currently supported keys are C<"server"> and C<"expire">.
|
||||
|
||||
Specifies the address of the Redis server used to perform global caching
|
||||
(C<cache.redis.server>) and an optional expire time (C<cache.redis.expire>).
|
||||
|
||||
C<cache.redis.server> must be a string in the form C<host:port>.
|
||||
C<cache.redis.expire> must be a non-negative integer and defines a time in seconds.
|
||||
Default is 300 seconds.
|
||||
|
||||
=head2 logfilter
|
||||
|
||||
A complex data structure. Default C<{}>.
|
||||
|
||||
Specifies the severity level of each tag emitted by a specific module.
|
||||
The intended use is to remove known erroneous results.
|
||||
E.g. if you know that a certain name server is recursive and for some
|
||||
reason should be, you can use this functionality to lower the severity
|
||||
of the complaint about it to a lower level than normal.
|
||||
The C<test_levels> item also specifies tag severity level, but with
|
||||
coarser granularity and lower precedence.
|
||||
|
||||
The data under the C<logfilter> key should be structured like this:
|
||||
|
||||
Module
|
||||
Tag
|
||||
Array of exceptions
|
||||
"when"
|
||||
Hash with conditions
|
||||
"set"
|
||||
Severity level to set if all conditions match
|
||||
|
||||
The hash with conditions should have keys matching the attributes of
|
||||
the log entry that's being filtered (check the translation files to see
|
||||
what they are). The values for the keys should be either a single value
|
||||
that the attribute should be, or an array of values any one of which the
|
||||
attribute should be.
|
||||
|
||||
A complete logfilter structure might look like this:
|
||||
|
||||
{
|
||||
"A_MODULE": {
|
||||
"SOME_TAG": [
|
||||
{
|
||||
"when": {
|
||||
"count": 1,
|
||||
"type": [
|
||||
"this",
|
||||
"or"
|
||||
]
|
||||
},
|
||||
"set": "INFO"
|
||||
},
|
||||
{
|
||||
"when": {
|
||||
"count": 128,
|
||||
"type": [
|
||||
"that"
|
||||
]
|
||||
},
|
||||
"set": "INFO"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ANOTHER_MODULE": {
|
||||
"OTHER_TAG": [
|
||||
{
|
||||
"when": {
|
||||
"bananas": 0
|
||||
},
|
||||
"set": "WARNING"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
This would set the severity level to C<INFO> for any C<A_MODULE:SOME_TAG>
|
||||
messages that had a C<count> attribute set to 1 and a C<type> attribute
|
||||
set to either C<this> or C<or>.
|
||||
This also would set the level to C<INFO> for any C<A_MODULE:SOME_TAG>
|
||||
messages that had a C<count> attribute set to 128 and a C<type> attribute
|
||||
set to C<that>.
|
||||
And this would set the level to C<WARNING> for any C<ANOTHER_MODULE:OTHER_TAG>
|
||||
messages that had a C<bananas> attribute set to 0.
|
||||
|
||||
=head2 test_levels
|
||||
|
||||
A complex data structure.
|
||||
|
||||
Specifies the severity level of each tag emitted by a specific module.
|
||||
The C<logfilter> item also specifies tag severity level, but with finer
|
||||
granularity and higher precedence.
|
||||
|
||||
At the top level of this data structure are two levels of nested hashrefs.
|
||||
The keys of the top level hash are names of test implementation modules
|
||||
(without the C<Zonemaster::Engine::Test::> prefix).
|
||||
The keys of the second level hashes are tags that the respective
|
||||
modules emit.
|
||||
The values of the second level hashes are mapped to severity levels.
|
||||
|
||||
The various L<test case specifications|
|
||||
https://github.com/zonemaster/zonemaster/tree/master/docs/specifications/tests/README.md>
|
||||
define the default severity level for some of the messages.
|
||||
These specifications are the only authoritative documents on the default
|
||||
severity level for the various messages.
|
||||
For messages not defined in any of these specifications you can use the
|
||||
following command to query the default severity level directly from the actual
|
||||
default profile.
|
||||
|
||||
```sh
|
||||
perl -MZonemaster::Engine::Test -E 'say Zonemaster::Engine::Profile->default->to_json' | jq -S .test_levels
|
||||
```
|
||||
|
||||
For messages neither defined in test specifications, nor listed in the default
|
||||
profile, the default severity level is C<DEBUG>.
|
||||
|
||||
I<Note:> Sometimes multiple test cases within the same test module define
|
||||
messages for the same tag.
|
||||
When they do, it is imperative that all test cases define the same severity
|
||||
level for the tag.
|
||||
|
||||
=head2 test_cases
|
||||
|
||||
An arrayref of names of implemented test cases (in all lower-case) as listed in the
|
||||
L<test case specifications|https://github.com/zonemaster/zonemaster/tree/master/docs/specifications/tests/ImplementedTestCases.md>.
|
||||
Default is an arrayref listing all the test cases.
|
||||
|
||||
Specifies which test cases can be run by the testing suite.
|
||||
|
||||
=head2 test_cases_vars.dnssec04.REMAINING_SHORT
|
||||
|
||||
A positive integer value.
|
||||
Recommended lower bound for signatures' remaining validity time (in seconds) in
|
||||
test case L<DNSSEC04|
|
||||
https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/DNSSEC-TP/dnssec04.md>.
|
||||
Related to the REMAINING_SHORT message tag from this test case.
|
||||
Default C<43200> (12 hours in seconds).
|
||||
|
||||
=head2 test_cases_vars.dnssec04.REMAINING_LONG
|
||||
|
||||
A positive integer value.
|
||||
Recommended upper bound for signatures' remaining validity time (in seconds) in
|
||||
test case L<DNSSEC04|
|
||||
https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/DNSSEC-TP/dnssec04.md>.
|
||||
Related to the REMAINING_LONG message tag from this test case.
|
||||
Default C<15552000> (180 days in seconds).
|
||||
|
||||
=head2 test_cases_vars.dnssec04.DURATION_LONG
|
||||
|
||||
A positive integer value.
|
||||
Recommended upper bound for signatures' lifetime (in seconds) in the test case
|
||||
L<DNSSEC04|
|
||||
https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/DNSSEC-TP/dnssec04.md>.
|
||||
Related to the DURATION_LONG message tag from this test case.
|
||||
Default C<15552000> (180 days in seconds).
|
||||
|
||||
=head2 test_cases_vars.zone02.SOA_REFRESH_MINIMUM_VALUE
|
||||
|
||||
A positive integer value.
|
||||
Recommended lower bound for SOA refresh values (in seconds) in test case
|
||||
L<ZONE02|
|
||||
https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/Zone-TP/zone02.md>.
|
||||
Related to the REFRESH_MINIMUM_VALUE_LOWER message tag from this test case.
|
||||
Default C<14400> (4 hours in seconds).
|
||||
|
||||
=head2 test_cases_vars.zone04.SOA_RETRY_MINIMUM_VALUE
|
||||
|
||||
A positive integer value.
|
||||
Recommended lower bound for SOA retry values (in seconds) in test case
|
||||
L<ZONE04|
|
||||
https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/Zone-TP/zone04.md>.
|
||||
Related to the RETRY_MINIMUM_VALUE_LOWER message tag from this test case.
|
||||
Default C<3600> (1 hour in seconds).
|
||||
|
||||
=head2 test_cases_vars.zone05.SOA_EXPIRE_MINIMUM_VALUE
|
||||
|
||||
A positive integer value.
|
||||
Recommended lower bound for SOA expire values (in seconds) in test case
|
||||
L<ZONE05|
|
||||
https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/Zone-TP/zone05.md>.
|
||||
Related to the EXPIRE_MINIMUM_VALUE_LOWER message tag from this test case.
|
||||
Default C<604800> (1 week in seconds).
|
||||
|
||||
=head2 test_cases_vars.zone06.SOA_DEFAULT_TTL_MINIMUM_VALUE
|
||||
|
||||
A positive integer value.
|
||||
Recommended lower bound for SOA minimum values (in seconds) in test case
|
||||
L<ZONE06|
|
||||
https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/Zone-TP/zone06.md>.
|
||||
Related to the SOA_DEFAULT_TTL_MAXIMUM_VALUE_LOWER message tag from this test case.
|
||||
Default C<300> (5 minutes in seconds).
|
||||
|
||||
=head2 test_cases_vars.zone06.SOA_DEFAULT_TTL_MAXIMUM_VALUE
|
||||
|
||||
A positive integer value.
|
||||
Recommended upper bound for SOA minimum values (in seconds) in test case
|
||||
L<ZONE06|
|
||||
https://github.com/zonemaster/zonemaster/blob/master/docs/specifications/tests/Zone-TP/zone06.md>.
|
||||
Related to the SOA_DEFAULT_TTL_MAXIMUM_VALUE_HIGHER message tag from this test case.
|
||||
Default C<86400> (1 day in seconds).
|
||||
|
||||
=head1 REPRESENTATIONS
|
||||
|
||||
=head2 JSON REPRESENTATION
|
||||
|
||||
Property names in L</PROFILE PROPERTIES> section correspond to paths in
|
||||
a datastructure of nested JSON objects.
|
||||
Property values are stored at their respective paths.
|
||||
Paths are formed from property names by splitting them at dot characters
|
||||
(U+002E).
|
||||
The left-most path component corresponds to a key in the top-most
|
||||
JSON object.
|
||||
Properties with unset values are omitted in the JSON representation.
|
||||
|
||||
For a complete example, refer to the file located by L<dist_file(
|
||||
"Zonemaster-Engine", "default.profile" )|File::ShareDir/dist_file>.
|
||||
A profile with the only two properties set, C<net.ipv4> = true and
|
||||
C<net.ipv6> = true has this JSON representation:
|
||||
|
||||
{
|
||||
"net": {
|
||||
"ipv4": true,
|
||||
"ipv6": true
|
||||
}
|
||||
}
|
||||
|
||||
=head2 YAML REPRESENTATION
|
||||
|
||||
Similar to the L</JSON REPRESENTATION> but uses a YAML format.
|
||||
|
||||
=cut
|
||||
665
zonemaster-engine/lib/Zonemaster/Engine/Recursor.pm
Normal file
665
zonemaster-engine/lib/Zonemaster/Engine/Recursor.pm
Normal file
@@ -0,0 +1,665 @@
|
||||
package Zonemaster::Engine::Recursor;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.1.0");
|
||||
|
||||
use Carp;
|
||||
use Class::Accessor "antlers";
|
||||
use File::ShareDir qw[dist_file];
|
||||
use File::Slurp qw( read_file );
|
||||
use JSON::PP;
|
||||
use Net::IP::XS;
|
||||
use List::MoreUtils qw[uniq];
|
||||
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Engine::DNSName;
|
||||
use Zonemaster::Engine::Util qw( name ns parse_hints );
|
||||
use Zonemaster::Engine::Constants ":cname";
|
||||
|
||||
our %recurse_cache;
|
||||
our %_fake_addresses_cache;
|
||||
|
||||
sub init_recursor {
|
||||
my $hints_path = dist_file( 'Zonemaster-Engine', 'named.root' );
|
||||
my $hints_text = read_file( $hints_path );
|
||||
my $hints_data = parse_hints( $hints_text );
|
||||
Zonemaster::Engine::Recursor->add_fake_addresses( '.', $hints_data );
|
||||
}
|
||||
|
||||
sub add_fake_addresses {
|
||||
my ( $class, $domain, $href ) = @_;
|
||||
$domain = lc $domain;
|
||||
|
||||
foreach my $name ( keys %{$href} ) {
|
||||
my @ips = uniq @{ $href->{$name} };
|
||||
$name = lc $name;
|
||||
|
||||
push @{ $_fake_addresses_cache{$domain}{$name} }, ();
|
||||
foreach my $ip ( @ips ) {
|
||||
push @{ $_fake_addresses_cache{$domain}{$name} }, $ip;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub has_fake_addresses {
|
||||
my ( $class, $domain ) = @_;
|
||||
$domain = lc $domain;
|
||||
|
||||
return !!$_fake_addresses_cache{$domain};
|
||||
}
|
||||
|
||||
sub get_fake_addresses {
|
||||
my ( $class, $domain, $nsname ) = @_;
|
||||
( defined $domain ) or croak 'Argument must be defined: $domain';
|
||||
|
||||
$domain = lc $domain;
|
||||
$nsname = ( defined $nsname ) ? lc $nsname : q{};
|
||||
|
||||
if ( exists $_fake_addresses_cache{$domain}{$nsname} ) {
|
||||
return @{ $_fake_addresses_cache{$domain}{$nsname} };
|
||||
}
|
||||
else {
|
||||
return ();
|
||||
}
|
||||
}
|
||||
|
||||
sub get_fake_names {
|
||||
my ( $class, $domain ) = @_;
|
||||
$domain = lc $domain;
|
||||
|
||||
if ( exists $_fake_addresses_cache{$domain} ) {
|
||||
return keys %{$_fake_addresses_cache{$domain}};
|
||||
}
|
||||
else {
|
||||
return ();
|
||||
}
|
||||
}
|
||||
|
||||
sub remove_fake_addresses {
|
||||
my ( $class, $domain ) = @_;
|
||||
$domain = lc $domain;
|
||||
|
||||
delete $_fake_addresses_cache{$domain};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub recurse {
|
||||
my ( $class, $name, $type, $dns_class, $ns ) = @_;
|
||||
$name = name( $name );
|
||||
$type //= 'A';
|
||||
$dns_class //= 'IN';
|
||||
|
||||
Zonemaster::Engine->logger->add( RECURSE => { name => $name, type => $type, class => $dns_class } );
|
||||
if ( exists $recurse_cache{$name}{$type}{$dns_class} ) {
|
||||
return $recurse_cache{$name}{$type}{$dns_class};
|
||||
}
|
||||
|
||||
my %state = ( ns => [ root_servers() ], count => 0, common => 0, seen => {}, glue => {} );
|
||||
if ( defined $ns ) {
|
||||
ref( $ns ) eq 'ARRAY' or croak 'Argument $ns must be an arrayref';
|
||||
$state{ns} = $ns;
|
||||
}
|
||||
|
||||
my ( $p, $state ) = $class->_recurse( $name, $type, $dns_class, \%state );
|
||||
$recurse_cache{$name}{$type}{$dns_class} = $p;
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
sub parent {
|
||||
my ( $class, $name ) = @_;
|
||||
$name = name( $name );
|
||||
|
||||
my ( $p, $state ) =
|
||||
$class->_recurse( $name, 'SOA', 'IN',
|
||||
{ ns => [ root_servers() ], count => 0, common => 0, seen => {}, glue => {} } );
|
||||
|
||||
my $pname;
|
||||
if ( name( $state->{trace}[0][0] ) eq name( $name ) ) {
|
||||
$pname = name( $state->{trace}[1][0] );
|
||||
}
|
||||
else {
|
||||
$pname = name( $state->{trace}[0][0] );
|
||||
}
|
||||
|
||||
# Extra check that parent really is parent.
|
||||
if ( $name->next_higher ne $pname ) {
|
||||
my $source_ns = $state->{trace}[0][1];
|
||||
my $source_ip = $state->{trace}[0][2];
|
||||
|
||||
# No $source_ns means we're looking at root taken from priming
|
||||
if ( $source_ns ) {
|
||||
my $pp;
|
||||
if ( $source_ns->can( 'query' ) ) {
|
||||
$pp = $source_ns->query( $name->next_higher->string, 'SOA' );
|
||||
}
|
||||
else {
|
||||
my $n = ns( $source_ns, $source_ip );
|
||||
$pp = $n->query( $name->next_higher->string, 'SOA' );
|
||||
}
|
||||
if ( $pp ) {
|
||||
my ( $rr ) = $pp->get_records( 'SOA', 'answer' );
|
||||
if ( $rr ) {
|
||||
$pname = name( $rr->owner );
|
||||
}
|
||||
}
|
||||
}
|
||||
} ## end if ( $name->next_higher...)
|
||||
|
||||
if ( wantarray() ) {
|
||||
return ( $pname, $p );
|
||||
}
|
||||
else {
|
||||
return $pname;
|
||||
}
|
||||
} ## end sub parent
|
||||
|
||||
sub _resolve_cname {
|
||||
my ( $class, $name, $type, $dns_class, $p, $state ) = @_;
|
||||
$name = name( $name );
|
||||
Zonemaster::Engine->logger->add( CNAME_START => { name => $name, type => $type, dns_class => $dns_class } );
|
||||
|
||||
my @cname_rrs = $p->get_records( 'CNAME', 'answer' );
|
||||
|
||||
# Remove duplicate CNAME RRs
|
||||
my ( %duplicate_cname_rrs, @original_rrs );
|
||||
for my $rr ( @cname_rrs ) {
|
||||
my $rr_hash = $rr->class . '/CNAME/' . lc($rr->owner) . '/' . lc($rr->cname);
|
||||
|
||||
if ( exists $duplicate_cname_rrs{$rr_hash} ) {
|
||||
$duplicate_cname_rrs{$rr_hash}++;
|
||||
}
|
||||
else {
|
||||
$duplicate_cname_rrs{$rr_hash} = 0;
|
||||
push @original_rrs, $rr;
|
||||
}
|
||||
}
|
||||
|
||||
unless ( scalar @original_rrs == scalar @cname_rrs ) {
|
||||
Zonemaster::Engine->logger->add( CNAME_RECORDS_DUPLICATES => {
|
||||
records => join(';', map { "$_ => $duplicate_cname_rrs{$_}" if $duplicate_cname_rrs{$_} > 0 } keys %duplicate_cname_rrs )
|
||||
}
|
||||
);
|
||||
@cname_rrs = @original_rrs;
|
||||
}
|
||||
|
||||
# Break if there are too many records
|
||||
if ( scalar @cname_rrs > $CNAME_MAX_RECORDS ) {
|
||||
Zonemaster::Engine->logger->add( CNAME_RECORDS_TOO_MANY => { name => $name, count => scalar @cname_rrs, max => $CNAME_MAX_RECORDS } );
|
||||
return ( undef, $state );
|
||||
}
|
||||
|
||||
my ( %cnames, %seen_targets, %forbidden_targets );
|
||||
for my $rr ( @cname_rrs ) {
|
||||
my $rr_owner = name( $rr->owner );
|
||||
my $rr_target = name( $rr->cname );
|
||||
|
||||
# Multiple CNAME records with same owner name
|
||||
if ( exists $forbidden_targets{lc( $rr_owner )} ) {
|
||||
Zonemaster::Engine->logger->add( CNAME_RECORDS_MULTIPLE_FOR_NAME => { name => $rr_owner } );
|
||||
return ( undef, $state );
|
||||
}
|
||||
|
||||
# CNAME owner name is target, or target has already been seen in this response, or owner name cannot be a target
|
||||
if ( lc( $rr_owner ) eq lc( $rr_target ) or exists $seen_targets{lc( $rr_target )} or grep { $_ eq lc( $rr_target ) } ( keys %forbidden_targets ) ) {
|
||||
Zonemaster::Engine->logger->add( CNAME_LOOP_INNER => { name => join( ';', map { $_->owner } @cname_rrs ), target => join( ';', map { $_->cname } @cname_rrs ) } );
|
||||
return ( undef, $state );
|
||||
}
|
||||
|
||||
$seen_targets{lc( $rr_target )} = 1;
|
||||
$forbidden_targets{lc( $rr_owner )} = 1;
|
||||
$cnames{$rr_owner} = $rr_target;
|
||||
}
|
||||
|
||||
# Get final CNAME target
|
||||
my $target = $name;
|
||||
my $cname_counter = 0;
|
||||
while ( $cnames{$target} ) {
|
||||
return ( undef, $state ) if $cname_counter > $CNAME_MAX_RECORDS; # Loop protection (for good measure only - data in %cnames is sanitized already)
|
||||
$target = $cnames{$target};
|
||||
$cname_counter++;
|
||||
}
|
||||
|
||||
# Make sure that the CNAME chain from the RRs is not broken
|
||||
if ( $cname_counter != scalar @cname_rrs ) {
|
||||
Zonemaster::Engine->logger->add( CNAME_RECORDS_CHAIN_BROKEN => { name => $name, cname_rrs => scalar @cname_rrs, cname_counter => $cname_counter } );
|
||||
return ( undef, $state );
|
||||
}
|
||||
|
||||
# Check if there are RRs of queried type (QTYPE) in the answer section of the response;
|
||||
if ( scalar $p->get_records( $type, 'answer' ) ) {
|
||||
# RR of type QTYPE for CNAME target is already in the response; no need to recurse
|
||||
if ( $p->has_rrs_of_type_for_name( $type, $target ) ) {
|
||||
Zonemaster::Engine->logger->add( CNAME_FOLLOWED_IN_ZONE => { name => $name, type => $type, target => $target } );
|
||||
return ( $p, $state );
|
||||
}
|
||||
|
||||
# There is a record of type QTYPE but with different owner name than CNAME target; no need to recurse
|
||||
Zonemaster::Engine->logger->add( CNAME_NO_MATCH => { name => $name, type => $type, target => $target, owner_names => join( ';', map { $_->owner } $p->get_records( $type ) ) } );
|
||||
return ( undef, $state );
|
||||
}
|
||||
|
||||
# CNAME target has already been followed (outer loop); no need to recurse
|
||||
if ( exists $state->{in_progress}{lc( $target )}{$type} ) {
|
||||
Zonemaster::Engine->logger->add( CNAME_LOOP_OUTER => { name => $name, target => $target, targets_seen => join( ';', keys %{ $state->{tseen} } ) } );
|
||||
return ( undef, $state );
|
||||
}
|
||||
|
||||
# Safe-guard against anormaly long consecutive CNAME chains; no need to recurse
|
||||
$state->{tseen}{lc( $target )} = 1;
|
||||
$state->{tcount} += 1;
|
||||
|
||||
if ( $state->{tcount} > $CNAME_MAX_CHAIN_LENGTH ) {
|
||||
Zonemaster::Engine->logger->add( CNAME_CHAIN_TOO_LONG => { count => $state->{tcount}, max => $CNAME_MAX_CHAIN_LENGTH } );
|
||||
return ( undef, $state );
|
||||
}
|
||||
|
||||
# Make sure that the CNAME target is out of zone before making a new recursive lookup for CNAME target
|
||||
unless ( $name->is_in_bailiwick( $target ) ) {
|
||||
Zonemaster::Engine->logger->add( CNAME_FOLLOWED_OUT_OF_ZONE => { name => $name, target => $target } );
|
||||
( $p, $state ) = $class->_recurse( $target, $type, $dns_class,
|
||||
{ ns => [ root_servers() ], count => 0, common => 0, seen => {}, tseen => $state->{tseen}, tcount => $state->{tcount}, glue => {}, in_progress => $state->{in_progress} });
|
||||
}
|
||||
else {
|
||||
# What do do here?
|
||||
}
|
||||
|
||||
return ( $p, $state );
|
||||
}
|
||||
|
||||
sub _recurse {
|
||||
my ( $class, $name, $type, $dns_class, $state ) = @_;
|
||||
$name = q{} . name( $name );
|
||||
$state->{qname} //= $name;
|
||||
|
||||
if ( $state->{in_progress}{$name}{$type} ) {
|
||||
return;
|
||||
}
|
||||
$state->{in_progress}{$name}{$type} = 1;
|
||||
|
||||
while ( my $ns = pop @{ $state->{ns} } ) {
|
||||
my $nsname = $ns->can( 'name' ) ? q{} . $ns->name : q{};
|
||||
my $nsaddress = $ns->can( 'address' ) ? $ns->address->ip : q{};
|
||||
Zonemaster::Engine->logger->add(
|
||||
RECURSE_QUERY => {
|
||||
source => "$ns",
|
||||
ns => $nsname,
|
||||
address => $nsaddress,
|
||||
name => $name,
|
||||
type => $type,
|
||||
class => $dns_class,
|
||||
}
|
||||
);
|
||||
my $p = $class->_do_query( $ns, $name, $type, { class => $dns_class }, $state );
|
||||
|
||||
next if not $p; # Ask next server if no response
|
||||
|
||||
if ( $p->rcode eq 'REFUSED' or $p->rcode eq 'SERVFAIL' ) {
|
||||
# Respond with these if we can't get a better response
|
||||
$state->{candidate} = $p;
|
||||
next;
|
||||
}
|
||||
|
||||
if ( $p->no_such_record ) { # Node exists, but not record
|
||||
return ( $p, $state );
|
||||
}
|
||||
|
||||
if ( $p->no_such_name ) { # Node does not exist
|
||||
return ( $p, $state );
|
||||
}
|
||||
|
||||
if ( $class->_is_answer( $p ) ) { # Return answer, or resolve CNAME
|
||||
if ( not $p->has_rrs_of_type_for_name( $type, $name ) and scalar $p->get_records_for_name( 'CNAME', $name, 'answer' ) ) {
|
||||
( $p, $state ) = $class->_resolve_cname( $name, $type, $dns_class, $p, $state );
|
||||
}
|
||||
|
||||
return ( $p, $state );
|
||||
}
|
||||
|
||||
# So it's not an error, not an empty response and not an answer
|
||||
|
||||
if ( $p->is_redirect ) {
|
||||
my $zname = name( lc( ( $p->get_records( 'ns' ) )[0]->name ) );
|
||||
|
||||
next if $zname eq '.'; # Redirect to root is never right.
|
||||
|
||||
next if $state->{seen}{$zname}; # We followed this redirect before
|
||||
|
||||
$state->{seen}{$zname} = 1;
|
||||
my $common = name( $zname )->common( name( $state->{qname} ) );
|
||||
|
||||
next if $common < $state->{common}; # Redirect going up the hierarchy is not OK
|
||||
|
||||
$state->{common} = $common;
|
||||
$state->{ns} = $class->get_ns_from( $p, $state ); # Follow redirect
|
||||
$state->{count} += 1;
|
||||
if ( $state->{count} > 20 ) { # Loop protection
|
||||
Zonemaster::Engine->logger->add( LOOP_PROTECTION => {
|
||||
caller => 'Zonemaster::Engine::Recursor->_recurse',
|
||||
child_zone_name => $name,
|
||||
name => $zname
|
||||
}
|
||||
);
|
||||
|
||||
return ( undef, $state );
|
||||
}
|
||||
unshift @{ $state->{trace} }, [ $zname, $ns, $p->answerfrom ];
|
||||
|
||||
next;
|
||||
} ## end if ( $p->is_redirect )
|
||||
} ## end while ( my $ns = pop @{ $state...})
|
||||
return ( $state->{candidate}, $state ) if $state->{candidate};
|
||||
|
||||
return ( undef, $state );
|
||||
} ## end sub _recurse
|
||||
|
||||
sub _do_query {
|
||||
my ( $class, $ns, $name, $type, $opts, $state ) = @_;
|
||||
|
||||
if ( ref( $ns ) and $ns->can( 'query' ) ) {
|
||||
my $p = $ns->query( $name, $type, $opts );
|
||||
|
||||
if ( $p ) {
|
||||
for my $rr ( grep { $_->type eq 'A' or $_->type eq 'AAAA' } $p->answer, $p->additional ) {
|
||||
$state->{glue}{ lc( Zonemaster::Engine::DNSName->from_string( $rr->name ) ) }{ $rr->address } = 1;
|
||||
}
|
||||
}
|
||||
return $p;
|
||||
}
|
||||
elsif ( my $href = $state->{glue}{ lc( name( $ns ) ) } ) {
|
||||
foreach my $addr ( keys %$href ) {
|
||||
my $realns = ns( $ns, $addr );
|
||||
my $p = $class->_do_query( $realns, $name, $type, $opts, $state );
|
||||
if ( $p ) {
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$state->{glue}{ lc( name( $ns ) ) } = {};
|
||||
my @addr = $class->get_addresses_for( $ns, $state );
|
||||
if ( @addr > 0 ) {
|
||||
foreach my $addr ( @addr ) {
|
||||
$state->{glue}{ lc( name( $ns ) ) }{ $addr->short } = 1;
|
||||
my $new = ns( $ns, $addr->short );
|
||||
my $p = $new->query( $name, $type, $opts );
|
||||
return $p if $p;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} ## end sub _do_query
|
||||
|
||||
sub get_ns_from {
|
||||
my ( $class, $p, $state ) = @_;
|
||||
my ( @new, @extra );
|
||||
|
||||
my @names = sort map { Zonemaster::Engine::DNSName->from_string( lc( $_->nsdname ) ) } $p->get_records( 'ns' );
|
||||
|
||||
$state->{glue}{ lc( Zonemaster::Engine::DNSName->from_string( $_->name ) ) }{ $_->address } = 1
|
||||
for ( $p->get_records( 'a' ), $p->get_records( 'aaaa' ) );
|
||||
|
||||
foreach my $name ( @names ) {
|
||||
if ( exists $state->{glue}{ lc( $name ) } ) {
|
||||
for my $addr ( keys %{ $state->{glue}{ lc( $name ) } } ) {
|
||||
push @new, ns( $name, $addr );
|
||||
}
|
||||
}
|
||||
else {
|
||||
push @extra, $name;
|
||||
}
|
||||
}
|
||||
|
||||
@new = sort { $a->name cmp $b->name or $a->address->ip cmp $b->address->ip } @new;
|
||||
@extra = sort { $a cmp $b } @extra;
|
||||
|
||||
return [ @new, @extra ];
|
||||
} ## end sub get_ns_from
|
||||
|
||||
sub get_addresses_for {
|
||||
my ( $class, $name, $state ) = @_;
|
||||
my @res;
|
||||
$state //=
|
||||
{ ns => [ root_servers() ], count => 0, common => 0, seen => {} };
|
||||
|
||||
my ( $pa ) = $class->_recurse(
|
||||
"$name", 'A', 'IN',
|
||||
{
|
||||
ns => [ root_servers() ],
|
||||
count => $state->{count},
|
||||
common => 0,
|
||||
in_progress => $state->{in_progress},
|
||||
glue => $state->{glue}
|
||||
}
|
||||
);
|
||||
|
||||
# Name does not exist, just stop
|
||||
if ( $pa and $pa->no_such_name ) {
|
||||
return;
|
||||
}
|
||||
|
||||
my ( $paaaa ) = $class->_recurse(
|
||||
"$name", 'AAAA', 'IN',
|
||||
{
|
||||
ns => [ root_servers() ],
|
||||
count => $state->{count},
|
||||
common => 0,
|
||||
in_progress => $state->{in_progress},
|
||||
glue => $state->{glue}
|
||||
}
|
||||
);
|
||||
|
||||
my @rrs;
|
||||
my %cname;
|
||||
if ( $pa ) {
|
||||
push @rrs, $pa->get_records( 'a' );
|
||||
$cname{ $_->cname } = 1 for $pa->get_records_for_name( 'CNAME', $name );
|
||||
}
|
||||
if ( $paaaa ) {
|
||||
push @rrs, $paaaa->get_records( 'aaaa' );
|
||||
$cname{ $_->cname } = 1 for $paaaa->get_records_for_name( 'CNAME', $name );
|
||||
}
|
||||
|
||||
foreach my $rr ( sort { $a->address cmp $b->address } @rrs ) {
|
||||
if ( name( $rr->name ) eq $name or $cname{ $rr->name } ) {
|
||||
push @res, Net::IP::XS->new( $rr->address );
|
||||
}
|
||||
}
|
||||
return @res;
|
||||
} ## end sub get_addresses_for
|
||||
|
||||
sub _is_answer {
|
||||
my ( $class, $packet ) = @_;
|
||||
|
||||
return ( $packet->type eq 'answer' );
|
||||
}
|
||||
|
||||
sub clear_cache {
|
||||
%recurse_cache = ();
|
||||
return;
|
||||
}
|
||||
|
||||
sub root_servers {
|
||||
my $root_addresses = $_fake_addresses_cache{'.'};
|
||||
|
||||
my @servers;
|
||||
for my $name ( sort keys %{ $root_addresses } ) {
|
||||
for my $address ( @{ $root_addresses->{$name} } ) {
|
||||
push @servers, ns( $name, $address );
|
||||
}
|
||||
}
|
||||
|
||||
return @servers;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Recursor - recursive resolver for Zonemaster
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $packet = Zonemaster::Engine::Recursor->recurse( $name, $type, $dns_class );
|
||||
my $pname = Zonemaster::Engine::Recursor->parent( 'example.org' );
|
||||
|
||||
=head1 CLASS VARIABLES
|
||||
|
||||
=head2 %recurse_cache
|
||||
|
||||
Will cache result of previous queries.
|
||||
|
||||
=head2 %_fake_addresses_cache
|
||||
|
||||
A hash of hashrefs of arrayrefs.
|
||||
The keys of the top level hash are domain names.
|
||||
The keys of the second level hashes are name server names (normalized to lower
|
||||
case).
|
||||
The elements of the third level arrayrefs are IP addresses.
|
||||
|
||||
The IP addresses are those of the nameservers which are used in case of fake
|
||||
delegations (pre-publication tests).
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=head2 init_recursor()
|
||||
|
||||
Initialize the recursor by loading the root hints.
|
||||
|
||||
=head2 recurse($name[, $type, $class, $ns])
|
||||
|
||||
Does a recursive resolution for the given name down from the root servers (or for the given name server(s), if any).
|
||||
Only the first argument is mandatory. The rest are optional and default to, respectively: 'A', 'IN', and L</root_servers()>.
|
||||
|
||||
Takes a string or a L<Zonemaster::Engine::DNSName> object (name); and optionally a string (query type), a string (query class),
|
||||
and an arrayref of L<Zonemaster::Engine::Nameserver> objects.
|
||||
|
||||
Returns a L<Zonemaster::Engine::Packet> object (which can be C<undef>).
|
||||
|
||||
=head2 parent($name)
|
||||
|
||||
Does a recursive resolution from the root down for the given name (using type C<SOA> and class C<IN>). If the resolution is successful, it returns
|
||||
the domain name of the second-to-last step. If the resolution is unsuccessful, it returns the domain name of the last step.
|
||||
|
||||
=head2 get_ns_from($packet, $state)
|
||||
|
||||
Internal method. Takes a packet and a recursion state and returns a list of ns objects. Used to follow redirections.
|
||||
|
||||
=head2 get_addresses_for($name[, $state])
|
||||
|
||||
Takes a name and returns a (possibly empty) list of IP addresses for
|
||||
that name (in the form of L<Net::IP::XS> objects). When used
|
||||
internally by the recursor it's passed a recursion state as its second
|
||||
argument.
|
||||
|
||||
=head2 add_fake_addresses($domain, $data)
|
||||
|
||||
Class method to create fake addresses for fake delegations for a specified domain from data provided.
|
||||
|
||||
=head2 has_fake_addresses($domain)
|
||||
|
||||
Check if there is at least one fake nameserver specified for the given domain.
|
||||
|
||||
=head2 get_fake_addresses($domain, $nsname)
|
||||
|
||||
Returns a list of all cached fake addresses for the given domain and name server name.
|
||||
Returns an empty list if no data is cached for the given arguments.
|
||||
|
||||
=head2 get_fake_names($domain)
|
||||
|
||||
Returns a list of all cached fake name server names for the given domain.
|
||||
Returns an empty list if no data is cached for the given argument.
|
||||
|
||||
=head2 remove_fake_addresses($domain)
|
||||
|
||||
Remove fake delegation data for a specified domain.
|
||||
|
||||
=head2 clear_cache()
|
||||
|
||||
Class method to empty the cache of responses to recursive queries (but not the ones for fake delegations).
|
||||
|
||||
N.B. This method does not affect fake delegation data.
|
||||
|
||||
=head2 root_servers()
|
||||
|
||||
Returns a list of ns objects representing the root servers.
|
||||
|
||||
my @name_servers = Zonemaster::Engine::Recursor->root_servers();
|
||||
|
||||
The default list of root servers is read from a file installed in the shared data directory.
|
||||
This list can be replaced like so:
|
||||
|
||||
Zonemaster::Engine::Recursor->remove_fake_addresses( '.' );
|
||||
Zonemaster::Engine::Recursor->add_fake_addresses(
|
||||
'.',
|
||||
{
|
||||
'ns1.example' => ['192.0.2.1'],
|
||||
'ns2.example' => ['192.0.2.2'],
|
||||
}
|
||||
);
|
||||
|
||||
=head1 INTERNAL METHODS
|
||||
|
||||
=head2 _recurse()
|
||||
|
||||
my ( $p, $state_hash ) = _recurse( $name, $type_string, $dns_class_string, $p, $state_hash );
|
||||
|
||||
Performs a recursive lookup resolution for the given arguments. Used by the L<recursive lookup|/recurse($name, $type, $class)> method in this module.
|
||||
|
||||
Takes a L<Zonemaster::Engine::DNSName> object, a string (query type), a string (DNS class), a L<Zonemaster::Engine::Packet> object, and a reference to a hash.
|
||||
The mandatory keys for that hash are 'ns' (arrayref), 'count' (integer), 'common' (integer), 'seen' (hash), 'glue' (hash) and optional keys are 'in_progress'
|
||||
(hash), 'candidate' (L<Zonemaster::Engine::Packet> object or C<undef>), 'trace' (array), 'tseen' (hash), 'tcount' (integer).
|
||||
|
||||
Returns a L<Zonemaster::Engine::Packet> (or C<undef>) and a hash.
|
||||
|
||||
=head2 _resolve_cname()
|
||||
|
||||
my ( $p, $state_hash ) = _resolve_cname( $name, $type_string, $dns_class_string, $p, $state_hash );
|
||||
|
||||
Performs CNAME resolution for the given arguments. Used by the L<recursive lookup|/_recurse()> helper method in this module.
|
||||
If CNAMEs are successfully resolved, a L<packet|Zonemaster::Engine::Packet> (which could be C<undef>) is returned and
|
||||
one of the following message tags is logged:
|
||||
|
||||
=over
|
||||
|
||||
=item CNAME_FOLLOWED_IN_ZONE
|
||||
|
||||
=item CNAME_FOLLOWED_OUT_OF_ZONE
|
||||
|
||||
=back
|
||||
|
||||
Note that CNAME records are also validated and, in case of an error, an empty (C<undef>) L<packet|Zonemaster::Engine::Packet>
|
||||
is returned and one of the following message tags will be logged:
|
||||
|
||||
=over
|
||||
|
||||
=item CNAME_CHAIN_TOO_LONG
|
||||
|
||||
=item CNAME_LOOP_INNER
|
||||
|
||||
=item CNAME_LOOP_OUTER
|
||||
|
||||
=item CNAME_NO_MATCH
|
||||
|
||||
=item CNAME_RECORDS_CHAIN_BROKEN
|
||||
|
||||
=item CNAME_RECORDS_MULTIPLE_FOR_NAME
|
||||
|
||||
=item CNAME_RECORDS_TOO_MANY
|
||||
|
||||
=back
|
||||
|
||||
Takes a L<Zonemaster::Engine::DNSName> object, a string (query type), a string (DNS class), a L<Zonemaster::Engine::Packet>, and a reference to a hash.
|
||||
|
||||
Returns a L<Zonemaster::Engine::Packet> (or C<undef>) and a reference to a hash.
|
||||
|
||||
=cut
|
||||
350
zonemaster-engine/lib/Zonemaster/Engine/Test.pm
Normal file
350
zonemaster-engine/lib/Zonemaster/Engine/Test.pm
Normal file
@@ -0,0 +1,350 @@
|
||||
package Zonemaster::Engine::Test;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare( "v1.1.12" );
|
||||
|
||||
use Readonly;
|
||||
use Module::Find;
|
||||
use Net::IP::XS;
|
||||
use List::MoreUtils;
|
||||
use Clone;
|
||||
|
||||
use Zonemaster::LDNS;
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Engine::Profile;
|
||||
use Zonemaster::Engine::Util;
|
||||
|
||||
use IO::Socket::INET6; # Lazy-loads, so make sure it's here for the version logging
|
||||
|
||||
use File::ShareDir qw[dist_file];
|
||||
use File::Slurp qw[read_file];
|
||||
use Scalar::Util qw[blessed];
|
||||
use POSIX qw[strftime];
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Test - Module implementing methods to find, load and execute all Test modules
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my @results = Zonemaster::Engine::Test->run_all_for($zone);
|
||||
my @results = Zonemaster::Engine::Test->run_module('DNSSEC', $zone);
|
||||
my @results = Zonemaster::Engine::Test->run_one('DNSSEC', 'dnssec01', $zone);
|
||||
|
||||
=head1 TEST MODULES
|
||||
|
||||
Test modules are defined as modules with names starting with C<Zonemaster::Engine::Test::>.
|
||||
They are expected to provide at least the following class methods:
|
||||
|
||||
=over
|
||||
|
||||
=item all()
|
||||
|
||||
This will be given a L<Zonemaster::Engine::Zone> object as its only argument, and, after running the
|
||||
Test Cases for that Test module, is expected to return a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
This is the entry point used by the L</run_all_for()> and L</run_module()> methods of this class.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
=over
|
||||
|
||||
=item version()
|
||||
|
||||
This must return the version of the Test module.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
=over
|
||||
|
||||
=item metadata()
|
||||
|
||||
This must return 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
|
||||
|
||||
=over
|
||||
|
||||
=item tag_descriptions()
|
||||
|
||||
This must return a reference to a hash, the keys of which are the message tags and the corresponding values
|
||||
are strings (message IDs) corresponding to user-friendly English translations of those message tags.
|
||||
Keep in mind that the message ids will be used as keys to look up translations into other languages,
|
||||
so think twice before editing them.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
my @all_test_modules;
|
||||
|
||||
BEGIN {
|
||||
@all_test_modules = split /\n/, read_file( dist_file( 'Zonemaster-Engine', 'modules.txt' ) );
|
||||
|
||||
for my $name ( @all_test_modules ) {
|
||||
require sprintf q{Zonemaster/Engine/Test/%s.pm}, $name;
|
||||
"Zonemaster::Engine::Test::$name"->import();
|
||||
}
|
||||
}
|
||||
|
||||
=head1 INTERNAL METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item _log_versions()
|
||||
|
||||
_log_versions();
|
||||
|
||||
Adds logging messages regarding the current version of some modules, specifically for L<Zonemaster::Engine> and other dependency modules (e.g. L<Zonemaster::LDNS>).
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub _log_versions {
|
||||
info( GLOBAL_VERSION => { version => Zonemaster::Engine->VERSION } );
|
||||
|
||||
info( DEPENDENCY_VERSION => { name => 'Zonemaster::LDNS', version => $Zonemaster::LDNS::VERSION } );
|
||||
info( DEPENDENCY_VERSION => { name => 'IO::Socket::INET6', version => $IO::Socket::INET6::VERSION } );
|
||||
info( DEPENDENCY_VERSION => { name => 'Module::Find', version => $Module::Find::VERSION } );
|
||||
info( DEPENDENCY_VERSION => { name => 'File::ShareDir', version => $File::ShareDir::VERSION } );
|
||||
info( DEPENDENCY_VERSION => { name => 'File::Slurp', version => $File::Slurp::VERSION } );
|
||||
info( DEPENDENCY_VERSION => { name => 'Net::IP::XS', version => $Net::IP::XS::VERSION } );
|
||||
info( DEPENDENCY_VERSION => { name => 'List::MoreUtils', version => $List::MoreUtils::VERSION } );
|
||||
info( DEPENDENCY_VERSION => { name => 'Clone', version => $Clone::VERSION } );
|
||||
info( DEPENDENCY_VERSION => { name => 'Readonly', version => $Readonly::VERSION } );
|
||||
|
||||
return;
|
||||
} ## end sub _log_versions
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item modules()
|
||||
|
||||
my @modules_array = modules();
|
||||
|
||||
Returns a list of strings containing the names of all available Test modules,
|
||||
based on the content of the B<share/modules.txt> file.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub modules {
|
||||
return @all_test_modules;
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item run_all_for()
|
||||
|
||||
my @logentry_array = run_all_for( $zone );
|
||||
|
||||
Runs the L<default set of tests|/all()> of L<all Test modules found|/modules()> for the given zone.
|
||||
|
||||
Test modules are L<looked up and loaded|/modules()> from the
|
||||
B<share/modules.txt> file, and executed in the order in which they appear in the
|
||||
file.
|
||||
The default set of tests (Test Cases) is specified in the L</all()> method of each Test module. They
|
||||
can be individually disabled by the L<profile|Zonemaster::Engine::Profile/test_cases>.
|
||||
|
||||
A test module may implement a C<can_continue()> method to indicate lack of an
|
||||
extremely minimal level of function for the zone (e.g., it must have a parent
|
||||
domain, and it must have at least one functional name server).
|
||||
If lack of such minimal function is indicated, the testing harness is aborted.
|
||||
See L<Zonemaster::Engine::Test::Basic/can_continue()> for an example.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub run_all_for {
|
||||
my ( $class, $zone ) = @_;
|
||||
my @results;
|
||||
|
||||
Zonemaster::Engine->start_time_now();
|
||||
push @results, info( START_TIME => { time_t => time(), string => strftime( "%F %T %z", ( localtime() ) ) } );
|
||||
push @results, info( TEST_TARGET => { zone => $zone->name->string, module => 'all' } );
|
||||
_log_versions();
|
||||
|
||||
if ( not( Zonemaster::Engine::Profile->effective->get( q{net.ipv4} ) or Zonemaster::Engine::Profile->effective->get( q{net.ipv6} ) ) ) {
|
||||
return info( NO_NETWORK => {} );
|
||||
}
|
||||
|
||||
if ( Zonemaster::Engine->can_continue() ) {
|
||||
foreach my $mod ( __PACKAGE__->modules ) {
|
||||
my $module = "Zonemaster::Engine::Test::$mod";
|
||||
|
||||
info( MODULE_VERSION => { module => $module, version => $module->version } );
|
||||
|
||||
my @module_results = eval { $module->all( $zone ) };
|
||||
push @results, @module_results;
|
||||
if ( $@ ) {
|
||||
my $err = $@;
|
||||
if ( blessed $err and $err->isa( 'Zonemaster::Engine::Exception' ) ) {
|
||||
die $err; # Utility exception, pass it on
|
||||
}
|
||||
else {
|
||||
push @results, info( MODULE_ERROR => { module => $module, msg => "$err" } );
|
||||
}
|
||||
}
|
||||
|
||||
info( MODULE_END => { module => $module } );
|
||||
|
||||
if ( $module->can( 'can_continue' ) && !$module->can_continue( $zone, @module_results ) ) {
|
||||
push @results, info( CANNOT_CONTINUE => { domain => $zone->name->string } );
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @results;
|
||||
} ## end sub run_all_for
|
||||
|
||||
=over
|
||||
|
||||
=item run_module()
|
||||
|
||||
my @logentry_array = run_module( $module, $zone );
|
||||
|
||||
Runs the L<default set of tests|/all()> of the given Test module for the given zone.
|
||||
|
||||
The Test module must be in the list of actively loaded modules (that is,
|
||||
a module defined in the B<share/modules.txt> file).
|
||||
The default set of tests (Test Cases) is specified in the L</all()> method of each Test module.
|
||||
They can be individually disabled by the L<profile|Zonemaster::Engine::Profile/test_cases>.
|
||||
|
||||
Takes a string (module name) and a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub run_module {
|
||||
my ( $class, $requested, $zone ) = @_;
|
||||
my @res;
|
||||
my ( $module ) = grep { lc( $requested ) eq lc( $_ ) } $class->modules;
|
||||
|
||||
Zonemaster::Engine->start_time_now();
|
||||
push @res, info( START_TIME => { time_t => time(), string => strftime( "%F %T %z", ( localtime() ) ) } );
|
||||
push @res, info( TEST_TARGET => { zone => $zone->name->string, module => $requested } );
|
||||
_log_versions();
|
||||
|
||||
if ( not( Zonemaster::Engine::Profile->effective->get( q{net.ipv4} ) or Zonemaster::Engine::Profile->effective->get( q{net.ipv6} ) ) ) {
|
||||
return info( NO_NETWORK => {} );
|
||||
}
|
||||
|
||||
if ( Zonemaster::Engine->can_continue() ) {
|
||||
if ( $module ) {
|
||||
my $m = "Zonemaster::Engine::Test::$module";
|
||||
info( MODULE_VERSION => { module => $m, version => $m->version } );
|
||||
push @res, eval { $m->all( $zone ) };
|
||||
if ( $@ ) {
|
||||
my $err = $@;
|
||||
if ( blessed $err and $err->isa( 'Zonemaster::Engine::Exception' ) ) {
|
||||
die $err; # Utility exception, pass it on
|
||||
}
|
||||
else {
|
||||
push @res, info( MODULE_ERROR => { module => $module, msg => "$err" } );
|
||||
}
|
||||
}
|
||||
info( MODULE_END => { module => $module } );
|
||||
return @res;
|
||||
}
|
||||
else {
|
||||
info( UNKNOWN_MODULE => { module => $requested, testcase => 'all', module_list => join( ':', sort $class->modules ) } );
|
||||
}
|
||||
}
|
||||
else {
|
||||
info( CANNOT_CONTINUE => { domain => $zone->name->string } );
|
||||
}
|
||||
|
||||
return;
|
||||
} ## end sub run_module
|
||||
|
||||
=over
|
||||
|
||||
=item run_one()
|
||||
|
||||
my @logentry_array = run_one( $module, $test_case, $zone );
|
||||
|
||||
Runs the given Test Case of the given Test module for the given zone.
|
||||
|
||||
The Test module must be in the list of actively loaded modules (that is,
|
||||
a module defined in the B<share/modules.txt> file), and the Test Case
|
||||
must be listed both in the L<metadata|/metadata()> of the Test module
|
||||
exports and in the L<profile|Zonemaster::Engine::Profile/test_cases>.
|
||||
|
||||
Takes a string (module name), a string (test case name) and an array of L<Zonemaster::Engine::Zone> objects.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub run_one {
|
||||
my ( $class, $requested, $test, $zone ) = @_;
|
||||
my @res;
|
||||
my ( $module ) = grep { lc( $requested ) eq lc( $_ ) } $class->modules;
|
||||
|
||||
Zonemaster::Engine->start_time_now();
|
||||
push @res, info( START_TIME => { time_t => time(), string => strftime( "%F %T %z", ( localtime() ) ) } );
|
||||
push @res, info( TEST_TARGET => { zone => $zone->name->string, module => $requested, testcase => $test } );
|
||||
_log_versions();
|
||||
|
||||
if ( not( Zonemaster::Engine::Profile->effective->get( q{net.ipv4} ) or Zonemaster::Engine::Profile->effective->get( q{net.ipv6} ) ) ) {
|
||||
return info( NO_NETWORK => {} );
|
||||
}
|
||||
|
||||
if ( Zonemaster::Engine->can_continue() ) {
|
||||
if ( $module ) {
|
||||
my $m = "Zonemaster::Engine::Test::$module";
|
||||
if ( $m->metadata->{$test} and Zonemaster::Engine::Util::should_run_test( $test ) ) {
|
||||
info( MODULE_VERSION => { module => $m, version => $m->version } );
|
||||
push @res, eval { $m->$test( $zone ) };
|
||||
if ( $@ ) {
|
||||
my $err = $@;
|
||||
if ( blessed $err and $err->isa( 'Zonemaster::Engine::Exception' ) ) {
|
||||
die $err; # Utility exception, pass it on
|
||||
}
|
||||
else {
|
||||
push @res, info( MODULE_ERROR => { module => $module, msg => "$err" } );
|
||||
}
|
||||
}
|
||||
info( MODULE_END => { module => $module } );
|
||||
return @res;
|
||||
}
|
||||
else {
|
||||
info( UNKNOWN_METHOD => { module => $m, testcase => $test } );
|
||||
}
|
||||
}
|
||||
else {
|
||||
info( UNKNOWN_MODULE => { module => $requested, testcase => $test, module_list => join( ':', sort $class->modules ) } );
|
||||
}
|
||||
}
|
||||
else {
|
||||
info( CANNOT_CONTINUE => { domain => $zone->name->string } );
|
||||
}
|
||||
|
||||
return;
|
||||
} ## end sub run_one
|
||||
|
||||
1;
|
||||
546
zonemaster-engine/lib/Zonemaster/Engine/Test/Address.pm
Normal file
546
zonemaster-engine/lib/Zonemaster/Engine/Test/Address.pm
Normal file
@@ -0,0 +1,546 @@
|
||||
package Zonemaster::Engine::Test::Address;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.8");
|
||||
|
||||
use Carp;
|
||||
use List::MoreUtils qw[none any uniq];
|
||||
use Locale::TextDomain qw[Zonemaster-Engine];
|
||||
use Readonly;
|
||||
|
||||
use Zonemaster::Engine::Recursor;
|
||||
use Zonemaster::Engine::Constants qw[:addresses :ip];
|
||||
use Zonemaster::Engine::TestMethods;
|
||||
use Zonemaster::Engine::Util qw[name should_run_test];
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Test::Address - Module implementing tests focused on IP addresses of name servers
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my @results = Zonemaster::Engine::Test::Address->all( $zone );
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item all()
|
||||
|
||||
my @logentry_array = all( $zone );
|
||||
|
||||
Runs the default set of tests for that module, i.e. between L<two and three tests|/TESTS> depending on the tested zone.
|
||||
If L<ADDRESS02|/address02()> passes, L<ADDRESS03|/address03()> is run.
|
||||
|
||||
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;
|
||||
|
||||
push @results, $class->address01( $zone )
|
||||
if should_run_test( q{address01} );
|
||||
|
||||
my $ns_with_reverse = 1;
|
||||
if ( should_run_test( q{address02} ) ) {
|
||||
push @results, $class->address02( $zone );
|
||||
$ns_with_reverse = any { $_->tag eq q{NAMESERVERS_IP_WITH_REVERSE} } @results;
|
||||
}
|
||||
|
||||
# Perform ADDRESS03 if ADDRESS02 passed or was skipped
|
||||
if ( $ns_with_reverse ) {
|
||||
push @results, $class->address03( $zone )
|
||||
if should_run_test( q{address03} );
|
||||
}
|
||||
|
||||
return @results;
|
||||
}
|
||||
|
||||
=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 {
|
||||
address01 => [
|
||||
qw(
|
||||
A01_ADDR_NOT_GLOBALLY_REACHABLE
|
||||
A01_DOCUMENTATION_ADDR
|
||||
A01_GLOBALLY_REACHABLE_ADDR
|
||||
A01_LOCAL_USE_ADDR
|
||||
A01_NO_GLOBALLY_REACHABLE_ADDR
|
||||
A01_NO_NAME_SERVERS_FOUND
|
||||
)
|
||||
],
|
||||
address02 => [
|
||||
qw(
|
||||
NAMESERVER_IP_WITHOUT_REVERSE
|
||||
NAMESERVERS_IP_WITH_REVERSE
|
||||
NO_RESPONSE_PTR_QUERY
|
||||
TEST_CASE_END
|
||||
TEST_CASE_START
|
||||
)
|
||||
],
|
||||
address03 => [
|
||||
qw(
|
||||
NAMESERVER_IP_WITHOUT_REVERSE
|
||||
NAMESERVER_IP_PTR_MISMATCH
|
||||
NAMESERVER_IP_PTR_MATCH
|
||||
NO_RESPONSE_PTR_QUERY
|
||||
TEST_CASE_END
|
||||
TEST_CASE_START
|
||||
)
|
||||
],
|
||||
};
|
||||
} ## end sub metadata
|
||||
|
||||
Readonly my %TAG_DESCRIPTIONS => (
|
||||
ADDRESS01 => sub {
|
||||
__x # ADDRESS:ADDRESS01
|
||||
'Name server address must be globally reachable';
|
||||
},
|
||||
ADDRESS02 => sub {
|
||||
__x # ADDRESS:ADDRESS02
|
||||
'Reverse DNS entry exists for name server IP address';
|
||||
},
|
||||
ADDRESS03 => sub {
|
||||
__x # ADDRESS:ADDRESS03
|
||||
'Reverse DNS entry matches name server name';
|
||||
},
|
||||
A01_ADDR_NOT_GLOBALLY_REACHABLE => sub {
|
||||
__x # ADDRESS:A01_ADDR_NOT_GLOBALLY_REACHABLE
|
||||
'IP address(es) not listed as globally reachable: "{ns_list}".', @_;
|
||||
},
|
||||
A01_DOCUMENTATION_ADDR => sub {
|
||||
__x # ADDRESS:A01_DOCUMENTATION_ADDR
|
||||
'IP address(es) intended for documentation purposes: "{ns_list}".', @_;
|
||||
},
|
||||
A01_GLOBALLY_REACHABLE_ADDR => sub {
|
||||
__x # ADDRESS:A01_GLOBALLY_REACHABLE_ADDR
|
||||
'Globally reachable IP address(es): "{ns_list}".', @_;
|
||||
},
|
||||
A01_LOCAL_USE_ADDR => sub {
|
||||
__x # ADDRESS:A01_LOCAL_USE_ADDR
|
||||
'IP address(es) intended for local use on network or service provider level: "{ns_list}".', @_;
|
||||
},
|
||||
A01_NO_GLOBALLY_REACHABLE_ADDR => sub {
|
||||
__x # ADDRESS:A01_NO_GLOBALLY_REACHABLE_ADDR
|
||||
'None of the name servers IP addresses are listed as globally reachable.';
|
||||
},
|
||||
A01_NO_NAME_SERVERS_FOUND => sub {
|
||||
__x # ADDRESS:A01_NO_NAME_SERVERS_FOUND
|
||||
'No name servers found.';
|
||||
},
|
||||
NAMESERVER_IP_WITHOUT_REVERSE => sub {
|
||||
__x # ADDRESS:NAMESERVER_IP_WITHOUT_REVERSE
|
||||
'Nameserver {nsname} has an IP address ({ns_ip}) without PTR configured.', @_;
|
||||
},
|
||||
NAMESERVER_IP_PTR_MISMATCH => sub {
|
||||
__x # ADDRESS:NAMESERVER_IP_PTR_MISMATCH
|
||||
'Nameserver {nsname} has an IP address ({ns_ip}) with mismatched PTR result ({names}).', @_;
|
||||
},
|
||||
NAMESERVERS_IP_WITH_REVERSE => sub {
|
||||
__x # ADDRESS:NAMESERVERS_IP_WITH_REVERSE
|
||||
"Reverse DNS entry exists for each Nameserver IP address.", @_;
|
||||
},
|
||||
NAMESERVER_IP_PTR_MATCH => sub {
|
||||
__x # ADDRESS:NAMESERVER_IP_PTR_MATCH
|
||||
"Every reverse DNS entry matches name server name.", @_;
|
||||
},
|
||||
NO_RESPONSE_PTR_QUERY => sub {
|
||||
__x # ADDRESS:NO_RESPONSE_PTR_QUERY
|
||||
'No response from nameserver(s) on PTR query ({domain}).', @_;
|
||||
},
|
||||
TEST_CASE_END => sub {
|
||||
__x # ADDRESS:TEST_CASE_END
|
||||
'TEST_CASE_END {testcase}.', @_;
|
||||
},
|
||||
TEST_CASE_START => sub {
|
||||
__x # ADDRESS:TEST_CASE_START
|
||||
'TEST_CASE_START {testcase}.', @_;
|
||||
},
|
||||
);
|
||||
|
||||
=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::Address::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, 'Address' ); }
|
||||
|
||||
=over
|
||||
|
||||
=item _find_special_address()
|
||||
|
||||
my $hash_ref = _find_special_address( $ip );
|
||||
|
||||
Verifies if an IP address is a special (private, reserved, ...) one.
|
||||
|
||||
Takes a L<Net::IP::XS> object.
|
||||
|
||||
Returns a reference to a hash if true (see L<Zonemaster::Engine::Constants/_extract_iana_ip_blocks()>), or C<undef> if false.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub _find_special_address {
|
||||
my ( $class, $ip ) = @_;
|
||||
my @special_addresses;
|
||||
|
||||
if ( $ip->version == $IP_VERSION_4 ) {
|
||||
@special_addresses = @IPV4_SPECIAL_ADDRESSES;
|
||||
}
|
||||
elsif ( $ip->version == $IP_VERSION_6 ) {
|
||||
@special_addresses = @IPV6_SPECIAL_ADDRESSES;
|
||||
}
|
||||
|
||||
foreach my $ip_details ( @special_addresses ) {
|
||||
if ( $ip->overlaps( ${$ip_details}{ip} ) ) {
|
||||
return $ip_details;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head1 TESTS
|
||||
|
||||
=over
|
||||
|
||||
=item address01()
|
||||
|
||||
my @logentry_array = address01( $zone );
|
||||
|
||||
Runs the L<Address01 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Address-TP/address01.md>.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub address01 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Address01';
|
||||
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
||||
|
||||
my @nss = uniq grep { $_->isa('Zonemaster::Engine::Nameserver') } (
|
||||
@{ Zonemaster::Engine::TestMethodsV2->get_del_ns_names_and_ips( $zone ) // [] },
|
||||
@{ Zonemaster::Engine::TestMethodsV2->get_zone_ns_names_and_ips( $zone ) // [] }
|
||||
);
|
||||
|
||||
unless ( @nss ) {
|
||||
push @results, _emit_log( A01_NO_NAME_SERVERS_FOUND => {} );
|
||||
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
||||
}
|
||||
|
||||
my ( @documentation_addr, @local_use_addr, @not_globally_reachable, @globally_reachable );
|
||||
my %ip_already_processed;
|
||||
|
||||
NSS:
|
||||
foreach my $ns ( @nss ) {
|
||||
my $ns_ip = $ns->address->short;
|
||||
|
||||
next if exists $ip_already_processed{$ns_ip};
|
||||
$ip_already_processed{$ns_ip} = [ grep { $_->address->short eq $ns_ip } @nss ];
|
||||
|
||||
my @matching_nss = @{ $ip_already_processed{$ns_ip} };
|
||||
|
||||
my $ip_details_ref = $class->_find_special_address( $ns->address );
|
||||
|
||||
if ( $ip_details_ref ) {
|
||||
my $ip_category = ${$ip_details_ref}{name};
|
||||
|
||||
if ( index( $ip_category, 'Documentation' ) != -1 ) {
|
||||
push @documentation_addr, @matching_nss;
|
||||
next;
|
||||
}
|
||||
|
||||
my @categories = ( 'Private-Use', 'Loopback', 'Link Local', 'Link-Local', 'Unique-Local', 'Shared Address Space' );
|
||||
foreach my $category ( @categories ) {
|
||||
if ( index( $ip_category, $category ) != -1 ) {
|
||||
push @local_use_addr, @matching_nss;
|
||||
next NSS;
|
||||
}
|
||||
}
|
||||
|
||||
if ( index( ${$ip_details_ref}{globally_reachable}, 'True' ) == -1 ) {
|
||||
push @not_globally_reachable, @matching_nss;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
push @globally_reachable, @matching_nss;
|
||||
}
|
||||
|
||||
if ( @globally_reachable ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
A01_GLOBALLY_REACHABLE_ADDR => {
|
||||
ns_list => join( q{;}, uniq sort @globally_reachable )
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
push @results,
|
||||
_emit_log(
|
||||
A01_NO_GLOBALLY_REACHABLE_ADDR => {}
|
||||
);
|
||||
}
|
||||
|
||||
if ( @documentation_addr ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
A01_DOCUMENTATION_ADDR => {
|
||||
ns_list => join( q{;}, uniq sort @documentation_addr )
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ( @local_use_addr ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
A01_LOCAL_USE_ADDR => {
|
||||
ns_list => join( q{;}, uniq sort @local_use_addr )
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ( @not_globally_reachable ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
A01_ADDR_NOT_GLOBALLY_REACHABLE => {
|
||||
ns_list => join( q{;}, uniq sort @not_globally_reachable )
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
||||
} ## end sub address01
|
||||
|
||||
=over
|
||||
|
||||
=item address02()
|
||||
|
||||
my @logentry_array = address02( $zone );
|
||||
|
||||
Runs the L<Address02 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Address-TP/address02.md>.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub address02 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Address02';
|
||||
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
||||
|
||||
my %ips;
|
||||
my $ptr_query;
|
||||
|
||||
foreach
|
||||
my $local_ns ( @{ Zonemaster::Engine::TestMethods->method4( $zone ) }, @{ Zonemaster::Engine::TestMethods->method5( $zone ) } )
|
||||
{
|
||||
|
||||
next if $ips{ $local_ns->address->short };
|
||||
|
||||
my $reverse_ip_query = $local_ns->address->reverse_ip;
|
||||
$ptr_query = $reverse_ip_query;
|
||||
|
||||
my $p = Zonemaster::Engine::Recursor->recurse( $ptr_query, q{PTR} );
|
||||
|
||||
# In case of Classless IN-ADDR.ARPA delegation, query returns
|
||||
# CNAME records. A PTR query is done on the CNAME.
|
||||
if ( $p and $p->rcode eq q{NOERROR} and $p->get_records( q{CNAME}, q{answer} ) ) {
|
||||
my ( $cname ) = $p->get_records( q{CNAME}, q{answer} );
|
||||
$ptr_query = $cname->cname;
|
||||
$p = Zonemaster::Engine::Recursor->recurse( $ptr_query, q{PTR} );
|
||||
}
|
||||
|
||||
if ( $p ) {
|
||||
if ( $p->rcode ne q{NOERROR} or not $p->get_records( q{PTR}, q{answer} ) ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
NAMESERVER_IP_WITHOUT_REVERSE => {
|
||||
nsname => $local_ns->name->string,
|
||||
ns_ip => $local_ns->address->short,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
push @results,
|
||||
_emit_log(
|
||||
NO_RESPONSE_PTR_QUERY => {
|
||||
domain => $ptr_query,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$ips{ $local_ns->address->short }++;
|
||||
|
||||
} ## end foreach my $local_ns ( @{ Zonemaster::Engine::TestMethods...})
|
||||
|
||||
if ( scalar keys %ips and not grep { $_->tag ne q{TEST_CASE_START} } @results ) {
|
||||
push @results, _emit_log( NAMESERVERS_IP_WITH_REVERSE => {} );
|
||||
}
|
||||
|
||||
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
||||
} ## end sub address02
|
||||
|
||||
=over
|
||||
|
||||
=item address03()
|
||||
|
||||
my @logentry_array = address03( $zone );
|
||||
|
||||
Runs the L<Address03 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Address-TP/address03.md>.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub address03 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Address03';
|
||||
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
||||
my $ptr_query;
|
||||
|
||||
my %ips;
|
||||
|
||||
foreach my $local_ns ( @{ Zonemaster::Engine::TestMethods->method5( $zone ) } ) {
|
||||
|
||||
next if $ips{ $local_ns->address->short };
|
||||
|
||||
my $reverse_ip_query = $local_ns->address->reverse_ip;
|
||||
$ptr_query = $reverse_ip_query;
|
||||
|
||||
my $p = Zonemaster::Engine::Recursor->recurse( $ptr_query, q{PTR} );
|
||||
|
||||
if ( $p ) {
|
||||
my @ptr = $p->get_records( q{PTR}, 'answer' );
|
||||
if ( $p->rcode eq q{NOERROR} and scalar @ptr ) {
|
||||
if ( none { name( $_->ptrdname ) eq $local_ns->name->string . q{.} } @ptr ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
NAMESERVER_IP_PTR_MISMATCH => {
|
||||
nsname => $local_ns->name->string,
|
||||
ns_ip => $local_ns->address->short,
|
||||
names => join( q{/}, map { $_->ptrdname } @ptr ),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
push @results,
|
||||
_emit_log(
|
||||
NAMESERVER_IP_WITHOUT_REVERSE => {
|
||||
nsname => $local_ns->name->string,
|
||||
ns_ip => $local_ns->address->short,
|
||||
}
|
||||
);
|
||||
}
|
||||
} ## end if ( $p )
|
||||
else {
|
||||
push @results,
|
||||
_emit_log(
|
||||
NO_RESPONSE_PTR_QUERY => {
|
||||
domain => $ptr_query,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$ips{ $local_ns->address->short }++;
|
||||
|
||||
} ## end foreach my $local_ns ( @{ Zonemaster::Engine::TestMethods...})
|
||||
|
||||
if ( scalar keys %ips and not grep { $_->tag ne q{TEST_CASE_START} } @results ) {
|
||||
push @results, _emit_log( NAMESERVER_IP_PTR_MATCH => {} );
|
||||
}
|
||||
|
||||
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
||||
} ## end sub address03
|
||||
|
||||
1;
|
||||
1131
zonemaster-engine/lib/Zonemaster/Engine/Test/Basic.pm
Normal file
1131
zonemaster-engine/lib/Zonemaster/Engine/Test/Basic.pm
Normal file
File diff suppressed because it is too large
Load Diff
901
zonemaster-engine/lib/Zonemaster/Engine/Test/Connectivity.pm
Normal file
901
zonemaster-engine/lib/Zonemaster/Engine/Test/Connectivity.pm
Normal file
@@ -0,0 +1,901 @@
|
||||
package Zonemaster::Engine::Test::Connectivity;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.1.0");
|
||||
|
||||
use Carp;
|
||||
use List::MoreUtils qw[uniq];
|
||||
use Locale::TextDomain qw[Zonemaster-Engine];
|
||||
use Readonly;
|
||||
|
||||
use Zonemaster::Engine::Profile;
|
||||
use Zonemaster::Engine::ASNLookup;
|
||||
use Zonemaster::Engine::Constants qw[:ip];
|
||||
use Zonemaster::Engine::TestMethods;
|
||||
use Zonemaster::Engine::TestMethodsV2;
|
||||
use Zonemaster::Engine::Util;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Test::Connectivity - Module implementing tests focused on name servers reachability
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my @results = Zonemaster::Engine::Test::Connectivity->all( $zone );
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item all()
|
||||
|
||||
my @array = all( $zone );
|
||||
|
||||
Runs the default set of tests for that module, i.e. L<four 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{connectivity01} ) ) {
|
||||
push @results, $class->connectivity01( $zone );
|
||||
}
|
||||
if ( Zonemaster::Engine::Util::should_run_test( q{connectivity02} ) ) {
|
||||
push @results, $class->connectivity02( $zone );
|
||||
}
|
||||
if ( Zonemaster::Engine::Util::should_run_test( q{connectivity03} ) ) {
|
||||
push @results, $class->connectivity03( $zone );
|
||||
}
|
||||
if ( Zonemaster::Engine::Util::should_run_test( q{connectivity04} ) ) {
|
||||
push @results, $class->connectivity04( $zone );
|
||||
}
|
||||
|
||||
return @results;
|
||||
}
|
||||
|
||||
=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 {
|
||||
connectivity01 => [
|
||||
qw(
|
||||
CN01_IPV4_DISABLED
|
||||
CN01_IPV6_DISABLED
|
||||
CN01_MISSING_NS_RECORD_UDP
|
||||
CN01_MISSING_SOA_RECORD_UDP
|
||||
CN01_NO_RESPONSE_NS_QUERY_UDP
|
||||
CN01_NO_RESPONSE_SOA_QUERY_UDP
|
||||
CN01_NO_RESPONSE_UDP
|
||||
CN01_NS_RECORD_NOT_AA_UDP
|
||||
CN01_SOA_RECORD_NOT_AA_UDP
|
||||
CN01_UNEXPECTED_RCODE_NS_QUERY_UDP
|
||||
CN01_UNEXPECTED_RCODE_SOA_QUERY_UDP
|
||||
CN01_WRONG_NS_RECORD_UDP
|
||||
CN01_WRONG_SOA_RECORD_UDP
|
||||
IPV4_DISABLED
|
||||
IPV6_DISABLED
|
||||
TEST_CASE_END
|
||||
TEST_CASE_START
|
||||
)
|
||||
],
|
||||
connectivity02 => [
|
||||
qw(
|
||||
CN02_MISSING_NS_RECORD_TCP
|
||||
CN02_MISSING_SOA_RECORD_TCP
|
||||
CN02_NO_RESPONSE_NS_QUERY_TCP
|
||||
CN02_NO_RESPONSE_SOA_QUERY_TCP
|
||||
CN02_NO_RESPONSE_TCP
|
||||
CN02_NS_RECORD_NOT_AA_TCP
|
||||
CN02_SOA_RECORD_NOT_AA_TCP
|
||||
CN02_UNEXPECTED_RCODE_NS_QUERY_TCP
|
||||
CN02_UNEXPECTED_RCODE_SOA_QUERY_TCP
|
||||
CN02_WRONG_NS_RECORD_TCP
|
||||
CN02_WRONG_SOA_RECORD_TCP
|
||||
IPV4_DISABLED
|
||||
IPV6_DISABLED
|
||||
TEST_CASE_END
|
||||
TEST_CASE_START
|
||||
)
|
||||
],
|
||||
connectivity03 => [
|
||||
qw(
|
||||
ASN_INFOS_RAW
|
||||
ASN_INFOS_ANNOUNCE_BY
|
||||
ASN_INFOS_ANNOUNCE_IN
|
||||
EMPTY_ASN_SET
|
||||
ERROR_ASN_DATABASE
|
||||
IPV4_DIFFERENT_ASN
|
||||
IPV4_ONE_ASN
|
||||
IPV4_SAME_ASN
|
||||
IPV6_DIFFERENT_ASN
|
||||
IPV6_ONE_ASN
|
||||
IPV6_SAME_ASN
|
||||
TEST_CASE_END
|
||||
TEST_CASE_START
|
||||
)
|
||||
],
|
||||
connectivity04 => [
|
||||
qw(
|
||||
ASN_INFOS_RAW
|
||||
ASN_INFOS_ANNOUNCE_IN
|
||||
CN04_EMPTY_PREFIX_SET
|
||||
CN04_ERROR_PREFIX_DATABASE
|
||||
CN04_IPV4_DIFFERENT_PREFIX
|
||||
CN04_IPV4_SAME_PREFIX
|
||||
CN04_IPV4_SINGLE_PREFIX
|
||||
CN04_IPV6_DIFFERENT_PREFIX
|
||||
CN04_IPV6_SAME_PREFIX
|
||||
CN04_IPV6_SINGLE_PREFIX
|
||||
TEST_CASE_END
|
||||
TEST_CASE_START
|
||||
)
|
||||
],
|
||||
};
|
||||
} ## end sub metadata
|
||||
|
||||
Readonly my %TAG_DESCRIPTIONS => (
|
||||
CONNECTIVITY01 => sub {
|
||||
__x # CONNECTIVITY:CONNECTIVITY01
|
||||
'UDP connectivity';
|
||||
},
|
||||
CONNECTIVITY02 => sub {
|
||||
__x # CONNECTIVITY:CONNECTIVITY02
|
||||
'TCP connectivity';
|
||||
},
|
||||
CONNECTIVITY03 => sub {
|
||||
__x # CONNECTIVITY:CONNECTIVITY03
|
||||
'AS Diversity';
|
||||
},
|
||||
CONNECTIVITY04 => sub {
|
||||
__x # CONNECTIVITY:CONNECTIVITY04
|
||||
'IP Prefix Diversity';
|
||||
},
|
||||
CN01_IPV4_DISABLED => sub {
|
||||
__x # CONNECTIVITY:CN01_IPV4_DISABLED
|
||||
'IPv4 is disabled. No DNS queries are sent to these name servers: "{ns_list}".', @_;
|
||||
},
|
||||
CN01_IPV6_DISABLED => sub {
|
||||
__x # CONNECTIVITY:CN01_IPV6_DISABLED
|
||||
'IPv6 is disabled. No DNS queries are sent to these name servers: "{ns_list}".', @_;
|
||||
},
|
||||
CN01_MISSING_NS_RECORD_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_MISSING_NS_RECORD_UDP
|
||||
'Nameserver {ns} responds to a NS query with no NS records in the answer section over UDP.', @_;
|
||||
},
|
||||
CN01_MISSING_SOA_RECORD_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_MISSING_SOA_RECORD_UDP
|
||||
'Nameserver {ns} responds to a SOA query with no SOA records in the answer section over UDP.', @_;
|
||||
},
|
||||
CN01_NO_RESPONSE_NS_QUERY_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_NO_RESPONSE_NS_QUERY_UDP
|
||||
'Nameserver {ns} does not respond to NS queries over UDP.', @_;
|
||||
},
|
||||
CN01_NO_RESPONSE_SOA_QUERY_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_NO_RESPONSE_SOA_QUERY_UDP
|
||||
'Nameserver {ns} does not respond to SOA queries over UDP.', @_;
|
||||
},
|
||||
CN01_NO_RESPONSE_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_NO_RESPONSE_UDP
|
||||
'Nameserver {ns} does not respond to any queries over UDP.', @_;
|
||||
},
|
||||
CN01_NS_RECORD_NOT_AA_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_NS_RECORD_NOT_AA_UDP
|
||||
'Nameserver {ns} does not give an authoritative response on an NS query over UDP.', @_;
|
||||
},
|
||||
CN01_SOA_RECORD_NOT_AA_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_SOA_RECORD_NOT_AA_UDP
|
||||
'Nameserver {ns} does not give an authoritative response on an SOA query over UDP.', @_;
|
||||
},
|
||||
CN01_UNEXPECTED_RCODE_NS_QUERY_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_UNEXPECTED_RCODE_NS_QUERY_UDP
|
||||
'Nameserver {ns} responds with an unexpected RCODE ({rcode}) on an NS query over UDP.', @_;
|
||||
},
|
||||
CN01_UNEXPECTED_RCODE_SOA_QUERY_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_UNEXPECTED_RCODE_SOA_QUERY_UDP
|
||||
'Nameserver {ns} responds with an unexpected RCODE ({rcode}) on an SOA query over UDP.', @_;
|
||||
},
|
||||
CN01_WRONG_NS_RECORD_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_WRONG_NS_RECORD_UDP
|
||||
'Nameserver {ns} responds with a wrong owner name ({domain_found} instead of {domain_expected}) on NS queries over UDP.', @_;
|
||||
},
|
||||
CN01_WRONG_SOA_RECORD_UDP => sub {
|
||||
__x # CONNECTIVITY:CN01_WRONG_SOA_RECORD_UDP
|
||||
'Nameserver {ns} responds with a wrong owner name ({domain_found} instead of {domain_expected}) on SOA queries over UDP.', @_;
|
||||
},
|
||||
CN02_MISSING_NS_RECORD_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_MISSING_NS_RECORD_TCP
|
||||
'Nameserver {ns} responds to a NS query with no NS records in the answer section over TCP.', @_;
|
||||
},
|
||||
CN02_MISSING_SOA_RECORD_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_MISSING_SOA_RECORD_TCP
|
||||
'Nameserver {ns} responds to a SOA query with no SOA records in the answer section over TCP.', @_;
|
||||
},
|
||||
CN02_NO_RESPONSE_NS_QUERY_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_NO_RESPONSE_NS_QUERY_TCP
|
||||
'Nameserver {ns} does not respond to NS queries over TCP.', @_;
|
||||
},
|
||||
CN02_NO_RESPONSE_SOA_QUERY_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_NO_RESPONSE_SOA_QUERY_TCP
|
||||
'Nameserver {ns} does not respond to SOA queries over TCP.', @_;
|
||||
},
|
||||
CN02_NO_RESPONSE_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_NO_RESPONSE_TCP
|
||||
'Nameserver {ns} does not respond to any queries over TCP.', @_;
|
||||
},
|
||||
CN02_NS_RECORD_NOT_AA_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_NS_RECORD_NOT_AA_TCP
|
||||
'Nameserver {ns} does not give an authoritative response on an NS query over TCP.', @_;
|
||||
},
|
||||
CN02_SOA_RECORD_NOT_AA_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_SOA_RECORD_NOT_AA_TCP
|
||||
'Nameserver {ns} does not give an authoritative response on an SOA query over TCP.', @_;
|
||||
},
|
||||
CN02_UNEXPECTED_RCODE_NS_QUERY_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_UNEXPECTED_RCODE_NS_QUERY_TCP
|
||||
'Nameserver {ns} responds with an unexpected RCODE ({rcode}) on an NS query over TCP.', @_;
|
||||
},
|
||||
CN02_UNEXPECTED_RCODE_SOA_QUERY_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_UNEXPECTED_RCODE_SOA_QUERY_TCP
|
||||
'Nameserver {ns} responds with an unexpected RCODE ({rcode}) on an SOA query over TCP.', @_;
|
||||
},
|
||||
CN02_WRONG_NS_RECORD_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_WRONG_NS_RECORD_TCP
|
||||
'Nameserver {ns} responds with a wrong owner name ({domain_found} instead of {domain_expected}) on NS queries over TCP.', @_;
|
||||
},
|
||||
CN02_WRONG_SOA_RECORD_TCP => sub {
|
||||
__x # CONNECTIVITY:CN02_WRONG_SOA_RECORD_TCP
|
||||
'Nameserver {ns} responds with a wrong owner name ({domain_found} instead of {domain_expected}) on SOA queries over TCP.', @_;
|
||||
},
|
||||
CN04_ASN_INFOS_ANNOUNCE_IN => sub {
|
||||
__x # CONNECTIVITY:ASN_INFOS_ANNOUNCE_IN
|
||||
'Name server IP address "{ns_ip}" is announced in prefix "{prefix}".', @_;
|
||||
},
|
||||
CN04_ASN_INFOS_RAW => sub {
|
||||
__x # CONNECTIVITY:ASN_INFOS_RAW
|
||||
'The ASN data for name server IP address "{ns_ip}" is "{data}".', @_;
|
||||
},
|
||||
CN04_EMPTY_PREFIX_SET => sub {
|
||||
__x # CONNECTIVITY:CN04_EMPTY_PREFIX_SET
|
||||
'Prefix database returned no information for IP address {ns_ip}.', @_;
|
||||
},
|
||||
CN04_ERROR_PREFIX_DATABASE => sub {
|
||||
__x # CONNECTIVITY:CN04_ERROR_PREFIX_DATABASE
|
||||
'Prefix database error for IP address {ns_ip}.', @_;
|
||||
},
|
||||
CN04_IPV4_DIFFERENT_PREFIX => sub {
|
||||
__x # CONNECTIVITY:CN04_IPV4_DIFFERENT_PREFIX
|
||||
'The following name server(s) are announced in unique IPv4 prefix(es): "{ns_list}"', @_;
|
||||
},
|
||||
CN04_IPV4_SAME_PREFIX => sub {
|
||||
__x # CONNECTIVITY:CN04_IPV4_SAME_PREFIX
|
||||
'The following name server(s) are announced in the same IPv4 prefix ({ip_prefix}): "{ns_list}"', @_;
|
||||
},
|
||||
CN04_IPV4_SINGLE_PREFIX => sub {
|
||||
__x # CONNECTIVITY:CN04_IPV4_SINGLE_PREFIX
|
||||
'All name server(s) IPv4 address(es) are announced in the same IPv4 prefix.';
|
||||
},
|
||||
CN04_IPV6_DIFFERENT_PREFIX => sub {
|
||||
__x # CONNECTIVITY:CN04_IPV6_DIFFERENT_PREFIX
|
||||
'The following name server(s) are announced in unique IPv6 prefix(es): "{ns_list}"', @_;
|
||||
},
|
||||
CN04_IPV6_SAME_PREFIX => sub {
|
||||
__x # CONNECTIVITY:CN04_IPV6_SAME_PREFIX
|
||||
'The following name server(s) are announced in the same IPv6 prefix ({ip_prefix}): "{ns_list}"', @_;
|
||||
},
|
||||
CN04_IPV6_SINGLE_PREFIX => sub {
|
||||
__x # CONNECTIVITY:CN04_IPV6_SINGLE_PREFIX
|
||||
'All name server(s) IPv6 address(es) are announced in the same IPv6 prefix.';
|
||||
},
|
||||
ERROR_ASN_DATABASE => sub {
|
||||
__x # CONNECTIVITY:ERROR_ASN_DATABASE
|
||||
'ASN Database error. No data to analyze for {ns_ip}.', @_;
|
||||
},
|
||||
EMPTY_ASN_SET => sub {
|
||||
__x # CONNECTIVITY:EMPTY_ASN_SET
|
||||
'AS database returned no informations for IP address {ns_ip}.', @_;
|
||||
},
|
||||
IPV4_SAME_ASN => sub {
|
||||
__x # CONNECTIVITY:IPV4_SAME_ASN
|
||||
'All authoritative nameservers have their IPv4 addresses in the same AS set ({asn_list}).', @_;
|
||||
},
|
||||
IPV4_ONE_ASN => sub {
|
||||
__x # CONNECTIVITY:IPV4_ONE_ASN
|
||||
'All authoritative nameservers have their IPv4 addresses in the same AS ({asn}).', @_;
|
||||
},
|
||||
IPV4_DIFFERENT_ASN => sub {
|
||||
__x # CONNECTIVITY:IPV4_DIFFERENT_ASN
|
||||
'At least two IPv4 addresses of the authoritative nameservers are announced by different AS sets. '
|
||||
. 'A merged list of all AS: ({asn_list}).', @_;
|
||||
},
|
||||
IPV6_SAME_ASN => sub {
|
||||
__x # CONNECTIVITY:IPV6_SAME_ASN
|
||||
'All authoritative nameservers have their IPv6 addresses in the same AS set ({asn_list}).', @_;
|
||||
},
|
||||
IPV6_ONE_ASN => sub {
|
||||
__x # CONNECTIVITY:IPV6_ONE_ASN
|
||||
'All authoritative nameservers have their IPv6 addresses in the same AS ({asn}).', @_;
|
||||
},
|
||||
IPV6_DIFFERENT_ASN => sub {
|
||||
__x # CONNECTIVITY:IPV6_DIFFERENT_ASN
|
||||
'At least two IPv6 addresses of the authoritative nameservers are announced by different AS sets. '
|
||||
. 'A merged list of all AS: ({asn_list}).', @_;
|
||||
},
|
||||
IPV4_DISABLED => sub {
|
||||
__x # CONNECTIVITY:IPV4_DISABLED
|
||||
'IPv4 is disabled, not sending "{rrtype}" query to {ns}.', @_;
|
||||
},
|
||||
IPV6_DISABLED => sub {
|
||||
__x # CONNECTIVITY:IPV6_DISABLED
|
||||
'IPv6 is disabled, not sending "{rrtype}" query to {ns}.', @_;
|
||||
},
|
||||
IPV4_ASN => sub {
|
||||
__x # CONNECTIVITY:IPV4_ASN
|
||||
'Name servers have IPv4 addresses in the following ASs: {asn}.', @_;
|
||||
},
|
||||
IPV6_ASN => sub {
|
||||
__x # CONNECTIVITY:IPV6_ASN
|
||||
'Name servers have IPv6 addresses in the following ASs: {asn}.', @_;
|
||||
},
|
||||
ASN_INFOS_RAW => sub {
|
||||
__x # CONNECTIVITY:ASN_INFOS_RAW
|
||||
'The ASN data for name server IP address "{ns_ip}" is "{data}".', @_;
|
||||
},
|
||||
ASN_INFOS_ANNOUNCE_BY => sub {
|
||||
__x # CONNECTIVITY:ASN_INFOS_ANNOUNCE_BY
|
||||
'Name server IP address "{ns_ip}" is announced by ASN {asn}.', @_;
|
||||
},
|
||||
ASN_INFOS_ANNOUNCE_IN => sub {
|
||||
__x # CONNECTIVITY:ASN_INFOS_ANNOUNCE_IN
|
||||
'Name server IP address "{ns_ip}" is announced in prefix "{prefix}".', @_;
|
||||
},
|
||||
TEST_CASE_END => sub {
|
||||
__x # CONNECTIVITY:TEST_CASE_END
|
||||
'TEST_CASE_END {testcase}.', @_;
|
||||
},
|
||||
TEST_CASE_START => sub {
|
||||
__x # CONNECTIVITY:TEST_CASE_START
|
||||
'TEST_CASE_START {testcase}.', @_;
|
||||
},
|
||||
);
|
||||
|
||||
=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 $string = version();
|
||||
|
||||
Returns a string containing the version of the current module.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub version {
|
||||
return "$Zonemaster::Engine::Test::Connectivity::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, 'Connectivity' ); }
|
||||
|
||||
=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;
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item _connectivity_loop()
|
||||
|
||||
_connectivity_loop( $testcase_string, $zone_name, $ns_array_ref, $logentry_array_ref );
|
||||
|
||||
Verifies name servers reachability. Used as an helper function for Test Cases L<Connectivity01/connectivity01()>
|
||||
and L<Connectivity02/connectivity02()>.
|
||||
|
||||
Takes a string (test case identifier), a L<Zonemaster::Engine::DNSName> object, a reference to an array of L<Zonemaster::Engine::Nameserver>
|
||||
objects and a reference to an array of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub _connectivity_loop {
|
||||
my ( $testcase, $name, $ns_list, $results ) = @_;
|
||||
|
||||
my ( $testcase_prefix, $use_tcp, $protocol );
|
||||
if ( $testcase eq 'connectivity01' ) {
|
||||
( $testcase_prefix, $use_tcp, $protocol ) = ( "CN01", 0, "UDP" );
|
||||
} elsif ( $testcase eq 'connectivity02' ) {
|
||||
( $testcase_prefix, $use_tcp, $protocol ) = ( "CN02", 1, "TCP" );
|
||||
}
|
||||
|
||||
foreach my $ns ( @$ns_list ) {
|
||||
if ( _ip_disabled_message( $results, $ns, qw{SOA NS} ) ) {
|
||||
next;
|
||||
}
|
||||
|
||||
my %packets = (
|
||||
'SOA' => $ns->query( $name, q{SOA}, { usevc => $use_tcp } ),
|
||||
'NS' => $ns->query( $name, q{NS}, { usevc => $use_tcp } )
|
||||
);
|
||||
|
||||
if ( not $packets{SOA} and not $packets{NS} ) {
|
||||
push @$results, _emit_log( "${testcase_prefix}_NO_RESPONSE_${protocol}" => { ns => $ns->string } );
|
||||
next;
|
||||
}
|
||||
|
||||
foreach my $qtype ( qw{SOA NS} ) {
|
||||
my $pkt = $packets{$qtype};
|
||||
|
||||
if ( not $pkt ) {
|
||||
push @$results, _emit_log( "${testcase_prefix}_NO_RESPONSE_${qtype}_QUERY_${protocol}" => { ns => $ns->string } );
|
||||
}
|
||||
elsif ( $pkt->rcode ne q{NOERROR} ) {
|
||||
push @$results, _emit_log( "${testcase_prefix}_UNEXPECTED_RCODE_${qtype}_QUERY_${protocol}" => {
|
||||
ns => $ns->string,
|
||||
rcode => $pkt->rcode
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
my ( $rr ) = $pkt->get_records( $qtype, q{answer} );
|
||||
if ( not $rr ) {
|
||||
push @$results, _emit_log( "${testcase_prefix}_MISSING_${qtype}_RECORD_${protocol}" => { ns => $ns->string } );
|
||||
}
|
||||
elsif ( lc($rr->owner) ne lc($name->fqdn) ) {
|
||||
push @$results, _emit_log( "${testcase_prefix}_WRONG_${qtype}_RECORD_${protocol}" => {
|
||||
ns => $ns->string,
|
||||
domain_found => lc($rr->owner),
|
||||
domain_expected => lc($name->fqdn)
|
||||
}
|
||||
);
|
||||
}
|
||||
elsif ( not $pkt->aa ) {
|
||||
push @$results, _emit_log( "${testcase_prefix}_${qtype}_RECORD_NOT_AA_${protocol}" => { ns => $ns->string } );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
=head1 TESTS
|
||||
|
||||
=over
|
||||
|
||||
=item connectivity01()
|
||||
|
||||
my @logentry_array = connectivity01( $zone );
|
||||
|
||||
Runs the L<Connectivity01 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Connectivity-TP/connectivity01.md>.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub connectivity01 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Connectivity01';
|
||||
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
||||
my $name = name( $zone );
|
||||
my @ns_list = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
||||
|
||||
my @ns_ipv4 = ();
|
||||
my @ns_ipv6 = ();
|
||||
foreach my $ns ( @ns_list ) {
|
||||
if ( $ns->address->version == $IP_VERSION_4 and not Zonemaster::Engine::Profile->effective->get(q{net.ipv4}) ) {
|
||||
push @ns_ipv4, $ns;
|
||||
}
|
||||
elsif ( $ns->address->version == $IP_VERSION_6 and not Zonemaster::Engine::Profile->effective->get(q{net.ipv6}) ) {
|
||||
push @ns_ipv6, $ns;
|
||||
}
|
||||
}
|
||||
if ( @ns_ipv4 ) {
|
||||
push @results, _emit_log( "CN01_IPV4_DISABLED" => { ns_list => join( ';', @ns_ipv4 ) } );
|
||||
}
|
||||
if ( @ns_ipv6 ) {
|
||||
push @results, _emit_log( "CN01_IPV6_DISABLED" => { ns_list => join( ';', @ns_ipv6 ) } );
|
||||
}
|
||||
|
||||
_connectivity_loop("connectivity01", $name, \@ns_list, \@results);
|
||||
|
||||
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
||||
} ## end sub connectivity01
|
||||
|
||||
=over
|
||||
|
||||
=item connectivity02()
|
||||
|
||||
my @logentry_array = connectivity02( $zone );
|
||||
|
||||
Runs the L<Connectivity02 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Connectivity-TP/connectivity02.md>.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub connectivity02 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Connectivity02';
|
||||
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
||||
my $name = name( $zone );
|
||||
my @ns_list = @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) };
|
||||
|
||||
_connectivity_loop("connectivity02", $name, \@ns_list, \@results);
|
||||
|
||||
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
||||
} ## end sub connectivity02
|
||||
|
||||
=over
|
||||
|
||||
=item connectivity03()
|
||||
|
||||
my @logentry_array = connectivity03( $zone );
|
||||
|
||||
Runs the L<Connectivity03 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Connectivity-TP/connectivity03.md>.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub connectivity03 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Connectivity03';
|
||||
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
||||
|
||||
my %ips = ( $IP_VERSION_4 => {}, $IP_VERSION_6 => {} );
|
||||
|
||||
foreach my $ns ( @{ Zonemaster::Engine::TestMethods->method4and5( $zone ) } ) {
|
||||
my $addr = $ns->address;
|
||||
$ips{ $addr->version }{ $addr->ip } = $addr;
|
||||
}
|
||||
|
||||
my @v4ips = values %{ $ips{$IP_VERSION_4} };
|
||||
my @v6ips = values %{ $ips{$IP_VERSION_6} };
|
||||
|
||||
my @v4asns;
|
||||
my @v4asnsets;
|
||||
my @v6asns;
|
||||
my @v6asnsets;
|
||||
|
||||
foreach my $v4ip ( @v4ips ) {
|
||||
my ( $asnref, $prefix, $raw, $ret_code ) = Zonemaster::Engine::ASNLookup->get_with_prefix( $v4ip );
|
||||
if ( defined $ret_code and ( $ret_code eq q{ERROR_ASN_DATABASE} or $ret_code eq q{EMPTY_ASN_SET} ) ) {
|
||||
push @results, _emit_log( $ret_code => { ns_ip => $v4ip->short } );
|
||||
}
|
||||
else {
|
||||
if ( $raw ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
ASN_INFOS_RAW => {
|
||||
ns_ip => $v4ip->short,
|
||||
data => $raw,
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( $asnref ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
ASN_INFOS_ANNOUNCE_BY => {
|
||||
ns_ip => $v4ip->short,
|
||||
asn => join( q{,}, sort @{$asnref} ),
|
||||
}
|
||||
);
|
||||
push @v4asns, @{$asnref};
|
||||
push @v4asnsets, join( q{,}, sort { $a <=> $b } @{$asnref} );
|
||||
}
|
||||
if ( $prefix ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
ASN_INFOS_ANNOUNCE_IN => {
|
||||
ns_ip => $v4ip->short,
|
||||
prefix => sprintf "%s/%d",
|
||||
$prefix->ip, $prefix->prefixlen,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
} ## end foreach my $v4ip ( @v4ips )
|
||||
foreach my $v6ip ( @v6ips ) {
|
||||
my ( $asnref, $prefix, $raw, $ret_code ) = Zonemaster::Engine::ASNLookup->get_with_prefix( $v6ip );
|
||||
if ( defined $ret_code and ( $ret_code eq q{ERROR_ASN_DATABASE} or $ret_code eq q{EMPTY_ASN_SET} ) ) {
|
||||
push @results, _emit_log( $ret_code => { ns_ip => $v6ip->short } );
|
||||
}
|
||||
else {
|
||||
if ( $raw ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
ASN_INFOS_RAW => {
|
||||
ns_ip => $v6ip->short,
|
||||
data => $raw,
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( $asnref ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
ASN_INFOS_ANNOUNCE_BY => {
|
||||
ns_ip => $v6ip->short,
|
||||
asn => join( q{,}, sort @{$asnref} ),
|
||||
}
|
||||
);
|
||||
push @v6asns, @{$asnref};
|
||||
push @v6asnsets, join( q{,}, sort { $a <=> $b } @{$asnref} );
|
||||
}
|
||||
if ( $prefix ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
ASN_INFOS_ANNOUNCE_IN => {
|
||||
ns_ip => $v6ip->short,
|
||||
prefix => sprintf "%s/%d",
|
||||
$prefix->short, $prefix->prefixlen,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
} ## end foreach my $v6ip ( @v6ips )
|
||||
|
||||
@v4asns = uniq sort { $a <=> $b } @v4asns;
|
||||
@v4asnsets = uniq sort @v4asnsets;
|
||||
@v6asns = uniq sort { $a <=> $b } @v6asns;
|
||||
@v6asnsets = uniq sort @v6asnsets;
|
||||
|
||||
if ( scalar @v4asns ) {
|
||||
if ( @v4asns == 1 ) {
|
||||
push @results, _emit_log( IPV4_ONE_ASN => { asn => $v4asns[0] } );
|
||||
}
|
||||
elsif ( @v4asnsets == 1 ) {
|
||||
push @results, _emit_log( IPV4_SAME_ASN => { asn_list => $v4asnsets[0] } );
|
||||
}
|
||||
else {
|
||||
push @results, _emit_log( IPV4_DIFFERENT_ASN => { asn_list => join( q{,}, @v4asns ) } );
|
||||
}
|
||||
}
|
||||
|
||||
if ( scalar @v6asns ) {
|
||||
if ( @v6asns == 1 ) {
|
||||
push @results, _emit_log( IPV6_ONE_ASN => { asn => $v6asns[0] } );
|
||||
}
|
||||
elsif ( @v6asnsets == 1 ) {
|
||||
push @results, _emit_log( IPV6_SAME_ASN => { asn_list => $v6asnsets[0] } );
|
||||
}
|
||||
else {
|
||||
push @results, _emit_log( IPV6_DIFFERENT_ASN => { asn_list => join( q{,}, @v6asns ) } );
|
||||
}
|
||||
}
|
||||
|
||||
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
||||
} ## end sub connectivity03
|
||||
|
||||
=over
|
||||
|
||||
=item connectivity04()
|
||||
|
||||
my @logentry_array = connectivity04( $zone );
|
||||
|
||||
Runs the L<Connectivity04 Test Case|https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/Connectivity-TP/connectivity04.md>.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns a list of L<Zonemaster::Engine::Logger::Entry> objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub connectivity04 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
local $Zonemaster::Engine::Logger::TEST_CASE_NAME = 'Connectivity04';
|
||||
push my @results, _emit_log( TEST_CASE_START => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } );
|
||||
|
||||
my %prefixes;
|
||||
my %ip_already_processed;
|
||||
|
||||
my @nss = grep { $_->isa('Zonemaster::Engine::Nameserver') } (
|
||||
@{ Zonemaster::Engine::TestMethodsV2->get_del_ns_names_and_ips( $zone ) // [] },
|
||||
@{ Zonemaster::Engine::TestMethodsV2->get_zone_ns_names_and_ips( $zone ) // [] }
|
||||
);
|
||||
|
||||
foreach my $ns ( @nss ) {
|
||||
my $ip = $ns->address;
|
||||
|
||||
next if exists $ip_already_processed{$ip->version}{$ip->short};
|
||||
$ip_already_processed{$ip->version}{$ip->short} = 1;
|
||||
|
||||
my ( $asnref, $prefix, $raw, $ret_code ) = Zonemaster::Engine::ASNLookup->get_with_prefix( $ip );
|
||||
|
||||
if ( defined $ret_code and ( $ret_code eq q{ERROR_ASN_DATABASE} or $ret_code eq q{EMPTY_ASN_SET} ) ) {
|
||||
if ( $ret_code eq 'ERROR_ASN_DATABASE' ) {
|
||||
$ret_code = 'CN04_ERROR_PREFIX_DATABASE';
|
||||
}
|
||||
elsif ( $ret_code eq 'EMPTY_ASN_SET' ) {
|
||||
$ret_code = 'CN04_EMPTY_PREFIX_SET';
|
||||
}
|
||||
|
||||
push @results, _emit_log( $ret_code => { ns_ip => $ip->short } );
|
||||
}
|
||||
else {
|
||||
if ( $raw ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
CN04_ASN_INFOS_RAW => {
|
||||
ns_ip => $ip->short,
|
||||
data => $raw,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ( $prefix ) {
|
||||
my $prefix_str;
|
||||
|
||||
if ( $prefix->version == 4 ) {
|
||||
$prefix_str = $prefix->prefix;
|
||||
}
|
||||
elsif ( $prefix->version == 6 ) {
|
||||
$prefix_str = $prefix->short . '/' . $prefix->prefixlen;
|
||||
}
|
||||
else {
|
||||
next;
|
||||
}
|
||||
|
||||
push @results,
|
||||
_emit_log(
|
||||
CN04_ASN_INFOS_ANNOUNCE_IN => {
|
||||
ns_ip => $ip->short,
|
||||
prefix => sprintf "%s", $prefix_str,
|
||||
}
|
||||
);
|
||||
|
||||
push @{ $prefixes{$prefix->version}{$prefix_str} }, $ns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $ip_version ( sort keys %prefixes ) {
|
||||
my @combined_ns;
|
||||
|
||||
foreach my $prefix ( keys %{ $prefixes{$ip_version} } ) {
|
||||
if ( scalar @{ $prefixes{$ip_version}{$prefix} } == 1 ) {
|
||||
push @combined_ns, @{ $prefixes{$ip_version}{$prefix} };
|
||||
}
|
||||
elsif ( scalar @{ $prefixes{$ip_version}{$prefix} } >= 2 ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
"CN04_IPV${ip_version}_SAME_PREFIX" => {
|
||||
ip_prefix => $prefix,
|
||||
ns_list => join( q{;}, sort @{ $prefixes{$ip_version}{$prefix} } )
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( scalar @combined_ns ) {
|
||||
push @results,
|
||||
_emit_log(
|
||||
"CN04_IPV${ip_version}_DIFFERENT_PREFIX" => {
|
||||
ns_list => join( q{;}, uniq sort @combined_ns )
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
push @results, _emit_log( "CN04_IPV${ip_version}_SINGLE_PREFIX" => {} ) if scalar keys %{ $prefixes{$ip_version} } == 1
|
||||
and scalar @{ (values %{ $prefixes{$ip_version} })[0] } == scalar keys %{ $ip_already_processed{$ip_version} };
|
||||
}
|
||||
|
||||
return ( @results, _emit_log( TEST_CASE_END => { testcase => $Zonemaster::Engine::Logger::TEST_CASE_NAME } ) );
|
||||
} ## end sub connectivity04
|
||||
|
||||
1;
|
||||
1044
zonemaster-engine/lib/Zonemaster/Engine/Test/Consistency.pm
Normal file
1044
zonemaster-engine/lib/Zonemaster/Engine/Test/Consistency.pm
Normal file
File diff suppressed because it is too large
Load Diff
5558
zonemaster-engine/lib/Zonemaster/Engine/Test/DNSSEC.pm
Normal file
5558
zonemaster-engine/lib/Zonemaster/Engine/Test/DNSSEC.pm
Normal file
File diff suppressed because it is too large
Load Diff
1106
zonemaster-engine/lib/Zonemaster/Engine/Test/Delegation.pm
Normal file
1106
zonemaster-engine/lib/Zonemaster/Engine/Test/Delegation.pm
Normal file
File diff suppressed because it is too large
Load Diff
1808
zonemaster-engine/lib/Zonemaster/Engine/Test/Nameserver.pm
Normal file
1808
zonemaster-engine/lib/Zonemaster/Engine/Test/Nameserver.pm
Normal file
File diff suppressed because it is too large
Load Diff
1134
zonemaster-engine/lib/Zonemaster/Engine/Test/Syntax.pm
Normal file
1134
zonemaster-engine/lib/Zonemaster/Engine/Test/Syntax.pm
Normal file
File diff suppressed because it is too large
Load Diff
1632
zonemaster-engine/lib/Zonemaster/Engine/Test/Zone.pm
Normal file
1632
zonemaster-engine/lib/Zonemaster/Engine/Test/Zone.pm
Normal file
File diff suppressed because it is too large
Load Diff
115
zonemaster-engine/lib/Zonemaster/Engine/TestMethods.pm
Normal file
115
zonemaster-engine/lib/Zonemaster/Engine/TestMethods.pm
Normal file
@@ -0,0 +1,115 @@
|
||||
package Zonemaster::Engine::TestMethods;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.3");
|
||||
|
||||
use List::MoreUtils qw[uniq];
|
||||
use Zonemaster::Engine::Util;
|
||||
|
||||
sub method1 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
return $zone->parent;
|
||||
}
|
||||
|
||||
sub method2 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
return $zone->glue_names;
|
||||
}
|
||||
|
||||
sub method3 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my @child_nsnames;
|
||||
my @nsnames;
|
||||
my $ns_aref = $zone->query_all( $zone->name, q{NS} );
|
||||
foreach my $p ( @{$ns_aref} ) {
|
||||
next if not $p;
|
||||
push @nsnames, $p->get_records_for_name( q{NS}, $zone->name );
|
||||
}
|
||||
@child_nsnames = uniq map { name( lc( $_->nsdname ) ) } @nsnames;
|
||||
|
||||
return [@child_nsnames];
|
||||
}
|
||||
|
||||
sub method4 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
return $zone->glue;
|
||||
}
|
||||
|
||||
sub method5 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
return $zone->ns;
|
||||
}
|
||||
|
||||
sub method2and3 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my %union = map { $_->string => $_ } @{ $class->method2( $zone ) }, @{ $class->method3( $zone ) };
|
||||
return [ @union{ sort keys %union } ];
|
||||
}
|
||||
|
||||
sub method4and5 {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my %union = map { $_->string => $_ } @{ $class->method4( $zone ) }, @{ $class->method5( $zone ) };
|
||||
return [ @union{ sort keys %union } ];
|
||||
}
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::TestMethods - Methods common to Test Specification used in test modules
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my @results = Zonemaster::Engine::TestMethods->method1($zone);
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
For details on what these methods implement, see the test
|
||||
specification documents.
|
||||
|
||||
=over
|
||||
|
||||
=item method1($zone)
|
||||
|
||||
Returns either a Zonemaster::Engine::Zone or undef.
|
||||
|
||||
=item method2($zone)
|
||||
|
||||
Returns an arrayref of Zonemaster::Engine::DNSName objects.
|
||||
|
||||
=item method3($zone)
|
||||
|
||||
Returns an arrayref of Zonemaster::Engine::DNSName objects.
|
||||
|
||||
=item method4($zone)
|
||||
|
||||
Returns something that behaves like an arrayref of Zonemaster::Engine::Nameserver objects.
|
||||
|
||||
=item method5($zone)
|
||||
|
||||
Returns something that behaves like an arrayref of Zonemaster::Engine::Nameserver objects.
|
||||
|
||||
=item method2and3($zone)
|
||||
|
||||
Returns the union of Zonemaster::Engine::DNSName objects returned by
|
||||
method2($zone) and method3($zone) in a arrayref.
|
||||
The elements are sorted according to their string representation.
|
||||
|
||||
=item method4and5($zone)
|
||||
|
||||
Returns the union of Zonemaster::Engine::Nameserver objects returned by
|
||||
method4($zone) and method5($zone) in a arrayref.
|
||||
The elements are sorted according to their string representation.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
836
zonemaster-engine/lib/Zonemaster/Engine/TestMethodsV2.pm
Normal file
836
zonemaster-engine/lib/Zonemaster/Engine/TestMethodsV2.pm
Normal file
@@ -0,0 +1,836 @@
|
||||
package Zonemaster::Engine::TestMethodsV2;
|
||||
|
||||
use v5.26.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.0");
|
||||
|
||||
use Carp;
|
||||
use List::MoreUtils qw[uniq];
|
||||
use Memoize;
|
||||
|
||||
use Zonemaster::Engine::Util;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::TestMethodsV2 - Version 2 of Methods common to Test Specifications used in Test modules
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my @results = Zonemaster::Engine::TestMethodsV2->get_parent_ns_ips($zone);
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
For details on what these Methods implement, see the Test Specifications document
|
||||
(https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/tests/MethodsV2.md).
|
||||
|
||||
=over
|
||||
|
||||
=item get_parent_ns_names_and_ips($zone)
|
||||
|
||||
[External]
|
||||
|
||||
This Method obtains the name server names and IP addresses that serve the parent zone, i.e. the zone from which the Child Zone is delegated.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an empty arrayref if C<$zone> is the root zone or if an undelegated test is in progress. Else, returns an arrayref of L<Zonemaster::Engine::Nameserver> objects, or C<undef> if no parent zone was found.
|
||||
|
||||
The result of this Method is cached for performance reasons. This cache can be invalidated by calling C<clear_cache()> if necessary.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_parent_ns_names_and_ips {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my $is_undelegated = Zonemaster::Engine::Recursor->has_fake_addresses( $zone->name->string );
|
||||
|
||||
if ( $zone->name->string eq "." or $is_undelegated ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
my %handled_servers;
|
||||
my @parent_ns;
|
||||
|
||||
my %rrs_ns;
|
||||
my $type_soa = q{SOA};
|
||||
my $type_ns = q{NS};
|
||||
|
||||
my %remaining_servers = ( '.' => [ Zonemaster::Engine::Recursor->root_servers ] );
|
||||
|
||||
my sub push_to_remaining_servers {
|
||||
my ( $ns, $zone_name ) = @_;
|
||||
|
||||
unless ( exists $handled_servers{$zone_name}{"$ns"} ) {
|
||||
unless ( grep { $_ eq $ns } @{ $remaining_servers{$zone_name} } ) {
|
||||
push @{ $remaining_servers{$zone_name} }, $ns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
while ( my $zone_name = ( sort keys %remaining_servers )[0] ) {
|
||||
CUR_SERVERS:
|
||||
while ( my $ns = shift @{ $remaining_servers{$zone_name} } ) {
|
||||
my $addr = $ns->address->short;
|
||||
if ( exists $handled_servers{$zone_name}{"$ns"} ) {
|
||||
push @parent_ns, $ns if ( grep { $_->address->short eq $addr and $_ ne $ns } @parent_ns );
|
||||
next CUR_SERVERS;
|
||||
}
|
||||
$handled_servers{$zone_name}{"$ns"} = 1;
|
||||
|
||||
if ( ( $ns->address->version == 4 and not Zonemaster::Engine::Profile->effective->get( q{net.ipv4} ) )
|
||||
or ( $ns->address->version == 6 and not Zonemaster::Engine::Profile->effective->get( q{net.ipv6} ) ) ) {
|
||||
next CUR_SERVERS;
|
||||
}
|
||||
|
||||
my $p_soa = $ns->query( $zone_name, $type_soa );
|
||||
|
||||
unless ( $p_soa and $p_soa->rcode eq 'NOERROR' and $p_soa->aa and scalar $p_soa->get_records_for_name( $type_soa, $zone_name, 'answer' ) == 1 ) {
|
||||
next CUR_SERVERS;
|
||||
}
|
||||
|
||||
my $p_ns = $ns->query( $zone_name, $type_ns );
|
||||
|
||||
unless ( $p_ns and $p_ns->rcode eq 'NOERROR' and $p_ns->aa and scalar $p_ns->get_records( $type_ns, 'answer' ) > 0
|
||||
and scalar $p_ns->get_records( $type_ns, 'answer' ) == scalar $p_ns->get_records_for_name( $type_ns, $zone_name, 'answer' )
|
||||
) {
|
||||
next CUR_SERVERS;
|
||||
}
|
||||
|
||||
%rrs_ns = map { name( $_->nsdname )->string => [] } $p_ns->get_records_for_name( $type_ns, $zone_name, 'answer' );
|
||||
|
||||
foreach my $rr ( $p_ns->get_records( 'A', 'additional' ), $p_ns->get_records( 'AAAA', 'additional' ) ) {
|
||||
if ( exists $rrs_ns{name( $rr->owner )->string} ) {
|
||||
push @{ $rrs_ns{name( $rr->owner )->string} }, $rr->address;
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $ns_name ( keys %rrs_ns ) {
|
||||
unless ( scalar @{ $rrs_ns{$ns_name} } ) {
|
||||
for my $qtype ( q{A}, q{AAAA} ) {
|
||||
my $p = Zonemaster::Engine::Recursor->recurse( $ns_name, $qtype );
|
||||
|
||||
if ( $p and $p->rcode eq 'NOERROR' ) {
|
||||
push @{ $rrs_ns{$ns_name} }, $_->address for $p->get_records_for_name( $qtype, $ns_name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $ns_ip ( @{ $rrs_ns{$ns_name} } ) {
|
||||
push_to_remaining_servers ns( $ns_name, $ns_ip ), $zone_name;
|
||||
}
|
||||
}
|
||||
|
||||
my $intermediate_query_name = name( $zone_name );
|
||||
my $loop_zone_name = $zone_name;
|
||||
my $loop_counter = 0;
|
||||
|
||||
LOOP:
|
||||
while() {
|
||||
$loop_counter += 1;
|
||||
if ( $loop_counter >= 1000 ) {
|
||||
Zonemaster::Engine->logger->add( LOOP_PROTECTION => {
|
||||
caller => 'Zonemaster::Engine::TestMethodsV2->get_parent_ns_ips',
|
||||
child_zone_name => $zone->name,
|
||||
name => $loop_zone_name,
|
||||
intermediate_query_name => $intermediate_query_name
|
||||
}
|
||||
);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
last if scalar @{ $intermediate_query_name->labels } >= scalar @{ $zone->name->labels };
|
||||
$intermediate_query_name = name( @{ $zone->name->labels }[ ( scalar @{ $zone->name->labels } - scalar @{ $intermediate_query_name->labels } ) - 1 ] . '.' . $intermediate_query_name->string );
|
||||
|
||||
$p_soa = $ns->query( $intermediate_query_name, $type_soa );
|
||||
|
||||
unless ( $p_soa ) {
|
||||
next CUR_SERVERS;
|
||||
}
|
||||
|
||||
if ( $p_soa->rcode eq 'NOERROR' and $p_soa->aa and scalar $p_soa->get_records_for_name( $type_soa, $intermediate_query_name, 'answer' ) == 1 ) {
|
||||
if ( $intermediate_query_name->string eq $zone->name->string ) {
|
||||
push @parent_ns, $ns;
|
||||
}
|
||||
else {
|
||||
$p_ns = $ns->query( $intermediate_query_name, $type_ns );
|
||||
|
||||
unless ( $p_ns and $p_ns->rcode eq 'NOERROR' and $p_ns->aa and scalar $p_ns->get_records( $type_ns, 'answer' ) > 0
|
||||
and scalar $p_ns->get_records( $type_ns, 'answer' ) == scalar $p_ns->get_records_for_name( $type_ns, $intermediate_query_name, 'answer' )
|
||||
) {
|
||||
next CUR_SERVERS;
|
||||
}
|
||||
|
||||
my %rrs_ns_bis = map { name( $_->nsdname )->string => [] } $p_ns->get_records_for_name( $type_ns, $intermediate_query_name, 'answer' );
|
||||
|
||||
foreach my $rr ( $p_ns->get_records( 'A', 'additional' ), $p_ns->get_records( 'AAAA', 'additional' ) ) {
|
||||
if ( exists $rrs_ns_bis{name( $rr->owner )->string} ) {
|
||||
push @{ $rrs_ns_bis{name( $rr->owner )->string} }, $rr->address;
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $ns_name ( keys %rrs_ns_bis ) {
|
||||
unless ( scalar @{ $rrs_ns_bis{$ns_name} } > 0 ) {
|
||||
for my $qtype ( q{A}, q{AAAA} ) {
|
||||
my $p = Zonemaster::Engine::Recursor->recurse( $ns_name, $qtype );
|
||||
|
||||
if ( $p and $p->rcode eq 'NOERROR' ) {
|
||||
push @{ $rrs_ns_bis{$ns_name} }, $_->address for $p->get_records_for_name( $qtype, $ns_name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $ns_ip ( @{ $rrs_ns_bis{$ns_name} } ) {
|
||||
push_to_remaining_servers ns( $ns_name, $ns_ip ), $intermediate_query_name;
|
||||
}
|
||||
}
|
||||
|
||||
$loop_zone_name = $intermediate_query_name->string;
|
||||
next LOOP;
|
||||
}
|
||||
}
|
||||
elsif ( $p_soa->is_redirect and scalar $p_soa->get_records_for_name( $type_ns, $intermediate_query_name, 'authority' ) ) {
|
||||
if ( $intermediate_query_name->string eq $zone->name->string ) {
|
||||
push @parent_ns, $ns;
|
||||
}
|
||||
else {
|
||||
my %rrs_ns_bis = map { name( $_->nsdname )->string => [] } $p_soa->get_records_for_name( $type_ns, $intermediate_query_name, 'authority' );
|
||||
|
||||
foreach my $rr ( $p_soa->get_records( 'A', 'additional' ), $p_soa->get_records( 'AAAA', 'additional' ) ) {
|
||||
if ( exists $rrs_ns_bis{name( $rr->owner )->string} ) {
|
||||
push @{ $rrs_ns_bis{name( $rr->owner )->string} }, $rr->address;
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $ns_name ( keys %rrs_ns_bis ) {
|
||||
unless ( scalar @{ $rrs_ns_bis{$ns_name} } > 0 ) {
|
||||
for my $qtype ( q{A}, q{AAAA} ) {
|
||||
my $p = Zonemaster::Engine::Recursor->recurse( $ns_name, $qtype );
|
||||
|
||||
if ( $p and $p->rcode eq 'NOERROR' ) {
|
||||
push @{ $rrs_ns_bis{$ns_name} }, $_->address for $p->get_records_for_name( $qtype, $ns_name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $ns_ip ( @{ $rrs_ns_bis{$ns_name} } ) {
|
||||
push_to_remaining_servers ns( $ns_name, $ns_ip ), $intermediate_query_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif ( $p_soa->rcode eq 'NOERROR' and $p_soa->aa ) {
|
||||
next LOOP if $intermediate_query_name->string ne $zone->name->string;
|
||||
}
|
||||
|
||||
next CUR_SERVERS;
|
||||
}
|
||||
}
|
||||
|
||||
delete $remaining_servers{$zone_name};
|
||||
}
|
||||
|
||||
if ( scalar @parent_ns ) {
|
||||
return [ uniq sort @parent_ns ]
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
# Memoize get_parent_ns_names_and_ips() because it is expensive and gets called a few
|
||||
# times with identical parameters.
|
||||
|
||||
memoize('get_parent_ns_names_and_ips',
|
||||
NORMALIZER => sub {
|
||||
my ( $class, $zone ) = @_;
|
||||
join "\034", ( $class, $zone->name );
|
||||
});
|
||||
|
||||
=over
|
||||
|
||||
=item get_parent_ns_ips($zone)
|
||||
|
||||
[External]
|
||||
|
||||
This Method obtains the name servers that serve the parent zone, i.e. the zone from which the Child Zone is delegated. If more than one name server share the same IP address, only one among them is kept.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an empty arrayref if C<$zone> is the root zone or if an undelegated test is in progress. Else, returns an arrayref of L<Zonemaster::Engine::Nameserver> objects, or C<undef> if no parent zone was found.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_parent_ns_ips {
|
||||
my ( $class, $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.
|
||||
|
||||
my $nameservers = $class->get_parent_ns_names_and_ips( $zone );
|
||||
|
||||
return undef unless defined $nameservers;
|
||||
|
||||
my %ns_by_ip = ();
|
||||
foreach my $ns ( @$nameservers ) {
|
||||
my $ip = $ns->address->short;
|
||||
$ns_by_ip{$ip} = $ns unless exists $ns_by_ip{$ip};
|
||||
}
|
||||
|
||||
return [ sort values %ns_by_ip ];
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item _get_oob_ips($zone, $ns_names_ref)
|
||||
|
||||
[Internal]
|
||||
|
||||
This Method will obtain the IP addresses of the Out-Of-Bailiwick name servers for the given zone and a given set of name server names.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object and an arrayref of L<Zonemaster::Engine::Nameserver> objects.
|
||||
|
||||
Returns an arrayref of L<Zonemaster::Engine::Nameserver> objects for each name server name that was successfully resolved to an IP address,
|
||||
and L<Zonemaster::Engine::DNSName> objects for each name server name that could not be resolved to an IP address.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub _get_oob_ips {
|
||||
my ( $class, $zone, $ns_names_ref ) = @_;
|
||||
|
||||
unless ( defined $ns_names_ref and scalar @{ $ns_names_ref } ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
my $is_undelegated = Zonemaster::Engine::Recursor->has_fake_addresses( $zone->name->string );
|
||||
my @oob_ns;
|
||||
my $found_ip;
|
||||
|
||||
for my $ns_name ( @{ $ns_names_ref } ) {
|
||||
$found_ip = 0;
|
||||
|
||||
unless ( $zone->name->is_in_bailiwick( $ns_name ) ) {
|
||||
if ( $is_undelegated and scalar Zonemaster::Engine::Recursor->get_fake_addresses( $zone->name->string, $ns_name->string ) ) {
|
||||
for my $ip ( Zonemaster::Engine::Recursor->get_fake_addresses( $zone->name->string, $ns_name->string ) ) {
|
||||
push @oob_ns, ns( $ns_name->string, $ip );
|
||||
$found_ip = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for my $qtype ( q{A}, q{AAAA} ) {
|
||||
my $p = Zonemaster::Engine::Recursor->recurse( $ns_name, $qtype );
|
||||
|
||||
if ( $p and $p->rcode eq q{NOERROR} ) {
|
||||
if ( $p->has_rrs_of_type_for_name( q{CNAME}, $ns_name, q{answer} ) ) {
|
||||
my %cnames = map { name( $_->owner ) => name( $_->cname ) } $p->get_records( q{CNAME}, q{answer} );
|
||||
my $target = $ns_name;
|
||||
$target = $cnames{$target} while $cnames{$target};
|
||||
|
||||
for my $rr ( $p->get_records_for_name( $qtype, $target ) ) {
|
||||
push @oob_ns, ns( $ns_name, $rr->address );
|
||||
$found_ip = 1;
|
||||
}
|
||||
}
|
||||
# CNAME was followed in a new recursive query
|
||||
elsif ( name( ($p->question)[0]->owner ) ne $ns_name and grep { $_->tag eq 'CNAME_FOLLOWED_OUT_OF_ZONE' and grep /^$ns_name$/, values %{ $_->args } } @{ Zonemaster::Engine->logger->entries } ) {
|
||||
my $cname_ns_name = name( ($p->question)[0]->owner );
|
||||
my $target = $cname_ns_name;
|
||||
|
||||
if ( $p->has_rrs_of_type_for_name( q{CNAME}, $cname_ns_name, q{answer} ) ) {
|
||||
my %cnames = map { name( $_->owner ) => name( $_->cname ) } $p->get_records( q{CNAME}, q{answer} );
|
||||
$target = $cnames{$target} while $cnames{$target};
|
||||
}
|
||||
|
||||
for my $rr ( $p->get_records_for_name( $qtype, $target ) ) {
|
||||
push @oob_ns, ns( $ns_name, $rr->address );
|
||||
$found_ip = 1;
|
||||
}
|
||||
}
|
||||
elsif ( $p->has_rrs_of_type_for_name( $qtype, $ns_name ) ) {
|
||||
for my $rr ( $p->get_records_for_name( $qtype, $ns_name ) ) {
|
||||
push @oob_ns, ns( $ns_name, $rr->address );
|
||||
$found_ip = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
push @oob_ns, $ns_name unless $found_ip;
|
||||
}
|
||||
}
|
||||
|
||||
return [ uniq sort @oob_ns ];
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item _get_delegation($zone)
|
||||
|
||||
[Internal]
|
||||
|
||||
This Method will obtain the name server names (from the NS records) and the IP addresses (from Glue records) from the delegation of the given zone from the parent zone.
|
||||
Glue Records are address records for In-Bailiwick name server names, if any.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an arrayref of L<Zonemaster::Engine::Nameserver> objects, or C<undef> if no parent zone was found.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub _get_delegation {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my $is_undelegated = Zonemaster::Engine::Recursor->has_fake_addresses( $zone->name->string );
|
||||
my %delegation_ns;
|
||||
my %aa_ns;
|
||||
my @ib_ns;
|
||||
|
||||
if ( $is_undelegated ) {
|
||||
for my $ns_name ( Zonemaster::Engine::Recursor->get_fake_names( $zone->name->string ) ) {
|
||||
if ( $zone->name->is_in_bailiwick( name( $ns_name ) ) ) {
|
||||
for my $ns_ip ( Zonemaster::Engine::Recursor->get_fake_addresses( $zone->name->string, $ns_name ) ){
|
||||
push @ib_ns, ns( $ns_name, $ns_ip);
|
||||
}
|
||||
}
|
||||
else {
|
||||
push @ib_ns, name( $ns_name );
|
||||
}
|
||||
}
|
||||
|
||||
return [ uniq sort @ib_ns ];
|
||||
}
|
||||
elsif ( $zone->name->string eq '.' ) {
|
||||
return [ uniq sort Zonemaster::Engine::Recursor->root_servers() ];
|
||||
}
|
||||
else {
|
||||
my $parent_ref = $class->get_parent_ns_ips( $zone );
|
||||
|
||||
return undef unless defined $parent_ref;
|
||||
|
||||
for my $ns ( @{ $parent_ref } ) {
|
||||
my $p = $ns->query( $zone->name, q{NS} );
|
||||
|
||||
if ( $p and $p->rcode eq q{NOERROR} ) {
|
||||
if ( $p->is_redirect ){
|
||||
for my $rr ( $p->get_records_for_name( q{NS}, $zone->name->string, q{authority} ) ) {
|
||||
$delegation_ns{$rr->nsdname} = [] unless exists $delegation_ns{$rr->nsdname};
|
||||
}
|
||||
|
||||
for my $rr ( $p->get_records( q{A}, q{additional} ), $p->get_records( q{AAAA}, q{additional} ) ) {
|
||||
if ( $zone->name->is_in_bailiwick( name( $rr->owner ) ) and scalar grep { $_ eq $rr->owner } keys %delegation_ns ) {
|
||||
push @{ $delegation_ns{$rr->owner} }, $rr->address;
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif ( $p->aa and scalar $p->get_records_for_name( q{NS}, $zone->name->string, q{answer} ) ) {
|
||||
for my $rr ( $p->get_records_for_name( q{NS}, $zone->name->string, q{answer} ) ) {
|
||||
$aa_ns{$rr->nsdname} = [] unless exists $aa_ns{$rr->nsdname};
|
||||
}
|
||||
|
||||
for my $rr ( $p->get_records( q{A}, q{additional} ), $p->get_records( q{AAAA}, q{additional} ) ) {
|
||||
if ( $zone->name->is_in_bailiwick( name( $rr->owner ) ) and scalar grep { $_ eq $rr->owner } keys %aa_ns ) {
|
||||
push @{ $aa_ns{$rr->owner} }, $rr->address;
|
||||
}
|
||||
}
|
||||
|
||||
for my $ns_name ( keys %aa_ns ) {
|
||||
unless ( scalar $aa_ns{$ns_name} ) {
|
||||
for my $qtype ( q{A}, q{AAAA} ) {
|
||||
my $p = Zonemaster::Engine::Recursor->recurse( $ns_name, $qtype );
|
||||
|
||||
if ( $p and $p->rcode eq q{NOERROR} ) {
|
||||
if ( $p->has_rrs_of_type_for_name( q{CNAME}, $ns_name, q{answer} ) ) {
|
||||
my %cnames = map { name( $_->owner ) => name( $_->cname ) } $p->get_records( q{CNAME}, q{answer} );
|
||||
my $target = $ns_name;
|
||||
$target = $cnames{$target} while $cnames{$target};
|
||||
|
||||
for my $rr ( $p->get_records_for_name( $qtype, $target ) ) {
|
||||
push @{ $aa_ns{$ns_name} }, $rr->address;
|
||||
}
|
||||
}
|
||||
# CNAME was followed in a new recursive query
|
||||
elsif ( name( ($p->question)[0]->owner ) ne $ns_name and grep { $_->tag eq 'CNAME_FOLLOWED_OUT_OF_ZONE' and grep /^$ns_name$/, values %{ $_->args } } @{ Zonemaster::Engine->logger->entries } ) {
|
||||
my $cname_ns_name = name( ($p->question)[0]->owner );
|
||||
my $target = $cname_ns_name;
|
||||
|
||||
if ( $p->has_rrs_of_type_for_name( q{CNAME}, $cname_ns_name, q{answer} ) ) {
|
||||
my %cnames = map { name( $_->owner ) => name( $_->cname ) } $p->get_records( q{CNAME}, q{answer} );
|
||||
$target = $cnames{$target} while $cnames{$target};
|
||||
}
|
||||
|
||||
for my $rr ( $p->get_records_for_name( $qtype, $target ) ) {
|
||||
push @{ $aa_ns{$ns_name} }, $rr->address;
|
||||
}
|
||||
}
|
||||
elsif ( $p->has_rrs_of_type_for_name( $qtype, $ns_name ) ) {
|
||||
for my $rr ( $p->get_records_for_name( $qtype, $ns_name ) ) {
|
||||
push @{ $aa_ns{$ns_name} }, $rr->address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $hash_ref;
|
||||
if ( scalar keys %delegation_ns ) {
|
||||
$hash_ref = \%delegation_ns;
|
||||
}
|
||||
elsif ( scalar keys %aa_ns ) {
|
||||
$hash_ref = \%aa_ns;
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
|
||||
for my $ns_name ( keys %{ $hash_ref } ) {
|
||||
if ( scalar @{ %{ $hash_ref }{$ns_name} } ) {
|
||||
for my $ns_ip ( uniq @{ %{ $hash_ref }{$ns_name} } ) {
|
||||
push @ib_ns, ns( $ns_name, $ns_ip );
|
||||
}
|
||||
}
|
||||
else {
|
||||
push @ib_ns, name( $ns_name );
|
||||
}
|
||||
}
|
||||
|
||||
return [ uniq sort @ib_ns ];
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item get_del_ns_names_and_ips($zone)
|
||||
|
||||
[External]
|
||||
|
||||
This Method will obtain the name server names (from the NS records) and the IP addresses (from Glue Records) from the delegation of the given zone from the parent zone.
|
||||
Glue Records, if any, are address records for name server names. Also obtain the IP addresses for the Out-Of-Bailiwick name server names, if any.
|
||||
If the Glue Records include address records for Out-Of-Bailiwick name servers they will be included twice, unless identical.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an arrayref of L<Zonemaster::Engine::Nameserver> objects for each name server name that was successfully resolved to an IP address,
|
||||
and L<Zonemaster::Engine::DNSName> objects for each name server name that could not be resolved to an IP address, or C<undef> if no parent zone was found.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_del_ns_names_and_ips {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my $ns_ref = $class->_get_delegation( $zone );
|
||||
|
||||
return undef unless defined $ns_ref;
|
||||
|
||||
my @ns_names = grep { $_->isa('Zonemaster::Engine::DNSName') } @{ $ns_ref };
|
||||
|
||||
my $oob_ns_ref = $class->_get_oob_ips( $zone, \@ns_names );
|
||||
|
||||
@{ $ns_ref } = grep { $_->isa('Zonemaster::Engine::Nameserver') } @{ $ns_ref };
|
||||
|
||||
return [ uniq sort (@{ $ns_ref }, @{ $oob_ns_ref }) ];
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item get_del_ns_names($zone)
|
||||
|
||||
[External]
|
||||
|
||||
This Method will obtain the name server names of the given zone as defined in the delegation from parent zone.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an arrayref of strings, or C<undef> if no parent zone was found.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_del_ns_names {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my $ns_ref = $class->get_del_ns_names_and_ips( $zone );
|
||||
|
||||
return undef unless defined $ns_ref;
|
||||
|
||||
return [ uniq sort map { $_->isa('Zonemaster::Engine::Nameserver') ? $_->name : $_ } @{ $ns_ref } ];
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item get_del_ns_ips($zone)
|
||||
|
||||
[External]
|
||||
|
||||
This Method will obtain the IP addresses (from Glue Records) from the delegation of the given zone from the parent zone.
|
||||
Glue Records are address records for In-Bailiwick name server names, if any. Also obtain the IP addresses for the Out-Of-Bailiwick name server names, if any.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an arrayref of strings, or C<undef> if no parent zone was found.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_del_ns_ips {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my $ns_ref = $class->get_del_ns_names_and_ips( $zone );
|
||||
|
||||
return undef unless defined $ns_ref;
|
||||
|
||||
return [ uniq sort map { $_->address->short } grep { $_->isa('Zonemaster::Engine::Nameserver') } @{ $ns_ref } ];
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item get_zone_ns_names($zone)
|
||||
|
||||
[External]
|
||||
|
||||
This Method will obtain the names of the authoritative name servers for the given zone as defined in the NS records in the zone itself.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an arrayref of strings, or C<undef> if no parent zone was found.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_zone_ns_names {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
# 'get_del_ns_names_and_ips' instead of 'get_del_ns_ips', because we need Zonemaster::Engine::Nameserver objects to be able to do queries.
|
||||
my $ns_ref = $class->get_del_ns_names_and_ips( $zone );
|
||||
|
||||
return undef unless defined $ns_ref;
|
||||
|
||||
my @ns_names;
|
||||
|
||||
for my $ns ( @{ $ns_ref } ) {
|
||||
if ( $ns->isa('Zonemaster::Engine::Nameserver') ) {
|
||||
my $p = $ns->query( $zone->name, q{NS} );
|
||||
|
||||
if ( $p and $p->aa and $p->rcode eq q{NOERROR} ) {
|
||||
push @ns_names, $p->get_records_for_name( q{NS}, $zone->name->string, q{answer} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [ uniq sort map { name( lc( $_->nsdname ) ) } @ns_names ];
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item _get_ib_addr_in_zone($zone)
|
||||
|
||||
[Internal]
|
||||
|
||||
This Method will obtain the address records matching the In-Bailiwick name server names from the given zone.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an arrayref of L<Zonemaster::Engine::Nameserver> objects, or C<undef> if no parent zone was found.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub _get_ib_addr_in_zone {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
# 'get_del_ns_names_and_ips' instead of 'get_del_ns_ips', because we need Zonemaster::Engine::Nameserver objects to be able to do queries.
|
||||
my $del_ips_ref = $class->get_del_ns_names_and_ips( $zone );
|
||||
my $ns_names_ref = $class->get_zone_ns_names( $zone );
|
||||
|
||||
return undef unless defined $del_ips_ref or defined $ns_names_ref or scalar @{ $del_ips_ref } or scalar @{ $ns_names_ref };
|
||||
|
||||
return [] unless scalar grep { $zone->name->is_in_bailiwick( $_ ) } @{ $ns_names_ref };
|
||||
|
||||
my %ib_ns;
|
||||
|
||||
for my $ns_name ( @{ $ns_names_ref } ) {
|
||||
if ( $zone->name->is_in_bailiwick( $ns_name ) ) {
|
||||
for my $ns ( @{ $del_ips_ref } ) {
|
||||
for my $qtype ( q{A}, q{AAAA} ) {
|
||||
my $p = Zonemaster::Engine::Recursor->recurse( $ns_name, $qtype, q{IN}, [ $ns ] );
|
||||
|
||||
if ( $p and $p->aa and $p->rcode eq q{NOERROR} ) {
|
||||
if ( $p->has_rrs_of_type_for_name( q{CNAME}, $ns_name, q{answer} ) ) {
|
||||
my %cnames = map { name( $_->owner ) => name( $_->cname ) } $p->get_records( q{CNAME}, q{answer} );
|
||||
my $target = $ns_name;
|
||||
$target = $cnames{$target} while $cnames{$target};
|
||||
|
||||
for my $rr ( $p->get_records_for_name( $qtype, $target ) ) {
|
||||
push @{ $ib_ns{$ns_name} }, $rr->address;
|
||||
}
|
||||
}
|
||||
# CNAME was followed in a new recursive query
|
||||
elsif ( name( ($p->question)[0]->owner ) ne $ns_name and grep { $_->tag eq 'CNAME_FOLLOWED_OUT_OF_ZONE' and grep /^$ns_name$/, values %{ $_->args } } @{ Zonemaster::Engine->logger->entries } ) {
|
||||
my $cname_ns_name = name( ($p->question)[0]->owner );
|
||||
my $target = $cname_ns_name;
|
||||
|
||||
if ( $p->has_rrs_of_type_for_name( q{CNAME}, $cname_ns_name, q{answer} ) ) {
|
||||
my %cnames = map { name( $_->owner ) => name( $_->cname ) } $p->get_records( q{CNAME}, q{answer} );
|
||||
$target = $cnames{$target} while $cnames{$target};
|
||||
}
|
||||
|
||||
for my $rr ( $p->get_records_for_name( $qtype, $target ) ) {
|
||||
push @{ $ib_ns{$ns_name} }, $rr->address;
|
||||
}
|
||||
}
|
||||
elsif ( $p->has_rrs_of_type_for_name( $qtype, $ns_name ) ) {
|
||||
for my $rr ( $p->get_records_for_name( $qtype, $ns_name ) ) {
|
||||
push @{ $ib_ns{$ns_name} }, $rr->address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @ib_ns_array;
|
||||
|
||||
for my $ns_name ( keys %ib_ns ) {
|
||||
for my $ns_ip ( uniq @{ $ib_ns{$ns_name} } ) {
|
||||
push @ib_ns_array, ns( $ns_name, $ns_ip );
|
||||
}
|
||||
}
|
||||
|
||||
return [ uniq sort @ib_ns_array ];
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item get_zone_ns_names_and_ips($zone)
|
||||
|
||||
[External]
|
||||
|
||||
This Method will obtain the name server names (extracted from the NS records) from the apex of the given zone.
|
||||
For the In-Bailiwick name server names obtain the IP addresses from the given zone. For the Out-Of-Bailiwick name server names obtain the IP addresses from recursive lookup.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an arrayref of L<Zonemaster::Engine::Nameserver> objects for each name server name that was successfully resolved to an IP address,
|
||||
and L<Zonemaster::Engine::DNSName> objects for each name server name that could not be resolved to an IP address, or C<undef> if no parent zone was found.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_zone_ns_names_and_ips {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my $ns_names_ref = $class->get_zone_ns_names( $zone );
|
||||
|
||||
return undef unless defined $ns_names_ref;
|
||||
|
||||
return [] unless scalar @{ $ns_names_ref };
|
||||
|
||||
my $ib_ns_ref = $class->_get_ib_addr_in_zone( $zone );
|
||||
my $oob_ns_ref = $class->_get_oob_ips( $zone, $ns_names_ref );
|
||||
|
||||
my @zone_ns;
|
||||
|
||||
for my $ns_name ( @{ $ns_names_ref } ) {
|
||||
if ( $zone->name->is_in_bailiwick( $ns_name ) ) {
|
||||
if ( $ib_ns_ref and scalar @{ $ib_ns_ref } ) {
|
||||
for my $ib_ns ( @{ $ib_ns_ref } ) {
|
||||
if ( $ns_name->string eq $ib_ns->name->string ) {
|
||||
push @zone_ns, ns( $ns_name, $ib_ns->address->short);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
push @zone_ns, $ns_name;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( $oob_ns_ref and scalar @{ $oob_ns_ref } ) {
|
||||
for my $oob_ns ( @{ $oob_ns_ref } ) {
|
||||
if ( $oob_ns->isa('Zonemaster::Engine::Nameserver') and $ns_name->string eq $oob_ns->name->string ) {
|
||||
push @zone_ns, ns( $ns_name, $oob_ns->address->short );
|
||||
}
|
||||
elsif ( $oob_ns->isa('Zonemaster::Engine::DNSName') and $ns_name->string eq $oob_ns->string ) {
|
||||
push @zone_ns, $ns_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
push @zone_ns, $ns_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [ uniq sort @zone_ns ];
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item get_zone_ns_ips($zone)
|
||||
|
||||
[External]
|
||||
|
||||
This Method will obtain the IP addresses of the name servers, as extracted from the NS records of apex of the given zone.
|
||||
|
||||
Takes a L<Zonemaster::Engine::Zone> object.
|
||||
|
||||
Returns an arrayref of strings, or C<undef> if no parent zone was found.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_zone_ns_ips {
|
||||
my ( $class, $zone ) = @_;
|
||||
|
||||
my $ns_ref = $class->get_zone_ns_names_and_ips( $zone );
|
||||
|
||||
return undef unless defined $ns_ref;
|
||||
|
||||
my @ns_ips;
|
||||
foreach my $ns ( @{ $ns_ref } ) {
|
||||
push @ns_ips, $ns->address->short if $ns->isa('Zonemaster::Engine::Nameserver');
|
||||
}
|
||||
|
||||
return [ uniq sort @ns_ips ];
|
||||
}
|
||||
|
||||
|
||||
=over
|
||||
|
||||
=item clear_cache()
|
||||
|
||||
Clears previously cached results of the C<get_parent_ns_names_and_ips()> method.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub clear_cache() {
|
||||
Memoize::flush_cache(\&get_parent_ns_names_and_ips);
|
||||
}
|
||||
|
||||
1;
|
||||
429
zonemaster-engine/lib/Zonemaster/Engine/Translator.pm
Normal file
429
zonemaster-engine/lib/Zonemaster/Engine/Translator.pm
Normal file
@@ -0,0 +1,429 @@
|
||||
package Zonemaster::Engine::Translator;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.8");
|
||||
|
||||
use Carp qw[confess croak];
|
||||
use Locale::Messages qw[textdomain];
|
||||
use Locale::TextDomain qw[Zonemaster-Engine];
|
||||
use POSIX qw[setlocale LC_MESSAGES];
|
||||
use Readonly;
|
||||
|
||||
use Zonemaster::Engine::Test;
|
||||
|
||||
###
|
||||
### Tag descriptions
|
||||
###
|
||||
|
||||
Readonly my %TAG_DESCRIPTIONS => (
|
||||
CANNOT_CONTINUE => sub {
|
||||
__x # SYSTEM:CANNOT_CONTINUE
|
||||
"Not enough data about {domain} was found to be able to run tests.", @_;
|
||||
},
|
||||
DEPENDENCY_VERSION => sub {
|
||||
__x # SYSTEM:DEPENDENCY_VERSION
|
||||
"Using prerequisite module {name} version {version}.", @_;
|
||||
},
|
||||
GLOBAL_VERSION => sub {
|
||||
__x # SYSTEM:GLOBAL_VERSION
|
||||
"Using version {version} of the Zonemaster engine.", @_;
|
||||
},
|
||||
LOGGER_CALLBACK_ERROR => sub {
|
||||
__x # SYSTEM:LOGGER_CALLBACK_ERROR
|
||||
"Logger callback died with error: {exception}", @_;
|
||||
},
|
||||
LOOKUP_ERROR => sub {
|
||||
__x # SYSTEM:LOOKUP_ERROR
|
||||
"DNS query to {ns} for {domain}/{type}/{class} failed with error: {message}", @_;
|
||||
},
|
||||
MODULE_ERROR => sub {
|
||||
__x # SYSTEM:MODULE_ERROR
|
||||
"Fatal error in {module}: {msg}", @_;
|
||||
},
|
||||
MODULE_VERSION => sub {
|
||||
__x # SYSTEM:MODULE_VERSION
|
||||
"Using module {module} version {version}.", @_;
|
||||
},
|
||||
MODULE_END => sub {
|
||||
__x # SYSTEM:MODULE_END
|
||||
"Module {module} finished running.", @_;
|
||||
},
|
||||
NO_NETWORK => sub {
|
||||
__x # SYSTEM:NO_NETWORK
|
||||
"Both IPv4 and IPv6 are disabled.";
|
||||
},
|
||||
UNKNOWN_METHOD => sub {
|
||||
__x # SYSTEM:UNKNOWN_METHOD
|
||||
"Request to run unknown method {testcase} in module {module}.", @_;
|
||||
},
|
||||
UNKNOWN_MODULE => sub {
|
||||
__x # SYSTEM:UNKNOWN_MODULE
|
||||
"Request to run {testcase} in unknown module {module}. Known modules: {module_list}.", @_;
|
||||
},
|
||||
SKIP_IPV4_DISABLED => sub {
|
||||
__x # SYSTEM:SKIP_IPV4_DISABLED
|
||||
"IPv4 is disabled, not sending \"{rrtype}\" query to {ns}.", @_;
|
||||
},
|
||||
SKIP_IPV6_DISABLED => sub {
|
||||
__x # SYSTEM:SKIP_IPV6_DISABLED
|
||||
"IPv6 is disabled, not sending \"{rrtype}\" query to {ns}.", @_;
|
||||
},
|
||||
FAKE_DELEGATION_TO_SELF => sub {
|
||||
__x # SYSTEM:FAKE_DELEGATION_TO_SELF
|
||||
"Name server {ns} not adding fake delegation for domain {domain} to itself.", @_;
|
||||
},
|
||||
FAKE_DELEGATION_IN_ZONE_NO_IP => sub {
|
||||
__x # SYSTEM:FAKE_DELEGATION_IN_ZONE_NO_IP
|
||||
"The fake delegation of domain {domain} includes an in-zone name server {nsname} "
|
||||
. "without mandatory glue (without IP address).",
|
||||
@_;
|
||||
},
|
||||
FAKE_DELEGATION_NO_IP => sub {
|
||||
__x # SYSTEM:FAKE_DELEGATION_NO_IP
|
||||
"The fake delegation of domain {domain} includes a name server {nsname} "
|
||||
. "that cannot be resolved to any IP address.",
|
||||
@_;
|
||||
},
|
||||
PACKET_BIG => sub {
|
||||
__x # SYSTEM:PACKET_BIG
|
||||
"Big packet size ({size}) (try with \"{command}\").", @_;
|
||||
},
|
||||
);
|
||||
|
||||
###
|
||||
### Construction
|
||||
###
|
||||
|
||||
my $instance;
|
||||
|
||||
sub new {
|
||||
my ( $class, %attrs ) = @_;
|
||||
|
||||
$class->initialize( %attrs );
|
||||
|
||||
return $class->instance;
|
||||
}
|
||||
|
||||
sub instance {
|
||||
my ( $class ) = @_;
|
||||
|
||||
if ( !defined $instance ) {
|
||||
$class->initialize();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
sub initialize {
|
||||
my ( $class, %attrs ) = @_;
|
||||
|
||||
if ( defined $instance ) {
|
||||
confess "already initialized";
|
||||
}
|
||||
|
||||
my $locale;
|
||||
if ( exists $attrs{locale} ) {
|
||||
$locale = delete $attrs{locale};
|
||||
|
||||
if ( !defined $locale || ref $locale ne '' ) {
|
||||
confess "argument 'locale' must not be a defined scalar";
|
||||
}
|
||||
}
|
||||
|
||||
my $obj = {
|
||||
_locale => $locale // _init_locale(),
|
||||
_all_tag_descriptions => $class->_build_all_tag_descriptions(),
|
||||
_last_language => _build_last_language(),
|
||||
};
|
||||
|
||||
$instance = bless $obj, $class;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
###
|
||||
### Builder Methods
|
||||
###
|
||||
|
||||
# Get the program's underlying LC_MESSAGES and make sure it can be effectively
|
||||
# updated down the line.
|
||||
#
|
||||
# If the underlying LC_MESSAGES is invalid, it attempts to second guess Perl's
|
||||
# fallback locale.
|
||||
#
|
||||
# Side effects:
|
||||
# * Updates the program's underlying LC_MESSAGES to the returned value.
|
||||
# * Unsets LC_ALL.
|
||||
sub _init_locale {
|
||||
my $locale = setlocale( LC_MESSAGES, "" );
|
||||
|
||||
delete $ENV{LC_ALL};
|
||||
|
||||
if ( !defined $locale ) {
|
||||
my $language = $ENV{LANGUAGE} // "";
|
||||
for my $value ( split /:/, $language ) {
|
||||
if ( $value ne "" && $value !~ /[.]/ ) {
|
||||
$value .= ".UTF-8";
|
||||
}
|
||||
$locale = setlocale( LC_MESSAGES, $value );
|
||||
if ( defined $locale ) {
|
||||
last;
|
||||
}
|
||||
}
|
||||
$locale //= "C";
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
sub _load_data {
|
||||
my $self = shift;
|
||||
|
||||
my $old_locale = $self->locale;
|
||||
|
||||
$self->locale( 'C' );
|
||||
|
||||
my %data;
|
||||
for my $mod ( keys %{ $self->all_tag_descriptions } ) {
|
||||
for my $tag ( keys %{ $self->all_tag_descriptions->{$mod} } ) {
|
||||
$data{$mod}{$tag} = $self->_translate_tag( $mod, $tag, {} );
|
||||
}
|
||||
}
|
||||
|
||||
$self->locale( $old_locale );
|
||||
|
||||
return \%data;
|
||||
}
|
||||
|
||||
sub _build_all_tag_descriptions {
|
||||
my ( $class ) = @_;
|
||||
|
||||
my %all_tag_descriptions;
|
||||
|
||||
$all_tag_descriptions{System} = \%TAG_DESCRIPTIONS;
|
||||
foreach my $mod ( Zonemaster::Engine::Test->modules ) {
|
||||
my $module = 'Zonemaster::Engine::Test::' . $mod;
|
||||
$all_tag_descriptions{ $mod } = $module->tag_descriptions;
|
||||
}
|
||||
|
||||
return \%all_tag_descriptions;
|
||||
}
|
||||
|
||||
sub _build_last_language {
|
||||
return $ENV{LANGUAGE} // '';
|
||||
}
|
||||
|
||||
###
|
||||
### Instance methods
|
||||
###
|
||||
|
||||
sub data {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( !exists $self->{_data} ) {
|
||||
$self->{_data} = $self->_load_data;
|
||||
}
|
||||
|
||||
return $self->{_data};
|
||||
}
|
||||
|
||||
sub all_tag_descriptions {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return $self->{_all_tag_descriptions};
|
||||
}
|
||||
|
||||
sub locale {
|
||||
my ( $self, @args ) = @_;
|
||||
|
||||
if ( @args ) {
|
||||
my $new_locale = shift @args;
|
||||
|
||||
# On some systems gettext takes its locale from setlocale().
|
||||
if ( !defined setlocale( LC_MESSAGES, $new_locale ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$self->_last_language( $ENV{LANGUAGE} // '' );
|
||||
|
||||
# On some systems gettext takes its locale from %ENV.
|
||||
$ENV{LC_MESSAGES} = $new_locale;
|
||||
|
||||
# On some systems gettext refuses to switch over to another locale unless
|
||||
# the textdomain is reset.
|
||||
textdomain( 'Zonemaster-Engine' );
|
||||
|
||||
if ( !defined $new_locale || ref $new_locale ne '' ) {
|
||||
croak "locale must be a defined scalar";
|
||||
}
|
||||
|
||||
$self->{_locale} = $new_locale;
|
||||
} ## end if ( @args )
|
||||
|
||||
return $self->{_locale};
|
||||
};
|
||||
|
||||
sub to_string {
|
||||
my ( $self, $entry ) = @_;
|
||||
|
||||
return sprintf( "%7.2f %-9s %s", $entry->timestamp, $entry->level, $self->translate_tag( $entry ) );
|
||||
}
|
||||
|
||||
sub translate_tag {
|
||||
my ( $self, $entry ) = @_;
|
||||
|
||||
return $self->_translate_tag( $entry->module, $entry->tag, $entry->printable_args ) // $entry->string;
|
||||
}
|
||||
|
||||
|
||||
sub test_case_description {
|
||||
my ( $self, $test_name ) = @_;
|
||||
|
||||
my $module = $test_name;
|
||||
$module =~ s/\d+$//;
|
||||
|
||||
return $self->_translate_tag( $module, uc $test_name, {} ) // $test_name;
|
||||
}
|
||||
|
||||
sub _last_language {
|
||||
my $self = shift;
|
||||
|
||||
if ( @_ ) {
|
||||
my $last_language = shift;
|
||||
if ( !defined $last_language || ref $last_language ne '' ) {
|
||||
croak "_last_language must be a defined scalar";
|
||||
}
|
||||
$self->{_last_language} = $last_language;
|
||||
}
|
||||
|
||||
return $self->{_last_language};
|
||||
}
|
||||
|
||||
sub _translate_tag {
|
||||
my ( $self, $module, $tag, $args ) = @_;
|
||||
|
||||
if ( $ENV{LANGUAGE} // '' ne $self->_last_language ) {
|
||||
$self->locale( $self->locale );
|
||||
}
|
||||
|
||||
my $code = $self->all_tag_descriptions->{$module}{$tag};
|
||||
|
||||
if ( $code ) {
|
||||
return $code->( %{$args} );
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Translator - translation support for Zonemaster
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Zonemaster::Engine::Translator->initialize( locale => 'sv_SE.UTF-8' );
|
||||
|
||||
my $trans = Zonemaster::Engine::Translator->instance;
|
||||
say $trans->to_string($entry);
|
||||
|
||||
This is a singleton class.
|
||||
|
||||
The instance of this class requires exclusive control over C<$ENV{LC_MESSAGES}>
|
||||
and the program's underlying LC_MESSAGES.
|
||||
At times it resets gettext's textdomain.
|
||||
On construction it unsets C<$ENV{LC_ALL}> and from then on it must remain unset.
|
||||
|
||||
On systems that support C<$ENV{LANGUAGE}>, this variable overrides the locale()
|
||||
attribute unless the locale() attribute is set to C<"C">.
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item locale
|
||||
|
||||
The locale used for localized messages.
|
||||
|
||||
say $translator->locale();
|
||||
if ( !$translator->locale( 'sv_SE.UTF-8' ) ) {
|
||||
say "failed to update locale";
|
||||
}
|
||||
|
||||
The value of this attribute is mirrored in C<$ENV{LC_MESSAGES}>.
|
||||
|
||||
When writing to this attribute, a request is made to update the program's
|
||||
underlying LC_MESSAGES.
|
||||
If this request fails, the attribute value remains unchanged and an empty list
|
||||
is returned.
|
||||
|
||||
As a side effect when successfully updating this attribute gettext's textdomain
|
||||
is reset.
|
||||
|
||||
=item data
|
||||
|
||||
A reference to a hash with translation data. This is unlikely to be useful to
|
||||
end-users.
|
||||
|
||||
=item all_tag_descriptions
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item initialize(%args)
|
||||
|
||||
Provide initial values for the single instance of this class.
|
||||
|
||||
Zonemaster::Engine::Translator->initialize( locale => 'sv_SE.UTF-8' );
|
||||
|
||||
This method must be called at most once and before the first call to instance().
|
||||
|
||||
=item instance()
|
||||
|
||||
Returns the single instance of this class.
|
||||
|
||||
my $translator = Zonemaster::Engine::Translator->instance;
|
||||
|
||||
If initialize() has not been called prior to the first call to instance(), it
|
||||
is the same as if initialize() had been called without arguments.
|
||||
|
||||
=item new(%args)
|
||||
|
||||
Use of this method is deprecated.
|
||||
|
||||
=over
|
||||
|
||||
=item locale
|
||||
|
||||
If no initial value is provided to the constructor, one is determined by calling
|
||||
setlocale( LC_MESSAGES, "" ).
|
||||
|
||||
=back
|
||||
|
||||
=item to_string($entry)
|
||||
|
||||
Takes a L<Zonemaster::Engine::Logger::Entry> object as its argument and returns a translated string with the timestamp, level, message and arguments in the
|
||||
entry.
|
||||
|
||||
=item translate_tag($entry)
|
||||
|
||||
Takes a L<Zonemaster::Engine::Logger::Entry> object as its argument and returns a translation of its tag and arguments.
|
||||
|
||||
=item test_case_description($testcase)
|
||||
|
||||
Takes a string (test case ID) and returns the translated test case description.
|
||||
|
||||
=item BUILD
|
||||
|
||||
Internal method that's only mentioned here to placate L<Pod::Coverage>.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
292
zonemaster-engine/lib/Zonemaster/Engine/Util.pm
Normal file
292
zonemaster-engine/lib/Zonemaster/Engine/Util.pm
Normal file
@@ -0,0 +1,292 @@
|
||||
package Zonemaster::Engine::Util;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.1.13");
|
||||
|
||||
use Exporter 'import';
|
||||
BEGIN {
|
||||
our @EXPORT_OK = qw[
|
||||
info
|
||||
ipversion_ok
|
||||
name
|
||||
ns
|
||||
parse_hints
|
||||
should_run_test
|
||||
scramble_case
|
||||
test_levels
|
||||
zone
|
||||
];
|
||||
our %EXPORT_TAGS = ( all => \@EXPORT_OK );
|
||||
|
||||
## no critic (Modules::ProhibitAutomaticExportation)
|
||||
our @EXPORT = qw[ ns info name scramble_case ];
|
||||
}
|
||||
|
||||
use Net::DNS::ZoneFile;
|
||||
use Pod::Simple::SimpleTree;
|
||||
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Engine::Constants qw[:ip :soa];
|
||||
use Zonemaster::Engine::DNSName;
|
||||
use Zonemaster::Engine::Profile;
|
||||
|
||||
sub ns {
|
||||
my ( $name, $address ) = @_;
|
||||
return Zonemaster::Engine::Nameserver->new( { name => $name, address => $address } );
|
||||
}
|
||||
|
||||
sub info {
|
||||
my ( $tag, $argref ) = @_;
|
||||
|
||||
return Zonemaster::Engine->logger->add( $tag, $argref );
|
||||
}
|
||||
|
||||
sub zone {
|
||||
my ( $name ) = @_;
|
||||
|
||||
return Zonemaster::Engine::Zone->new( { name => Zonemaster::Engine::DNSName->new( $name ) } );
|
||||
}
|
||||
|
||||
sub should_run_test {
|
||||
my ( $test_name ) = @_;
|
||||
my %test_names = map { $_ => 1 } @{ Zonemaster::Engine::Profile->effective->get( q{test_cases} ) };
|
||||
|
||||
return exists $test_names{$test_name};
|
||||
}
|
||||
|
||||
sub ipversion_ok {
|
||||
my ( $version ) = @_;
|
||||
|
||||
if ( $version == $IP_VERSION_4 ) {
|
||||
return Zonemaster::Engine::Profile->effective->get( q{net.ipv4} );
|
||||
}
|
||||
elsif ( $version == $IP_VERSION_6 ) {
|
||||
return Zonemaster::Engine::Profile->effective->get( q{net.ipv6} );
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub test_levels {
|
||||
return Zonemaster::Engine::Profile->effective->get( q{test_levels} );
|
||||
}
|
||||
|
||||
## no critic (Subroutines::RequireArgUnpacking)
|
||||
sub name {
|
||||
# We do not unpack @_ here for performance reasons.
|
||||
# If we did, the calling convention is: my ( $name ) = @_.
|
||||
return Zonemaster::Engine::DNSName->new( @_ );
|
||||
}
|
||||
|
||||
# Function from CPAN package Text::Capitalize that causes
|
||||
# issues when installing ZM.
|
||||
#
|
||||
sub scramble_case {
|
||||
my $string = shift;
|
||||
my ( @chars, $uppity, $newstring, $uppers, $downers );
|
||||
|
||||
@chars = split //, $string;
|
||||
|
||||
$uppers = 2;
|
||||
$downers = 1;
|
||||
foreach my $c ( @chars ) {
|
||||
$uppity = int( rand( 1 + $downers / $uppers ) );
|
||||
|
||||
if ( $uppity ) {
|
||||
$c = uc( $c );
|
||||
$uppers++;
|
||||
}
|
||||
else {
|
||||
$c = lc( $c );
|
||||
$downers++;
|
||||
}
|
||||
}
|
||||
$newstring = join q{}, @chars;
|
||||
return $newstring;
|
||||
} # end sub scramble_case
|
||||
|
||||
sub parse_hints {
|
||||
my $string = shift;
|
||||
|
||||
# Reject anything that is forbidden in hints files but allowed in zone files
|
||||
# in general.
|
||||
if ( $string =~ /^\$(TTL|INCLUDE|ORIGIN|GENERATE)/m ) {
|
||||
die "Forbidden directive \$$1\n";
|
||||
}
|
||||
|
||||
my $rrs = Net::DNS::ZoneFile->parse( \$string );
|
||||
if ( !defined $rrs ) {
|
||||
die "Unable to parse root hints\n";
|
||||
}
|
||||
|
||||
my %ns;
|
||||
my %glue;
|
||||
for my $rr ( @$rrs ) {
|
||||
if ( $rr->class ne 'IN' ) {
|
||||
my $rrclass = $rr->class;
|
||||
die "Forbidden RR class $rrclass\n";
|
||||
}
|
||||
|
||||
if ( $rr->type eq 'NS' ) {
|
||||
if ( $rr->owner ne '.' ) {
|
||||
my $owner = $rr->owner;
|
||||
die "Owner name for NS record must be \".\"\n";
|
||||
}
|
||||
$ns{ $rr->nsdname } = 0;
|
||||
}
|
||||
elsif ( $rr->type eq 'A' || $rr->type eq 'AAAA' ) {
|
||||
$glue{ $rr->owner } = $rr->type;
|
||||
}
|
||||
else {
|
||||
my $rrtype = $rr->type;
|
||||
die "Forbidden RR type $rrtype\n";
|
||||
}
|
||||
} ## end for my $rr ( @$rrs )
|
||||
|
||||
for my $owner ( sort keys %glue ) {
|
||||
if ( exists $ns{$owner} ) {
|
||||
$ns{$owner} = 1;
|
||||
}
|
||||
else {
|
||||
my $rrtype = $glue{$owner};
|
||||
die "Owner name of $rrtype record does not match any NS RDATA\n";
|
||||
}
|
||||
}
|
||||
|
||||
for my $nsdname ( sort keys %ns ) {
|
||||
if ( $ns{$nsdname} == 0 ) {
|
||||
die "No address record found for NS $nsdname\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ( !%ns ) {
|
||||
die "No NS record found\n";
|
||||
}
|
||||
|
||||
# Extract hint data
|
||||
my %hints;
|
||||
for my $rr ( @{ $rrs } ) {
|
||||
if ( $rr->type eq 'A' or $rr->type eq 'AAAA' ) {
|
||||
push @{ $hints{$rr->owner} }, $rr->address;
|
||||
}
|
||||
}
|
||||
|
||||
return \%hints;
|
||||
}
|
||||
|
||||
sub serial_gt {
|
||||
my ( $sa, $sb ) = @_;
|
||||
|
||||
return ( ( $sa < $sb and ( ($sb - $sa) > 2**( $SERIAL_BITS - 1 ) ) ) or
|
||||
( $sa > $sb and ( ($sa - $sb) < 2**( $SERIAL_BITS - 1 ) ) )
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Util - utility functions for other Zonemaster modules
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Zonemaster::Engine::Util;
|
||||
info(TAG => { some => 'argument'});
|
||||
my $ns = ns($name, $address);
|
||||
my $name = name('whatever.example.org');
|
||||
|
||||
=head1 EXPORTED FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item info($tag, $href)
|
||||
|
||||
Creates and returns a L<Zonemaster::Engine::Logger::Entry> object. The object
|
||||
is also added to the global logger object's list of entries.
|
||||
|
||||
=item ns($name, $address)
|
||||
|
||||
Creates and returns a nameserver object with the given name and address.
|
||||
|
||||
=item name($string_name_or_zone)
|
||||
|
||||
Creates and returns a L<Zonemaster::Engine::DNSName> object for the given argument.
|
||||
|
||||
=item zone($name)
|
||||
|
||||
Returns a L<Zonemaster::Engine::Zone> object for the given name.
|
||||
|
||||
=item parse_hints($string)
|
||||
|
||||
Parses a string in the root hints format into the format expected by
|
||||
Zonemaster::Engine::Resolver->add_fake_addresses().
|
||||
|
||||
Returns a hashref with domain names as keys and arrayrefs to IP addresses as
|
||||
values.
|
||||
|
||||
Throws an exception if the inputs is not valid root hints text.
|
||||
|
||||
A root hints file is a valid RFC 1035 zone file of the same type IANA publishes
|
||||
to be used as hint file for name servers
|
||||
L<https://www.internic.net/domain/named.root>.
|
||||
|
||||
In addition to being valid zone file the following restrictions are imposed on
|
||||
the root hints format:
|
||||
|
||||
=over
|
||||
|
||||
=item *
|
||||
The file must not contain any $TTL, $ORIGIN, $INCLUDE or $GENERATE directives.
|
||||
|
||||
=item *
|
||||
The class field of all records must be "IN" or absent. If class is absent, IN is
|
||||
assumed.
|
||||
|
||||
=item *
|
||||
The TTL field may be absent or present. The TTL value is ignored.
|
||||
|
||||
=item *
|
||||
The RR type of all DNS records must be NS, A or AAAA.
|
||||
|
||||
=item *
|
||||
The file must contain at least one NS record.
|
||||
|
||||
=item *
|
||||
The owner name of all NS records must be C<.>.
|
||||
|
||||
=item *
|
||||
For every NS record there must be at least one address record (A or AAAA) whose
|
||||
owner name is identical to the domain name in the RDATA of the NS record.
|
||||
|
||||
=item *
|
||||
All address records (A or AAAA) must have an owner name that is identical to the
|
||||
domain name in the RDATA of some NS record in the zone.
|
||||
|
||||
=back
|
||||
|
||||
=item serial_gt($serial_a, $serial_b)
|
||||
Checks if serial_a is greater than serial_b, according to
|
||||
serial number arithmetic as defined in RFC1982, section 3.2.
|
||||
|
||||
Return a boolean.
|
||||
|
||||
=item scramble_case
|
||||
|
||||
This routine provides a special effect: sCraMBliNg tHe CaSe
|
||||
|
||||
=item should_run_test
|
||||
|
||||
Check if a test is blacklisted and should run or not.
|
||||
|
||||
=item ipversion_ok
|
||||
|
||||
Check if IP version operations are permitted. Tests are done against Zonemaster::Engine::Profile->effective content.
|
||||
|
||||
=item test_levels
|
||||
|
||||
WIP, here to please L<Pod::Coverage>.
|
||||
|
||||
=back
|
||||
94
zonemaster-engine/lib/Zonemaster/Engine/Validation.pm
Normal file
94
zonemaster-engine/lib/Zonemaster/Engine/Validation.pm
Normal file
@@ -0,0 +1,94 @@
|
||||
package Zonemaster::Engine::Validation;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.0.0");
|
||||
|
||||
use Exporter 'import';
|
||||
BEGIN {
|
||||
our @EXPORT_OK = qw[
|
||||
validate_ipv4
|
||||
validate_ipv6
|
||||
];
|
||||
our %EXPORT_TAGS = ( all => \@EXPORT_OK );
|
||||
|
||||
## no critic (Modules::ProhibitAutomaticExportation)
|
||||
our @EXPORT = qw[
|
||||
validate_ipv4
|
||||
validate_ipv6
|
||||
];
|
||||
}
|
||||
|
||||
use Readonly;
|
||||
use Net::IP::XS;
|
||||
|
||||
use Zonemaster::Engine::Constants qw[:ip];
|
||||
|
||||
Readonly our $IPV4_RE => qr/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/;
|
||||
Readonly our $IPV6_RE => qr/^[0-9a-f:]*:[0-9a-f:]+(:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})?$/i;
|
||||
|
||||
sub validate_ipv4 {
|
||||
my ( $ip ) = @_;
|
||||
|
||||
if ( defined $ip and $ip ne '') {
|
||||
if ( Net::IP::XS->new( $ip ) ) {
|
||||
if ( Net::IP::XS::ip_is_ipv4( $ip ) and $ip =~ /($IPV4_RE)/ ) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub validate_ipv6 {
|
||||
my ( $ip ) = @_;
|
||||
|
||||
if ( defined $ip and $ip ne '' ) {
|
||||
if ( Net::IP::XS->new( $ip ) ) {
|
||||
if ( Net::IP::XS::ip_is_ipv6( $ip ) and $ip =~ /($IPV6_RE)/ ) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Validation - validation functions for other Zonemaster modules
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Zonemaster::Engine::Validation qw( validate_ipv4 validate_ipv6 );
|
||||
my $ip_is_valid = validate_ipv4( $ip_address );
|
||||
|
||||
=head1 EXPORTED FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item validate_ipv4
|
||||
|
||||
my $ip_is_valid = validate_ipv4( $ip_address );
|
||||
|
||||
Checks if the given IP address is a valid IPv4 address.
|
||||
|
||||
Takes a string (IP address).
|
||||
|
||||
Returns a boolean.
|
||||
|
||||
=item validate_ipv6
|
||||
|
||||
my $ip_is_valid = validate_ipv6( $ip_address );
|
||||
|
||||
Checks if the given IP address is a valid IPv6 address.
|
||||
|
||||
Takes a string (IP address).
|
||||
|
||||
Returns a boolean.
|
||||
|
||||
=back
|
||||
486
zonemaster-engine/lib/Zonemaster/Engine/Zone.pm
Normal file
486
zonemaster-engine/lib/Zonemaster/Engine/Zone.pm
Normal file
@@ -0,0 +1,486 @@
|
||||
package Zonemaster::Engine::Zone;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare("v1.1.9");
|
||||
|
||||
use Carp qw( confess croak );
|
||||
use List::MoreUtils qw[uniq];
|
||||
|
||||
use Zonemaster::Engine::DNSName;
|
||||
use Zonemaster::Engine::Recursor;
|
||||
use Zonemaster::Engine::NSArray;
|
||||
use Zonemaster::Engine::Constants qw[:ip];
|
||||
|
||||
sub new {
|
||||
my ( $class, $attrs ) = @_;
|
||||
|
||||
my $name = delete $attrs->{name} // confess "required argument 'name' not found";
|
||||
if ( %$attrs ) {
|
||||
confess "unexpected arguments: " . join ', ', sort keys %$attrs;
|
||||
}
|
||||
|
||||
if ( blessed $name ne 'Zonemaster::Engine::DNSName' ) {
|
||||
confess "argument 'name' must be a Zonemaster::Engine::DNSName";
|
||||
}
|
||||
|
||||
my $obj = { _name => $name };
|
||||
|
||||
return bless $obj, $class;
|
||||
}
|
||||
|
||||
sub name {
|
||||
my ( $self ) = @_;
|
||||
|
||||
return $self->{_name};
|
||||
}
|
||||
|
||||
sub parent {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( !exists $self->{_parent} ) {
|
||||
$self->{_parent} = $self->_build_parent;
|
||||
}
|
||||
|
||||
return $self->{_parent};
|
||||
}
|
||||
|
||||
sub glue_names {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( !exists $self->{_glue_names} ) {
|
||||
$self->{_glue_names} = $self->_build_glue_names;
|
||||
}
|
||||
|
||||
return $self->{_glue_names};
|
||||
}
|
||||
|
||||
sub glue {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( !exists $self->{_glue} ) {
|
||||
$self->{_glue} = $self->_build_glue;
|
||||
}
|
||||
|
||||
return $self->{_glue};
|
||||
}
|
||||
|
||||
sub ns_names {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( !exists $self->{_ns_names} ) {
|
||||
$self->{_ns_names} = $self->_build_ns_names;
|
||||
}
|
||||
|
||||
return $self->{_ns_names};
|
||||
}
|
||||
|
||||
sub ns {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( !exists $self->{_ns} ) {
|
||||
$self->{_ns} = $self->_build_ns;
|
||||
}
|
||||
|
||||
return $self->{_ns};
|
||||
}
|
||||
|
||||
sub glue_addresses {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( !exists $self->{_glue_addresses} ) {
|
||||
$self->{_glue_addresses} = $self->_build_glue_addresses;
|
||||
}
|
||||
|
||||
return $self->{_glue_addresses};
|
||||
}
|
||||
|
||||
###
|
||||
### Builders
|
||||
###
|
||||
|
||||
sub _build_parent {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( $self->name eq '.' ) {
|
||||
return $self;
|
||||
}
|
||||
|
||||
my $pname = Zonemaster::Engine::Recursor->parent( q{} . $self->name );
|
||||
return if not $pname;
|
||||
## no critic (Modules::RequireExplicitInclusion)
|
||||
return __PACKAGE__->new( { name => $pname } );
|
||||
}
|
||||
|
||||
sub _build_glue_names {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( not $self->parent ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
my $p = $self->parent->query_persistent( $self->name, '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 ) ];
|
||||
}
|
||||
|
||||
sub _build_glue {
|
||||
my ( $self ) = @_;
|
||||
my @glue_names = @{ $self->glue_names };
|
||||
my $zname = $self->name->string;
|
||||
|
||||
if ( Zonemaster::Engine::Recursor->has_fake_addresses( $zname ) ) {
|
||||
my @ns_list;
|
||||
foreach my $ns ( @glue_names ) {
|
||||
foreach my $ip ( Zonemaster::Engine::Recursor->get_fake_addresses( $zname, $ns ) ) {
|
||||
push @ns_list, Zonemaster::Engine::Nameserver->new( { name => $ns, address => $ip } );
|
||||
}
|
||||
}
|
||||
return \@ns_list;
|
||||
}
|
||||
else {
|
||||
|
||||
my $aref = [];
|
||||
tie @$aref, 'Zonemaster::Engine::NSArray', @glue_names;
|
||||
|
||||
return $aref;
|
||||
}
|
||||
}
|
||||
|
||||
sub _build_ns_names {
|
||||
my ( $self ) = @_;
|
||||
|
||||
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' );
|
||||
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 ) ];
|
||||
} ## end sub _build_ns_names
|
||||
|
||||
sub _build_ns {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( $self->name eq '.' ) { # Root is a special case
|
||||
return [ Zonemaster::Engine::Recursor->root_servers ];
|
||||
}
|
||||
|
||||
my $aref = [];
|
||||
tie @$aref, 'Zonemaster::Engine::NSArray', @{ $self->ns_names };
|
||||
|
||||
return $aref;
|
||||
}
|
||||
|
||||
sub _build_glue_addresses {
|
||||
my ( $self ) = @_;
|
||||
|
||||
if ( not $self->parent ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
my $p = $self->parent->query_one( $self->name, 'NS' );
|
||||
croak "Failed to get glue addresses" if not defined( $p );
|
||||
|
||||
return [ $p->get_records( 'a' ), $p->get_records( 'aaaa' ) ];
|
||||
}
|
||||
|
||||
sub _is_ip_version_disabled {
|
||||
my ( $ns, $type ) = @_;
|
||||
|
||||
if ( not Zonemaster::Engine::Profile->effective->get(q{net.ipv4}) and $ns->address->version == $IP_VERSION_4 ) {
|
||||
Zonemaster::Engine->logger->add(
|
||||
SKIP_IPV4_DISABLED => {
|
||||
ns => $ns->string,
|
||||
rrtype => $type
|
||||
}
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ( not Zonemaster::Engine::Profile->effective->get(q{net.ipv6}) and $ns->address->version == $IP_VERSION_6 ) {
|
||||
Zonemaster::Engine->logger->add(
|
||||
SKIP_IPV6_DISABLED => {
|
||||
ns => $ns->string,
|
||||
rrtype => $type
|
||||
}
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
###
|
||||
### Public Methods
|
||||
###
|
||||
|
||||
sub query_one {
|
||||
my ( $self, $name, $type, $flags ) = @_;
|
||||
|
||||
# Return response from the first server that gives one
|
||||
my $i = 0;
|
||||
while ( my $ns = $self->ns->[$i] ) {
|
||||
if ( _is_ip_version_disabled( $ns, $type ) ) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $p = $ns->query( $name, $type, $flags );
|
||||
return $p if defined( $p );
|
||||
}
|
||||
continue {
|
||||
$i += 1;
|
||||
}
|
||||
|
||||
return;
|
||||
} ## end sub query_one
|
||||
|
||||
sub query_all {
|
||||
my ( $self, $name, $type, $flags ) = @_;
|
||||
|
||||
my @servers = @{ $self->ns };
|
||||
|
||||
if ( not Zonemaster::Engine::Profile->effective->get(q{net.ipv4}) ) {
|
||||
my @nope = grep { $_->address->version == $IP_VERSION_4 } @servers;
|
||||
@servers = grep { $_->address->version == $IP_VERSION_6 } @servers;
|
||||
map {
|
||||
Zonemaster::Engine->logger->add(
|
||||
SKIP_IPV4_DISABLED => {
|
||||
ns => $_->string,
|
||||
rrtype => $type
|
||||
}
|
||||
)
|
||||
} @nope;
|
||||
}
|
||||
|
||||
if ( not Zonemaster::Engine::Profile->effective->get(q{net.ipv6}) ) {
|
||||
my @nope = grep { $_->address->version == $IP_VERSION_6 } @servers;
|
||||
@servers = grep { $_->address->version == $IP_VERSION_4 } @servers;
|
||||
map {
|
||||
Zonemaster::Engine->logger->add(
|
||||
SKIP_IPV6_DISABLED => {
|
||||
ns => $_->string,
|
||||
rrtype => $type
|
||||
}
|
||||
)
|
||||
} @nope;
|
||||
}
|
||||
|
||||
return [ map { $_->query( $name, $type, $flags ) } @servers ];
|
||||
}
|
||||
|
||||
sub query_auth {
|
||||
my ( $self, $name, $type, $flags ) = @_;
|
||||
|
||||
# Return response from the first server that replies with AA set
|
||||
my $i = 0;
|
||||
while ( my $ns = $self->ns->[$i] ) {
|
||||
if ( _is_ip_version_disabled( $ns, $type ) ) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $p = $ns->query( $name, $type, $flags );
|
||||
if ( $p and $p->aa ) {
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
continue {
|
||||
$i += 1;
|
||||
}
|
||||
|
||||
return;
|
||||
} ## end sub query_auth
|
||||
|
||||
sub query_persistent {
|
||||
my ( $self, $name, $type, $flags ) = @_;
|
||||
|
||||
# Return response from the first server that has a record like the one asked for
|
||||
my $i = 0;
|
||||
while ( my $ns = $self->ns->[$i] ) {
|
||||
if ( _is_ip_version_disabled( $ns, $type ) ) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $p = $ns->query( $name, $type, $flags );
|
||||
if ( $p and scalar( $p->get_records_for_name( $type, $name ) ) > 0 ) {
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
continue {
|
||||
$i += 1;
|
||||
}
|
||||
|
||||
return;
|
||||
} ## end sub query_persistent
|
||||
|
||||
sub is_in_zone {
|
||||
my ( $self, $name ) = @_;
|
||||
|
||||
if ( not ref( $name ) or ref( $name ) ne 'Zonemaster::Engine::DNSName' ) {
|
||||
$name = Zonemaster::Engine::DNSName->new( $name );
|
||||
}
|
||||
|
||||
if ( scalar( @{ $self->name->labels } ) != $self->name->common( $name ) ) {
|
||||
return 0; # Zone name cannot be a suffix of tested name
|
||||
}
|
||||
|
||||
my $p = $self->query_auth( "$name", 'SOA' );
|
||||
if ( not $p ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $p->is_redirect ) {
|
||||
return 0; # Authoritative servers redirect us, so name must be out-of-zone
|
||||
}
|
||||
|
||||
my ( $soa ) = $p->get_records( 'SOA' );
|
||||
|
||||
if ( not $soa ) {
|
||||
return 0; # Auth server is broken, call it a "no".
|
||||
}
|
||||
|
||||
if ( Zonemaster::Engine::DNSName->new( $soa->name ) eq $self->name ) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
} ## end sub is_in_zone
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::Zone - Object representing a DNS zone
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $zone = Zonemaster::Engine::Zone->new({ name => 'nic.se' });
|
||||
my $packet = $zone->parent->query_one($zone->name, 'NS');
|
||||
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Objects of this class represent zones in DNS. As far as possible, test
|
||||
implementations should access information about zones via these
|
||||
objects. Doing so will provide lazy-loading of the information,
|
||||
well-defined methods in which the information is fetched, logging and
|
||||
the ability to do things like testing zones that have not yet been
|
||||
delegated.
|
||||
|
||||
=head1 CONSTRUCTORS
|
||||
|
||||
=over
|
||||
|
||||
=item new
|
||||
|
||||
Construct a new instance.
|
||||
|
||||
=back
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=over
|
||||
|
||||
=item name
|
||||
|
||||
A L<Zonemaster::Engine::DNSName> object representing the name of the zone.
|
||||
|
||||
=item parent
|
||||
|
||||
A L<Zonemaster::Engine::Zone> object for this domain's parent domain. As a
|
||||
special case, the root zone is considered to be its own parent (so
|
||||
look for that if you recurse up the tree).
|
||||
|
||||
=item ns_names
|
||||
|
||||
A reference to an array of L<Zonemaster::Engine::DNSName> objects, holding the
|
||||
names of the nameservers for the domain, as returned by the first
|
||||
responding nameserver in the glue list.
|
||||
|
||||
=item ns
|
||||
|
||||
A reference to an array of L<Zonemaster::Engine::Nameserver> objects for the
|
||||
domain, built by taking the list returned from L<ns_names()> and
|
||||
looking up addresses for the names. One element will be added to this
|
||||
list for each unique name/IP pair. Names for which no addresses could
|
||||
be found will not be in this list. The list is lazy-loading, so take
|
||||
care to only look at as many entries as you really need. There are
|
||||
zones with more than 20 nameserver, and looking up the addresses of
|
||||
them all can take som considerable time.
|
||||
|
||||
=item glue_names
|
||||
|
||||
A reference to a an array of L<Zonemaster::Engine::DNSName> objects, holding the names
|
||||
of this zones nameservers as listed at the first responding nameserver of the
|
||||
parent zone.
|
||||
|
||||
=item glue
|
||||
|
||||
A reference to an array of L<Zonemaster::Engine::Nameserver> objects for the
|
||||
domain, built by taking the list returned from L<glue_names()> and
|
||||
looking up addresses for the names. One element will be added to this
|
||||
list for each unique name/IP pair. Names for which no addresses could
|
||||
be found will not be in this list. In this case, the list is lazy-loading, so take
|
||||
care to only look at as many entries as you really need. In case of
|
||||
undelegated tests and fake delegation the IP associated with name servers
|
||||
for the tested zone will be the ones set by users (saved in
|
||||
%Zonemaster::Engine::Recursor::fake_addresses_cache), instead of the ones
|
||||
found recursively.
|
||||
|
||||
=item glue_addresses
|
||||
|
||||
A list of L<Zonemaster::LDNS::RR::A> and L<Zonemaster::LDNS::RR::AAAA> records returned in
|
||||
the Additional section of an NS query to the first listed nameserver for the
|
||||
parent domain.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item query_one($name[, $type[, $flags]])
|
||||
|
||||
Sends (or retrieves from cache) a query for the given name, type and flags sent to the first nameserver in the zone's ns list. If there is a
|
||||
response, it will be returned in a L<Zonemaster::Engine::Packet> object. If the type arguments is not given, it defaults to 'A'. If the flags are not given, they default to C<class> IN and C<dnssec>, C<usevc> and C<recurse> according to configuration (which is by default off on all three).
|
||||
|
||||
=item query_persistent($name[, $type[, $flags]])
|
||||
|
||||
Identical to L<query_one>, except that instead of returning the packet from the
|
||||
first server that returns one, it returns the first packet that actually
|
||||
contains a resource record matching the requested name and type.
|
||||
|
||||
=item query_auth($name[, $type[, $flags]])
|
||||
|
||||
Identical to L<query_one>, except that instead of returning the packet from the
|
||||
first server that returns one, it returns the first packet that has the AA flag set.
|
||||
|
||||
=item query_all($name, $type, $flags)
|
||||
|
||||
Sends (or retrieves from cache) queries to all the nameservers listed in the zone's ns list, and returns a reference to an array with the
|
||||
responses. The responses can be either L<Zonemaster::Engine::Packet> objects or C<undef> values. The arguments are the same as for L<query_one>.
|
||||
|
||||
=item is_in_zone($name)
|
||||
|
||||
Returns true if the given name is in the zone, false if not. If it could not be
|
||||
determined with a sufficient degree of certainty if the name is in the zone or
|
||||
not, C<undef> is returned.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
54
zonemaster-engine/share/GNUmakefile
Normal file
54
zonemaster-engine/share/GNUmakefile
Normal file
@@ -0,0 +1,54 @@
|
||||
.POSIX:
|
||||
.SUFFIXES: .po .mo
|
||||
.PHONY: all check-msg-args dist extract-pot tidy-po show-fuzzy update-po new-po check-po
|
||||
|
||||
POFILES := $(shell find . -maxdepth 1 -type f -name '*.po' -exec basename {} \;)
|
||||
MOFILES := $(POFILES:%.po=%.mo)
|
||||
POTFILE = Zonemaster-Engine.pot
|
||||
PMFILES := $(shell find ../lib -type f -name '*.pm' | sort)
|
||||
TESTMODULEFILES := $(shell find ../lib/Zonemaster/Engine/Test -type f -name '*.pm' | sort)
|
||||
|
||||
all: $(MOFILES) modules.txt
|
||||
|
||||
# Tidy the formatting of all PO files
|
||||
tidy-po:
|
||||
@tmpdir="`mktemp -d tidy-po.XXXXXXXX`" ;\
|
||||
trap 'rm -rf "$$tmpdir"' EXIT ;\
|
||||
for f in $(POFILES) ; do msgcat $$f -o $$tmpdir/$$f && mv -f $$tmpdir/$$f $$f ; done
|
||||
|
||||
update-po: extract-pot
|
||||
@for f in $(POFILES) ; do msgmerge --update --backup=none --quiet --no-location $(MSGMERGE_OPTS) $$f $(POTFILE) ; done
|
||||
|
||||
extract-pot:
|
||||
@xgettext --output $(POTFILE) --sort-by-file --add-comments --language=Perl --from-code=UTF-8 -k__ -k\$$__ -k%__ -k__x -k__n:1,2 -k__nx:1,2 -k__xn:1,2 -kN__ -kN__n:1,2 -k__p:1c,2 -k__np:1c,2,3 -kN__p:1c,2 -kN__np:1c,2,3 $(PMFILES)
|
||||
|
||||
$(POTFILE): extract-pot
|
||||
|
||||
# Create a new empty PO file with basename provided with the POLANG variable
|
||||
# Update the Language field in the header
|
||||
new-po: extract-pot
|
||||
@[ -n "$(POLANG)" ] || ( echo "Usage: make POLANG=xx new-po" && exit 1 )
|
||||
@cp $(POTFILE) $(POLANG).po
|
||||
@perl -pi -e 's/^("Project-Id-Version:) .+(\\n)/$$1 1.0.0$$2/;' \
|
||||
-e 's/^("Language-Team:) .+(\\n)/$$1 Zonemaster Team$$2/;' \
|
||||
-e 's/^"Language: /$$&$(POLANG)/;' \
|
||||
-e 's/^("Content-Type:.+charset=)CHARSET/$${1}UTF-8/;' $(POLANG).po
|
||||
@perl -ni -e 'print unless /^#( |$$)/' $(POLANG).po
|
||||
|
||||
check-po:
|
||||
@for f in $(POFILES) ; do msgfmt -c $$f ; done
|
||||
|
||||
.po.mo:
|
||||
@msgfmt -o $@ $<
|
||||
@mkdir -p locale/`basename $@ .mo`/LC_MESSAGES
|
||||
@ln -vf $@ locale/`basename $@ .mo`/LC_MESSAGES/Zonemaster-Engine.mo
|
||||
|
||||
show-fuzzy:
|
||||
@for f in $(POFILES) ; do msgattrib --only-fuzzy $$f ; done
|
||||
|
||||
check-msg-args:
|
||||
@for f in $(POFILES) ; do ../util/check-msg-args $$f ; done
|
||||
|
||||
modules.txt: $(TESTMODULEFILES)
|
||||
@echo Basic > modules.txt
|
||||
@echo $(TESTMODULEFILES) | xargs basename -s .pm -a | grep -vE '^Basic$$' | sort >> modules.txt
|
||||
15
zonemaster-engine/share/Makefile
Normal file
15
zonemaster-engine/share/Makefile
Normal file
@@ -0,0 +1,15 @@
|
||||
# This is a wrapper for BSD Make (FreeBSD) to execute
|
||||
# GNU Make (gmake) and the primary makefile GNUmakefile.
|
||||
|
||||
GNUMAKE ?= gmake
|
||||
FILES != ls *
|
||||
|
||||
# File targets should be evaluated by gmake.
|
||||
.PHONY: all $(FILES)
|
||||
|
||||
all:
|
||||
@${GNUMAKE} $@
|
||||
|
||||
.DEFAULT:
|
||||
@${GNUMAKE} $@
|
||||
|
||||
3708
zonemaster-engine/share/da.po
Normal file
3708
zonemaster-engine/share/da.po
Normal file
File diff suppressed because it is too large
Load Diff
4002
zonemaster-engine/share/es.po
Normal file
4002
zonemaster-engine/share/es.po
Normal file
File diff suppressed because it is too large
Load Diff
3318
zonemaster-engine/share/fi.po
Normal file
3318
zonemaster-engine/share/fi.po
Normal file
File diff suppressed because it is too large
Load Diff
4231
zonemaster-engine/share/fr.po
Normal file
4231
zonemaster-engine/share/fr.po
Normal file
File diff suppressed because it is too large
Load Diff
26
zonemaster-engine/share/iana-ipv4-special-registry.csv
Normal file
26
zonemaster-engine/share/iana-ipv4-special-registry.csv
Normal file
@@ -0,0 +1,26 @@
|
||||
Address Block,Name,RFC,Allocation Date,Termination Date,Source,Destination,Forwardable,Globally Reachable,Reserved-by-Protocol
|
||||
0.0.0.0/8,"""This network""","[RFC791], Section 3.2",1981-09,N/A,True,False,False,False,True
|
||||
0.0.0.0/32,"""This host on this network""","[RFC1122], Section 3.2.1.3",1981-09,N/A,True,False,False,False,True
|
||||
10.0.0.0/8,Private-Use,[RFC1918],1996-02,N/A,True,True,True,False,False
|
||||
100.64.0.0/10,Shared Address Space,[RFC6598],2012-04,N/A,True,True,True,False,False
|
||||
127.0.0.0/8,Loopback,"[RFC1122], Section 3.2.1.3",1981-09,N/A,False [1],False [1],False [1],False [1],True
|
||||
169.254.0.0/16,Link Local,[RFC3927],2005-05,N/A,True,True,False,False,True
|
||||
172.16.0.0/12,Private-Use,[RFC1918],1996-02,N/A,True,True,True,False,False
|
||||
192.0.0.0/24 [2],IETF Protocol Assignments,"[RFC6890], Section 2.1",2010-01,N/A,False,False,False,False,False
|
||||
192.0.0.0/29,IPv4 Service Continuity Prefix,[RFC7335],2011-06,N/A,True,True,True,False,False
|
||||
192.0.0.8/32,IPv4 dummy address,[RFC7600],2015-03,N/A,True,False,False,False,False
|
||||
192.0.0.9/32,Port Control Protocol Anycast,[RFC7723],2015-10,N/A,True,True,True,True,False
|
||||
192.0.0.10/32,Traversal Using Relays around NAT Anycast,[RFC8155],2017-02,N/A,True,True,True,True,False
|
||||
"192.0.0.170/32, 192.0.0.171/32",NAT64/DNS64 Discovery,"[RFC8880][RFC7050], Section 2.2",2013-02,N/A,False,False,False,False,True
|
||||
192.0.2.0/24,Documentation (TEST-NET-1),[RFC5737],2010-01,N/A,False,False,False,False,False
|
||||
192.31.196.0/24,AS112-v4,[RFC7535],2014-12,N/A,True,True,True,True,False
|
||||
192.52.193.0/24,AMT,[RFC7450],2014-12,N/A,True,True,True,True,False
|
||||
192.88.99.0/24,Deprecated (6to4 Relay Anycast),[RFC7526],2001-06,2015-03,,,,,
|
||||
192.168.0.0/16,Private-Use,[RFC1918],1996-02,N/A,True,True,True,False,False
|
||||
192.175.48.0/24,Direct Delegation AS112 Service,[RFC7534],1996-01,N/A,True,True,True,True,False
|
||||
198.18.0.0/15,Benchmarking,[RFC2544],1999-03,N/A,True,True,True,False,False
|
||||
198.51.100.0/24,Documentation (TEST-NET-2),[RFC5737],2010-01,N/A,False,False,False,False,False
|
||||
203.0.113.0/24,Documentation (TEST-NET-3),[RFC5737],2010-01,N/A,False,False,False,False,False
|
||||
240.0.0.0/4,Reserved,"[RFC1112], Section 4",1989-08,N/A,False,False,False,False,True
|
||||
255.255.255.255/32,Limited Broadcast,"[RFC8190]
|
||||
[RFC919], Section 7",1984-10,N/A,False,True,False,False,True
|
||||
|
28
zonemaster-engine/share/iana-ipv6-special-registry.csv
Normal file
28
zonemaster-engine/share/iana-ipv6-special-registry.csv
Normal file
@@ -0,0 +1,28 @@
|
||||
Address Block,Name,RFC,Allocation Date,Termination Date,Source,Destination,Forwardable,Globally Reachable,Reserved-by-Protocol
|
||||
::1/128,Loopback Address,[RFC4291],2006-02,N/A,False,False,False,False,True
|
||||
::/128,Unspecified Address,[RFC4291],2006-02,N/A,True,False,False,False,True
|
||||
::ffff:0:0/96,IPv4-mapped Address,[RFC4291],2006-02,N/A,False,False,False,False,True
|
||||
64:ff9b::/96,IPv4-IPv6 Translat.,[RFC6052],2010-10,N/A,True,True,True,True,False
|
||||
64:ff9b:1::/48,IPv4-IPv6 Translat.,[RFC8215],2017-06,N/A,True,True,True,False,False
|
||||
100::/64,Discard-Only Address Block,[RFC6666],2012-06,N/A,True,True,True,False,False
|
||||
100:0:0:1::/64,Dummy IPv6 Prefix,[RFC-ietf-mpls-p2mp-bfd-11],2025-04,N/A,True,False,False,False,False
|
||||
2001::/23,IETF Protocol Assignments,[RFC2928],2000-09,N/A,False [1],False [1],False [1],False [1],False
|
||||
2001::/32,TEREDO,"[RFC4380]
|
||||
[RFC8190]",2006-01,N/A,True,True,True,N/A [2],False
|
||||
2001:1::1/128,Port Control Protocol Anycast,[RFC7723],2015-10,N/A,True,True,True,True,False
|
||||
2001:1::2/128,Traversal Using Relays around NAT Anycast,[RFC8155],2017-02,N/A,True,True,True,True,False
|
||||
2001:1::3/128,DNS-SD Service Registration Protocol Anycast,[RFC-ietf-dnssd-srp-25],2024-04,N/A,True,True,True,True,False
|
||||
2001:2::/48,Benchmarking,[RFC5180][RFC Errata 1752],2008-04,N/A,True,True,True,False,False
|
||||
2001:3::/32,AMT,[RFC7450],2014-12,N/A,True,True,True,True,False
|
||||
2001:4:112::/48,AS112-v6,[RFC7535],2014-12,N/A,True,True,True,True,False
|
||||
2001:10::/28,Deprecated (previously ORCHID),[RFC4843],2007-03,2014-03,,,,,
|
||||
2001:20::/28,ORCHIDv2,[RFC7343],2014-07,N/A,True,True,True,True,False
|
||||
2001:30::/28,Drone Remote ID Protocol Entity Tags (DETs) Prefix,[RFC9374],2022-12,N/A,True,True,True,True,False
|
||||
2001:db8::/32,Documentation,[RFC3849],2004-07,N/A,False,False,False,False,False
|
||||
2002::/16 [3],6to4,[RFC3056],2001-02,N/A,True,True,True,N/A [3],False
|
||||
2620:4f:8000::/48,Direct Delegation AS112 Service,[RFC7534],2011-05,N/A,True,True,True,True,False
|
||||
3fff::/20,Documentation,[RFC9637],2024-07,N/A,False,False,False,False,False
|
||||
5f00::/16,Segment Routing (SRv6) SIDs,[RFC9602],2024-04,N/A,True,True,True,False,False
|
||||
fc00::/7,Unique-Local,"[RFC4193]
|
||||
[RFC8190]",2005-10,N/A,True,True,True,False [4],False
|
||||
fe80::/10,Link-Local Unicast,[RFC4291],2006-02,N/A,True,True,False,False,True
|
||||
|
33
zonemaster-engine/share/json2yaml.pl
Executable file
33
zonemaster-engine/share/json2yaml.pl
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use 5.014002;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::ShareDir qw[dist_file];
|
||||
use File::Slurp;
|
||||
|
||||
use Zonemaster::Engine::Profile;
|
||||
|
||||
my $json_file = @ARGV ? $ARGV[0] : dist_file( 'Zonemaster-Engine', 'profile.json');
|
||||
my $json = read_file( $json_file );
|
||||
my $profile = Zonemaster::Engine::Profile->from_json( $json );
|
||||
my $yaml = $profile->to_yaml();
|
||||
say $yaml;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
json2yaml - Convert a JSON profile into YAML
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
./json2yaml.pl
|
||||
./json2yaml.pl profile.json
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
json2yaml converts a JSON profile into YAML. The JSON profile can be passed as
|
||||
an argument. If no argument is provided, the script will look for the default
|
||||
profile. The YAML profile is written to the standard output.
|
||||
|
||||
=cut
|
||||
9
zonemaster-engine/share/modules.txt
Normal file
9
zonemaster-engine/share/modules.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Basic
|
||||
Address
|
||||
Connectivity
|
||||
Consistency
|
||||
Delegation
|
||||
DNSSEC
|
||||
Nameserver
|
||||
Syntax
|
||||
Zone
|
||||
92
zonemaster-engine/share/named.root
Normal file
92
zonemaster-engine/share/named.root
Normal file
@@ -0,0 +1,92 @@
|
||||
; This file holds the information on root name servers needed to
|
||||
; initialize cache of Internet domain name servers
|
||||
; (e.g. reference this file in the "cache . <file>"
|
||||
; configuration file of BIND domain name servers).
|
||||
;
|
||||
; This file is made available by InterNIC
|
||||
; under anonymous FTP as
|
||||
; file /domain/named.cache
|
||||
; on server FTP.INTERNIC.NET
|
||||
; -OR- RS.INTERNIC.NET
|
||||
;
|
||||
; last update: January 15, 2026
|
||||
; related version of root zone: 2026011501
|
||||
;
|
||||
; FORMERLY NS.INTERNIC.NET
|
||||
;
|
||||
. 3600000 NS A.ROOT-SERVERS.NET.
|
||||
A.ROOT-SERVERS.NET. 3600000 A 198.41.0.4
|
||||
A.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:ba3e::2:30
|
||||
;
|
||||
; FORMERLY NS1.ISI.EDU
|
||||
;
|
||||
. 3600000 NS B.ROOT-SERVERS.NET.
|
||||
B.ROOT-SERVERS.NET. 3600000 A 170.247.170.2
|
||||
B.ROOT-SERVERS.NET. 3600000 AAAA 2801:1b8:10::b
|
||||
;
|
||||
; FORMERLY C.PSI.NET
|
||||
;
|
||||
. 3600000 NS C.ROOT-SERVERS.NET.
|
||||
C.ROOT-SERVERS.NET. 3600000 A 192.33.4.12
|
||||
C.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2::c
|
||||
;
|
||||
; FORMERLY TERP.UMD.EDU
|
||||
;
|
||||
. 3600000 NS D.ROOT-SERVERS.NET.
|
||||
D.ROOT-SERVERS.NET. 3600000 A 199.7.91.13
|
||||
D.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2d::d
|
||||
;
|
||||
; FORMERLY NS.NASA.GOV
|
||||
;
|
||||
. 3600000 NS E.ROOT-SERVERS.NET.
|
||||
E.ROOT-SERVERS.NET. 3600000 A 192.203.230.10
|
||||
E.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:a8::e
|
||||
;
|
||||
; FORMERLY NS.ISC.ORG
|
||||
;
|
||||
. 3600000 NS F.ROOT-SERVERS.NET.
|
||||
F.ROOT-SERVERS.NET. 3600000 A 192.5.5.241
|
||||
F.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2f::f
|
||||
;
|
||||
; FORMERLY NS.NIC.DDN.MIL
|
||||
;
|
||||
. 3600000 NS G.ROOT-SERVERS.NET.
|
||||
G.ROOT-SERVERS.NET. 3600000 A 192.112.36.4
|
||||
G.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:12::d0d
|
||||
;
|
||||
; FORMERLY AOS.ARL.ARMY.MIL
|
||||
;
|
||||
. 3600000 NS H.ROOT-SERVERS.NET.
|
||||
H.ROOT-SERVERS.NET. 3600000 A 198.97.190.53
|
||||
H.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:1::53
|
||||
;
|
||||
; FORMERLY NIC.NORDU.NET
|
||||
;
|
||||
. 3600000 NS I.ROOT-SERVERS.NET.
|
||||
I.ROOT-SERVERS.NET. 3600000 A 192.36.148.17
|
||||
I.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fe::53
|
||||
;
|
||||
; OPERATED BY VERISIGN, INC.
|
||||
;
|
||||
. 3600000 NS J.ROOT-SERVERS.NET.
|
||||
J.ROOT-SERVERS.NET. 3600000 A 192.58.128.30
|
||||
J.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:c27::2:30
|
||||
;
|
||||
; OPERATED BY RIPE NCC
|
||||
;
|
||||
. 3600000 NS K.ROOT-SERVERS.NET.
|
||||
K.ROOT-SERVERS.NET. 3600000 A 193.0.14.129
|
||||
K.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fd::1
|
||||
;
|
||||
; OPERATED BY ICANN
|
||||
;
|
||||
. 3600000 NS L.ROOT-SERVERS.NET.
|
||||
L.ROOT-SERVERS.NET. 3600000 A 199.7.83.42
|
||||
L.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:9f::42
|
||||
;
|
||||
; OPERATED BY WIDE
|
||||
;
|
||||
. 3600000 NS M.ROOT-SERVERS.NET.
|
||||
M.ROOT-SERVERS.NET. 3600000 A 202.12.27.33
|
||||
M.ROOT-SERVERS.NET. 3600000 AAAA 2001:dc3::35
|
||||
; End of file
|
||||
3691
zonemaster-engine/share/nb.po
Normal file
3691
zonemaster-engine/share/nb.po
Normal file
File diff suppressed because it is too large
Load Diff
662
zonemaster-engine/share/profile.json
Normal file
662
zonemaster-engine/share/profile.json
Normal file
@@ -0,0 +1,662 @@
|
||||
{
|
||||
"asn_db" : {
|
||||
"style" : "Cymru",
|
||||
"sources" : {
|
||||
"Cymru" : [ "asnlookup.zonemaster.net", "asn.cymru.com" ],
|
||||
"RIPE" : [ "riswhois.ripe.net" ]
|
||||
}
|
||||
},
|
||||
"net" : {
|
||||
"ipv4" : true,
|
||||
"ipv6" : true
|
||||
},
|
||||
"no_network" : false,
|
||||
"resolver" : {
|
||||
"defaults" : {
|
||||
"debug" : false,
|
||||
"igntc" : false,
|
||||
"fallback" : true,
|
||||
"recurse" : false,
|
||||
"retrans" : 3,
|
||||
"retry" : 2,
|
||||
"usevc" : false,
|
||||
"timeout": 5
|
||||
}
|
||||
},
|
||||
"test_levels" : {
|
||||
"ADDRESS" : {
|
||||
"A01_GLOBALLY_REACHABLE_ADDR" : "INFO",
|
||||
"A01_ADDR_NOT_GLOBALLY_REACHABLE" : "ERROR",
|
||||
"A01_DOCUMENTATION_ADDR" : "ERROR",
|
||||
"A01_LOCAL_USE_ADDR" : "ERROR",
|
||||
"A01_NO_GLOBALLY_REACHABLE_ADDR" : "ERROR",
|
||||
"A01_NO_NAME_SERVERS_FOUND" : "CRITICAL",
|
||||
"NAMESERVER_IP_PTR_MATCH" : "INFO",
|
||||
"NAMESERVER_IP_PTR_MISMATCH" : "NOTICE",
|
||||
"NAMESERVER_IP_WITHOUT_REVERSE" : "WARNING",
|
||||
"NAMESERVERS_IP_WITH_REVERSE" : "INFO",
|
||||
"NO_RESPONSE_PTR_QUERY" : "WARNING",
|
||||
"TEST_CASE_END" : "DEBUG",
|
||||
"TEST_CASE_START" : "DEBUG"
|
||||
},
|
||||
"BASIC" : {
|
||||
"A_QUERY_NO_RESPONSES" : "INFO",
|
||||
"B01_CHILD_IS_ALIAS" : "NOTICE",
|
||||
"B01_CHILD_FOUND" : "INFO",
|
||||
"B01_INCONSISTENT_ALIAS" : "ERROR",
|
||||
"B01_INCONSISTENT_DELEGATION" : "ERROR",
|
||||
"B01_NO_CHILD" : "ERROR",
|
||||
"B01_PARENT_DISREGARDED" : "INFO",
|
||||
"B01_PARENT_FOUND" : "INFO",
|
||||
"B01_PARENT_NOT_FOUND" : "WARNING",
|
||||
"B01_PARENT_UNDETERMINED" : "WARNING",
|
||||
"B01_ROOT_HAS_NO_PARENT" : "INFO",
|
||||
"B01_SERVER_ZONE_ERROR" : "DEBUG",
|
||||
"B02_AUTH_RESPONSE_SOA" : "INFO",
|
||||
"B02_NO_DELEGATION" : "CRITICAL",
|
||||
"B02_NO_WORKING_NS" : "CRITICAL",
|
||||
"B02_NS_BROKEN" : "ERROR",
|
||||
"B02_NS_NOT_AUTH" : "ERROR",
|
||||
"B02_NS_NO_IP_ADDR" : "ERROR",
|
||||
"B02_NS_NO_RESPONSE" : "WARNING",
|
||||
"B02_UNEXPECTED_RCODE" : "ERROR",
|
||||
"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",
|
||||
"IPV4_DISABLED" : "DEBUG",
|
||||
"IPV4_ENABLED" : "DEBUG",
|
||||
"IPV6_DISABLED" : "DEBUG",
|
||||
"IPV6_ENABLED" : "DEBUG",
|
||||
"TEST_CASE_END" : "DEBUG",
|
||||
"TEST_CASE_START" : "DEBUG"
|
||||
},
|
||||
"CONNECTIVITY" : {
|
||||
"ASN_INFOS_ANNOUNCE_BY" : "DEBUG",
|
||||
"ASN_INFOS_ANNOUNCE_IN" : "DEBUG",
|
||||
"ASN_INFOS_RAW" : "DEBUG",
|
||||
"CN01_IPV4_DISABLED": "NOTICE",
|
||||
"CN01_IPV6_DISABLED": "NOTICE",
|
||||
"CN01_MISSING_NS_RECORD_UDP": "WARNING",
|
||||
"CN01_MISSING_SOA_RECORD_UDP": "WARNING",
|
||||
"CN01_NO_RESPONSE_NS_QUERY_UDP": "WARNING",
|
||||
"CN01_NO_RESPONSE_SOA_QUERY_UDP": "WARNING",
|
||||
"CN01_NO_RESPONSE_UDP": "WARNING",
|
||||
"CN01_NS_RECORD_NOT_AA_UDP": "WARNING",
|
||||
"CN01_SOA_RECORD_NOT_AA_UDP": "WARNING",
|
||||
"CN01_UNEXPECTED_RCODE_NS_QUERY_UDP": "WARNING",
|
||||
"CN01_UNEXPECTED_RCODE_SOA_QUERY_UDP": "WARNING",
|
||||
"CN01_WRONG_NS_RECORD_UDP": "WARNING",
|
||||
"CN01_WRONG_SOA_RECORD_UDP": "WARNING",
|
||||
"CN02_MISSING_NS_RECORD_TCP": "WARNING",
|
||||
"CN02_MISSING_SOA_RECORD_TCP": "WARNING",
|
||||
"CN02_NO_RESPONSE_NS_QUERY_TCP": "WARNING",
|
||||
"CN02_NO_RESPONSE_SOA_QUERY_TCP": "WARNING",
|
||||
"CN02_NO_RESPONSE_TCP": "WARNING",
|
||||
"CN02_NS_RECORD_NOT_AA_TCP": "WARNING",
|
||||
"CN02_SOA_RECORD_NOT_AA_TCP": "WARNING",
|
||||
"CN02_UNEXPECTED_RCODE_NS_QUERY_TCP": "WARNING",
|
||||
"CN02_UNEXPECTED_RCODE_SOA_QUERY_TCP": "WARNING",
|
||||
"CN02_WRONG_NS_RECORD_TCP": "WARNING",
|
||||
"CN02_WRONG_SOA_RECORD_TCP": "WARNING",
|
||||
"CN04_ASN_INFOS_ANNOUNCE_IN": "DEBUG",
|
||||
"CN04_ASN_INFOS_RAW": "DEBUG",
|
||||
"CN04_EMPTY_PREFIX_SET": "NOTICE",
|
||||
"CN04_ERROR_PREFIX_DATABASE": "NOTICE",
|
||||
"CN04_IPV4_DIFFERENT_PREFIX": "INFO",
|
||||
"CN04_IPV4_SAME_PREFIX": "NOTICE",
|
||||
"CN04_IPV4_SINGLE_PREFIX": "WARNING",
|
||||
"CN04_IPV6_DIFFERENT_PREFIX": "INFO",
|
||||
"CN04_IPV6_SAME_PREFIX": "NOTICE",
|
||||
"CN04_IPV6_SINGLE_PREFIX": "WARNING",
|
||||
"EMPTY_ASN_SET" : "NOTICE",
|
||||
"ERROR_ASN_DATABASE" : "NOTICE",
|
||||
"IPV4_DIFFERENT_ASN" : "INFO",
|
||||
"IPV4_DISABLED" : "DEBUG",
|
||||
"IPV4_ONE_ASN" : "WARNING",
|
||||
"IPV4_SAME_ASN" : "NOTICE",
|
||||
"IPV6_DIFFERENT_ASN" : "INFO",
|
||||
"IPV6_DISABLED" : "DEBUG",
|
||||
"IPV6_ONE_ASN" : "WARNING",
|
||||
"IPV6_SAME_ASN" : "NOTICE",
|
||||
"NAMESERVER_HAS_TCP_53" : "DEBUG",
|
||||
"NAMESERVER_HAS_UDP_53" : "DEBUG",
|
||||
"NAMESERVER_NO_TCP_53" : "ERROR",
|
||||
"NAMESERVER_NO_UDP_53" : "ERROR",
|
||||
"TEST_CASE_END" : "DEBUG",
|
||||
"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",
|
||||
"IPV4_DISABLED" : "DEBUG",
|
||||
"IPV6_DISABLED" : "DEBUG",
|
||||
"MULTIPLE_NS_SET" : "NOTICE",
|
||||
"MULTIPLE_SOA_MNAMES" : "NOTICE",
|
||||
"MULTIPLE_SOA_RNAMES" : "NOTICE",
|
||||
"MULTIPLE_SOA_SERIALS" : "WARNING",
|
||||
"MULTIPLE_SOA_TIME_PARAMETER_SET" : "NOTICE",
|
||||
"NO_RESPONSE" : "DEBUG",
|
||||
"NO_RESPONSE_NS_QUERY" : "DEBUG",
|
||||
"NO_RESPONSE_SOA_QUERY" : "DEBUG",
|
||||
"NS_SET" : "INFO",
|
||||
"ONE_NS_SET" : "INFO",
|
||||
"ONE_SOA_MNAME" : "INFO",
|
||||
"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",
|
||||
"SOA_TIME_PARAMETER_SET" : "INFO",
|
||||
"TEST_CASE_END" : "DEBUG",
|
||||
"TEST_CASE_START" : "DEBUG"
|
||||
},
|
||||
"DELEGATION" : {
|
||||
"ARE_AUTHORITATIVE" : "INFO",
|
||||
"CHILD_DISTINCT_NS_IP" : "INFO",
|
||||
"CHILD_NS_SAME_IP" : "ERROR",
|
||||
"DEL_DISTINCT_NS_IP" : "INFO",
|
||||
"DEL_NS_SAME_IP" : "ERROR",
|
||||
"DISTINCT_IP_ADDRESS" : "INFO",
|
||||
"ENOUGH_IPV4_NS_CHILD" : "INFO",
|
||||
"ENOUGH_IPV4_NS_DEL" : "INFO",
|
||||
"ENOUGH_IPV6_NS_CHILD" : "INFO",
|
||||
"ENOUGH_IPV6_NS_DEL" : "INFO",
|
||||
"ENOUGH_NS_CHILD" : "INFO",
|
||||
"ENOUGH_NS_DEL" : "INFO",
|
||||
"EXTRA_NAME_CHILD" : "NOTICE",
|
||||
"EXTRA_NAME_PARENT" : "ERROR",
|
||||
"IPV4_DISABLED" : "DEBUG",
|
||||
"IPV6_DISABLED" : "DEBUG",
|
||||
"IS_NOT_AUTHORITATIVE" : "WARNING",
|
||||
"NAMES_MATCH" : "INFO",
|
||||
"NO_IPV4_NS_CHILD" : "WARNING",
|
||||
"NO_IPV4_NS_DEL" : "WARNING",
|
||||
"NO_IPV6_NS_CHILD" : "NOTICE",
|
||||
"NO_IPV6_NS_DEL" : "NOTICE",
|
||||
"NO_NS_CNAME" : "INFO",
|
||||
"NO_RESPONSE" : "DEBUG",
|
||||
"NOT_ENOUGH_IPV4_NS_CHILD" : "ERROR",
|
||||
"NOT_ENOUGH_IPV4_NS_DEL" : "ERROR",
|
||||
"NOT_ENOUGH_IPV6_NS_CHILD" : "ERROR",
|
||||
"NOT_ENOUGH_IPV6_NS_DEL" : "ERROR",
|
||||
"NOT_ENOUGH_NS_CHILD" : "ERROR",
|
||||
"NOT_ENOUGH_NS_DEL" : "ERROR",
|
||||
"NS_IS_CNAME" : "ERROR",
|
||||
"REFERRAL_SIZE_OK" : "INFO",
|
||||
"REFERRAL_SIZE_TOO_LARGE" : "WARNING",
|
||||
"SAME_IP_ADDRESS" : "ERROR",
|
||||
"SOA_EXISTS" : "INFO",
|
||||
"SOA_NOT_EXISTS" : "ERROR",
|
||||
"TEST_CASE_END" : "DEBUG",
|
||||
"TEST_CASE_START" : "DEBUG",
|
||||
"TOTAL_NAME_MISMATCH" : "ERROR",
|
||||
"UNEXPECTED_RCODE" : "WARNING"
|
||||
},
|
||||
"DNSSEC" : {
|
||||
"DNSKEY_SMALLER_THAN_REC" : "WARNING",
|
||||
"DNSKEY_TOO_LARGE_FOR_ALGO" : "ERROR",
|
||||
"DNSKEY_TOO_SMALL_FOR_ALGO" : "ERROR",
|
||||
"DS01_DS_ALGO_2_MISSING" : "NOTICE",
|
||||
"DS01_DS_ALGO_DEPRECATED" : "ERROR",
|
||||
"DS01_DS_ALGO_NOT_DS" : "ERROR",
|
||||
"DS01_DS_ALGO_OK" : "INFO",
|
||||
"DS01_DS_ALGO_PRIVATE" : "ERROR",
|
||||
"DS01_DS_ALGO_RESERVED" : "ERROR",
|
||||
"DS01_DS_ALGO_UNASSIGNED" : "ERROR",
|
||||
"DS01_NO_RESPONSE" : "WARNING",
|
||||
"DS01_PARENT_SERVER_NO_DS" : "ERROR",
|
||||
"DS01_PARENT_ZONE_NO_DS" : "NOTICE",
|
||||
"DS01_ROOT_N_NO_UNDEL_DS" : "INFO",
|
||||
"DS01_UNDEL_N_NO_UNDEL_DS" : "INFO",
|
||||
"DS02_ALGO_NOT_SUPPORTED_BY_ZM" : "NOTICE",
|
||||
"DS02_DNSKEY_NOT_FOR_ZONE_SIGNING" : "ERROR",
|
||||
"DS02_DNSKEY_NOT_SEP" : "NOTICE",
|
||||
"DS02_DNSKEY_NOT_SIGNED_BY_ANY_DS" : "ERROR",
|
||||
"DS02_NO_DNSKEY_FOR_DS" : "WARNING",
|
||||
"DS02_NO_MATCHING_DNSKEY_RRSIG" : "WARNING",
|
||||
"DS02_NO_MATCH_DS_DNSKEY" : "ERROR",
|
||||
"DS02_NO_VALID_DNSKEY_FOR_ANY_DS" : "ERROR",
|
||||
"DS02_RRSIG_NOT_VALID_BY_DNSKEY" : "ERROR",
|
||||
"DS03_ERROR_RESPONSE_NSEC_QUERY" : "ERROR",
|
||||
"DS03_ERR_MULT_NSEC3" : "ERROR",
|
||||
"DS03_ILLEGAL_HASH_ALGO" : "ERROR",
|
||||
"DS03_ILLEGAL_ITERATION_VALUE" : "WARNING",
|
||||
"DS03_ILLEGAL_SALT_LENGTH" : "WARNING",
|
||||
"DS03_INCONSISTENT_HASH_ALGO" : "ERROR",
|
||||
"DS03_INCONSISTENT_ITERATION" : "ERROR",
|
||||
"DS03_INCONSISTENT_NSEC3_FLAGS" : "ERROR",
|
||||
"DS03_INCONSISTENT_SALT_LENGTH" : "ERROR",
|
||||
"DS03_LEGAL_EMPTY_SALT" : "INFO",
|
||||
"DS03_LEGAL_HASH_ALGO" : "INFO",
|
||||
"DS03_LEGAL_ITERATION_VALUE" : "INFO",
|
||||
"DS03_NO_DNSSEC_SUPPORT" : "NOTICE",
|
||||
"DS03_NO_NSEC3" : "INFO",
|
||||
"DS03_NO_RESPONSE_NSEC_QUERY": "ERROR",
|
||||
"DS03_NSEC3_OPT_OUT_DISABLED" : "INFO",
|
||||
"DS03_NSEC3_OPT_OUT_ENABLED_NON_TLD" : "NOTICE",
|
||||
"DS03_NSEC3_OPT_OUT_ENABLED_TLD" : "INFO",
|
||||
"DS03_SERVER_NO_DNSSEC_SUPPORT" : "ERROR",
|
||||
"DS03_SERVER_NO_NSEC3" : "ERROR",
|
||||
"DS03_UNASSIGNED_FLAG_USED" : "ERROR",
|
||||
"DS05_ALGO_DEPRECATED" : "ERROR",
|
||||
"DS05_ALGO_NOT_RECOMMENDED" : "WARNING",
|
||||
"DS05_ALGO_NOT_ZONE_SIGN" : "ERROR",
|
||||
"DS05_ALGO_OK" : "INFO",
|
||||
"DS05_ALGO_PRIVATE" : "ERROR",
|
||||
"DS05_ALGO_RESERVED" : "ERROR",
|
||||
"DS05_ALGO_UNASSIGNED" : "ERROR",
|
||||
"DS05_NO_RESPONSE" : "WARNING",
|
||||
"DS05_SERVER_NO_DNSSEC" : "ERROR",
|
||||
"DS05_ZONE_NO_DNSSEC" : "NOTICE",
|
||||
"DS07_DS_FOR_SIGNED_ZONE" : "INFO",
|
||||
"DS07_DS_ON_PARENT_SERVER" : "INFO",
|
||||
"DS07_INCONSISTENT_DS" : "ERROR",
|
||||
"DS07_INCONSISTENT_SIGNED" : "ERROR",
|
||||
"DS07_NON_AUTH_RESPONSE_DNSKEY" : "WARNING",
|
||||
"DS07_NOT_SIGNED" : "WARNING",
|
||||
"DS07_NOT_SIGNED_ON_SERVER" : "WARNING",
|
||||
"DS07_NO_DS_ON_PARENT_SERVER" : "WARNING",
|
||||
"DS07_NO_DS_FOR_SIGNED_ZONE" : "WARNING",
|
||||
"DS07_NO_RESPONSE_DNSKEY" : "WARNING",
|
||||
"DS07_SIGNED" : "INFO",
|
||||
"DS07_SIGNED_ON_SERVER" : "INFO",
|
||||
"DS07_UNEXP_RCODE_RESP_DNSKEY" : "WARNING",
|
||||
"DS08_ALGO_NOT_SUPPORTED_BY_ZM" : "NOTICE",
|
||||
"DS08_DNSKEY_RRSIG_EXPIRED" : "ERROR",
|
||||
"DS08_DNSKEY_RRSIG_NOT_YET_VALID" : "ERROR",
|
||||
"DS08_MISSING_RRSIG_IN_RESPONSE" : "ERROR",
|
||||
"DS08_NO_MATCHING_DNSKEY" : "ERROR",
|
||||
"DS08_RRSIG_NOT_VALID_BY_DNSKEY" : "ERROR",
|
||||
"DS09_ALGO_NOT_SUPPORTED_BY_ZM" : "NOTICE",
|
||||
"DS09_MISSING_RRSIG_IN_RESPONSE" : "ERROR",
|
||||
"DS09_NO_MATCHING_DNSKEY" : "ERROR",
|
||||
"DS09_RRSIG_NOT_VALID_BY_DNSKEY" : "ERROR",
|
||||
"DS09_SOA_RRSIG_EXPIRED" : "ERROR",
|
||||
"DS09_SOA_RRSIG_NOT_YET_VALID" : "ERROR",
|
||||
"DS10_ALGO_NOT_SUPPORTED_BY_ZM" : "NOTICE",
|
||||
"DS10_ERR_MULT_NSEC" : "ERROR",
|
||||
"DS10_ERR_MULT_NSEC3" : "ERROR",
|
||||
"DS10_ERR_MULT_NSEC3PARAM" : "ERROR",
|
||||
"DS10_EXPECTED_NSEC_NSEC3_MISSING" : "ERROR",
|
||||
"DS10_HAS_NSEC" : "INFO",
|
||||
"DS10_HAS_NSEC3" : "INFO",
|
||||
"DS10_INCONSISTENT_NSEC" : "ERROR",
|
||||
"DS10_INCONSISTENT_NSEC3" : "ERROR",
|
||||
"DS10_INCONSISTENT_NSEC_NSEC3" : "ERROR",
|
||||
"DS10_MIXED_NSEC_NSEC3" : "ERROR",
|
||||
"DS10_NSEC3PARAM_GIVES_ERR_ANSWER" : "ERROR",
|
||||
"DS10_NSEC3PARAM_MISMATCHES_APEX" : "ERROR",
|
||||
"DS10_NSEC3PARAM_QUERY_RESPONSE_ERR" : "ERROR",
|
||||
"DS10_NSEC3_ERR_TYPE_LIST" : "ERROR",
|
||||
"DS10_NSEC3_MISMATCHES_APEX" : "ERROR",
|
||||
"DS10_NSEC3_MISSING_SIGNATURE" : "ERROR",
|
||||
"DS10_NSEC3_NODATA_MISSING_SOA" : "ERROR",
|
||||
"DS10_NSEC3_NODATA_WRONG_SOA" : "ERROR",
|
||||
"DS10_NSEC3_NO_VERIFIED_SIGNATURE" : "ERROR",
|
||||
"DS10_NSEC3_RRSIG_EXPIRED" : "ERROR",
|
||||
"DS10_NSEC3_RRSIG_NOT_YET_VALID" : "ERROR",
|
||||
"DS10_NSEC3_RRSIG_NO_DNSKEY" : "WARNING",
|
||||
"DS10_NSEC3_RRSIG_VERIFY_ERROR" : "ERROR",
|
||||
"DS10_NSEC_ERR_TYPE_LIST" : "ERROR",
|
||||
"DS10_NSEC_GIVES_ERR_ANSWER" : "ERROR",
|
||||
"DS10_NSEC_MISMATCHES_APEX" : "ERROR",
|
||||
"DS10_NSEC_MISSING_SIGNATURE" : "ERROR",
|
||||
"DS10_NSEC_NODATA_MISSING_SOA" : "ERROR",
|
||||
"DS10_NSEC_NODATA_WRONG_SOA" : "ERROR",
|
||||
"DS10_NSEC_NO_VERIFIED_SIGNATURE" : "ERROR",
|
||||
"DS10_NSEC_QUERY_RESPONSE_ERR" : "ERROR",
|
||||
"DS10_NSEC_RRSIG_EXPIRED" : "ERROR",
|
||||
"DS10_NSEC_RRSIG_NOT_YET_VALID" : "ERROR",
|
||||
"DS10_NSEC_RRSIG_NO_DNSKEY" : "WARNING",
|
||||
"DS10_NSEC_RRSIG_VERIFY_ERROR" : "ERROR",
|
||||
"DS10_SERVER_NO_DNSSEC" : "ERROR",
|
||||
"DS10_ZONE_NO_DNSSEC" : "NOTICE",
|
||||
"DS11_INCONSISTENT_DS" : "WARNING",
|
||||
"DS11_INCONSISTENT_SIGNED_ZONE" : "ERROR",
|
||||
"DS11_UNDETERMINED_DS" : "ERROR",
|
||||
"DS11_UNDETERMINED_SIGNED_ZONE" : "ERROR",
|
||||
"DS11_PARENT_WITHOUT_DS" : "NOTICE",
|
||||
"DS11_PARENT_WITH_DS" : "NOTICE",
|
||||
"DS11_NS_WITH_SIGNED_ZONE" : "NOTICE",
|
||||
"DS11_NS_WITH_UNSIGNED_ZONE" : "WARNING",
|
||||
"DS11_DS_BUT_UNSIGNED_ZONE" : "ERROR",
|
||||
"DS13_ALGO_NOT_SIGNED_DNSKEY" : "WARNING",
|
||||
"DS13_ALGO_NOT_SIGNED_NS" : "WARNING",
|
||||
"DS13_ALGO_NOT_SIGNED_SOA" : "WARNING",
|
||||
"DS15_HAS_CDNSKEY_NO_CDS" : "NOTICE",
|
||||
"DS15_HAS_CDS_AND_CDNSKEY" : "INFO",
|
||||
"DS15_HAS_CDS_NO_CDNSKEY" : "NOTICE",
|
||||
"DS15_INCONSISTENT_CDNSKEY" : "ERROR",
|
||||
"DS15_INCONSISTENT_CDS" : "ERROR",
|
||||
"DS15_MISMATCH_CDS_CDNSKEY" : "ERROR",
|
||||
"DS15_NO_CDS_CDNSKEY" : "INFO",
|
||||
"DS16_CDS_INVALID_RRSIG" : "ERROR",
|
||||
"DS16_CDS_MATCHES_NON_SEP_DNSKEY" : "NOTICE",
|
||||
"DS16_CDS_MATCHES_NON_ZONE_DNSKEY" : "ERROR",
|
||||
"DS16_CDS_MATCHES_NO_DNSKEY" : "WARNING",
|
||||
"DS16_CDS_NOT_SIGNED_BY_CDS" : "NOTICE",
|
||||
"DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY" : "ERROR",
|
||||
"DS16_CDS_UNSIGNED" : "ERROR",
|
||||
"DS16_CDS_WITHOUT_DNSKEY" : "ERROR",
|
||||
"DS16_DELETE_CDS" : "INFO",
|
||||
"DS16_DNSKEY_NOT_SIGNED_BY_CDS" : "WARNING",
|
||||
"DS16_MIXED_DELETE_CDS" : "ERROR",
|
||||
"DS17_CDNSKEY_INVALID_RRSIG" : "ERROR",
|
||||
"DS17_CDNSKEY_IS_NON_SEP" : "NOTICE",
|
||||
"DS17_CDNSKEY_IS_NON_ZONE" : "ERROR",
|
||||
"DS17_CDNSKEY_MATCHES_NO_DNSKEY" : "WARNING",
|
||||
"DS17_CDNSKEY_NOT_SIGNED_BY_CDNSKEY" : "NOTICE",
|
||||
"DS17_CDNSKEY_SIGNED_BY_UNKNOWN_DNSKEY" : "ERROR",
|
||||
"DS17_CDNSKEY_UNSIGNED" : "ERROR",
|
||||
"DS17_CDNSKEY_WITHOUT_DNSKEY" : "ERROR",
|
||||
"DS17_DELETE_CDNSKEY" : "INFO",
|
||||
"DS17_DNSKEY_NOT_SIGNED_BY_CDNSKEY" : "WARNING",
|
||||
"DS17_MIXED_DELETE_CDNSKEY" : "ERROR",
|
||||
"DS18_NO_MATCH_CDS_RRSIG_DS" : "ERROR",
|
||||
"DS18_NO_MATCH_CDNSKEY_RRSIG_DS" : "ERROR",
|
||||
"DURATION_LONG" : "WARNING",
|
||||
"DURATION_OK" : "DEBUG",
|
||||
"EXTRA_PROCESSING_BROKEN" : "ERROR",
|
||||
"EXTRA_PROCESSING_OK" : "DEBUG",
|
||||
"HAS_NSEC3_OPTOUT" : "INFO",
|
||||
"INVALID_NAME_RCODE" : "NOTICE",
|
||||
"IPV4_DISABLED" : "DEBUG",
|
||||
"IPV6_DISABLED" : "DEBUG",
|
||||
"KEY_SIZE_OK" : "INFO",
|
||||
"NO_RESPONSE" : "DEBUG",
|
||||
"NO_RESPONSE_DNSKEY" : "ERROR",
|
||||
"REMAINING_LONG" : "WARNING",
|
||||
"REMAINING_SHORT" : "WARNING",
|
||||
"RRSIG_EXPIRATION" : "INFO",
|
||||
"RRSIG_EXPIRED" : "ERROR",
|
||||
"TEST_CASE_END" : "DEBUG",
|
||||
"TEST_CASE_START" : "DEBUG"
|
||||
},
|
||||
"NAMESERVER" : {
|
||||
"AAAA_BAD_RDATA" : "ERROR",
|
||||
"AAAA_QUERY_DROPPED" : "ERROR",
|
||||
"AAAA_UNEXPECTED_RCODE" : "ERROR",
|
||||
"AAAA_WELL_PROCESSED" : "INFO",
|
||||
"AXFR_AVAILABLE" : "NOTICE",
|
||||
"AXFR_FAILURE" : "INFO",
|
||||
"A_UNEXPECTED_RCODE" : "WARNING",
|
||||
"BREAKS_ON_EDNS" : "ERROR",
|
||||
"CAN_BE_RESOLVED" : "INFO",
|
||||
"CAN_NOT_BE_RESOLVED" : "ERROR",
|
||||
"CASE_QUERIES_RESULTS_DIFFER" : "ERROR",
|
||||
"CASE_QUERIES_RESULTS_OK" : "INFO",
|
||||
"CASE_QUERY_DIFFERENT_ANSWER" : "WARNING",
|
||||
"CASE_QUERY_DIFFERENT_RC" : "WARNING",
|
||||
"CASE_QUERY_NO_ANSWER" : "WARNING",
|
||||
"CASE_QUERY_SAME_ANSWER" : "DEBUG",
|
||||
"CASE_QUERY_SAME_RC" : "DEBUG",
|
||||
"DIFFERENT_SOURCE_IP" : "WARNING",
|
||||
"EDNS0_SUPPORT" : "INFO",
|
||||
"EDNS_RESPONSE_WITHOUT_EDNS" : "ERROR",
|
||||
"EDNS_VERSION_ERROR" : "ERROR",
|
||||
"IPV4_DISABLED" : "DEBUG",
|
||||
"IPV6_DISABLED" : "DEBUG",
|
||||
"IS_A_RECURSOR" : "ERROR",
|
||||
"MISSING_OPT_IN_TRUNCATED" : "WARNING",
|
||||
"NO_EDNS_SUPPORT" : "WARNING",
|
||||
"NO_RECURSOR" : "INFO",
|
||||
"NO_RESOLUTION" : "ERROR",
|
||||
"NO_RESPONSE" : "DEBUG",
|
||||
"NO_UPWARD_REFERRAL" : "INFO",
|
||||
"NS_ERROR" : "WARNING",
|
||||
"N10_NO_RESPONSE_EDNS1_QUERY" : "WARNING",
|
||||
"N10_UNEXPECTED_RCODE" : "WARNING",
|
||||
"N10_EDNS_RESPONSE_ERROR" : "WARNING",
|
||||
"N11_NO_EDNS" : "WARNING",
|
||||
"N11_NO_RESPONSE" : "WARNING",
|
||||
"N11_RETURNS_UNKNOWN_OPTION_CODE" : "WARNING",
|
||||
"N11_UNEXPECTED_ANSWER_SECTION" : "WARNING",
|
||||
"N11_UNEXPECTED_RCODE" : "WARNING",
|
||||
"N11_UNSET_AA" : "WARNING",
|
||||
"N15_ERROR_ON_VERSION_QUERY" : "NOTICE",
|
||||
"N15_NO_VERSION_REVEALED" : "INFO",
|
||||
"N15_SOFTWARE_VERSION" : "NOTICE",
|
||||
"N15_WRONG_CLASS" : "WARNING",
|
||||
"QNAME_CASE_INSENSITIVE" : "WARNING",
|
||||
"QNAME_CASE_SENSITIVE" : "INFO",
|
||||
"QUERY_DROPPED" : "NOTICE",
|
||||
"SAME_SOURCE_IP" : "INFO",
|
||||
"TEST_CASE_END" : "DEBUG",
|
||||
"TEST_CASE_START" : "DEBUG",
|
||||
"UPWARD_REFERRAL" : "WARNING",
|
||||
"UPWARD_REFERRAL_IRRELEVANT" : "INFO"
|
||||
},
|
||||
"SYNTAX" : {
|
||||
"DISCOURAGED_DOUBLE_DASH" : "WARNING",
|
||||
"INITIAL_HYPHEN" : "ERROR",
|
||||
"MNAME_DISCOURAGED_DOUBLE_DASH" : "WARNING",
|
||||
"MNAME_NON_ALLOWED_CHARS" : "WARNING",
|
||||
"MNAME_NUMERIC_TLD" : "WARNING",
|
||||
"MNAME_SYNTAX_OK" : "INFO",
|
||||
"MX_DISCOURAGED_DOUBLE_DASH" : "WARNING",
|
||||
"MX_NON_ALLOWED_CHARS" : "WARNING",
|
||||
"MX_NUMERIC_TLD" : "WARNING",
|
||||
"MX_SYNTAX_OK" : "INFO",
|
||||
"NAMESERVER_DISCOURAGED_DOUBLE_DASH" : "WARNING",
|
||||
"NAMESERVER_NON_ALLOWED_CHARS" : "ERROR",
|
||||
"NAMESERVER_NUMERIC_TLD" : "ERROR",
|
||||
"NAMESERVER_SYNTAX_OK" : "INFO",
|
||||
"NO_DOUBLE_DASH" : "INFO",
|
||||
"NO_ENDING_HYPHENS" : "INFO",
|
||||
"NON_ALLOWED_CHARS" : "ERROR",
|
||||
"NO_RESPONSE" : "DEBUG",
|
||||
"NO_RESPONSE_MX_QUERY" : "DEBUG",
|
||||
"NO_RESPONSE_SOA_QUERY" : "DEBUG",
|
||||
"ONLY_ALLOWED_CHARS" : "INFO",
|
||||
"RNAME_MAIL_DOMAIN_INVALID" : "NOTICE",
|
||||
"RNAME_MISUSED_AT_SIGN" : "WARNING",
|
||||
"RNAME_NO_AT_SIGN" : "INFO",
|
||||
"RNAME_RFC822_INVALID" : "WARNING",
|
||||
"RNAME_RFC822_VALID" : "INFO",
|
||||
"TERMINAL_HYPHEN" : "ERROR",
|
||||
"TEST_CASE_END" : "DEBUG",
|
||||
"TEST_CASE_START" : "DEBUG"
|
||||
},
|
||||
"SYSTEM" : {
|
||||
"ASN_LOOKUP_SOURCE": "DEBUG",
|
||||
"BLACKLISTING" : "DEBUG",
|
||||
"CACHE_CREATED" : "DEBUG2",
|
||||
"CACHE_FETCHED" : "DEBUG2",
|
||||
"CACHED_RETURN" : "DEBUG3",
|
||||
"CANNOT_CONTINUE" : "CRITICAL",
|
||||
"CNAME_CHAIN_TOO_LONG" : "ERROR",
|
||||
"CNAME_FOLLOWED_IN_ZONE" : "DEBUG",
|
||||
"CNAME_FOLLOWED_OUT_OF_ZONE" : "DEBUG",
|
||||
"CNAME_LOOP_INNER" : "ERROR",
|
||||
"CNAME_LOOP_OUTER" : "ERROR",
|
||||
"CNAME_NO_MATCH" : "ERROR",
|
||||
"CNAME_RECORDS_CHAIN_BROKEN" : "ERROR",
|
||||
"CNAME_RECORDS_DUPLICATES" : "DEBUG",
|
||||
"CNAME_RECORDS_MULTIPLE_FOR_NAME" : "ERROR",
|
||||
"CNAME_RECORDS_TOO_MANY" : "ERROR",
|
||||
"CNAME_START" : "DEBUG",
|
||||
"DEPENDENCY_VERSION" : "DEBUG",
|
||||
"EMPTY_RETURN" : "DEBUG3",
|
||||
"EXTERNAL_RESPONSE" : "DEBUG3",
|
||||
"FAKE_DELEGATION_ADDED" : "DEBUG2",
|
||||
"FAKE_DELEGATION_IN_ZONE_NO_IP" : "ERROR",
|
||||
"FAKE_DELEGATION_NO_IP" : "ERROR",
|
||||
"FAKE_DELEGATION_RETURNED" : "DEBUG2",
|
||||
"FAKE_DELEGATION_TO_SELF" : "DEBUG2",
|
||||
"FAKE_DS_ADDED" : "DEBUG2",
|
||||
"FAKE_DS_RETURNED" : "DEBUG2",
|
||||
"FAKE_PACKET_RETURNED" : "DEBUG3",
|
||||
"GLOBAL_VERSION" : "INFO",
|
||||
"IPV4_BLOCKED" : "DEBUG2",
|
||||
"IPV6_BLOCKED" : "DEBUG2",
|
||||
"IS_BLACKLISTED" : "DEBUG",
|
||||
"IS_REDIRECT" : "DEBUG2",
|
||||
"LOGGER_CALLBACK_ERROR" : "DEBUG",
|
||||
"LOOKUP_ERROR" : "DEBUG",
|
||||
"LOOP_PROTECTION" : "DEBUG2",
|
||||
"MODULE_ERROR" : "CRITICAL",
|
||||
"MODULE_VERSION" : "DEBUG",
|
||||
"MODULE_END" : "DEBUG",
|
||||
"NO_NETWORK": "CRITICAL",
|
||||
"NO_SUCH_NAME" : "DEBUG2",
|
||||
"NO_SUCH_RECORD" : "DEBUG2",
|
||||
"NS_CREATED" : "DEBUG2",
|
||||
"PACKET_BIG" : "DEBUG",
|
||||
"QUERY" : "DEBUG2",
|
||||
"RECURSE" : "DEBUG2",
|
||||
"RECURSE_QUERY" : "DEBUG2",
|
||||
"RESTORED_NS_CACHE" : "DEBUG2",
|
||||
"SAVED_NS_CACHE" : "DEBUG2",
|
||||
"SKIP_IPV4_DISABLED": "DEBUG",
|
||||
"SKIP_IPV6_DISABLED": "DEBUG",
|
||||
"START_TIME": "DEBUG",
|
||||
"TEST_TARGET": "DEBUG",
|
||||
"UNKNOWN_METHOD" : "CRITICAL",
|
||||
"UNKNOWN_MODULE" : "CRITICAL"
|
||||
},
|
||||
"ZONE" : {
|
||||
"EXPIRE_LOWER_THAN_REFRESH" : "WARNING",
|
||||
"EXPIRE_MINIMUM_VALUE_LOWER" : "WARNING",
|
||||
"EXPIRE_MINIMUM_VALUE_OK" : "INFO",
|
||||
"MNAME_HAS_NO_ADDRESS" : "WARNING",
|
||||
"MNAME_IS_CNAME" : "NOTICE",
|
||||
"MNAME_IS_NOT_CNAME" : "INFO",
|
||||
"MULTIPLE_SOA" : "ERROR",
|
||||
"MX_RECORD_IS_CNAME" : "ERROR",
|
||||
"MX_RECORD_IS_NOT_CNAME" : "INFO",
|
||||
"NO_RESPONSE" : "DEBUG",
|
||||
"NO_RESPONSE_MX_QUERY" : "DEBUG",
|
||||
"NO_RESPONSE_SOA_QUERY" : "DEBUG",
|
||||
"NO_SOA_IN_RESPONSE" : "DEBUG",
|
||||
"ONE_SOA" : "INFO",
|
||||
"REFRESH_HIGHER_THAN_RETRY" : "INFO",
|
||||
"REFRESH_LOWER_THAN_RETRY" : "INFO",
|
||||
"REFRESH_MINIMUM_VALUE_LOWER" : "NOTICE",
|
||||
"REFRESH_MINIMUM_VALUE_OK" : "INFO",
|
||||
"RETRY_MINIMUM_VALUE_LOWER" : "NOTICE",
|
||||
"RETRY_MINIMUM_VALUE_OK" : "INFO",
|
||||
"SOA_DEFAULT_TTL_MAXIMUM_VALUE_HIGHER" : "NOTICE",
|
||||
"SOA_DEFAULT_TTL_MAXIMUM_VALUE_LOWER" : "NOTICE",
|
||||
"SOA_DEFAULT_TTL_MAXIMUM_VALUE_OK" : "INFO",
|
||||
"TEST_CASE_END" : "DEBUG",
|
||||
"TEST_CASE_START" : "DEBUG",
|
||||
"WRONG_SOA" : "DEBUG",
|
||||
"Z01_MNAME_HAS_LOCALHOST_ADDR": "NOTICE",
|
||||
"Z01_MNAME_IS_DOT": "NOTICE",
|
||||
"Z01_MNAME_IS_LOCALHOST": "NOTICE",
|
||||
"Z01_MNAME_IS_MASTER": "DEBUG",
|
||||
"Z01_MNAME_MISSING_SOA_RECORD": "NOTICE",
|
||||
"Z01_MNAME_NO_RESPONSE": "NOTICE",
|
||||
"Z01_MNAME_NOT_AUTHORITATIVE": "NOTICE",
|
||||
"Z01_MNAME_NOT_IN_NS_LIST": "INFO",
|
||||
"Z01_MNAME_NOT_MASTER": "NOTICE",
|
||||
"Z01_MNAME_NOT_RESOLVE": "NOTICE",
|
||||
"Z01_MNAME_UNEXPECTED_RCODE": "NOTICE",
|
||||
"Z09_INCONSISTENT_MX" : "WARNING",
|
||||
"Z09_INCONSISTENT_MX_DATA" : "WARNING",
|
||||
"Z09_MISSING_MAIL_TARGET" : "NOTICE",
|
||||
"Z09_MX_DATA" : "INFO",
|
||||
"Z09_MX_FOUND" : "INFO",
|
||||
"Z09_NON_AUTH_MX_RESPONSE" : "WARNING",
|
||||
"Z09_NO_MX_FOUND" : "INFO",
|
||||
"Z09_NO_RESPONSE_MX_QUERY" : "WARNING",
|
||||
"Z09_NULL_MX_NON_ZERO_PREF" : "NOTICE",
|
||||
"Z09_NULL_MX_WITH_OTHER_MX" : "WARNING",
|
||||
"Z09_ROOT_EMAIL_DOMAIN" : "NOTICE",
|
||||
"Z09_TLD_EMAIL_DOMAIN" : "WARNING",
|
||||
"Z09_UNEXPECTED_RCODE_MX" : "WARNING",
|
||||
"Z11_DIFFERENT_SPF_POLICIES_FOUND" : "NOTICE",
|
||||
"Z11_INCONSISTENT_SPF_POLICIES" : "WARNING",
|
||||
"Z11_NO_SPF_FOUND" : "NOTICE",
|
||||
"Z11_NO_SPF_NON_MAIL_DOMAIN" : "INFO",
|
||||
"Z11_NON_NULL_SPF_NON_MAIL_DOMAIN" : "NOTICE",
|
||||
"Z11_NULL_SPF_NON_MAIL_DOMAIN" : "INFO",
|
||||
"Z11_SPF_MULTIPLE_RECORDS" : "WARNING",
|
||||
"Z11_SPF_SYNTAX_ERROR" : "WARNING",
|
||||
"Z11_SPF_SYNTAX_OK" : "INFO",
|
||||
"Z11_UNABLE_TO_CHECK_FOR_SPF" : "WARNING"
|
||||
}
|
||||
},
|
||||
"test_cases": [
|
||||
"address01",
|
||||
"address02",
|
||||
"address03",
|
||||
"basic01",
|
||||
"basic02",
|
||||
"basic03",
|
||||
"connectivity01",
|
||||
"connectivity02",
|
||||
"connectivity03",
|
||||
"connectivity04",
|
||||
"consistency01",
|
||||
"consistency02",
|
||||
"consistency03",
|
||||
"consistency04",
|
||||
"consistency05",
|
||||
"consistency06",
|
||||
"dnssec01",
|
||||
"dnssec02",
|
||||
"dnssec03",
|
||||
"dnssec04",
|
||||
"dnssec05",
|
||||
"dnssec06",
|
||||
"dnssec07",
|
||||
"dnssec08",
|
||||
"dnssec09",
|
||||
"dnssec10",
|
||||
"dnssec11",
|
||||
"dnssec13",
|
||||
"dnssec14",
|
||||
"dnssec15",
|
||||
"dnssec16",
|
||||
"dnssec17",
|
||||
"dnssec18",
|
||||
"delegation01",
|
||||
"delegation02",
|
||||
"delegation03",
|
||||
"delegation04",
|
||||
"delegation05",
|
||||
"delegation06",
|
||||
"delegation07",
|
||||
"nameserver01",
|
||||
"nameserver02",
|
||||
"nameserver03",
|
||||
"nameserver04",
|
||||
"nameserver05",
|
||||
"nameserver06",
|
||||
"nameserver07",
|
||||
"nameserver08",
|
||||
"nameserver09",
|
||||
"nameserver10",
|
||||
"nameserver11",
|
||||
"nameserver12",
|
||||
"nameserver13",
|
||||
"nameserver15",
|
||||
"syntax01",
|
||||
"syntax02",
|
||||
"syntax03",
|
||||
"syntax04",
|
||||
"syntax05",
|
||||
"syntax06",
|
||||
"syntax07",
|
||||
"syntax08",
|
||||
"zone01",
|
||||
"zone02",
|
||||
"zone03",
|
||||
"zone04",
|
||||
"zone05",
|
||||
"zone06",
|
||||
"zone07",
|
||||
"zone08",
|
||||
"zone09",
|
||||
"zone10",
|
||||
"zone11"
|
||||
]
|
||||
}
|
||||
645
zonemaster-engine/share/profile.yaml
Normal file
645
zonemaster-engine/share/profile.yaml
Normal file
@@ -0,0 +1,645 @@
|
||||
---
|
||||
asn_db:
|
||||
sources:
|
||||
cymru:
|
||||
- asnlookup.zonemaster.net
|
||||
ripe:
|
||||
- riswhois.ripe.net
|
||||
style: cymru
|
||||
net:
|
||||
ipv4: true
|
||||
ipv6: true
|
||||
no_network: false
|
||||
resolver:
|
||||
defaults:
|
||||
debug: false
|
||||
fallback: true
|
||||
igntc: false
|
||||
recurse: false
|
||||
retrans: 3
|
||||
retry: 2
|
||||
timeout: 5
|
||||
usevc: false
|
||||
test_cases:
|
||||
- address01
|
||||
- address02
|
||||
- address03
|
||||
- basic01
|
||||
- basic02
|
||||
- basic03
|
||||
- connectivity01
|
||||
- connectivity02
|
||||
- connectivity03
|
||||
- consistency01
|
||||
- consistency02
|
||||
- consistency03
|
||||
- consistency04
|
||||
- consistency05
|
||||
- consistency06
|
||||
- dnssec01
|
||||
- dnssec02
|
||||
- dnssec03
|
||||
- dnssec04
|
||||
- dnssec05
|
||||
- dnssec06
|
||||
- dnssec07
|
||||
- dnssec08
|
||||
- dnssec09
|
||||
- dnssec10
|
||||
- dnssec11
|
||||
- dnssec13
|
||||
- dnssec14
|
||||
- dnssec15
|
||||
- dnssec16
|
||||
- dnssec17
|
||||
- dnssec18
|
||||
- delegation01
|
||||
- delegation02
|
||||
- delegation03
|
||||
- delegation04
|
||||
- delegation05
|
||||
- delegation06
|
||||
- delegation07
|
||||
- nameserver01
|
||||
- nameserver02
|
||||
- nameserver03
|
||||
- nameserver04
|
||||
- nameserver05
|
||||
- nameserver06
|
||||
- nameserver07
|
||||
- nameserver08
|
||||
- nameserver09
|
||||
- nameserver10
|
||||
- nameserver11
|
||||
- nameserver12
|
||||
- nameserver13
|
||||
- nameserver15
|
||||
- syntax01
|
||||
- syntax02
|
||||
- syntax03
|
||||
- syntax04
|
||||
- syntax05
|
||||
- syntax06
|
||||
- syntax07
|
||||
- syntax08
|
||||
- zone01
|
||||
- zone02
|
||||
- zone03
|
||||
- zone04
|
||||
- zone05
|
||||
- zone06
|
||||
- zone07
|
||||
- zone08
|
||||
- zone09
|
||||
- zone10
|
||||
- zone11
|
||||
test_levels:
|
||||
ADDRESS:
|
||||
A01_GLOBALLY_REACHABLE_ADDR: INFO
|
||||
A01_ADDR_NOT_GLOBALLY_REACHABLE: ERROR
|
||||
A01_DOCUMENTATION_ADDR: ERROR
|
||||
A01_LOCAL_USE_ADDR: ERROR
|
||||
A01_NO_GLOBALLY_REACHABLE_ADDR: ERROR
|
||||
A01_NO_NAME_SERVERS_FOUND: CRITICAL
|
||||
NAMESERVERS_IP_WITH_REVERSE: INFO
|
||||
NAMESERVER_IP_PTR_MATCH: INFO
|
||||
NAMESERVER_IP_PTR_MISMATCH: NOTICE
|
||||
NAMESERVER_IP_WITHOUT_REVERSE: WARNING
|
||||
NO_RESPONSE_PTR_QUERY: WARNING
|
||||
TEST_CASE_END: DEBUG
|
||||
TEST_CASE_START: DEBUG
|
||||
BASIC:
|
||||
A_QUERY_NO_RESPONSES: INFO
|
||||
B01_CHILD_IS_ALIAS : NOTICE
|
||||
B01_CHILD_FOUND : INFO
|
||||
B01_INCONSISTENT_ALIAS : ERROR
|
||||
B01_INCONSISTENT_DELEGATION : ERROR
|
||||
B01_NO_CHILD : ERROR
|
||||
B01_PARENT_DISREGARDED : INFO
|
||||
B01_PARENT_FOUND : INFO
|
||||
B01_PARENT_NOT_FOUND : WARNING
|
||||
B01_PARENT_UNDETERMINED : WARNING
|
||||
B01_ROOT_HAS_NO_PARENT : INFO
|
||||
B01_SERVER_ZONE_ERROR : DEBUG
|
||||
B02_AUTH_RESPONSE_SOA : INFO
|
||||
B02_NO_DELEGATION : CRITICAL
|
||||
B02_NO_WORKING_NS : CRITICAL
|
||||
B02_NS_BROKEN : ERROR
|
||||
B02_NS_NOT_AUTH : ERROR
|
||||
B02_NS_NO_IP_ADDR : ERROR
|
||||
B02_NS_NO_RESPONSE : WARNING
|
||||
B02_UNEXPECTED_RCODE : ERROR
|
||||
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
|
||||
IPV4_DISABLED: DEBUG
|
||||
IPV4_ENABLED: DEBUG
|
||||
IPV6_DISABLED: DEBUG
|
||||
IPV6_ENABLED: DEBUG
|
||||
TEST_CASE_END: DEBUG
|
||||
TEST_CASE_START: DEBUG
|
||||
CONNECTIVITY:
|
||||
ASN_INFOS_ANNOUNCE_BY: DEBUG
|
||||
ASN_INFOS_ANNOUNCE_IN: DEBUG
|
||||
ASN_INFOS_RAW: DEBUG
|
||||
CN01_IPV4_DISABLED: NOTICE
|
||||
CN01_IPV6_DISABLED: NOTICE
|
||||
CN01_MISSING_NS_RECORD_UDP: WARNING
|
||||
CN01_MISSING_SOA_RECORD_UDP: WARNING
|
||||
CN01_NO_RESPONSE_NS_QUERY_UDP: WARNING
|
||||
CN01_NO_RESPONSE_SOA_QUERY_UDP: WARNING
|
||||
CN01_NO_RESPONSE_UDP: WARNING
|
||||
CN01_NS_RECORD_NOT_AA_UDP: WARNING
|
||||
CN01_SOA_RECORD_NOT_AA_UDP: WARNING
|
||||
CN01_UNEXPECTED_RCODE_NS_QUERY_UDP: WARNING
|
||||
CN01_UNEXPECTED_RCODE_SOA_QUERY_UDP: WARNING
|
||||
CN01_WRONG_NS_RECORD_UDP: WARNING
|
||||
CN01_WRONG_SOA_RECORD_UDP: WARNING
|
||||
CN02_MISSING_NS_RECORD_TCP: WARNING
|
||||
CN02_MISSING_SOA_RECORD_TCP: WARNING
|
||||
CN02_NO_RESPONSE_NS_QUERY_TCP: WARNING
|
||||
CN02_NO_RESPONSE_SOA_QUERY_TCP: WARNING
|
||||
CN02_NO_RESPONSE_TCP: WARNING
|
||||
CN02_NS_RECORD_NOT_AA_TCP: WARNING
|
||||
CN02_SOA_RECORD_NOT_AA_TCP: WARNING
|
||||
CN02_UNEXPECTED_RCODE_NS_QUERY_TCP: WARNING
|
||||
CN02_UNEXPECTED_RCODE_SOA_QUERY_TCP: WARNING
|
||||
CN02_WRONG_NS_RECORD_TCP: WARNING
|
||||
CN02_WRONG_SOA_RECORD_TCP: WARNING
|
||||
CN04_ASN_INFOS_ANNOUNCE_IN: DEBUG
|
||||
CN04_ASN_INFOS_RAW: DEBUG
|
||||
CN04_EMPTY_PREFIX_SET: NOTICE
|
||||
CN04_ERROR_PREFIX_DATABASE: NOTICE
|
||||
CN04_IPV4_DIFFERENT_PREFIX: INFO
|
||||
CN04_IPV4_SAME_PREFIX: NOTICE
|
||||
CN04_IPV4_SINGLE_PREFIX: WARNING
|
||||
CN04_IPV6_DIFFERENT_PREFIX: INFO
|
||||
CN04_IPV6_SAME_PREFIX: NOTICE
|
||||
CN04_IPV6_SINGLE_PREFIX: WARNING
|
||||
EMPTY_ASN_SET: NOTICE
|
||||
ERROR_ASN_DATABASE: NOTICE
|
||||
IPV4_DIFFERENT_ASN: INFO
|
||||
IPV4_DISABLED: DEBUG
|
||||
IPV4_ONE_ASN: WARNING
|
||||
IPV4_SAME_ASN: NOTICE
|
||||
IPV6_DIFFERENT_ASN: INFO
|
||||
IPV6_DISABLED: DEBUG
|
||||
IPV6_ONE_ASN: WARNING
|
||||
IPV6_SAME_ASN: NOTICE
|
||||
NAMESERVER_HAS_TCP_53: DEBUG
|
||||
NAMESERVER_HAS_UDP_53: DEBUG
|
||||
NAMESERVER_NO_TCP_53: ERROR
|
||||
NAMESERVER_NO_UDP_53: ERROR
|
||||
TEST_CASE_END: DEBUG
|
||||
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
|
||||
IPV4_DISABLED: DEBUG
|
||||
IPV6_DISABLED: DEBUG
|
||||
MULTIPLE_NS_SET: NOTICE
|
||||
MULTIPLE_SOA_MNAMES: NOTICE
|
||||
MULTIPLE_SOA_RNAMES: NOTICE
|
||||
MULTIPLE_SOA_SERIALS: WARNING
|
||||
MULTIPLE_SOA_TIME_PARAMETER_SET: NOTICE
|
||||
NO_RESPONSE: DEBUG
|
||||
NO_RESPONSE_NS_QUERY: DEBUG
|
||||
NO_RESPONSE_SOA_QUERY: DEBUG
|
||||
NS_SET: INFO
|
||||
ONE_NS_SET: INFO
|
||||
ONE_SOA_MNAME: INFO
|
||||
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
|
||||
SOA_TIME_PARAMETER_SET: INFO
|
||||
TEST_CASE_END: DEBUG
|
||||
TEST_CASE_START: DEBUG
|
||||
DELEGATION:
|
||||
ARE_AUTHORITATIVE: INFO
|
||||
CHILD_DISTINCT_NS_IP: INFO
|
||||
CHILD_NS_SAME_IP: ERROR
|
||||
DEL_DISTINCT_NS_IP: INFO
|
||||
DEL_NS_SAME_IP: ERROR
|
||||
DISTINCT_IP_ADDRESS: INFO
|
||||
ENOUGH_IPV4_NS_CHILD: INFO
|
||||
ENOUGH_IPV4_NS_DEL: INFO
|
||||
ENOUGH_IPV6_NS_CHILD: INFO
|
||||
ENOUGH_IPV6_NS_DEL: INFO
|
||||
ENOUGH_NS_CHILD: INFO
|
||||
ENOUGH_NS_DEL: INFO
|
||||
EXTRA_NAME_CHILD: NOTICE
|
||||
EXTRA_NAME_PARENT: ERROR
|
||||
IPV4_DISABLED: DEBUG
|
||||
IPV6_DISABLED: DEBUG
|
||||
IS_NOT_AUTHORITATIVE: WARNING
|
||||
NAMES_MATCH: INFO
|
||||
NOT_ENOUGH_IPV4_NS_CHILD: ERROR
|
||||
NOT_ENOUGH_IPV4_NS_DEL: ERROR
|
||||
NOT_ENOUGH_IPV6_NS_CHILD: ERROR
|
||||
NOT_ENOUGH_IPV6_NS_DEL: ERROR
|
||||
NOT_ENOUGH_NS_CHILD: ERROR
|
||||
NOT_ENOUGH_NS_DEL: ERROR
|
||||
NO_IPV4_NS_CHILD: WARNING
|
||||
NO_IPV4_NS_DEL: WARNING
|
||||
NO_IPV6_NS_CHILD: NOTICE
|
||||
NO_IPV6_NS_DEL: NOTICE
|
||||
NO_NS_CNAME: INFO
|
||||
NO_RESPONSE: DEBUG
|
||||
NS_IS_CNAME: ERROR
|
||||
REFERRAL_SIZE_OK: INFO
|
||||
REFERRAL_SIZE_TOO_LARGE: WARNING
|
||||
SAME_IP_ADDRESS: ERROR
|
||||
SOA_EXISTS: INFO
|
||||
SOA_NOT_EXISTS: ERROR
|
||||
TEST_CASE_END: DEBUG
|
||||
TEST_CASE_START: DEBUG
|
||||
TOTAL_NAME_MISMATCH: ERROR
|
||||
UNEXPECTED_RCODE: WARNING
|
||||
DNSSEC:
|
||||
DNSKEY_SMALLER_THAN_REC: WARNING
|
||||
DNSKEY_TOO_LARGE_FOR_ALGO: ERROR
|
||||
DNSKEY_TOO_SMALL_FOR_ALGO: ERROR
|
||||
DS01_DS_ALGO_2_MISSING: NOTICE
|
||||
DS01_DS_ALGO_DEPRECATED: ERROR
|
||||
DS01_DS_ALGO_NOT_DS: ERROR
|
||||
DS01_DS_ALGO_OK: INFO
|
||||
DS01_DS_ALGO_PRIVATE: ERROR
|
||||
DS01_DS_ALGO_RESERVED: ERROR
|
||||
DS01_DS_ALGO_UNASSIGNED: ERROR
|
||||
DS01_NO_RESPONSE: WARNING
|
||||
DS01_PARENT_SERVER_NO_DS: ERROR
|
||||
DS01_PARENT_ZONE_NO_DS: NOTICE
|
||||
DS01_ROOT_N_NO_UNDEL_DS: INFO
|
||||
DS01_UNDEL_N_NO_UNDEL_DS: INFO
|
||||
DS02_ALGO_NOT_SUPPORTED_BY_ZM: NOTICE
|
||||
DS02_DNSKEY_NOT_FOR_ZONE_SIGNING: ERROR
|
||||
DS02_DNSKEY_NOT_SEP: NOTICE
|
||||
DS02_DNSKEY_NOT_SIGNED_BY_ANY_DS: ERROR
|
||||
DS02_NO_DNSKEY_FOR_DS: WARNING
|
||||
DS02_NO_MATCHING_DNSKEY_RRSIG: WARNING
|
||||
DS02_NO_MATCH_DS_DNSKEY: ERROR
|
||||
DS02_NO_VALID_DNSKEY_FOR_ANY_DS: ERROR
|
||||
DS02_RRSIG_NOT_VALID_BY_DNSKEY: ERROR
|
||||
DS03_ERROR_RESPONSE_NSEC_QUERY: ERROR
|
||||
DS03_ERR_MULT_NSEC3 : ERROR
|
||||
DS03_ILLEGAL_HASH_ALGO : ERROR
|
||||
DS03_ILLEGAL_ITERATION_VALUE : WARNING
|
||||
DS03_ILLEGAL_SALT_LENGTH : WARNING
|
||||
DS03_INCONSISTENT_HASH_ALGO : ERROR
|
||||
DS03_INCONSISTENT_ITERATION : ERROR
|
||||
DS03_INCONSISTENT_NSEC3_FLAGS : ERROR
|
||||
DS03_INCONSISTENT_SALT_LENGTH : ERROR
|
||||
DS03_LEGAL_EMPTY_SALT : INFO
|
||||
DS03_LEGAL_HASH_ALGO : INFO
|
||||
DS03_LEGAL_ITERATION_VALUE : INFO
|
||||
DS03_NO_DNSSEC_SUPPORT : NOTICE
|
||||
DS03_NO_NSEC3 : INFO
|
||||
DS03_NO_RESPONSE_NSEC_QUERY : ERROR
|
||||
DS03_NSEC3_OPT_OUT_DISABLED : INFO
|
||||
DS03_NSEC3_OPT_OUT_ENABLED_NON_TLD : NOTICE
|
||||
DS03_NSEC3_OPT_OUT_ENABLED_TLD : INFO
|
||||
DS03_SERVER_NO_DNSSEC_SUPPORT : ERROR
|
||||
DS03_SERVER_NO_NSEC3 : ERROR
|
||||
DS03_UNASSIGNED_FLAG_USED : ERROR
|
||||
DS05_ALGO_DEPRECATED: ERROR
|
||||
DS05_ALGO_NOT_RECOMMENDED: WARNING
|
||||
DS05_ALGO_NOT_ZONE_SIGN: ERROR
|
||||
DS05_ALGO_OK: INFO
|
||||
DS05_ALGO_PRIVATE: ERROR
|
||||
DS05_ALGO_RESERVED: ERROR
|
||||
DS05_ALGO_UNASSIGNED: ERROR
|
||||
DS05_NO_RESPONSE: WARNING
|
||||
DS05_SERVER_NO_DNSSEC: ERROR
|
||||
DS05_ZONE_NO_DNSSEC: NOTICE
|
||||
DS07_DS_FOR_SIGNED_ZONE: INFO
|
||||
DS07_DS_ON_PARENT_SERVER: INFO
|
||||
DS07_INCONSISTENT_DS: ERROR
|
||||
DS07_INCONSISTENT_SIGNED: ERROR
|
||||
DS07_NON_AUTH_RESPONSE_DNSKEY: WARNING
|
||||
DS07_NOT_SIGNED: WARNING
|
||||
DS07_NOT_SIGNED_ON_SERVER: WARNING
|
||||
DS07_NO_DS_ON_PARENT_SERVER: WARNING
|
||||
DS07_NO_DS_FOR_SIGNED_ZONE: WARNING
|
||||
DS07_NO_RESPONSE_DNSKEY: WARNING
|
||||
DS07_SIGNED: INFO
|
||||
DS07_SIGNED_ON_SERVER: INFO
|
||||
DS07_UNEXP_RCODE_RESP_DNSKEY: WARNING
|
||||
DS08_ALGO_NOT_SUPPORTED_BY_ZM: NOTICE
|
||||
DS08_DNSKEY_RRSIG_EXPIRED: ERROR
|
||||
DS08_DNSKEY_RRSIG_NOT_YET_VALID: ERROR
|
||||
DS08_MISSING_RRSIG_IN_RESPONSE: ERROR
|
||||
DS08_NO_MATCHING_DNSKEY: ERROR
|
||||
DS08_RRSIG_NOT_VALID_BY_DNSKEY: ERROR
|
||||
DS09_ALGO_NOT_SUPPORTED_BY_ZM: NOTICE
|
||||
DS09_MISSING_RRSIG_IN_RESPONSE: ERROR
|
||||
DS09_NO_MATCHING_DNSKEY: ERROR
|
||||
DS09_RRSIG_NOT_VALID_BY_DNSKEY: ERROR
|
||||
DS09_SOA_RRSIG_EXPIRED: ERROR
|
||||
DS09_SOA_RRSIG_NOT_YET_VALID: ERROR
|
||||
DS10_ALGO_NOT_SUPPORTED_BY_ZM: NOTICE
|
||||
DS10_ERR_MULT_NSEC: ERROR
|
||||
DS10_ERR_MULT_NSEC3: ERROR
|
||||
DS10_ERR_MULT_NSEC3PARAM: ERROR
|
||||
DS10_EXPECTED_NSEC_NSEC3_MISSING: ERROR
|
||||
DS10_HAS_NSEC: INFO
|
||||
DS10_HAS_NSEC3: INFO
|
||||
DS10_INCONSISTENT_NSEC: ERROR
|
||||
DS10_INCONSISTENT_NSEC3: ERROR
|
||||
DS10_INCONSISTENT_NSEC_NSEC3: ERROR
|
||||
DS10_MIXED_NSEC_NSEC3: ERROR
|
||||
DS10_NSEC3PARAM_GIVES_ERR_ANSWER: ERROR
|
||||
DS10_NSEC3PARAM_MISMATCHES_APEX: ERROR
|
||||
DS10_NSEC3PARAM_QUERY_RESPONSE_ERR: ERROR
|
||||
DS10_NSEC3_ERR_TYPE_LIST: ERROR
|
||||
DS10_NSEC3_MISMATCHES_APEX: ERROR
|
||||
DS10_NSEC3_MISSING_SIGNATURE: ERROR
|
||||
DS10_NSEC3_NODATA_MISSING_SOA: ERROR
|
||||
DS10_NSEC3_NODATA_WRONG_SOA: ERROR
|
||||
DS10_NSEC3_NO_VERIFIED_SIGNATURE: ERROR
|
||||
DS10_NSEC3_RRSIG_EXPIRED: ERROR
|
||||
DS10_NSEC3_RRSIG_NOT_YET_VALID: ERROR
|
||||
DS10_NSEC3_RRSIG_NO_DNSKEY: WARNING
|
||||
DS10_NSEC3_RRSIG_VERIFY_ERROR: ERROR
|
||||
DS10_NSEC_ERR_TYPE_LIST: ERROR
|
||||
DS10_NSEC_GIVES_ERR_ANSWER: ERROR
|
||||
DS10_NSEC_MISMATCHES_APEX: ERROR
|
||||
DS10_NSEC_MISSING_SIGNATURE: ERROR
|
||||
DS10_NSEC_NODATA_MISSING_SOA: ERROR
|
||||
DS10_NSEC_NODATA_WRONG_SOA: ERROR
|
||||
DS10_NSEC_NO_VERIFIED_SIGNATURE: ERROR
|
||||
DS10_NSEC_QUERY_RESPONSE_ERR: ERROR
|
||||
DS10_NSEC_RRSIG_EXPIRED: ERROR
|
||||
DS10_NSEC_RRSIG_NOT_YET_VALID: ERROR
|
||||
DS10_NSEC_RRSIG_NO_DNSKEY: WARNING
|
||||
DS10_NSEC_RRSIG_VERIFY_ERROR: ERROR
|
||||
DS10_SERVER_NO_DNSSEC: ERROR
|
||||
DS10_ZONE_NO_DNSSEC: NOTICE
|
||||
DS11_DS_BUT_UNSIGNED_ZONE: ERROR
|
||||
DS11_INCONSISTENT_DS: WARNING
|
||||
DS11_INCONSISTENT_SIGNED_ZONE: ERROR
|
||||
DS11_NS_WITH_SIGNED_ZONE: NOTICE
|
||||
DS11_NS_WITH_UNSIGNED_ZONE: WARNING
|
||||
DS11_PARENT_WITHOUT_DS: NOTICE
|
||||
DS11_PARENT_WITH_DS: NOTICE
|
||||
DS11_UNDETERMINED_DS: ERROR
|
||||
DS11_UNDETERMINED_SIGNED_ZONE: ERROR
|
||||
DS13_ALGO_NOT_SIGNED_DNSKEY: WARNING
|
||||
DS13_ALGO_NOT_SIGNED_NS: WARNING
|
||||
DS13_ALGO_NOT_SIGNED_SOA: WARNING
|
||||
DS15_HAS_CDNSKEY_NO_CDS: NOTICE
|
||||
DS15_HAS_CDS_AND_CDNSKEY: INFO
|
||||
DS15_HAS_CDS_NO_CDNSKEY: NOTICE
|
||||
DS15_INCONSISTENT_CDNSKEY: ERROR
|
||||
DS15_INCONSISTENT_CDS: ERROR
|
||||
DS15_MISMATCH_CDS_CDNSKEY: ERROR
|
||||
DS15_NO_CDS_CDNSKEY: INFO
|
||||
DS16_CDS_INVALID_RRSIG: ERROR
|
||||
DS16_CDS_MATCHES_NON_SEP_DNSKEY: NOTICE
|
||||
DS16_CDS_MATCHES_NON_ZONE_DNSKEY: ERROR
|
||||
DS16_CDS_MATCHES_NO_DNSKEY: WARNING
|
||||
DS16_CDS_NOT_SIGNED_BY_CDS: NOTICE
|
||||
DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY: ERROR
|
||||
DS16_CDS_UNSIGNED: ERROR
|
||||
DS16_CDS_WITHOUT_DNSKEY: ERROR
|
||||
DS16_DELETE_CDS: INFO
|
||||
DS16_DNSKEY_NOT_SIGNED_BY_CDS: WARNING
|
||||
DS16_MIXED_DELETE_CDS: ERROR
|
||||
DS17_CDNSKEY_INVALID_RRSIG: ERROR
|
||||
DS17_CDNSKEY_IS_NON_SEP: NOTICE
|
||||
DS17_CDNSKEY_IS_NON_ZONE: ERROR
|
||||
DS17_CDNSKEY_MATCHES_NO_DNSKEY: WARNING
|
||||
DS17_CDNSKEY_NOT_SIGNED_BY_CDNSKEY: NOTICE
|
||||
DS17_CDNSKEY_SIGNED_BY_UNKNOWN_DNSKEY: ERROR
|
||||
DS17_CDNSKEY_UNSIGNED: ERROR
|
||||
DS17_CDNSKEY_WITHOUT_DNSKEY: ERROR
|
||||
DS17_DELETE_CDNSKEY: INFO
|
||||
DS17_DNSKEY_NOT_SIGNED_BY_CDNSKEY: WARNING
|
||||
DS17_MIXED_DELETE_CDNSKEY: ERROR
|
||||
DS18_NO_MATCH_CDNSKEY_RRSIG_DS: ERROR
|
||||
DS18_NO_MATCH_CDS_RRSIG_DS: ERROR
|
||||
DURATION_LONG: WARNING
|
||||
DURATION_OK: DEBUG
|
||||
EXTRA_PROCESSING_BROKEN: ERROR
|
||||
EXTRA_PROCESSING_OK: DEBUG
|
||||
HAS_NSEC3_OPTOUT: INFO
|
||||
INVALID_NAME_RCODE: NOTICE
|
||||
IPV4_DISABLED: DEBUG
|
||||
IPV6_DISABLED: DEBUG
|
||||
KEY_SIZE_OK: INFO
|
||||
NO_RESPONSE: DEBUG
|
||||
NO_RESPONSE_DNSKEY: ERROR
|
||||
REMAINING_LONG: WARNING
|
||||
REMAINING_SHORT: WARNING
|
||||
RRSIG_EXPIRATION: INFO
|
||||
RRSIG_EXPIRED: ERROR
|
||||
TEST_CASE_END: DEBUG
|
||||
TEST_CASE_START: DEBUG
|
||||
NAMESERVER:
|
||||
AAAA_BAD_RDATA: ERROR
|
||||
AAAA_QUERY_DROPPED: ERROR
|
||||
AAAA_UNEXPECTED_RCODE: ERROR
|
||||
AAAA_WELL_PROCESSED: INFO
|
||||
AXFR_AVAILABLE: NOTICE
|
||||
AXFR_FAILURE: INFO
|
||||
A_UNEXPECTED_RCODE: WARNING
|
||||
BREAKS_ON_EDNS: ERROR
|
||||
CAN_BE_RESOLVED: INFO
|
||||
CAN_NOT_BE_RESOLVED: ERROR
|
||||
CASE_QUERIES_RESULTS_DIFFER: ERROR
|
||||
CASE_QUERIES_RESULTS_OK: INFO
|
||||
CASE_QUERY_DIFFERENT_ANSWER: WARNING
|
||||
CASE_QUERY_DIFFERENT_RC: WARNING
|
||||
CASE_QUERY_NO_ANSWER: WARNING
|
||||
CASE_QUERY_SAME_ANSWER: DEBUG
|
||||
CASE_QUERY_SAME_RC: DEBUG
|
||||
DIFFERENT_SOURCE_IP: WARNING
|
||||
EDNS0_SUPPORT: INFO
|
||||
EDNS_RESPONSE_WITHOUT_EDNS: ERROR
|
||||
EDNS_VERSION_ERROR: ERROR
|
||||
IPV4_DISABLED: DEBUG
|
||||
IPV6_DISABLED: DEBUG
|
||||
IS_A_RECURSOR: ERROR
|
||||
MISSING_OPT_IN_TRUNCATED: WARNING
|
||||
N10_EDNS_RESPONSE_ERROR: WARNING
|
||||
N10_NO_RESPONSE_EDNS1_QUERY: WARNING
|
||||
N10_UNEXPECTED_RCODE: WARNING
|
||||
N11_NO_EDNS: WARNING
|
||||
N11_NO_RESPONSE: WARNING
|
||||
N11_RETURNS_UNKNOWN_OPTION_CODE: WARNING
|
||||
N11_UNEXPECTED_ANSWER_SECTION: WARNING
|
||||
N11_UNEXPECTED_RCODE: WARNING
|
||||
N11_UNSET_AA: WARNING
|
||||
N15_ERROR_ON_VERSION_QUERY: NOTICE
|
||||
N15_NO_VERSION_REVEALED: INFO
|
||||
N15_SOFTWARE_VERSION: NOTICE
|
||||
N15_WRONG_CLASS: WARNING
|
||||
NO_EDNS_SUPPORT: WARNING
|
||||
NO_RECURSOR: INFO
|
||||
NO_RESOLUTION: ERROR
|
||||
NO_RESPONSE: DEBUG
|
||||
NO_UPWARD_REFERRAL: INFO
|
||||
NS_ERROR: WARNING
|
||||
QNAME_CASE_INSENSITIVE: WARNING
|
||||
QNAME_CASE_SENSITIVE: INFO
|
||||
QUERY_DROPPED: NOTICE
|
||||
SAME_SOURCE_IP: INFO
|
||||
TEST_CASE_END: DEBUG
|
||||
TEST_CASE_START: DEBUG
|
||||
UPWARD_REFERRAL: WARNING
|
||||
UPWARD_REFERRAL_IRRELEVANT: INFO
|
||||
SYNTAX:
|
||||
DISCOURAGED_DOUBLE_DASH: WARNING
|
||||
INITIAL_HYPHEN: ERROR
|
||||
MNAME_DISCOURAGED_DOUBLE_DASH: WARNING
|
||||
MNAME_NON_ALLOWED_CHARS: WARNING
|
||||
MNAME_NUMERIC_TLD: WARNING
|
||||
MNAME_SYNTAX_OK: INFO
|
||||
MX_DISCOURAGED_DOUBLE_DASH: WARNING
|
||||
MX_NON_ALLOWED_CHARS: WARNING
|
||||
MX_NUMERIC_TLD: WARNING
|
||||
MX_SYNTAX_OK: INFO
|
||||
NAMESERVER_DISCOURAGED_DOUBLE_DASH: WARNING
|
||||
NAMESERVER_NON_ALLOWED_CHARS: ERROR
|
||||
NAMESERVER_NUMERIC_TLD: ERROR
|
||||
NAMESERVER_SYNTAX_OK: INFO
|
||||
NON_ALLOWED_CHARS: ERROR
|
||||
NO_DOUBLE_DASH: INFO
|
||||
NO_ENDING_HYPHENS: INFO
|
||||
NO_RESPONSE: DEBUG
|
||||
NO_RESPONSE_MX_QUERY: DEBUG
|
||||
NO_RESPONSE_SOA_QUERY: DEBUG
|
||||
ONLY_ALLOWED_CHARS: INFO
|
||||
RNAME_MAIL_DOMAIN_INVALID: NOTICE
|
||||
RNAME_MISUSED_AT_SIGN: WARNING
|
||||
RNAME_NO_AT_SIGN: INFO
|
||||
RNAME_RFC822_INVALID: WARNING
|
||||
RNAME_RFC822_VALID: INFO
|
||||
TERMINAL_HYPHEN: ERROR
|
||||
TEST_CASE_END: DEBUG
|
||||
TEST_CASE_START: DEBUG
|
||||
SYSTEM:
|
||||
ASN_LOOKUP_SOURCE: DEBUG
|
||||
BLACKLISTING: DEBUG
|
||||
CACHED_RETURN: DEBUG3
|
||||
CACHE_CREATED: DEBUG2
|
||||
CACHE_FETCHED: DEBUG2
|
||||
CANNOT_CONTINUE: CRITICAL
|
||||
CNAME_CHAIN_TOO_LONG: ERROR
|
||||
CNAME_FOLLOWED_IN_ZONE: DEBUG
|
||||
CNAME_FOLLOWED_OUT_OF_ZONE: DEBUG
|
||||
CNAME_LOOP_INNER: ERROR
|
||||
CNAME_LOOP_OUTER: ERROR
|
||||
CNAME_NO_MATCH: ERROR
|
||||
CNAME_RECORDS_CHAIN_BROKEN: ERROR
|
||||
CNAME_RECORDS_DUPLICATES: DEBUG
|
||||
CNAME_RECORDS_MULTIPLE_FOR_NAME: ERROR
|
||||
CNAME_RECORDS_TOO_MANY: ERROR
|
||||
CNAME_START: DEBUG
|
||||
DEPENDENCY_VERSION: DEBUG
|
||||
EMPTY_RETURN: DEBUG3
|
||||
EXTERNAL_RESPONSE: DEBUG3
|
||||
FAKE_DELEGATION_ADDED: DEBUG2
|
||||
FAKE_DELEGATION_IN_ZONE_NO_IP: ERROR
|
||||
FAKE_DELEGATION_NO_IP: ERROR
|
||||
FAKE_DELEGATION_RETURNED: DEBUG2
|
||||
FAKE_DELEGATION_TO_SELF: DEBUG2
|
||||
FAKE_DS_ADDED: DEBUG2
|
||||
FAKE_DS_RETURNED: DEBUG2
|
||||
FAKE_PACKET_RETURNED: DEBUG3
|
||||
GLOBAL_VERSION: INFO
|
||||
IPV4_BLOCKED: DEBUG2
|
||||
IPV6_BLOCKED: DEBUG2
|
||||
IS_BLACKLISTED: DEBUG
|
||||
IS_REDIRECT: DEBUG2
|
||||
LOGGER_CALLBACK_ERROR: DEBUG
|
||||
LOOKUP_ERROR: DEBUG
|
||||
LOOP_PROTECTION: DEBUG2
|
||||
MODULE_END: DEBUG
|
||||
MODULE_ERROR: CRITICAL
|
||||
MODULE_VERSION: DEBUG
|
||||
NO_NETWORK: CRITICAL
|
||||
NO_SUCH_NAME: DEBUG2
|
||||
NO_SUCH_RECORD: DEBUG2
|
||||
NS_CREATED: DEBUG2
|
||||
PACKET_BIG: DEBUG
|
||||
QUERY: DEBUG2
|
||||
RECURSE: DEBUG2
|
||||
RECURSE_QUERY: DEBUG2
|
||||
RESTORED_NS_CACHE: DEBUG2
|
||||
SAVED_NS_CACHE: DEBUG2
|
||||
SKIP_IPV4_DISABLED: DEBUG
|
||||
SKIP_IPV6_DISABLED: DEBUG
|
||||
START_TIME: DEBUG
|
||||
TEST_TARGET: DEBUG
|
||||
UNKNOWN_METHOD: CRITICAL
|
||||
UNKNOWN_MODULE: CRITICAL
|
||||
ZONE:
|
||||
EXPIRE_LOWER_THAN_REFRESH: WARNING
|
||||
EXPIRE_MINIMUM_VALUE_LOWER: WARNING
|
||||
EXPIRE_MINIMUM_VALUE_OK: INFO
|
||||
MNAME_HAS_NO_ADDRESS: WARNING
|
||||
MNAME_IS_CNAME: NOTICE
|
||||
MNAME_IS_NOT_CNAME: INFO
|
||||
MULTIPLE_SOA: ERROR
|
||||
MX_RECORD_IS_CNAME: ERROR
|
||||
MX_RECORD_IS_NOT_CNAME: INFO
|
||||
NO_RESPONSE: DEBUG
|
||||
NO_RESPONSE_MX_QUERY: DEBUG
|
||||
NO_RESPONSE_SOA_QUERY: DEBUG
|
||||
NO_SOA_IN_RESPONSE: DEBUG
|
||||
ONE_SOA: INFO
|
||||
REFRESH_HIGHER_THAN_RETRY: INFO
|
||||
REFRESH_LOWER_THAN_RETRY: INFO
|
||||
REFRESH_MINIMUM_VALUE_LOWER: NOTICE
|
||||
REFRESH_MINIMUM_VALUE_OK: INFO
|
||||
RETRY_MINIMUM_VALUE_LOWER: NOTICE
|
||||
RETRY_MINIMUM_VALUE_OK: INFO
|
||||
SOA_DEFAULT_TTL_MAXIMUM_VALUE_HIGHER: NOTICE
|
||||
SOA_DEFAULT_TTL_MAXIMUM_VALUE_LOWER: NOTICE
|
||||
SOA_DEFAULT_TTL_MAXIMUM_VALUE_OK: INFO
|
||||
TEST_CASE_END: DEBUG
|
||||
TEST_CASE_START: DEBUG
|
||||
WRONG_SOA: DEBUG
|
||||
Z01_MNAME_HAS_LOCALHOST_ADDR: NOTICE
|
||||
Z01_MNAME_IS_DOT: NOTICE
|
||||
Z01_MNAME_IS_LOCALHOST: NOTICE
|
||||
Z01_MNAME_IS_MASTER: DEBUG
|
||||
Z01_MNAME_MISSING_SOA_RECORD: NOTICE
|
||||
Z01_MNAME_NOT_AUTHORITATIVE: NOTICE
|
||||
Z01_MNAME_NOT_IN_NS_LIST: INFO
|
||||
Z01_MNAME_NOT_MASTER: NOTICE
|
||||
Z01_MNAME_NOT_RESOLVE: NOTICE
|
||||
Z01_MNAME_NO_RESPONSE: NOTICE
|
||||
Z01_MNAME_UNEXPECTED_RCODE: NOTICE
|
||||
Z09_INCONSISTENT_MX: WARNING
|
||||
Z09_INCONSISTENT_MX_DATA: WARNING
|
||||
Z09_MISSING_MAIL_TARGET: NOTICE
|
||||
Z09_MX_DATA: INFO
|
||||
Z09_MX_FOUND: INFO
|
||||
Z09_NON_AUTH_MX_RESPONSE: WARNING
|
||||
Z09_NO_MX_FOUND: INFO
|
||||
Z09_NO_RESPONSE_MX_QUERY: WARNING
|
||||
Z09_NULL_MX_NON_ZERO_PREF: NOTICE
|
||||
Z09_NULL_MX_WITH_OTHER_MX: WARNING
|
||||
Z09_ROOT_EMAIL_DOMAIN: NOTICE
|
||||
Z09_TLD_EMAIL_DOMAIN: WARNING
|
||||
Z09_UNEXPECTED_RCODE_MX: WARNING
|
||||
Z11_DIFFERENT_SPF_POLICIES_FOUND: NOTICE
|
||||
Z11_INCONSISTENT_SPF_POLICIES: WARNING
|
||||
Z11_NO_SPF_FOUND: NOTICE
|
||||
Z11_NO_SPF_NON_MAIL_DOMAIN: INFO
|
||||
Z11_NON_NULL_SPF_NON_MAIL_DOMAIN: NOTICE
|
||||
Z11_NULL_SPF_NON_MAIL_DOMAIN: INFO
|
||||
Z11_SPF_MULTIPLE_RECORDS: WARNING
|
||||
Z11_SPF_SYNTAX_ERROR: WARNING
|
||||
Z11_SPF_SYNTAX_OK: INFO
|
||||
Z11_UNABLE_TO_CHECK_FOR_SPF: WARNING
|
||||
68
zonemaster-engine/share/profile_additional_properties.json
Normal file
68
zonemaster-engine/share/profile_additional_properties.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"resolver" : {
|
||||
"source4": "192.0.2.53",
|
||||
"source6": "2001:db8::42"
|
||||
},
|
||||
"cache": {
|
||||
"redis": {
|
||||
"server": "127.0.0.1:6379",
|
||||
"expire": 300
|
||||
}
|
||||
},
|
||||
"logfilter" : {
|
||||
"BASIC" : {
|
||||
"IPV6_ENABLED" : [
|
||||
{
|
||||
"when" : {
|
||||
"rrtype" : "NS",
|
||||
"ns" : "f.ext.nic.fr",
|
||||
"address" : "2001:67c:1010:11::53"
|
||||
},
|
||||
"set" : "WARNING"
|
||||
},
|
||||
{
|
||||
"when" : {
|
||||
"ns" : "h.ext.nic.fr"
|
||||
},
|
||||
"set" : "ERROR"
|
||||
}
|
||||
]
|
||||
},
|
||||
"CONNECTIVITY" : {
|
||||
"NAMESERVER_HAS_TCP_53" : [
|
||||
{
|
||||
"when" : {
|
||||
"ns" : [ "ns1.nic.fr", "ns2.nic.fr" ]
|
||||
},
|
||||
"set" : "WARNING"
|
||||
},
|
||||
{
|
||||
"when" : {
|
||||
"address" : "2001:620:0:ff::2f"
|
||||
},
|
||||
"set" : "ERROR"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"test_cases_vars": {
|
||||
"dnssec04" : {
|
||||
"DURATION_LONG" : 26179200,
|
||||
"REMAINING_LONG" : 26179200,
|
||||
"REMAINING_SHORT" : 43200
|
||||
},
|
||||
"zone02" : {
|
||||
"SOA_REFRESH_MINIMUM_VALUE" : 14400
|
||||
},
|
||||
"zone04" : {
|
||||
"SOA_RETRY_MINIMUM_VALUE" : 3600
|
||||
},
|
||||
"zone05" : {
|
||||
"SOA_EXPIRE_MINIMUM_VALUE" : 604800
|
||||
},
|
||||
"zone06" : {
|
||||
"SOA_DEFAULT_TTL_MAXIMUM_VALUE" : 86400,
|
||||
"SOA_DEFAULT_TTL_MINIMUM_VALUE" : 300
|
||||
}
|
||||
}
|
||||
}
|
||||
3644
zonemaster-engine/share/sl.po
Normal file
3644
zonemaster-engine/share/sl.po
Normal file
File diff suppressed because it is too large
Load Diff
3932
zonemaster-engine/share/sv.po
Normal file
3932
zonemaster-engine/share/sv.po
Normal file
File diff suppressed because it is too large
Load Diff
9
zonemaster-engine/share/update-po
Executable file
9
zonemaster-engine/share/update-po
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$1" ] ; then
|
||||
echo "error: No PO file specified." >&2
|
||||
exit 2
|
||||
fi
|
||||
po_file="$1" ; shift
|
||||
|
||||
make update-po POFILES="$po_file"
|
||||
14
zonemaster-engine/t/00-load.t
Normal file
14
zonemaster-engine/t/00-load.t
Normal file
@@ -0,0 +1,14 @@
|
||||
use 5.014002;
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use Test::More;
|
||||
|
||||
plan tests => 1;
|
||||
|
||||
BEGIN {
|
||||
use_ok( 'Zonemaster::Engine' ) || print "Bail out!\n";
|
||||
}
|
||||
|
||||
diag( "Testing Zonemaster Engine $Zonemaster::Engine::VERSION, Perl $], $^X" );
|
||||
|
||||
done_testing;
|
||||
368
zonemaster-engine/t/Test-address.data
Normal file
368
zonemaster-engine/t/Test-address.data
Normal file
File diff suppressed because one or more lines are too long
108
zonemaster-engine/t/Test-address.t
Normal file
108
zonemaster-engine/t/Test-address.t
Normal file
@@ -0,0 +1,108 @@
|
||||
use Test::More;
|
||||
use File::Slurp;
|
||||
|
||||
BEGIN {
|
||||
use_ok( q{Zonemaster::Engine} );
|
||||
use_ok( q{Zonemaster::Engine::Nameserver} );
|
||||
use_ok( q{Zonemaster::Engine::Test::Address} );
|
||||
}
|
||||
|
||||
my $datafile = q{t/Test-address.data};
|
||||
if ( not $ENV{ZONEMASTER_RECORD} ) {
|
||||
die "Stored data file missing" if not -r $datafile;
|
||||
Zonemaster::Engine::Nameserver->restore( $datafile );
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
}
|
||||
|
||||
my $json = read_file( "t/profiles/Test-address-all.json" );
|
||||
my $profile_test = Zonemaster::Engine::Profile->from_json( $json );
|
||||
Zonemaster::Engine::Profile->effective->merge( $profile_test );
|
||||
|
||||
my @special_addresses = qw(0.255.255.255
|
||||
10.255.255.255
|
||||
192.168.255.255
|
||||
172.17.255.255
|
||||
100.65.255.255
|
||||
127.255.255.255
|
||||
169.254.255.255
|
||||
192.0.0.255
|
||||
192.0.0.7
|
||||
192.0.0.170
|
||||
192.0.0.171
|
||||
192.0.2.255
|
||||
198.51.100.255
|
||||
203.0.113.255
|
||||
192.88.99.255
|
||||
198.19.255.255
|
||||
240.255.255.255
|
||||
255.255.255.255
|
||||
::1
|
||||
::
|
||||
::ffff:cafe:cafe
|
||||
64:ff9b::cafe:cafe
|
||||
100::cafe:cafe:cafe:cafe
|
||||
2001:1ff:cafe:cafe:cafe:cafe:cafe:cafe
|
||||
2001::cafe:cafe:cafe:cafe:cafe:cafe
|
||||
2001:2::cafe:cafe:cafe:cafe:cafe
|
||||
2001:db8:cafe:cafe:cafe:cafe:cafe:cafe
|
||||
2001:1f::cafe:cafe:cafe:cafe:cafe
|
||||
2002:cafe:cafe:cafe:cafe:cafe:cafe:cafe
|
||||
fdff:cafe:cafe:cafe:cafe:cafe:cafe:cafe
|
||||
febf:cafe:cafe:cafe:cafe:cafe:cafe:cafe
|
||||
);
|
||||
|
||||
foreach my $addr ( @special_addresses ) {
|
||||
ok(
|
||||
defined(
|
||||
Zonemaster::Engine::Test::Address->_find_special_address(
|
||||
Net::IP::XS->new( $addr )
|
||||
)
|
||||
),
|
||||
"Special address: $addr"
|
||||
);
|
||||
}
|
||||
|
||||
###########
|
||||
# address
|
||||
###########
|
||||
|
||||
my %res;
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{address}, q{nic.fr} );
|
||||
|
||||
ok( $res{NAMESERVER_IP_PTR_MISMATCH}, q{Nameserver IP PTR mismatch} );
|
||||
ok( $res{A01_GLOBALLY_REACHABLE_ADDR}, q{All Nameserver addresses are in the routable public addressing space} );
|
||||
ok( $res{NAMESERVERS_IP_WITH_REVERSE}, q{Reverse DNS entry exist for all Nameserver IP addresses} );
|
||||
|
||||
###########
|
||||
# address01
|
||||
###########
|
||||
|
||||
# See t/Test-address01.t
|
||||
|
||||
###########
|
||||
# address02
|
||||
###########
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{address}, q{address02.zut-root.rd.nic.fr} );
|
||||
ok( $res{NAMESERVER_IP_WITHOUT_REVERSE}, q{Nameserver IP without PTR} );
|
||||
|
||||
my $zone = Zonemaster::Engine->zone( q{torsas.se} );
|
||||
my @res = Zonemaster::Engine->test_method( q{Address}, q{address02}, $zone );
|
||||
ok( !( grep { $_->tag eq 'NAMESERVER_IP_WITHOUT_REVERSE' } @res ), 'Classless in-addr.arpa properly handled when querying PTR.' );
|
||||
|
||||
###########
|
||||
# address03
|
||||
###########
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{address}, q{is.se} );
|
||||
ok( $res{NAMESERVER_IP_PTR_MATCH}, q{All reverse DNS entry matches name server name} );
|
||||
|
||||
@res = Zonemaster::Engine->test_method( q{Address}, q{address03}, $zone );
|
||||
ok( !( grep { $_->tag eq 'NAMESERVER_IP_WITHOUT_REVERSE' } @res ), 'Classless in-addr.arpa properly handled when querying PTR.' );
|
||||
|
||||
###########
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
Zonemaster::Engine::Nameserver->save( $datafile );
|
||||
}
|
||||
|
||||
done_testing;
|
||||
54
zonemaster-engine/t/Test-address01.data
Normal file
54
zonemaster-engine/t/Test-address01.data
Normal file
File diff suppressed because one or more lines are too long
158
zonemaster-engine/t/Test-address01.t
Normal file
158
zonemaster-engine/t/Test-address01.t
Normal file
@@ -0,0 +1,158 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Test::More;
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use lib dirname( rel2abs( $0 ) );
|
||||
|
||||
BEGIN {
|
||||
use_ok( q{Zonemaster::Engine} );
|
||||
use_ok( q{Zonemaster::Engine::Nameserver} );
|
||||
use_ok( q{Zonemaster::Engine::Test::Address} );
|
||||
use_ok( q{TestUtil}, qw( perform_testcase_testing ) );
|
||||
}
|
||||
|
||||
###########
|
||||
# address01 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Address-TP/address01.md
|
||||
my $test_module = 'Address';
|
||||
my $test_case = 'address01';
|
||||
my @all_tags = qw(A01_ADDR_NOT_GLOBALLY_REACHABLE
|
||||
A01_DOCUMENTATION_ADDR
|
||||
A01_GLOBALLY_REACHABLE_ADDR
|
||||
A01_LOCAL_USE_ADDR
|
||||
A01_NO_GLOBALLY_REACHABLE_ADDR
|
||||
A01_NO_NAME_SERVERS_FOUND);
|
||||
|
||||
# Common hint file (test-zone-data/COMMON/hintfile)
|
||||
Zonemaster::Engine::Recursor->remove_fake_addresses( '.' );
|
||||
Zonemaster::Engine::Recursor->add_fake_addresses( '.',
|
||||
{ 'ns1' => [ '127.1.0.1', 'fda1:b2:c3::127:1:0:1' ],
|
||||
'ns2' => [ '127.1.0.2', 'fda1:b2:c3::127:1:0:2' ],
|
||||
}
|
||||
);
|
||||
|
||||
# Test zone scenarios
|
||||
# - Documentation: L<TestUtil/perform_testcase_testing()>
|
||||
# - Format: { SCENARIO_NAME => [
|
||||
# testable,
|
||||
# zone_name,
|
||||
# [ MANDATORY_MESSAGE_TAGS ],
|
||||
# [ FORBIDDEN_MESSAGE_TAGS ],
|
||||
# [ UNDELEGATED_NS ],
|
||||
# [ UNDELEGATED_DS ],
|
||||
# ] }
|
||||
#
|
||||
# - One of MANDATORY_MESSAGE_TAGS and FORBIDDEN_MESSAGE_TAGS may be undefined.
|
||||
# See documentation for the meaning of that.
|
||||
|
||||
my %subtests = (
|
||||
'GOOD-1' => [
|
||||
1,
|
||||
q(good-1.address01.xa),
|
||||
[ qw(A01_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'ALL-NON-REACHABLE' => [
|
||||
1,
|
||||
q(all-non-reachable.address01.xa),
|
||||
[ qw(A01_ADDR_NOT_GLOBALLY_REACHABLE A01_LOCAL_USE_ADDR A01_DOCUMENTATION_ADDR A01_NO_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'MIXED-LOCAL-DOC-1' => [
|
||||
1,
|
||||
q(mixed-local-doc-1.address01.xa),
|
||||
[ qw(A01_LOCAL_USE_ADDR A01_DOCUMENTATION_ADDR A01_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'MIXED-LOCAL-DOC-2' => [
|
||||
1,
|
||||
q(mixed-local-doc-2.address01.xa),
|
||||
[ qw(A01_LOCAL_USE_ADDR A01_DOCUMENTATION_ADDR A01_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'MIXED-LOCAL-OTHER-1' => [
|
||||
1,
|
||||
q(mixed-local-other-1.address01.xa),
|
||||
[ qw(A01_LOCAL_USE_ADDR A01_ADDR_NOT_GLOBALLY_REACHABLE A01_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'MIXED-LOCAL-OTHER-2' => [
|
||||
1,
|
||||
q(mixed-local-other-2.address01.xa),
|
||||
[ qw(A01_LOCAL_USE_ADDR A01_ADDR_NOT_GLOBALLY_REACHABLE A01_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'MIXED-DOC-OTHER-1' => [
|
||||
1,
|
||||
q(mixed-doc-other-1.address01.xa),
|
||||
[ qw(A01_DOCUMENTATION_ADDR A01_ADDR_NOT_GLOBALLY_REACHABLE A01_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'MIXED-DOC-OTHER-2' => [
|
||||
1,
|
||||
q(mixed-doc-other-2.address01.xa),
|
||||
[ qw(A01_DOCUMENTATION_ADDR A01_ADDR_NOT_GLOBALLY_REACHABLE A01_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'MIXED-ALL-1' => [
|
||||
1,
|
||||
q(mixed-all-1.address01.xa),
|
||||
[ qw(A01_ADDR_NOT_GLOBALLY_REACHABLE A01_DOCUMENTATION_ADDR A01_LOCAL_USE_ADDR A01_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'MIXED-ALL-2' => [
|
||||
1,
|
||||
q(mixed-all-2.address01.xa),
|
||||
[ qw(A01_ADDR_NOT_GLOBALLY_REACHABLE A01_DOCUMENTATION_ADDR A01_LOCAL_USE_ADDR A01_GLOBALLY_REACHABLE_ADDR) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'NO-NAME-SERVERS' => [
|
||||
1,
|
||||
q(no-name-servers.address01.xa),
|
||||
[ qw(A01_NO_NAME_SERVERS_FOUND) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
]
|
||||
);
|
||||
|
||||
###########
|
||||
|
||||
my $datafile = 't/' . basename ($0, '.t') . '.data';
|
||||
|
||||
if ( not $ENV{ZONEMASTER_RECORD} ) {
|
||||
die q{Stored data file missing} if not -r $datafile;
|
||||
Zonemaster::Engine::Nameserver->restore( $datafile );
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
}
|
||||
|
||||
Zonemaster::Engine::Profile->effective->merge( Zonemaster::Engine::Profile->from_json( qq({ "test_cases": [ "$test_case" ] }) ) );
|
||||
|
||||
perform_testcase_testing( $test_case, $test_module, \@all_tags, \%subtests, $ENV{ZONEMASTER_SELECTED_SCENARIOS}, $ENV{ZONEMASTER_DISABLED_SCENARIOS} );
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
Zonemaster::Engine::Nameserver->save( $datafile );
|
||||
}
|
||||
|
||||
done_testing;
|
||||
38
zonemaster-engine/t/Test-address03.data
Normal file
38
zonemaster-engine/t/Test-address03.data
Normal file
File diff suppressed because one or more lines are too long
58
zonemaster-engine/t/Test-address03.t
Normal file
58
zonemaster-engine/t/Test-address03.t
Normal file
@@ -0,0 +1,58 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use lib dirname( rel2abs( $0 ) );
|
||||
use TestUtil::DSL;
|
||||
|
||||
testing_test_case 'Address', 'address03';
|
||||
|
||||
all_tags qw(NAMESERVER_IP_PTR_MATCH
|
||||
NAMESERVER_IP_PTR_MISMATCH
|
||||
NAMESERVER_IP_WITHOUT_REVERSE
|
||||
NO_RESPONSE_PTR_QUERY);
|
||||
|
||||
# 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) ];
|
||||
|
||||
zone_name_template '{SCENARIO}.{TESTCASE}.xa';
|
||||
|
||||
# Test zone scenarios
|
||||
scenario 'ALL-NS-HAVE-PTR-{1..2}' => sub {
|
||||
expect 'NAMESERVER_IP_PTR_MATCH';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NO-NS-HAVE-PTR' => sub {
|
||||
expect 'NAMESERVER_IP_WITHOUT_REVERSE';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'INCOMPLETE-PTR-{1..2}' => sub {
|
||||
expect 'NAMESERVER_IP_WITHOUT_REVERSE';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NON-MATCHING-NAMES' => sub {
|
||||
expect 'NAMESERVER_IP_PTR_MISMATCH';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'PTR-IS-GOOD-CNAME-{1..2}' => sub {
|
||||
expect 'NAMESERVER_IP_PTR_MATCH';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'PTR-IS-DANGLING-CNAME' => sub {
|
||||
expect 'NAMESERVER_IP_WITHOUT_REVERSE';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'PTR-IS-ILLEGAL-CNAME' => sub {
|
||||
expect 'NAMESERVER_IP_WITHOUT_REVERSE';
|
||||
forbid 'NAMESERVER_IP_PTR_MATCH';
|
||||
};
|
||||
|
||||
no_more_scenarios;
|
||||
38
zonemaster-engine/t/Test-basic.data
Normal file
38
zonemaster-engine/t/Test-basic.data
Normal file
File diff suppressed because one or more lines are too long
101
zonemaster-engine/t/Test-basic.t
Normal file
101
zonemaster-engine/t/Test-basic.t
Normal file
@@ -0,0 +1,101 @@
|
||||
use Test::More;
|
||||
use File::Slurp;
|
||||
|
||||
BEGIN {
|
||||
use_ok( q{Zonemaster::Engine} );
|
||||
use_ok( q{Zonemaster::Engine::Nameserver} );
|
||||
use_ok( q{Zonemaster::Engine::Test::Basic} );
|
||||
use_ok( q{Zonemaster::Engine::Util} );
|
||||
}
|
||||
|
||||
my $checking_module = q{Basic};
|
||||
|
||||
sub name_gives {
|
||||
my ( $test, $name, $gives ) = @_;
|
||||
|
||||
my @res = Zonemaster::Engine->test_method( q{Basic}, $test, $name );
|
||||
|
||||
ok( ( grep { $_->tag eq $gives } @res ), "$name gives $gives" );
|
||||
}
|
||||
|
||||
sub name_gives_not {
|
||||
my ( $test, $name, $gives ) = @_;
|
||||
|
||||
my @res = Zonemaster::Engine->test_method( q{Basic}, $test, $name );
|
||||
|
||||
ok( !( grep { $_->tag eq $gives } @res ), "$name does not give $gives" );
|
||||
}
|
||||
|
||||
sub zone_gives {
|
||||
my ( $test, $zone, $gives_ref ) = @_;
|
||||
|
||||
Zonemaster::Engine->logger->clear_history();
|
||||
my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone );
|
||||
foreach my $gives ( @{$gives_ref} ) {
|
||||
ok( ( grep { $_->tag eq $gives } @res ), $zone->name->string . " gives $gives" );
|
||||
}
|
||||
return scalar( @res );
|
||||
}
|
||||
|
||||
sub zone_gives_not {
|
||||
my ( $test, $zone, $gives_ref ) = @_;
|
||||
|
||||
Zonemaster::Engine->logger->clear_history();
|
||||
my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone );
|
||||
foreach my $gives ( @{$gives_ref} ) {
|
||||
ok( !( grep { $_->tag eq $gives } @res ), $zone->name->string . " does not give $gives" );
|
||||
}
|
||||
return scalar( @res );
|
||||
}
|
||||
|
||||
my $datafile = q{t/Test-basic.data};
|
||||
if ( not $ENV{ZONEMASTER_RECORD} ) {
|
||||
die q{Stored data file missing} if not -r $datafile;
|
||||
Zonemaster::Engine::Nameserver->restore( $datafile );
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
}
|
||||
|
||||
my ($json, $profile_test);
|
||||
$json = read_file( 't/profiles/Test-basic-all.json' );
|
||||
$profile_test = Zonemaster::Engine::Profile->from_json( $json );
|
||||
Zonemaster::Engine::Profile->effective->merge( $profile_test );
|
||||
|
||||
my %res;
|
||||
my $zone;
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{basic}, q{nic.tf} );
|
||||
ok( $res{B02_AUTH_RESPONSE_SOA}, q{B02_AUTH_RESPONSE_SOA} );
|
||||
ok( $res{HAS_NAMESERVER_NO_WWW_A_TEST}, q{HAS_NAMESERVER_NO_WWW_A_TEST} );
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
Zonemaster::Engine::Nameserver->save( $datafile );
|
||||
}
|
||||
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 0 );
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 0 );
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 0 );
|
||||
|
||||
$zone = Zonemaster::Engine->zone( q{afnic.fr} );
|
||||
zone_gives( q{basic02}, $zone, [qw{NO_NETWORK}] );
|
||||
zone_gives_not( q{basic02}, $zone, [qw{IPV4_ENABLED}] );
|
||||
zone_gives_not( q{basic02}, $zone, [qw{IPV6_ENABLED}] );
|
||||
zone_gives_not( q{basic02}, $zone, [qw{IPV4_DISABLED}] );
|
||||
zone_gives_not( q{basic02}, $zone, [qw{IPV6_DISABLED}] );
|
||||
zone_gives( q{basic03}, $zone, [qw{NO_NETWORK}] );
|
||||
zone_gives_not( q{basic03}, $zone, [qw{IPV4_ENABLED}] );
|
||||
zone_gives_not( q{basic03}, $zone, [qw{IPV6_ENABLED}] );
|
||||
zone_gives_not( q{basic03}, $zone, [qw{IPV4_DISABLED}] );
|
||||
zone_gives_not( q{basic03}, $zone, [qw{IPV6_DISABLED}] );
|
||||
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
|
||||
TODO: {
|
||||
local $TODO = "Need to find/create zones with that error";
|
||||
|
||||
#basic03
|
||||
ok( $tag{A_QUERY_NO_RESPONSES}, q{A_QUERY_NO_RESPONSES} );
|
||||
ok( $tag{HAS_A_RECORDS}, q{HAS_A_RECORDS} );
|
||||
ok( $tag{NO_A_RECORDS}, q{NO_A_RECORDS} );
|
||||
}
|
||||
|
||||
done_testing;
|
||||
266
zonemaster-engine/t/Test-basic01.data
Normal file
266
zonemaster-engine/t/Test-basic01.data
Normal file
File diff suppressed because one or more lines are too long
126
zonemaster-engine/t/Test-basic01.t
Normal file
126
zonemaster-engine/t/Test-basic01.t
Normal file
@@ -0,0 +1,126 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use lib dirname( rel2abs( $0 ) );
|
||||
use TestUtil::DSL;
|
||||
|
||||
###########
|
||||
# basic01 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Basic-TP/basic01.md
|
||||
testing_test_case 'Basic', 'basic01';
|
||||
|
||||
all_tags qw(B01_CHILD_IS_ALIAS
|
||||
B01_CHILD_FOUND
|
||||
B01_INCONSISTENT_ALIAS
|
||||
B01_INCONSISTENT_DELEGATION
|
||||
B01_NO_CHILD
|
||||
B01_PARENT_DISREGARDED
|
||||
B01_PARENT_FOUND
|
||||
B01_PARENT_NOT_FOUND
|
||||
B01_PARENT_UNDETERMINED
|
||||
B01_ROOT_HAS_NO_PARENT
|
||||
B01_SERVER_ZONE_ERROR);
|
||||
|
||||
# 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) ];
|
||||
|
||||
# Test zone scenarios
|
||||
zone_name_template 'child.parent.{SCENARIO}.{TESTCASE}.xa';
|
||||
|
||||
scenario qw(GOOD-1 GOOD-MIXED-{1..2} GOOD-PARENT-HOST-1 GOOD-GRANDPARENT-HOST-1) => sub {
|
||||
expect qw(B01_CHILD_FOUND B01_PARENT_FOUND);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario qw(GOOD-UNDEL-1 GOOD-MIXED-UNDEL-{1..2} NO-DEL-UNDEL-1 NO-DEL-MIXED-UNDEL-1) => sub {
|
||||
fake_ns 'ns3-undelegated-child.basic01.xa';
|
||||
fake_ns 'ns4-undelegated-child.basic01.xa';
|
||||
expect qw(B01_CHILD_FOUND B01_PARENT_DISREGARDED);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NO-DEL-MIXED-UNDEL-2' => sub {
|
||||
zone 'child.w.x.parent.y.z.{SCENARIO}.{TESTCASE}.xa';
|
||||
fake_ns 'ns3-undelegated-child.basic01.xa';
|
||||
fake_ns 'ns4-undelegated-child.basic01.xa';
|
||||
expect qw(B01_CHILD_FOUND B01_PARENT_DISREGARDED);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NO-CHILD-{1..2}' => sub {
|
||||
expect qw(B01_NO_CHILD B01_PARENT_FOUND);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NO-CHLD-PAR-UNDETER-1' => sub {
|
||||
expect qw(B01_NO_CHILD B01_PARENT_FOUND B01_PARENT_UNDETERMINED);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHLD-FOUND-PAR-UNDET-1' => sub {
|
||||
expect qw(B01_CHILD_FOUND B01_PARENT_FOUND B01_PARENT_UNDETERMINED);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHLD-FOUND-INCONSIST-{1..3}' => sub {
|
||||
expect qw(B01_CHILD_FOUND B01_INCONSISTENT_DELEGATION B01_PARENT_FOUND);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHLD-FOUND-INCONSIST-4' => sub {
|
||||
expect qw(B01_CHILD_IS_ALIAS B01_CHILD_FOUND B01_INCONSISTENT_DELEGATION B01_PARENT_FOUND);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHLD-FOUND-INCONSIST-{5..8}' => sub {
|
||||
expect qw(B01_CHILD_FOUND B01_INCONSISTENT_DELEGATION B01_PARENT_FOUND);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHLD-FOUND-INCONSIST-9' => sub {
|
||||
expect qw(B01_CHILD_IS_ALIAS B01_CHILD_FOUND B01_INCONSISTENT_DELEGATION B01_PARENT_FOUND);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHLD-FOUND-INCONSIST-10' => sub {
|
||||
expect qw(B01_CHILD_FOUND B01_INCONSISTENT_DELEGATION B01_PARENT_FOUND);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario qw(NO-DEL-UNDEL-NO-PAR-1 NO-DEL-UNDEL-PAR-UND-1) => sub {
|
||||
fake_ns 'ns3-undelegated-child.basic01.xa';
|
||||
fake_ns 'ns4-undelegated-child.basic01.xa';
|
||||
expect qw(B01_CHILD_FOUND B01_PARENT_DISREGARDED);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NO-CHLD-NO-PAR-1' => sub {
|
||||
expect qw(B01_NO_CHILD B01_PARENT_NOT_FOUND B01_SERVER_ZONE_ERROR);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHILD-ALIAS-1' => sub {
|
||||
expect qw(B01_CHILD_IS_ALIAS B01_NO_CHILD B01_PARENT_FOUND);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHILD-ALIAS-2' => sub {
|
||||
expect qw(B01_CHILD_IS_ALIAS B01_NO_CHILD B01_INCONSISTENT_ALIAS
|
||||
B01_PARENT_FOUND);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ZONE-ERR-GRANDPARENT-{1..3}' => sub {
|
||||
expect qw(B01_CHILD_FOUND B01_PARENT_FOUND B01_SERVER_ZONE_ERROR);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ROOT-ZONE' => sub {
|
||||
zone '.';
|
||||
expect qw(B01_CHILD_FOUND B01_ROOT_HAS_NO_PARENT);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
no_more_scenarios;
|
||||
121
zonemaster-engine/t/Test-basic02.data
Normal file
121
zonemaster-engine/t/Test-basic02.data
Normal file
File diff suppressed because one or more lines are too long
161
zonemaster-engine/t/Test-basic02.t
Normal file
161
zonemaster-engine/t/Test-basic02.t
Normal file
@@ -0,0 +1,161 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use lib dirname( rel2abs( $0 ) );
|
||||
use TestUtil::DSL;
|
||||
|
||||
###########
|
||||
# basic02 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Basic-TP/basic02.md
|
||||
testing_test_case 'Basic', 'basic02';
|
||||
|
||||
all_tags qw(B02_AUTH_RESPONSE_SOA
|
||||
B02_NO_DELEGATION
|
||||
B02_NO_WORKING_NS
|
||||
B02_NS_BROKEN
|
||||
B02_NS_NOT_AUTH
|
||||
B02_NS_NO_IP_ADDR
|
||||
B02_NS_NO_RESPONSE
|
||||
B02_UNEXPECTED_RCODE);
|
||||
|
||||
# Specific hint file (test-zone-data/Basic-TP/basic02/hintfile.zone)
|
||||
root_hints 'root-ns1.xa' => [ qw(127.12.2.23 fda1:b2:c3::127:12:2:23) ],
|
||||
'root-ns2.xa' => [ qw(127.12.2.24 fda1:b2:c3::127:12:2:24) ];
|
||||
|
||||
zone_name_template '{SCENARIO}.{TESTCASE}.xa';
|
||||
|
||||
scenario 'GOOD-{1..2}' => sub {
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-1' => sub {
|
||||
fake_ns 'ns1.good-undel-1.basic02.xa' => '127.12.2.31', 'fda1:b2:c3:0:127:12:2:31';
|
||||
fake_ns 'ns2.good-undel-1.basic02.xa' => '127.12.2.32', 'fda1:b2:c3:0:127:12:2:32';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-2' => sub {
|
||||
fake_ns 'ns1.good-undel-2.basic02.xb';
|
||||
fake_ns 'ns2.good-undel-2.basic02.xb';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-3' => sub {
|
||||
fake_ns 'ns3.good-undel-3.basic02.xb';
|
||||
fake_ns 'ns4.good-undel-3.basic02.xb';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-4' => sub {
|
||||
fake_ns 'ns1.good-undel-4.basic02.xb';
|
||||
fake_ns 'ns2.good-undel-4.basic02.xb';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-5' => sub {
|
||||
fake_ns 'ns1.good-undel-5.basic02.xa' => '127.12.2.31', 'fda1:b2:c3:0:127:12:2:31';
|
||||
fake_ns 'ns2.good-undel-5.basic02.xa' => '127.12.2.32', 'fda1:b2:c3:0:127:12:2:32';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-6' => sub {
|
||||
fake_ns 'ns3.good-undel-6.basic02.xa' => '127.12.2.33', 'fda1:b2:c3:0:127:12:2:33';
|
||||
fake_ns 'ns4.good-undel-6.basic02.xa' => '127.12.2.34', 'fda1:b2:c3:0:127:12:2:34';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-7' => sub {
|
||||
fake_ns 'ns3.good-undel-7.basic02.xb' => '127.12.2.33', 'fda1:b2:c3:0:127:12:2:33';
|
||||
fake_ns 'ns4.good-undel-7.basic02.xb' => '127.12.2.34';
|
||||
fake_ns 'ns5.good-undel-7.basic02.xb' => 'fda1:b2:c3:0:127:12:2:34';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-8' => sub {
|
||||
fake_ns 'dns1.good-undel-8.basic02.xa' => '127.12.2.33', 'fda1:b2:c3:0:127:12:2:33';
|
||||
fake_ns 'dns2.good-undel-8.basic02.xa' => '127.12.2.34', 'fda1:b2:c3:0:127:12:2:34';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-9' => sub {
|
||||
fake_ns 'dns1.good-undel-9.basic02.xb' => '127.12.2.33', 'fda1:b2:c3:0:127:12:2:33';
|
||||
fake_ns 'dns2.good-undel-9.basic02.xb' => '127.12.2.34', 'fda1:b2:c3:0:127:12:2:34';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-10' => sub {
|
||||
fake_ns 'ns3.good-undel-10.basic02.xb' => '127.12.2.33', 'fda1:b2:c3:0:127:12:2:33';
|
||||
fake_ns 'ns4.good-undel-10.basic02.xb' => '127.12.2.34', 'fda1:b2:c3:0:127:12:2:34';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-UNDEL-11' => sub {
|
||||
fake_ns 'ns3.good-undel-11.basic02.xb';
|
||||
fake_ns 'ns4.good-undel-11.basic02.xb';
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'MIXED-1' => sub {
|
||||
expect 'B02_AUTH_RESPONSE_SOA';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NO-DELEGATION-1' => sub {
|
||||
zone 'no-delegation.{TESTCASE}.xa';
|
||||
expect 'B02_NO_DELEGATION';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NS-BROKEN-1' => sub {
|
||||
expect qw(B02_NS_BROKEN B02_NO_WORKING_NS);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NS-NOT-AUTH-1' => sub {
|
||||
expect qw(B02_NS_NOT_AUTH B02_NO_WORKING_NS);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NS-NO-IP-{1..3}' => sub {
|
||||
expect qw(B02_NS_NO_IP_ADDR B02_NO_WORKING_NS);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NS-NO-IP-UNDEL-1' => sub {
|
||||
fake_ns 'ns1.ns-no-ip-undel-1.basic02.xa';
|
||||
fake_ns 'ns2.ns-no-ip-undel-1.basic02.xa';
|
||||
expect qw(B02_NS_NO_IP_ADDR B02_NO_WORKING_NS);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NS-NO-IP-UNDEL-2' => sub {
|
||||
fake_ns 'ns1.ns-no-ip-undel-2.basic02.xb';
|
||||
fake_ns 'ns2.ns-no-ip-undel-2.basic02.xb';
|
||||
expect qw(B02_NS_NO_IP_ADDR B02_NO_WORKING_NS);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NS-NO-RESPONSE-1' => sub {
|
||||
expect qw(B02_NS_NO_RESPONSE B02_NO_WORKING_NS);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'UNEXPECTED-RCODE-1' => sub {
|
||||
expect qw(B02_UNEXPECTED_RCODE B02_NO_WORKING_NS);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
no_more_scenarios;
|
||||
70
zonemaster-engine/t/Test-connectivity.data
Normal file
70
zonemaster-engine/t/Test-connectivity.data
Normal file
File diff suppressed because one or more lines are too long
143
zonemaster-engine/t/Test-connectivity.t
Normal file
143
zonemaster-engine/t/Test-connectivity.t
Normal file
@@ -0,0 +1,143 @@
|
||||
use Test::More;
|
||||
use Test::Differences;
|
||||
use File::Slurp;
|
||||
|
||||
use List::MoreUtils qw[uniq none any];
|
||||
|
||||
BEGIN {
|
||||
use_ok( q{Zonemaster::Engine} );
|
||||
use_ok( q{Zonemaster::Engine::Nameserver} );
|
||||
use_ok( q{Zonemaster::Engine::Test::Connectivity} );
|
||||
use_ok( q{Zonemaster::Engine::Util} );
|
||||
}
|
||||
|
||||
my $datafile = q{t/Test-connectivity.data};
|
||||
if ( not $ENV{ZONEMASTER_RECORD} ) {
|
||||
die q{Stored data file missing} if not -r $datafile;
|
||||
Zonemaster::Engine::Nameserver->restore( $datafile );
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
}
|
||||
|
||||
my ($json, $profile_test);
|
||||
foreach my $testcase ( qw{connectivity01 connectivity02 connectivity03 connectivity04} ) {
|
||||
$json = read_file( 't/profiles/Test-'.$testcase.'-only.json' );
|
||||
$profile_test = Zonemaster::Engine::Profile->from_json( $json );
|
||||
Zonemaster::Engine::Profile->effective->merge( $profile_test );
|
||||
my %testcases;
|
||||
foreach my $result ( Zonemaster::Engine->test_module( q{connectivity}, q{afnic.fr} ) ) {
|
||||
if ( $result->testcase && $result->testcase ne 'Unspecified' ) {
|
||||
$testcases{$result->testcase} = 1;
|
||||
}
|
||||
}
|
||||
eq_or_diff( [ map { lc $_ } keys %testcases ], [ $testcase ], 'expected test-case ('. $testcase .')' );
|
||||
}
|
||||
|
||||
$json = read_file( 't/profiles/Test-connectivity-all.json' );
|
||||
$profile_test = Zonemaster::Engine::Profile->from_json( $json );
|
||||
Zonemaster::Engine::Profile->effective->merge( $profile_test );
|
||||
|
||||
my @res;
|
||||
my %res;
|
||||
my %should_emit;
|
||||
|
||||
my $metadata = Zonemaster::Engine::Test::Connectivity->metadata();
|
||||
my $test_levels = Zonemaster::Engine::Profile->effective->{profile}->{test_levels}->{CONNECTIVITY};
|
||||
|
||||
sub check_output_connectivity_testcase {
|
||||
my ( $testcase, $res, $should_emit ) = @_;
|
||||
|
||||
return if ( $testcase !~ q/connectivity0[1-4]/ );
|
||||
|
||||
for my $key ( @{ $metadata->{$testcase} } ) {
|
||||
next if ( $test_levels->{$key} =~ q/DEBUG/ );
|
||||
if ( $should_emit->{$key} ) {
|
||||
ok( $res->{$key}, "Should emit $key" );
|
||||
} else {
|
||||
ok( !$res->{$key}, "Should NOT emit $key" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub check_output_connectivity_all {
|
||||
my ( $res, $should_emit ) = @_;
|
||||
|
||||
check_output_connectivity_testcase( 'connectivity01', $res, $should_emit );
|
||||
check_output_connectivity_testcase( 'connectivity02', $res, $should_emit );
|
||||
check_output_connectivity_testcase( 'connectivity03', $res, $should_emit );
|
||||
check_output_connectivity_testcase( 'connectivity04', $res, $should_emit );
|
||||
}
|
||||
|
||||
subtest 'All good' => sub {
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{connectivity}, q{afnic.fr} );
|
||||
ok( !$res{MODULE_ERROR}, q{Test module completes normally} );
|
||||
%should_emit = (
|
||||
IPV4_DIFFERENT_ASN => 1,
|
||||
IPV6_DIFFERENT_ASN => 1,
|
||||
CN04_IPV4_DIFFERENT_PREFIX => 1,
|
||||
CN04_IPV6_DIFFERENT_PREFIX => 1
|
||||
);
|
||||
check_output_connectivity_all( \%res, \%should_emit );
|
||||
};
|
||||
|
||||
subtest 'No IPv6 (profile with IPv4 only)' => sub {
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 1 );
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 0 );
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{connectivity}, q{afnic.fr} );
|
||||
|
||||
subtest 'UDP' => sub {
|
||||
%should_emit = (
|
||||
CN01_IPV6_DISABLED => 1
|
||||
);
|
||||
check_output_connectivity_testcase( 'connectivity01', \%res, \%should_emit );
|
||||
};
|
||||
|
||||
subtest 'TCP (no messages)' => sub {
|
||||
%should_emit = ();
|
||||
check_output_connectivity_testcase( 'connectivity02', \%res, \%should_emit );
|
||||
};
|
||||
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 1 );
|
||||
};
|
||||
|
||||
subtest 'No IPv4 (profile with IPv6 only)' => sub {
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 0 );
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 1 );
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{connectivity}, q{afnic.fr} );
|
||||
|
||||
subtest 'UDP' => sub {
|
||||
%should_emit = (
|
||||
CN01_IPV4_DISABLED => 1
|
||||
);
|
||||
check_output_connectivity_testcase( 'connectivity01', \%res, \%should_emit );
|
||||
};
|
||||
|
||||
subtest 'TCP (no messages)' => sub {
|
||||
%should_emit = ();
|
||||
check_output_connectivity_testcase( 'connectivity02', \%res, \%should_emit );
|
||||
};
|
||||
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 1 );
|
||||
};
|
||||
|
||||
subtest 'No network' => sub {
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 0 );
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 0 );
|
||||
|
||||
%res = map{ $_->tag => 1 } Zonemaster::Engine->test_module( q{connectivity}, q{afnic.fr} );
|
||||
ok( $res{NO_NETWORK}, 'IPv6 and IPv4 disabled' );
|
||||
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 1 );
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 1 );
|
||||
};
|
||||
|
||||
# connectivity03 -- See t/Test-connectivity03.t instead.
|
||||
|
||||
# connectivity04 -- See t/Test-connectivity04.t instead.
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
Zonemaster::Engine::Nameserver->save( $datafile );
|
||||
}
|
||||
|
||||
done_testing;
|
||||
119
zonemaster-engine/t/Test-connectivity03.data
Normal file
119
zonemaster-engine/t/Test-connectivity03.data
Normal file
File diff suppressed because one or more lines are too long
74
zonemaster-engine/t/Test-connectivity03.t
Normal file
74
zonemaster-engine/t/Test-connectivity03.t
Normal file
@@ -0,0 +1,74 @@
|
||||
use Test::More;
|
||||
use File::Slurp;
|
||||
use File::Basename;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use_ok( q{Zonemaster::Engine} );
|
||||
use_ok( q{Zonemaster::Engine::Nameserver} );
|
||||
use_ok( q{Zonemaster::Engine::Test::Connectivity} );
|
||||
use_ok( q{Zonemaster::Engine::Util} );
|
||||
}
|
||||
|
||||
my $checking_module = q{Connectivity};
|
||||
my $testcase = 'connectivity03';
|
||||
my $datafile = 't/' . basename ($0, '.t') . '.data';
|
||||
|
||||
sub zone_gives {
|
||||
my ( $test, $zone, $gives_ref ) = @_;
|
||||
Zonemaster::Engine->logger->clear_history();
|
||||
my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone );
|
||||
foreach my $gives ( @{$gives_ref} ) {
|
||||
ok( ( grep { $_->tag eq $gives } @res ), $zone->name->string . " gives $gives" );
|
||||
}
|
||||
return scalar( @res );
|
||||
}
|
||||
|
||||
sub zone_gives_not {
|
||||
my ( $test, $zone, $gives_ref ) = @_;
|
||||
|
||||
Zonemaster::Engine->logger->clear_history();
|
||||
my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone );
|
||||
foreach my $gives ( @{$gives_ref} ) {
|
||||
ok( !( grep { $_->tag eq $gives } @res ), $zone->name->string . " does not give $gives" );
|
||||
}
|
||||
return scalar( @res );
|
||||
}
|
||||
|
||||
if ( not $ENV{ZONEMASTER_RECORD} ) {
|
||||
die q{Stored data file missing} if not -r $datafile;
|
||||
Zonemaster::Engine::Nameserver->restore( $datafile );
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
}
|
||||
|
||||
my ($json, $profile_test);
|
||||
$json = qq({ "test_cases": [ "$testcase" ] });
|
||||
$profile_test = Zonemaster::Engine::Profile->from_json( $json );
|
||||
Zonemaster::Engine::Profile->effective->merge( $profile_test );
|
||||
|
||||
###
|
||||
my $zone = Zonemaster::Engine->zone( q{001.tf} );
|
||||
zone_gives( $testcase, $zone, [qw{IPV4_ONE_ASN IPV6_ONE_ASN}] );
|
||||
zone_gives_not( $testcase, $zone, [qw{EMPTY_ASN_SET ERROR_ASN_DATABASE IPV4_DIFFERENT_ASN IPV4_SAME_ASN IPV6_DIFFERENT_ASN IPV6_SAME_ASN}] );
|
||||
|
||||
$zone = Zonemaster::Engine->zone( q{zonemaster.net} );
|
||||
zone_gives( $testcase, $zone, [qw{IPV4_DIFFERENT_ASN IPV6_DIFFERENT_ASN}] );
|
||||
zone_gives_not( $testcase, $zone, [qw{EMPTY_ASN_SET ERROR_ASN_DATABASE IPV4_SAME_ASN IPV6_SAME_ASN IPV4_ONE_ASN IPV6_ONE_ASN}] );
|
||||
|
||||
$zone = Zonemaster::Engine->zone( q{zut-root.rd.nic.fr} );
|
||||
zone_gives( $testcase, $zone, [qw{IPV4_ONE_ASN}] );
|
||||
zone_gives_not( $testcase, $zone, [qw{EMPTY_ASN_SET ERROR_ASN_DATABASE IPV4_DIFFERENT_ASN IPV4_SAME_ASN IPV6_DIFFERENT_ASN IPV6_ONE_ASN IPV6_SAME_ASN}] );
|
||||
|
||||
TODO: {
|
||||
my @missing = qw( EMPTY_ASN_SET ERROR_ASN_DATABASE IPV4_SAME_ASN IPV6_SAME_ASN );
|
||||
local $TODO = "Need to find/create zones with those errors: ";
|
||||
warn $TODO, "\n\t", join("\n\t", @missing), "\n";
|
||||
}
|
||||
###
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
Zonemaster::Engine::Nameserver->save( $datafile );
|
||||
}
|
||||
|
||||
done_testing;
|
||||
61
zonemaster-engine/t/Test-connectivity04.data
Normal file
61
zonemaster-engine/t/Test-connectivity04.data
Normal file
File diff suppressed because one or more lines are too long
129
zonemaster-engine/t/Test-connectivity04.t
Normal file
129
zonemaster-engine/t/Test-connectivity04.t
Normal file
@@ -0,0 +1,129 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use lib dirname( rel2abs( $0 ) );
|
||||
use TestUtil::DSL;
|
||||
|
||||
###########
|
||||
# connectivity04 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Connectivity-TP/connectivity04.md
|
||||
testing_test_case 'Connectivity', 'connectivity04';
|
||||
|
||||
all_tags qw(CN04_EMPTY_PREFIX_SET
|
||||
CN04_ERROR_PREFIX_DATABASE
|
||||
CN04_IPV4_DIFFERENT_PREFIX
|
||||
CN04_IPV4_SAME_PREFIX
|
||||
CN04_IPV4_SINGLE_PREFIX
|
||||
CN04_IPV6_DIFFERENT_PREFIX
|
||||
CN04_IPV6_SAME_PREFIX
|
||||
CN04_IPV6_SINGLE_PREFIX);
|
||||
|
||||
# Specific hint file (https://github.com/zonemaster/zonemaster/blob/master/test-zone-data/Connectivity-TP/connectivity04/hintfile.zone)
|
||||
root_hints 'root-ns1.xa' => [ qw(127.13.4.23 fda1:b2:c3::127:13:4:23) ],
|
||||
'root-ns2.xa' => [ qw(127.13.4.24 fda1:b2:c3::127:13:4:24) ];
|
||||
|
||||
# Test zone scenarios
|
||||
zone_name_template '{SCENARIO}.{TESTCASE}.xa';
|
||||
|
||||
scenario 'GOOD-1' => sub {
|
||||
expect qw(CN04_IPV4_DIFFERENT_PREFIX CN04_IPV6_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-2' => sub {
|
||||
expect qw(CN04_IPV4_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'GOOD-3' => sub {
|
||||
expect qw(CN04_IPV6_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'EMPTY-PREFIX-SET-{1..2}' => sub {
|
||||
expect qw(CN04_EMPTY_PREFIX_SET);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ERROR-PREFIX-DATABASE-{1..2}' => sub {
|
||||
expect qw(CN04_ERROR_PREFIX_DATABASE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
# scenario 'ERROR-PREFIX-DATABASE-3' => tested out of order; see end of file.
|
||||
|
||||
# scenario 'ERROR-PREFIX-DATABASE-{4..5}' => do not exist
|
||||
|
||||
scenario 'ERROR-PREFIX-DATABASE-6' => sub {
|
||||
expect qw(CN04_IPV4_DIFFERENT_PREFIX CN04_IPV6_DIFFERENT_PREFIX CN04_ERROR_PREFIX_DATABASE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ERROR-PREFIX-DATABASE-{7..8}' => sub {
|
||||
expect qw(CN04_ERROR_PREFIX_DATABASE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'HAS-NON-ASN-TXT-1' => sub {
|
||||
expect qw(CN04_IPV4_DIFFERENT_PREFIX CN04_IPV6_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'HAS-NON-ASN-TXT-2' => sub {
|
||||
expect qw(CN04_EMPTY_PREFIX_SET);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IPV4-ONE-PREFIX-1' => sub {
|
||||
expect qw(CN04_IPV4_SAME_PREFIX CN04_IPV4_SINGLE_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IPV4-TWO-PREFIXES-1' => sub {
|
||||
expect qw(CN04_IPV4_SAME_PREFIX CN04_IPV4_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IPV6-ONE-PREFIX-1' => sub {
|
||||
expect qw(CN04_IPV6_SAME_PREFIX CN04_IPV6_SINGLE_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IPV6-TWO-PREFIXES-1' => sub {
|
||||
expect qw(CN04_IPV6_SAME_PREFIX CN04_IPV6_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IPV4-SINGLE-NS-1' => sub {
|
||||
expect qw(CN04_IPV4_SINGLE_PREFIX CN04_IPV4_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IPV6-SINGLE-NS-1' => sub {
|
||||
expect qw(CN04_IPV6_SINGLE_PREFIX CN04_IPV6_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'DOUBLE-PREFIX-1' => sub {
|
||||
expect qw(CN04_IPV4_DIFFERENT_PREFIX CN04_IPV6_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'DOUBLE-PREFIX-2' => sub {
|
||||
expect qw(CN04_IPV4_DIFFERENT_PREFIX CN04_IPV6_DIFFERENT_PREFIX);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
# The scenario below needs to be tested out of order, and with an empty cache,
|
||||
# because a previously cached non-response from the ASN lookup zone (which was
|
||||
# intentional) causes negative side effects when testing this scenario.
|
||||
|
||||
clear_cache;
|
||||
|
||||
scenario 'ERROR-PREFIX-DATABASE-3' => sub {
|
||||
expect qw(CN04_ERROR_PREFIX_DATABASE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
no_more_scenarios;
|
||||
115
zonemaster-engine/t/Test-consistency.data
Normal file
115
zonemaster-engine/t/Test-consistency.data
Normal file
File diff suppressed because one or more lines are too long
141
zonemaster-engine/t/Test-consistency.t
Normal file
141
zonemaster-engine/t/Test-consistency.t
Normal file
@@ -0,0 +1,141 @@
|
||||
use Test::More;
|
||||
use Test::Differences;
|
||||
use File::Slurp;
|
||||
|
||||
use List::MoreUtils qw[uniq none any];
|
||||
|
||||
BEGIN {
|
||||
use_ok( q{Zonemaster::Engine} );
|
||||
use_ok( q{Zonemaster::Engine::Test::Consistency} );
|
||||
use_ok( q{Zonemaster::Engine::Util} );
|
||||
}
|
||||
|
||||
my $datafile = q{t/Test-consistency.data};
|
||||
if ( not $ENV{ZONEMASTER_RECORD} ) {
|
||||
die q{Stored data file missing} if not -r $datafile;
|
||||
Zonemaster::Engine::Nameserver->restore( $datafile );
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
}
|
||||
|
||||
my ($json, $profile_test);
|
||||
foreach my $testcase ( qw{consistency01 consistency02 consistency03 consistency04} ) {
|
||||
$json = read_file( 't/profiles/Test-'.$testcase.'-only.json' );
|
||||
$profile_test = Zonemaster::Engine::Profile->from_json( $json );
|
||||
Zonemaster::Engine::Profile->effective->merge( $profile_test );
|
||||
my %testcases;
|
||||
Zonemaster::Engine->logger->clear_history();
|
||||
foreach my $result ( Zonemaster::Engine->test_module( q{consistency}, q{afnic.fr} ) ) {
|
||||
if ( $result->testcase && $result->testcase ne 'Unspecified' ) {
|
||||
$testcases{$result->testcase} = 1;
|
||||
}
|
||||
}
|
||||
eq_or_diff( [ map { lc $_ } keys %testcases ], [ $testcase ], 'expected test-case ('. $testcase .')' );
|
||||
}
|
||||
|
||||
$json = read_file( 't/profiles/Test-consistency-all.json' );
|
||||
$profile_test = Zonemaster::Engine::Profile->from_json( $json );
|
||||
Zonemaster::Engine::Profile->effective->merge( $profile_test );
|
||||
|
||||
my @res;
|
||||
my %res;
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{consistency}, q{consistency01.zut-root.rd.nic.fr} );
|
||||
ok( $res{SOA_SERIAL_VARIATION}, q{Big variation between multiple SOA serials} );
|
||||
ok( $res{MULTIPLE_SOA_SERIALS}, q{Multiple SOA serials} );
|
||||
ok( $res{SOA_SERIAL}, q{SOA serial details} );
|
||||
ok( $res{ONE_NS_SET}, q{A unique NS set was seen} );
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{consistency}, q{consistency02.zut-root.rd.nic.fr} );
|
||||
ok( $res{MULTIPLE_SOA_RNAMES}, q{Multiple SOA rname} );
|
||||
ok( $res{SOA_RNAME}, q{SOA rname details} );
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{consistency}, q{consistency03.zut-root.rd.nic.fr} );
|
||||
ok( $res{MULTIPLE_SOA_TIME_PARAMETER_SET}, q{Multiple SOA time parameters} );
|
||||
ok( $res{SOA_TIME_PARAMETER_SET}, q{SOA time parameters details} );
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{consistency}, q{consistency04.zut-root.rd.nic.fr} );
|
||||
ok( $res{MULTIPLE_NS_SET}, q{Saw several NS set} );
|
||||
ok( $res{NS_SET}, q{NS set details} );
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{consistency}, q{afnic.fr} );
|
||||
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 );
|
||||
}
|
||||
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 0 );
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 0 );
|
||||
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 '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' );
|
||||
@res = Zonemaster::Engine->test_method( 'Consistency', 'consistency02', Zonemaster::Engine->zone( q{afnic.fr} ) );
|
||||
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' );
|
||||
@res = Zonemaster::Engine->test_method( 'Consistency', 'consistency03', Zonemaster::Engine->zone( q{afnic.fr} ) );
|
||||
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' );
|
||||
@res = Zonemaster::Engine->test_method( 'Consistency', 'consistency04', Zonemaster::Engine->zone( q{afnic.fr} ) );
|
||||
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 );
|
||||
|
||||
done_testing;
|
||||
118
zonemaster-engine/t/Test-consistency05.data
Normal file
118
zonemaster-engine/t/Test-consistency05.data
Normal file
File diff suppressed because one or more lines are too long
130
zonemaster-engine/t/Test-consistency05.t
Normal file
130
zonemaster-engine/t/Test-consistency05.t
Normal file
@@ -0,0 +1,130 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use lib dirname( rel2abs( $0 ) );
|
||||
use TestUtil::DSL;
|
||||
|
||||
###########
|
||||
# consistency05 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Consistency-TP/consistency05.md
|
||||
|
||||
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) ];
|
||||
|
||||
# Test zone scenarios
|
||||
zone_name_template '{SCENARIO}.{TESTCASE}.xa';
|
||||
|
||||
scenario 'ADDRESSES-MATCH-{1..2}' => sub {
|
||||
expect 'ADDRESSES_MATCH';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ADDRESSES-MATCH-{3..4}' => sub {
|
||||
expect qw(ADDRESSES_MATCH CHILD_NS_FAILED);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ADDRESSES-MATCH-5' => sub {
|
||||
expect qw(ADDRESSES_MATCH NO_RESPONSE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ADDRESSES-MATCH-6' => sub {
|
||||
zone 'child.{SCENARIO}.{TESTCASE}.xa';
|
||||
expect 'ADDRESSES_MATCH';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ADDRESSES-MATCH-7' => sub {
|
||||
expect 'ADDRESSES_MATCH';
|
||||
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';
|
||||
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';
|
||||
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';
|
||||
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';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHILD-ZONE-LAME-1' => sub {
|
||||
todo 'see https://github.com/zonemaster/zonemaster-engine/issues/1301';
|
||||
|
||||
expect qw(CHILD_ZONE_LAME NO_RESPONSE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'CHILD-ZONE-LAME-2' => sub {
|
||||
expect qw(CHILD_ZONE_LAME CHILD_NS_FAILED);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IB-ADDR-MISMATCH-1' => sub {
|
||||
expect qw(IN_BAILIWICK_ADDR_MISMATCH EXTRA_ADDRESS_CHILD);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IB-ADDR-MISMATCH-2' => sub {
|
||||
expect 'IN_BAILIWICK_ADDR_MISMATCH';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IB-ADDR-MISMATCH-3' => sub {
|
||||
todo 'see https://github.com/zonemaster/zonemaster-engine/issues/1301';
|
||||
|
||||
expect qw(IN_BAILIWICK_ADDR_MISMATCH NO_RESPONSE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IB-ADDR-MISMATCH-4' => sub {
|
||||
todo 'see https://github.com/zonemaster/zonemaster-engine/issues/1349';
|
||||
|
||||
expect 'IN_BAILIWICK_ADDR_MISMATCH';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'OOB-ADDR-MISMATCH' => sub {
|
||||
zone 'child.{SCENARIO}.{TESTCASE}.xa';
|
||||
expect 'OUT_OF_BAILIWICK_ADDR_MISMATCH';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'EXTRA-ADDRESS-CHILD' => sub {
|
||||
expect 'EXTRA_ADDRESS_CHILD';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
no_more_scenarios;
|
||||
60
zonemaster-engine/t/Test-consistency06.data
Normal file
60
zonemaster-engine/t/Test-consistency06.data
Normal file
File diff suppressed because one or more lines are too long
80
zonemaster-engine/t/Test-consistency06.t
Normal file
80
zonemaster-engine/t/Test-consistency06.t
Normal file
@@ -0,0 +1,80 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use lib dirname( rel2abs( $0 ) );
|
||||
use TestUtil::DSL;
|
||||
|
||||
###########
|
||||
# consistency06 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Consistency-TP/consistency06.md
|
||||
|
||||
testing_test_case 'Consistency', 'consistency06';
|
||||
|
||||
all_tags qw(ONE_SOA_MNAME
|
||||
NO_RESPONSE
|
||||
NO_RESPONSE_SOA_QUERY
|
||||
MULTIPLE_SOA_MNAMES);
|
||||
|
||||
# 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) ];
|
||||
|
||||
zone_name_template '{SCENARIO}.{TESTCASE}.xa';
|
||||
|
||||
scenario 'ONE-SOA-MNAME-1' => sub {
|
||||
expect 'ONE_SOA_MNAME';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ONE-SOA-MNAME-2' => sub {
|
||||
expect qw(ONE_SOA_MNAME NO_RESPONSE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ONE-SOA-MNAME-3' => sub {
|
||||
expect qw(ONE_SOA_MNAME NO_RESPONSE_SOA_QUERY);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ONE-SOA-MNAME-4' => sub {
|
||||
todo 'see https://github.com/zonemaster/zonemaster-engine/issues/1300';
|
||||
|
||||
expect qw(ONE_SOA_MNAME NO_RESPONSE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'MULTIPLE-SOA-MNAMES-1' => sub {
|
||||
expect 'MULTIPLE_SOA_MNAMES';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'MULTIPLE-SOA-MNAMES-2' => sub {
|
||||
expect qw(MULTIPLE_SOA_MNAMES NO_RESPONSE);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'MULT-SOA-MNAMES-NO-DEL-UNDEL-1' => sub {
|
||||
fake_ns 'ns1.mult-soa-mnames-no-del-undel-1.consistency06.xa' => '127.14.6.31', 'fda1:b2:c3:0:127:14:6:31';
|
||||
fake_ns 'ns2.mult-soa-mnames-no-del-undel-1.consistency06.xa' => '127.14.6.32', 'fda1:b2:c3:0:127:14:6:32';
|
||||
|
||||
expect 'MULTIPLE_SOA_MNAMES';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'MULT-SOA-MNAMES-NO-DEL-UNDEL-2' => sub {
|
||||
fake_ns 'ns3.mult-soa-mnames-no-del-undel-2.consistency06.xb';
|
||||
fake_ns 'ns4.mult-soa-mnames-no-del-undel-2.consistency06.xb';
|
||||
|
||||
expect 'MULTIPLE_SOA_MNAMES';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NO-RESPONSE' => sub {
|
||||
todo 'see https://github.com/zonemaster/zonemaster-engine/issues/1300';
|
||||
|
||||
expect 'NO_RESPONSE';
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
no_more_scenarios;
|
||||
122
zonemaster-engine/t/Test-delegation.data
Normal file
122
zonemaster-engine/t/Test-delegation.data
Normal file
File diff suppressed because one or more lines are too long
71
zonemaster-engine/t/Test-delegation.t
Normal file
71
zonemaster-engine/t/Test-delegation.t
Normal file
@@ -0,0 +1,71 @@
|
||||
use Test::More;
|
||||
use File::Slurp;
|
||||
|
||||
use List::MoreUtils qw[uniq none any];
|
||||
|
||||
BEGIN {
|
||||
use_ok( q{Zonemaster::Engine} );
|
||||
use_ok( q{Zonemaster::Engine::Test::Delegation} );
|
||||
use_ok( q{Zonemaster::Engine::Util} );
|
||||
}
|
||||
|
||||
my $datafile = q{t/Test-delegation.data};
|
||||
if ( not $ENV{ZONEMASTER_RECORD} ) {
|
||||
die q{Stored data file missing} if not -r $datafile;
|
||||
Zonemaster::Engine::Nameserver->restore( $datafile );
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
}
|
||||
|
||||
my ($json, $profile_test);
|
||||
$json = read_file( 't/profiles/Test-delegation-all.json' );
|
||||
$profile_test = Zonemaster::Engine::Profile->from_json( $json );
|
||||
Zonemaster::Engine::Profile->effective->merge( $profile_test );
|
||||
|
||||
my @res;
|
||||
my %res;
|
||||
|
||||
my $iis = Zonemaster::Engine->zone( q{iis.se} );
|
||||
%res = map { $_->tag => $_ } Zonemaster::Engine::Test::Delegation->all( $iis );
|
||||
ok( $res{NAMES_MATCH}, q{NAMES_MATCH} );
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{delegation}, q{crystone.se} );
|
||||
ok( $res{EXTRA_NAME_PARENT}, q{EXTRA_NAME_PARENT} );
|
||||
ok( $res{EXTRA_NAME_CHILD}, q{EXTRA_NAME_CHILD} );
|
||||
ok( $res{TOTAL_NAME_MISMATCH}, q{TOTAL_NAME_MISMATCH} );
|
||||
ok( $res{NO_NS_CNAME}, q{NO_NS_CNAME} );
|
||||
ok( $res{SOA_EXISTS}, q{SOA_EXISTS} );
|
||||
ok( $res{ARE_AUTHORITATIVE}, q{ARE_AUTHORITATIVE} );
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{delegation}, q{woli.se} );
|
||||
ok( $res{SOA_NOT_EXISTS}, q{SOA_NOT_EXISTS} );
|
||||
|
||||
TODO: {
|
||||
local $TODO = "Need to find domain name with that error";
|
||||
|
||||
%res = map { $_->tag => 1 } Zonemaster::Engine->test_module( q{delegation}, q{elsine.se} );
|
||||
ok( $res{IS_NOT_AUTHORITATIVE}, q{IS_NOT_AUTHORITATIVE} );
|
||||
|
||||
ok( $res{NS_IS_CNAME}, q{NS_IS_CNAME} );
|
||||
|
||||
}
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
Zonemaster::Engine::Nameserver->save( $datafile );
|
||||
}
|
||||
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 0 );
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 0 );
|
||||
Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 0 );
|
||||
@res = Zonemaster::Engine->test_method( 'Delegation', 'delegation04', Zonemaster::Engine->zone( q{iis.se} ) );
|
||||
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' );
|
||||
@res = Zonemaster::Engine->test_method( 'Delegation', 'delegation06', Zonemaster::Engine->zone( q{iis.se} ) );
|
||||
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{no_network}, 1 );
|
||||
|
||||
done_testing;
|
||||
64
zonemaster-engine/t/Test-delegation01.data
Normal file
64
zonemaster-engine/t/Test-delegation01.data
Normal file
File diff suppressed because one or more lines are too long
137
zonemaster-engine/t/Test-delegation01.t
Normal file
137
zonemaster-engine/t/Test-delegation01.t
Normal file
@@ -0,0 +1,137 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use lib dirname( rel2abs( $0 ) );
|
||||
use TestUtil::DSL;
|
||||
|
||||
###########
|
||||
# Delegation01 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Delegation-TP/delegation01.md
|
||||
testing_test_case 'Delegation', 'delegation01';
|
||||
|
||||
all_tags qw(ENOUGH_IPV4_NS_CHILD
|
||||
ENOUGH_IPV4_NS_DEL
|
||||
ENOUGH_IPV6_NS_CHILD
|
||||
ENOUGH_IPV6_NS_DEL
|
||||
ENOUGH_NS_CHILD
|
||||
ENOUGH_NS_DEL
|
||||
NOT_ENOUGH_IPV4_NS_CHILD
|
||||
NOT_ENOUGH_IPV4_NS_DEL
|
||||
NOT_ENOUGH_IPV6_NS_CHILD
|
||||
NOT_ENOUGH_IPV6_NS_DEL
|
||||
NOT_ENOUGH_NS_CHILD
|
||||
NOT_ENOUGH_NS_DEL
|
||||
NO_IPV4_NS_CHILD
|
||||
NO_IPV4_NS_DEL
|
||||
NO_IPV6_NS_CHILD
|
||||
NO_IPV6_NS_DEL);
|
||||
|
||||
# Specific hint file (https://github.com/zonemaster/zonemaster/blob/master/test-zone-data/Delegation-TP/delegation01/hintfile.zone)
|
||||
root_hints 'root-ns1.xa' => [ qw(127.16.1.27 fda1:b2:c3::127:16:1:27) ],
|
||||
'root-ns2.xa' => [ qw(127.16.1.28 fda1:b2:c3::127:16:1:28) ];
|
||||
|
||||
zone_name_template '{SCENARIO}.{TESTCASE}.xa';
|
||||
|
||||
# Test zone scenarios
|
||||
scenario 'ENOUGH-{1..3}' => sub {
|
||||
expect qw(ENOUGH_IPV4_NS_CHILD
|
||||
ENOUGH_IPV4_NS_DEL
|
||||
ENOUGH_IPV6_NS_CHILD
|
||||
ENOUGH_IPV6_NS_DEL
|
||||
ENOUGH_NS_CHILD
|
||||
ENOUGH_NS_DEL);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ENOUGH-DEL-NOT-CHILD' => sub {
|
||||
todo; # FIXME: why?
|
||||
|
||||
expect qw(ENOUGH_IPV4_NS_DEL
|
||||
ENOUGH_IPV6_NS_DEL
|
||||
ENOUGH_NS_DEL
|
||||
NOT_ENOUGH_IPV4_NS_CHILD
|
||||
NOT_ENOUGH_IPV6_NS_CHILD
|
||||
NOT_ENOUGH_NS_CHILD);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'ENOUGH-CHILD-NOT-DEL' => sub {
|
||||
expect qw(ENOUGH_IPV4_NS_CHILD
|
||||
ENOUGH_IPV6_NS_CHILD
|
||||
ENOUGH_NS_CHILD
|
||||
NOT_ENOUGH_IPV4_NS_DEL
|
||||
NOT_ENOUGH_IPV6_NS_DEL
|
||||
NOT_ENOUGH_NS_DEL);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IPV6-AND-DEL-OK-NO-IPV4-CHILD' => sub {
|
||||
todo; # FIXME: why?
|
||||
|
||||
expect qw(ENOUGH_IPV4_NS_DEL
|
||||
ENOUGH_IPV6_NS_CHILD
|
||||
ENOUGH_IPV6_NS_DEL
|
||||
ENOUGH_NS_CHILD
|
||||
ENOUGH_NS_DEL
|
||||
NO_IPV4_NS_CHILD);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'IPV4-AND-DEL-OK-NO-IPV6-CHILD' => sub {
|
||||
todo; # FIXME: why?
|
||||
|
||||
expect qw(ENOUGH_IPV4_NS_DEL
|
||||
ENOUGH_IPV4_NS_CHILD
|
||||
ENOUGH_IPV6_NS_DEL
|
||||
ENOUGH_NS_CHILD
|
||||
ENOUGH_NS_DEL
|
||||
NO_IPV6_NS_CHILD);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NO-IPV4-{1..3}' => sub {
|
||||
expect qw(ENOUGH_IPV6_NS_CHILD
|
||||
ENOUGH_IPV6_NS_DEL
|
||||
ENOUGH_NS_CHILD
|
||||
ENOUGH_NS_DEL
|
||||
NO_IPV4_NS_CHILD
|
||||
NO_IPV4_NS_DEL);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'NO-IPV6-{1..3}' => sub {
|
||||
expect qw(ENOUGH_IPV4_NS_CHILD
|
||||
ENOUGH_IPV4_NS_DEL
|
||||
ENOUGH_NS_CHILD
|
||||
ENOUGH_NS_DEL
|
||||
NO_IPV6_NS_CHILD
|
||||
NO_IPV6_NS_DEL);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'MISMATCH-DELEGATION-CHILD-1' => sub {
|
||||
todo; # FIXME: why?
|
||||
|
||||
expect qw(ENOUGH_IPV4_NS_CHILD
|
||||
NOT_ENOUGH_IPV4_NS_DEL
|
||||
ENOUGH_IPV6_NS_CHILD
|
||||
NOT_ENOUGH_IPV6_NS_DEL
|
||||
ENOUGH_NS_CHILD
|
||||
ENOUGH_NS_DEL);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
scenario 'MISMATCH-DELEGATION-CHILD-2' => sub {
|
||||
todo; # FIXME: why?
|
||||
|
||||
expect qw(NOT_ENOUGH_IPV4_NS_CHILD
|
||||
ENOUGH_IPV4_NS_DEL
|
||||
NOT_ENOUGH_IPV6_NS_CHILD
|
||||
ENOUGH_IPV6_NS_DEL
|
||||
ENOUGH_NS_CHILD
|
||||
ENOUGH_NS_DEL);
|
||||
forbid_others;
|
||||
};
|
||||
|
||||
no_more_scenarios;
|
||||
63
zonemaster-engine/t/Test-delegation02.data
Normal file
63
zonemaster-engine/t/Test-delegation02.data
Normal file
File diff suppressed because one or more lines are too long
170
zonemaster-engine/t/Test-delegation02.t
Normal file
170
zonemaster-engine/t/Test-delegation02.t
Normal file
@@ -0,0 +1,170 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Test::More;
|
||||
use File::Basename;
|
||||
use File::Spec::Functions qw( rel2abs );
|
||||
use lib dirname( rel2abs( $0 ) );
|
||||
|
||||
BEGIN {
|
||||
use_ok( q{Zonemaster::Engine} );
|
||||
use_ok( q{Zonemaster::Engine::Test::Delegation} );
|
||||
use_ok( q{TestUtil}, qw( perform_testcase_testing ) );
|
||||
}
|
||||
|
||||
###########
|
||||
# Delegation02 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Delegation-TP/delegation02.md
|
||||
my $test_module = 'Delegation';
|
||||
my $test_case = 'delegation02';
|
||||
my @all_tags = qw(
|
||||
DEL_DISTINCT_NS_IP
|
||||
CHILD_DISTINCT_NS_IP
|
||||
DEL_NS_SAME_IP
|
||||
CHILD_NS_SAME_IP
|
||||
);
|
||||
|
||||
# Specific hint file (https://github.com/zonemaster/zonemaster/blob/master/test-zone-data/Delegation-TP/delegation02/hintfile.zone)
|
||||
Zonemaster::Engine::Recursor->remove_fake_addresses( '.' );
|
||||
Zonemaster::Engine::Recursor->add_fake_addresses( '.',
|
||||
{ 'root-ns1.xa' => [ '127.16.2.27', 'fda1:b2:c3::127:16:2:27' ],
|
||||
'root-ns2.xa' => [ '127.16.2.28', 'fda1:b2:c3::127:16:2:28' ],
|
||||
}
|
||||
);
|
||||
|
||||
# Test zone scenarios
|
||||
# - Documentation: L<TestUtil/perform_testcase_testing()>
|
||||
# - Format: { SCENARIO_NAME => [
|
||||
# testable,
|
||||
# zone_name,
|
||||
# [ MANDATORY_MESSAGE_TAGS ],
|
||||
# [ FORBIDDEN_MESSAGE_TAGS ],
|
||||
# [ UNDELEGATED_NS ],
|
||||
# [ UNDELEGATED_DS ],
|
||||
# ] }
|
||||
#
|
||||
# - One of MANDATORY_MESSAGE_TAGS and FORBIDDEN_MESSAGE_TAGS may be undefined.
|
||||
# See documentation for the meaning of that.
|
||||
|
||||
my %subtests = (
|
||||
'ALL-DISTINCT-1' =>
|
||||
[
|
||||
1,
|
||||
q(all-distinct-1.delegation02.xa),
|
||||
[ qw( DEL_DISTINCT_NS_IP CHILD_DISTINCT_NS_IP ) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'ALL-DISTINCT-2' =>
|
||||
[
|
||||
1,
|
||||
q(all-distinct-2.delegation02.xa),
|
||||
[ qw( DEL_DISTINCT_NS_IP CHILD_DISTINCT_NS_IP ) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'ALL-DISTINCT-3' =>
|
||||
[
|
||||
1,
|
||||
q(all-distinct-3.delegation02.xa),
|
||||
[ qw( DEL_DISTINCT_NS_IP CHILD_DISTINCT_NS_IP ) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'DEL-NON-DISTINCT' =>
|
||||
[
|
||||
0,
|
||||
q(del-non-distinct.delegation02.xa),
|
||||
[ qw( DEL_NS_SAME_IP CHILD_DISTINCT_NS_IP ) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'DEL-NON-DISTINCT-UND' =>
|
||||
[
|
||||
1,
|
||||
q(del-non-distinct.delegation02.xa),
|
||||
[ qw( DEL_NS_SAME_IP CHILD_DISTINCT_NS_IP ) ],
|
||||
undef,
|
||||
[ qw(
|
||||
ns1a.del-non-distinct-und.delegation02.xa/127.16.2.31
|
||||
ns1a.del-non-distinct-und.delegation02.xa/fda1:b2:c3:0:127:16:2:31
|
||||
ns1b.del-non-distinct-und.delegation02.xa/127.16.2.31
|
||||
ns1b.del-non-distinct-und.delegation02.xa/fda1:b2:c3:0:127:16:2:31
|
||||
)
|
||||
],
|
||||
[],
|
||||
],
|
||||
'CHILD-NON-DISTINCT' =>
|
||||
[
|
||||
0,
|
||||
q(child-non-distinct.delegation02.xa),
|
||||
[ qw( DEL_DISTINCT_NS_IP CHILD_NS_SAME_IP ) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'CHILD-NON-DISTINCT-UND' =>
|
||||
[
|
||||
1,
|
||||
q(child-non-distinct.delegation02.xa),
|
||||
[ qw( DEL_DISTINCT_NS_IP CHILD_NS_SAME_IP ) ],
|
||||
undef,
|
||||
[ qw(
|
||||
ns1a.child-non-distinct-und.delegation02.xa/127.16.2.31
|
||||
ns1a.child-non-distinct-und.delegation02.xa/fda1:b2:c3:0:127:16:2:31
|
||||
ns1b.child-non-distinct-und.delegation02.xa/127.16.2.32
|
||||
ns1b.child-non-distinct-und.delegation02.xa/fda1:b2:c3:0:127:16:2:32
|
||||
)
|
||||
],
|
||||
[],
|
||||
],
|
||||
'NON-DISTINCT-1' =>
|
||||
[
|
||||
1,
|
||||
q(non-distinct-1.delegation02.xa),
|
||||
[ qw( DEL_NS_SAME_IP CHILD_NS_SAME_IP ) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'NON-DISTINCT-2' =>
|
||||
[
|
||||
1,
|
||||
q(non-distinct-2.delegation02.xa),
|
||||
[ qw( DEL_NS_SAME_IP CHILD_NS_SAME_IP ) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
'NON-DISTINCT-3' =>
|
||||
[
|
||||
1,
|
||||
q(non-distinct-3.delegation02.xa),
|
||||
[ qw( DEL_NS_SAME_IP CHILD_NS_SAME_IP ) ],
|
||||
undef,
|
||||
[],
|
||||
[],
|
||||
],
|
||||
);
|
||||
###########
|
||||
|
||||
my $datafile = 't/' . basename ($0, '.t') . '.data';
|
||||
|
||||
if ( not $ENV{ZONEMASTER_RECORD} ) {
|
||||
die q{Stored data file missing} if not -r $datafile;
|
||||
Zonemaster::Engine::Nameserver->restore( $datafile );
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
}
|
||||
|
||||
Zonemaster::Engine::Profile->effective->merge( Zonemaster::Engine::Profile->from_json( qq({ "test_cases": [ "$test_case" ] }) ) );
|
||||
|
||||
perform_testcase_testing( $test_case, $test_module, \@all_tags, \%subtests, $ENV{ZONEMASTER_SELECTED_SCENARIOS}, $ENV{ZONEMASTER_DISABLED_SCENARIOS} );
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
Zonemaster::Engine::Nameserver->save( $datafile );
|
||||
}
|
||||
|
||||
done_testing;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user