Package: dns-sprockets
Overviewdns_sprocketsis a command-line framework for loading and validating DNS zones.
It is written in Python and uses the excellentdnspythonlibrary for much of its functionality.“Why call it dns_sprockets?”  Well, if you think of a DNS zone as being a chain
of resource records (thinking especially of NSEC3 records), this application is a
set of sprockets that tests every link of the chain.Hey, it kind of works ;)AudiencePossible users include DNS code developers and quality assurance, internet
customer service, system administrators, and end-users who are interested to
know if their DNS zones are valid.FeaturesThe command-line tool returns useful return codes, supporting automated build
systems.  It is built around the concept of plug-ins for implementing both the
zone loading and zone validating functions, and easily allows end-users the
ability to define new loaders and validators.Zones may be loaded using various means.  The framework supports “loader”
plug-ins that pull DNS zone data from any source.  Initially provided are
‘File’ and ‘Xfr’ plugins to pull zone data from host files and XFR servers,
respectively.Validations are modular pieces of code that may be selectively enabled.  The
framework supports “validator” plug-ins that operate in one of four views:Zone: Some aspect of the entire zone is validated.Node: Every node (i.e. list of RRSets with the same owner name) can be validated.RRSet: Every RRSet can be validated.Record: Every DNS record can be validated.Initially provided in this package are some basic zone validators, and a
fairly complete set of DNSSEC-type zone validators.The Node, RRSet, and Record views may be optionally filtered by one or more
resource record types, to simplify and focus the validation code.  Additionally,
each validator can be marked to run for non-DNSSEC zones, NSEC1-style DNSSEC
zones, or NSEC3-style DNSSEC zones.InstallationEasy!  Use pip (preferably in a virtual python environment):$ pip install dns_sprocketsUsageOnce installed, simply issue the dns_sprockets command.  For example to get a
usage message:$ dns_sprockets --help

# dns_sprockets (1.1.8) - A DNS Zone validation tool
usage: dns_sprockets.py [-h] [-z s] [-l s] [-s s] [-i s] [-x s] [-d s] [-f s]
                        [-e] [-v]

optional arguments:
  -h, --help            show this help message and exit
  -z s, --zone s        Name of zone to validate [www.ultradns.com]
  -l s, --loader s      Zone loader method to use (one of: file, xfr) [xfr]
  -s s, --source s      Loader source to use [127.0.0.1#53]
  -i s, --include s     Only include this validator (can use multiple times)
  -x s, --exclude s     Exclude this validator (can use multiple times)
  -d s, --define s      Define other params (can use multiple times)
  -f s, --force-dnssec-type s
                        Use DNSSEC type (one of: detect, unsigned, NSEC,
                        NSEC3) [detect]
  -e, --errors-only     Show validation errors only [False]
  -v, --verbose         Show detailed processing info [False]

Use @filename to read some/all arguments from a file.

Use -d's to define optional, module-specific parameters if desired (e.g. to tell
'xfr' loader to use a specific source address, use "-d xfr_source=1.2.3.4").
The optional parameters are listed under each loader and validator description
in DEFINE lines, if available.  The "short form" of parameter names (i.e.
without the module name prefix) may be used instead, if multiple modules define
the same "short form" name (e.g. -d now=20160328120000 can be used, and will set
both 'rrsig_missing' and 'rrsig_orphan' validators' rrsig_missing_now and
rrsig_orphan_now values, respectively).

By default, all tests are run.  Use -i's to explicitly specify desired tests,
or -x's to eliminate undesired tests.

The list of available loaders is:
---------------------------------------------------------------------------
LOADER: file - Loads a zone from a file in AXFR-type or Bind host-type format.
    DEFINE: (file_)allow_include - Allow file to include other files (default=1)
    DEFINE: (file_)rdclass - Class of records to pull (default=IN)
LOADER: xfr - Loads a zone by XFR from a name server.
    DEFINE: (xfr_)af - The address family to use, AF_INET or AF_INET6 (default=None)
    DEFINE: (xfr_)keyalgorithm - The TSIG algorithm to use, one of: HMAC-MD5.SIG-ALG.REG.INT. hmac-sha1. hmac-sha224. hmac-sha256. hmac-sha384. hmac-sha512. (default=HMAC-MD5.SIG-ALG.REG.INT.)
    DEFINE: (xfr_)keyname - The name of the TSIG to use (default=None)
    DEFINE: (xfr_)keyring - The TSIG keyring to use, a text dict of name->base64_secret e.g. "{'n1':'H477A900','n2':'K845CL21'}" (default=None)
    DEFINE: (xfr_)lifetime - Total seconds to wait for complete transfer (default=None)
    DEFINE: (xfr_)rdclass - Class of records to pull (default=IN)
    DEFINE: (xfr_)rdtype - Type of XFR to perform, AXFR or IXFR (default=AXFR)
    DEFINE: (xfr_)serial - SOA serial number to use as base for IXFR diff (default=0)
    DEFINE: (xfr_)source - Source address for the transfer (default=None)
    DEFINE: (xfr_)source_port - Source port for the transfer (default=0)
    DEFINE: (xfr_)timeout - Seconds to wait for each response message (default=5.0)
    DEFINE: (xfr_)use_udp - Use UDP for IXFRing (default=0)

The list of available validators is:
---------------------------------------------------------------------------
TEST: dnskey_bits (REC_TEST[DNSKEY]) - Checks DNSKEY flags and protocol.
TEST: dnskey_origin (ZONE_TEST) - Checks for a ZSK at zone origin.
TEST: dnssectype_ambiguous (ZONE_TEST) - Checks for existence of both NSEC and NSEC3 in the zone.
TEST: ns_origin (ZONE_TEST) - Checks for at least one NS at zone origin.
TEST: nsec3_chain (ZONE_TEST) - Checks for valid NSEC3 chain.
TEST: nsec3_missing (RRSET_TEST) - Checks that all (non-NSEC3/RRSIG, non-delegated) RRSets are covered with an NSEC3.
TEST: nsec3_orphan (REC_TEST[NSEC3]) - Checks for orphan or invalid-covers NSEC3s.
TEST: nsec3param_origin (ZONE_TEST) - Checks for an NSEC3PARAM at zone origin for nsec3-type zones.
TEST: nsec_chain (ZONE_TEST) - Checks for valid NSEC chain.
TEST: nsec_missing (RRSET_TEST) - Checks that all (non-NSEC/RRSIG, non-delegated) RRSets are covered with an NSEC.
TEST: nsec_orphan (REC_TEST[NSEC]) - Checks for orphan or invalid-covers NSECs.
TEST: nsecx_ttls_match (REC_TEST[NSEC,NSEC3]) - Checks that NSECx TTL's match SOA's minimum.
TEST: rrsig_covers (REC_TEST[RRSIG]) - Checks RRSIG's don't cover RRSIG's.
TEST: rrsig_missing (RRSET_TEST) - Checks that all (non-RRSIG, non-delegated) RRSets are covered with an RRSIG.
    DEFINE: (rrsig_missing_)now - Time to use for validating RRSIG time windows, e.g. 20150101123000 (default=None)
    DEFINE: (rrsig_missing_)now_offset - Number of seconds to offset the "now" value, e.g. -86400) (default=None)
TEST: rrsig_orphan (REC_TEST[RRSIG]) - Checks for orphan RRSIGs.
    DEFINE: (rrsig_orphan_)now - Time to use for validating RRSIG time windows, e.g. 20150101123000 (default=None)
    DEFINE: (rrsig_orphan_)now_offset - Number of seconds to offset the "now" value, e.g. -86400) (default=None)
TEST: rrsig_signer_match (REC_TEST[RRSIG]) - Checks RRSIG signers match the zone.
TEST: rrsig_time (REC_TEST[RRSIG]) - Checks RRSIG's inception <= expiration.
TEST: rrsig_ttls_match (REC_TEST[RRSIG]) - Checks RRSIG TTL's match original and covered TTL's.
TEST: soa_origin (ZONE_TEST) - Checks for an SOA at zone origin.
TEST: soa_unique (ZONE_TEST) - Checks for a single SOA in the zone.Sample UsageLet’s say you want to validate and only see errors an NSEC3-style DNSSEC zone
called “example”, from a file, and wish to run all available/applicable validations.
Since this will check RRSIG signatures, you’ll need to add a few defines to properly
state the “now” time to use for two of the validators.  Assuming a bash-like shell:$ ZONE_FILE=$VIRTUAL_ENV/lib/python2.7/site-packages/dns_sprockets_lib/tests/data/rfc5155_example.

$ TIME_NOW=20100101000000

$ dns_sprockets -z example -l file -s $ZONE_FILE -e \
    -d rrsig_missing_now=$TIME_NOW -d rrsig_orphan_now=$TIME_NOW

# dns_sprockets (1.0.0) - A DNS Zone validation tool
# Checking zone: example.
# Loader: file from: rfc5155_example. elapsed=0.018354 secs
# Zone appears to be DNSSEC type: NSEC3
# Extra defines: ['rrsig_missing_now=20100101000000', 'rrsig_orphan_now=20100101000000']
# Skipping test: nsec_chain  (DNSSEC type for zone: NSEC3, for test: NSEC)
# Skipping test: nsec_missing  (DNSSEC type for zone: NSEC3, for test: NSEC)
# Skipping test: nsec_orphan  (DNSSEC type for zone: NSEC3, for test: NSEC)
# Running tests: ['dnskey_origin', 'dnssectype_ambiguous', 'ns_origin', 'nsec3_chain', 'nsec3param_origin', 'soa_origin', 'soa_unique', 'nsec3_missing', 'rrsig_missing', 'dnskey_bits', 'nsec3_orphan', 'nsecx_ttls_match', 'rrsig_covers', 'rrsig_orphan', 'rrsig_signer_match', 'rrsig_time', 'rrsig_ttls_match']
# END RESULT: 0 ERRORS in 229 tests
# TOTAL ELAPSED TIME: 0.063526 SECS  LOAD TIME: 0.018354 SECS  TEST TIME: 0.045172 SECS

$ echo $?
0UPDATENew in version 1.1.8: Can now just specify “-d now=$TIME_NOW” as a
shortcut for “-d rrsig_missing_now=$TIME_NOW -d rrsig_orphan_now=$TIME_NOW”OK, all tests passed, but that’s not too interesting.  Let’s repeat that, except
with a slightly modified zone file: one of the NSEC3’s (and its associated RRSIG
record) has been removed:$ ZONE_FILE=$VIRTUAL_ENV/lib/python2.7/site-packages/dns_sprockets_lib/tests/data/rfc5155_example._nsec3_missing

$ dns_sprockets -z example -l file -s $ZONE_FILE -e \
    -d rrsig_missing_now=$TIME_NOW -d rrsig_orphan_now=$TIME_NOW

# dns_sprockets (1.0.0) - A DNS Zone validation tool
# Checking zone: example.
# Loader: file from: dns_sprockets_lib/tests/data/rfc5155_example._nsec3_missing elapsed=0.023993 secs
# Zone appears to be DNSSEC type: NSEC3
# Extra defines: ['rrsig_missing_now=20100101000000', 'rrsig_orphan_now=20100101000000']
# Skipping test: nsec_chain  (DNSSEC type for zone: NSEC3, for test: NSEC)
# Skipping test: nsec_missing  (DNSSEC type for zone: NSEC3, for test: NSEC)
# Skipping test: nsec_orphan  (DNSSEC type for zone: NSEC3, for test: NSEC)
# Running tests: ['dnskey_origin', 'dnssectype_ambiguous', 'ns_origin', 'nsec3_chain', 'nsec3param_origin', 'soa_origin', 'soa_unique', 'nsec3_missing', 'rrsig_missing', 'dnskey_bits', 'nsec3_orphan', 'nsecx_ttls_match', 'rrsig_covers', 'rrsig_orphan', 'rrsig_signer_match', 'rrsig_time', 'rrsig_ttls_match']
TEST nsec3_chain(ZONE(example. IN)) => FAIL: Chain broken at R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN (next=T644EBQK9BIBCNA874GIVR6JOJ62MLHV doesn't exist)
TEST nsec3_missing(RRSET(xx.example. IN A)) => FAIL: No NSEC3's found for name: t644ebqk9bibcna874givr6joj62mlhv.example.
TEST nsec3_missing(RRSET(xx.example. IN HINFO)) => FAIL: No NSEC3's found for name: t644ebqk9bibcna874givr6joj62mlhv.example.
TEST nsec3_missing(RRSET(xx.example. IN AAAA)) => FAIL: No NSEC3's found for name: t644ebqk9bibcna874givr6joj62mlhv.example.
# END RESULT: 4 ERRORS in 221 tests
# TOTAL ELAPSED TIME: 0.064603 SECS  LOAD TIME: 0.023993 SECS  TEST TIME: 0.040610 SECS

$ echo $?
4This time, we get errors from two validators.  The nsec3_chain validator issues a
“chain broken” error, and the nsec3_missing validator sees three RRSet’s with the
same owner name that are “not covered” by the missing NSEC3.Incidentally, these two data files (and others) are included in the package for
unit testing purposes, but can be useful to play with to see how dns_sprockets
reports various problems.Return CodesThe application returns a numerical value back to the user:0If there were no failed validations.1-254The number of failed validations, up to a limit of 254.255A special code for fatal exceptions.TO-DO’sThe following is a non-exhaustive list of things to do (help anyone?):Respect the “opt-out” flag in NSEC3 records; right now, assuming none are opt-out.More loaders and (especially) validators!More real-world trials.Developer InformationThislongsection discusses dns_sprockets for those who may be interested in
adding more loaders or validators.  If that’s you, great!  Please consider
contributing your work to the project, it is most welcome!  Especially welcome
are unit tests that accompany any new code!  (currently using Nose for testing).Framework ArchitectureInspiration for this application comes from a similar tool written in Perl calleddonuts.  It too uses the concept of plugins for
its validators.This framework essentially revolves around the two types of plugins: Loaders and
validator plugins, which are stored in two project subfolders
(dns_sprockets_lib/loaders and dns_sprockets_lib/validators, respectively).  At
runtime, the app scans both folders and makes their contents available for use:A note on the naming conventions: plugins are stored in files with
underscore-style names (e.g. nsec3_chain.py) and are expected to
contain a class that implements the plugin, with a camelcase-style
name that corresponds to the file name (e.g. Nsec3Chain).The main logic of the app resides in the DNSSprocketsImpl.run() method (in
dns_sprockets_lib/dns_sprockets_impl.py).  Pseudo-code is:Scan zone loaders and load them into memory as Python classes.Create an instance of the specified zone loader.Scan validators and load them into memory as Python classes.Instantiate specified validators and categorize by validator type.Run the specified zone loader instance to obtain a dns.zone.Zone object.Construct a “Context” instance, initialized by the dns.zone.Zone object.Filter-out any validator instances that do not make sense for the DNSSEC type of the zone.Run the zone-type validators against the Context.Iterate Nodes in the zone object:Run the node-type validators against the Context and Node.Iterate RRSets in the Node:Run the RRSet-type validators against the Context and RRSet.Iterate Records in the RRSet:Run the record-type validators against the Context and Record.The use of thednspythonlibrary pervades the
application (so if you’re familiar with it already, you’ve got an excellent start):
The loaders read from some source and return a dnspython dns.zone.Zone object to
the framework.  Similarly, the framework presents to the validators the same
dns.zone.Zone object for examination.Zone LoadersZone loaders are classes derived from dns_sprockets_lib.loaders.ZoneLoader
(in the dns_sprockets_lib/loaders/__init__.py file), which defines the interface
expected by the framework:class ZoneLoader(object):
    '''
    [Base class for zone loaders]
    '''
    LOADER_NAME = None  # Automatically set in __init__.
    LOADER_OPTARGS = {}  # Override possible!  e.g.: {'now': (None, 'Time to use for now')}

    def __init__(self, args):
        '''
        Ctor, caches the arguments used to run the application, and grabs any
        optional test arguments.
        '''
        self.LOADER_NAME = utils.camelcase_to_underscores(self.__class__.__name__)
        self.args = args

        utils.process_optargs(self.LOADER_OPTARGS, self.LOADER_NAME, self)

    def run(self):
        '''
        Runs the zone loader -- must override!

        :return: A dns.zone.Zone instance.
        '''
        passTwo class variables are expected:LOADER_NAMEContains the underscore-style name of the loader, and is
automatically set up in the __init__() method.LOADER_OPTARGSContains any plugin-specific parameters that may be set from
the command-line…more on this later.Two methods are expected:__init__()Takes the arguments object containing the command-line
options passed by the user to the application.run()Invokes the zone loader functionality and returns a dns.zone.Zone
object.As an example, the code for the File loader is show here.  It is almost trivial
because it takes advantage of the built-in host file loading available in the
dnspython library:class File(loaders.ZoneLoader):
    '''
    Loads a zone from a file in AXFR-type or Bind host-type format.
    '''
    LOADER_OPTARGS = {
        'rdclass': ('IN', 'Class of records to pull'),
        'allow_include': ('1', 'Allow file to include other files')}

    def __init__(self, args):
        '''
        Ctor.
        '''
        self.rdclass = None
        self.allow_include = None
        super(File, self).__init__(args)

    def run(self):
        '''
        :return: A dns.zone.Zone instance.
        '''
        other_args = {
            'origin': self.args.zone,
            'relativize': False,
            'filename': self.args.source,
            'check_origin': False,
            'rdclass': dns.rdataclass.from_text(self.rdclass),
            'allow_include': bool(int(self.allow_include))}

        return dns.zone.from_file(self.args.source, **other_args)Please note the __init__() method calls back into the base class to include its
useful and necessary functionality!  Also be aware that the class docstring is
used for the description of the loader, as shown in the –help output (keep it
brief!)Validation ContextOnce the framework obtains a dns.zone.Zone instance from the specified zone
loader, it constructs a Context instance from it, which is passed to the
validators.  In addition to the application’s command-line arguments (ascontext.args) and the actual dns.zone.Zone instance created by the loader
(ascontext.zone_obj), it contains some other attributes for the convenience
of validators (code for the Context class can be found in the
dns_sprockets_lib/validators/__init__.py file).  Some of these are useful to
some validators, but can be ignored if not useful:context.node_namesContains DNSSEC-ordered list of all node names present
in the zone (includingempty-non-terminal names implied by wildcard names).context.soa_rdatasetContains the zone’s SOA RRSet.context.dnskey_rdatasetContains the zone’s DNSKEY RRSet.context.nsec3param_rdatasetContains the zone’s NSEC3PARAM RRSet.context.delegated_namesContains list of any delegated names in the zone.context.dnssec_typeIndicates the DNSSEC type of the zone.A method calledis_delegated()is also available, which lets clients easily
determine if a given owner name is delegated.ValidatorsValidators are classesultimatelyderived from dns_sprockets_lib.validators._Validator
(in the dns_sprockets_lib/validators/__init__.py file).  This is the base class
for the four more specialized validator classes (ZoneTest, NodeTest, RRSetTest, and
RecordTest):class _Validator(object):
    '''
    [Base class for validator classes]
    '''
    TEST_NAME = None  # Automatically set in __init__.
    TEST_TYPE = None  # Override expected!  e.g.: ZONE_TEST
    TEST_DNSSECTYPE = None  # Override possible!  one of: None, True, 'NSEC' or 'NSEC3'
    TEST_RRTYPE = None  # Override possible!  e.g.: 'A', or 'RRSIG,NSEC3PARAM'
    TEST_OPTARGS = {}  # Override possible!  e.g.: {'now': (None, 'Time to use for now')}

    def __init__(self, args):
        '''
        Ctor, caches the arguments used to run the application, and grabs any
        optional test arguments.
        '''
        self.TEST_NAME = utils.camelcase_to_underscores(self.__class__.__name__)
        self.args = args

        utils.process_optargs(self.TEST_OPTARGS, self.TEST_NAME, self)Five class variables are expected:TEST_NAMEContains the underscore-style name of the validator, and is
automatically set up in the __init__() method.TEST_TYPEIndicates the type of validator.TEST_DNSSECTYPEIndicates the DNSSEC-type of the validator.TEST_RRTYPEIndicates zero or more resource record types the validator is
specialized for.  If no types specified, ALL types are assumed.TEST_OPTARGSContains any plugin-specific parameters that may be set from
the command-line…more on this later.One method is provided:__init__()Convenince method for use by sub-classes.There are four _Validator-derived classes for use by plugins (also defined in the
dns_sprockets_lib/validators/__init__.py file).  They provide slight convenience
by definingTEST_TYPEproperly, but more importantly expose differentrun()signatures, specific to each type of validator:class ZoneTest(_Validator):
    '''
    [Base class for zone-type validators]
    '''
    TEST_TYPE = ZONE_TEST

    def run(self, suggested_tested, context):
        '''
        Runs the zone-type validator.

        :param str suggested_tested: A suggested tested value.
        :param obj context: The testing context.
        :return: A tuple (tested, result)
        '''
        return ('OOPS!', 'ERROR: run() not overridden for %s' % (self.TEST_NAME))


class NodeTest(_Validator):
    '''
    [Base class for node-type validators.  Derived classes *may* be restricted
    to specific RRType's by specifying a TEST_RRTYPE]
    '''
    TEST_TYPE = NODE_TEST

    def run(self, context, suggested_tested, name, node):
        '''
        Runs the node-type validator.  If a TEST_RRTYPE specified, the node
        presented to the validator will be filtered accordingly.

        :param obj context: The testing context.
        :param str suggested_tested: A suggested tested value.
        :param str name: The name being tested.
        :param obj node: The dns.Node corresponding to the name.
        :return: A tuple (tested, result)
        '''
        return ('OOPS!', 'ERROR: run() not overridden for %s' % (self.TEST_NAME))


class RRSetTest(_Validator):
    '''
    [Base class for rrset-type validators.  Derived classes *may* be restricted
    to specific RRType's by specifying a TEST_RRTYPE]
    '''
    TEST_TYPE = RRSET_TEST

    def run(self, context, suggested_tested, name, rdataset):
        '''
        Runs the name-type validator.  If a TEST_RRTYPE is specified, the RRSet
        presented to the validator will be filtered accordingly.

        :param obj context: The testing context.
        :param str suggested_tested: A suggested tested value.
        :param str name: The name being tested.
        :param obj rdataset: The dns.rdataset corresponding to the name.
        :return: A tuple (tested, result)
        '''
        return ('OOPS!', 'ERROR: run() not overridden for %s' % (self.TEST_NAME))


class RecTest(_Validator):
    '''
    [Base class for record-type validators.  Derived classes *may* be restricted
    to specific RRType's by specifying a TEST_RRTYPE]
    '''
    TEST_TYPE = REC_TEST

    def run(self, context, suggested_tested, name, ttl, rdata):
        '''
        Runs the record-type validator.  If a TEST_RRTYPE is specified, the
        validator will only see those types of records.

        :param obj context: The testing context.
        :param str suggested_tested: A suggested tested value.
        :param str name: The name of the record being tested.
        :param int ttl: The TTL of the record being tested.
        :param obj rdata: The dns.rdata.Rdata object being tested.
        :return: A tuple (tested, result)
        '''
        return ('OOPS!', 'ERROR: run() not overridden for %s' % (self.TEST_NAME))Thesuggested_testedstring contains a default name of the object being tested,
be it a zone, node, RRSet or record.  It can be used in most instances as the first
item in the returned tuple fromrun():Notes on the run() return tuple (tested, result):

- If a validation is skipped for whatever reason, the 'tested'
  value should be None, which causes the framework to ignore the
  run.  Otherwise, a value describing the object being tested
  should be set (and as mentioned 'suggested_tested' is a good
  value).

- The actual result of an un-skipped test is returned in
  'result'.  If the test passes, simply return None.  Otherwise,
  return a string describing the failure.As an example, the code for the RrsigTime validator is as follows.  TheTEST_DNSSECTYPEis set to True to indicate the validation only makes sense
for DNSSEC-type zones.  It is a record-type test, and only receives RRSIG records
due to theTEST_RRTYPEfiltering applied.  The “context”, “name” and “ttl”
parameters are ignored for this validation.  The “rdata” parameter is used,
and is of type dns.rdata.Rdata (a type defined in dnspython):class RrsigTime(validators.RecTest):
    '''
    Checks RRSIG's inception <= expiration.
    '''
    TEST_DNSSECTYPE = True
    TEST_RRTYPE = 'RRSIG'

    def run(self, context, suggested_tested, name, ttl, rdata):

        result = None
        if rdata.inception > rdata.expiration:
            result = 'Inception time greater than expiration time'
        return (suggested_tested, result)Please note that if your validator needs to define an__init__()method,
it must call the base’s__init__()to receive its useful and necessary
functionality!  Also be aware that the class docstring is used for the
description of the validator, as shown in the –help output (keep it brief!)Plugin-Specific ArgumentsLoaders and validators may have parameters that are specific to themselves.
The framework’s –define command-line switch is used to pass these parameters
to the plugins.The names of the –define parameters are of the form: <pluginname>_<paramname>
(e.g. “rrsig_missing_now” specifies the “now” parameter for the rrsig_missing
validator), and are translated and set as plugin attributes as <paramname>
(e.g. self.now in rrsig_missing methods).As an example (and shown earlier), the File zone loader plugin defines two
parameters specific to loading zone files:LOADER_OPTARGS = {
    'rdclass': ('IN', 'Class of records to pull'),
    'allow_include': ('1', 'Allow file to include other files')}LOADER_OPTARGS(andTEST_OPTARGSfor validators) is a dictionary of
parameter descriptors; each entry is keyed by <paramname>, and indexes a 2-tuple
of (<defaultvalue>, <description>).  If no –define for the parameter is passed,
the <defaultvalue> will be set.  The <description> is used for –help output,
so keep it brief please!