Compare commits

..
Author SHA1 Message Date
Mark Andrews 5c7397b9cb Return raw zone serial for inline zones 2025-03-24 17:32:52 +11:00
Mark Andrews a5ae37ad0b Disable ZONEVERSION for built-in chaos and empty zones 2025-03-24 17:32:52 +11:00
Mark Andrews e081ab6ecf Check that 'provide-zoneversion no;' works 2025-03-24 17:32:52 +11:00
Mark Andrews 9604b13d00 Add an option to disable ZONEVERSION responses
The option provide-zoneversion controls whether ZONEVERSION is
returned.  This applies to primary, secondary and mirror zones.
2025-03-24 17:32:52 +11:00
Mark Andrews d7a667d91b Check that received ZONEVERSION is logged 2025-03-24 17:32:52 +11:00
Mark Andrews 70470169a9 Add option request-zoneversion
This can be set at the option, view and server levels and causes
named to add an EDNS ZONEVERSION option to requests.  Replies are
logged to the 'zoneversion' category.
2025-03-24 14:22:07 +11:00
Mark Andrews 9bb834750d Add system tests for EDNS zoneversion 2025-03-24 11:08:25 +11:00
Mark Andrews d17eda82e0 Return EDNS ZONEVERSION if requested
If there was an EDNS ZONEVERSION option in the DNS request and the
answer was from a zone, return the zone's serial and number of
labels excluding the root label with the type set to 0 (ZONE-SERIAL).
2025-03-24 11:08:25 +11:00
Mark Andrews ca3e61a462 Add dns_zone_getzoneversion
Returns the EDNS ZONEVERSION for the zone.  Return the database
specific version otherwise return a type 0 version (serial).
2025-03-24 11:08:25 +11:00
Mark Andrews 7f07c989b5 Add dns_db_getzoneversion
Provides a database method to return a database specific EDNS
ZONEVERSION option.  The default EDNS ZONEVERSION is serial.
2025-03-24 11:08:25 +11:00
Mark Andrews 83115b931d Add EDNS ZONEVERSION option counter 2025-03-24 11:08:24 +11:00
Mark Andrews 184c7caff9 Add support for EDNS ZONEVERSION to dig
This add the +[no]zoneversion option to dig which adds the
EDNS ZONEVERSION option to requests.
2025-03-24 11:08:24 +11:00
Mark Andrews 4199d2d5e4 Extend message code to display ZONEVERSION 2025-03-24 11:08:24 +11:00
Mark Andrews 6a5e60231f Check EDNS ZONEVERSION when parsing OPT record 2025-03-21 15:38:56 +11:00
Mark Andrews 49ecb158d4 fix: dev: Fix adbname reference
Call `dns_adbname_ref` before calling `dns_resolver_createfetch` to
ensure `adbname->name` remains stable for the life of the fetch.

Closes #5239

Merge branch '5239-fix-adb-reference-counting' into 'main'

See merge request isc-projects/bind9!10290
2025-03-21 00:26:25 +00:00
Mark Andrews 8e7229f641 Fix gaining adbname reference
Call dns_adbname_ref before calling dns_resolver_createfetch to
ensure adbname->name remains stable for the life of the fetch.
2025-03-20 23:25:29 +00:00
Evan Hunt 3415392d01 fix: dev: Optimize key ID check when searching for matching keys
When searching through a DNSKEY or KEY rrset for the key matching a particular algorithm and ID, it's a waste of time to convert every key into a `dst_key` object; it's faster to compute the key ID from the rdata, then do the full key conversion after determining that we've found the right key. This optimization was already used in the validator, but it's been refactored for code clarity, and is now also used in query.c and message.c.

Merge branch 'each-refactor-key-search' into 'main'

See merge request isc-projects/bind9!10258
2025-03-20 18:25:05 +00:00
Evan Hunt 2e6107008d optimize key ID check when searching for matching keys
when searching a DNSKEY or KEY rrset for the key that matches
a particular algorithm and ID, it's a waste of time to convert
every key into a dst_key object; it's faster to compute the key
ID by checksumming the region, and then only do the full key
conversion once we know we've found the correct key.

this optimization was already in use in the validator, but it's
been refactored for code clarity, and is now also used in query.c
and message.c.
2025-03-20 18:22:58 +00:00
Evan Hunt 341b962665 move dns_zonekey_iszonekey() to dns_dnssec module
dns_zonekey_iszonekey() was the only function defined in the
dns_zonekey module, and was only called from one place. it
makes more sense to group this with dns_dnssec functions.
2025-03-20 18:22:58 +00:00
Alessio Podda d3db9ccf53 chg: dev: Switch symtab to use fxhash hashing
This merge request resolves some performance regressions introduced
with the change from isc_symtab_t to isc_hashmap_t.

The key improvements are:

1. Using a faster hash function than both isc_hashmap_t and
   isc_symtab_t. The previous implementation used SipHash, but the
   hashflood resistance properties of SipHash are unneeded for config
   parsing.
2. Shrinking the initial size of the isc_hashmap_t used inside
   isc_symtab_t. Symtab is mainly used for config parsing, and the
   when used that way it will have between 1 and 50 keys, but the
   previous implementation initialized a map with 128 slots.
   By initializing a smaller map, we speed up mallocs and optimize for
   the typical case of few config keys.
3. Slight optimization of the string matching in the hashmap, so that
   the tail is handled in a single load + comparison, instead of byte
   by byte.
   Of the three improvements, this is the least important.

Merge branch 'alessio/fxhash-symtab' into 'main'

See merge request isc-projects/bind9!10204
2025-03-20 13:00:12 +00:00
alessio e1e10adc3a Switch symtab to use fxhash hashing
This merge request resolves some performance regressions introduced
with the change from isc_symtab_t to isc_hashmap_t.

The key improvements are:

1. Using a faster hash function than both isc_hashmap_t and
   isc_symtab_t. The previous implementation used SipHash, but the
   hashflood resistance properties of SipHash are unneeded for config
   parsing.
2. Shrinking the initial size of the isc_hashmap_t used inside
   isc_symtab_t. Symtab is mainly used for config parsing, and the
   when used that way it will have between 1 and ~50 keys, but the
   previous implementation initialized a map with 128 slots.
   By initializing a smaller map, we speed up mallocs and optimize for
   the typical case of few config keys.
3. Slight optimization of the string matching in the hashmap, so that
   the tail is handled in a single load + comparison, instead of byte
   by byte.
   Of the three improvements, this is the least important.
2025-03-20 11:26:09 +01:00
Matthijs Mekking d2214cb704 fix: usr: Fix several small DNSSEC timing issues
The following small issues related to `dnssec-policy` have been fixed:
- In some cases the key manager inside BIND 9 could run every hour, while it could have run less often.
- While `CDS` and `CDNSKEY` records will be removed correctly from the zone when the corresponding `DS` record needs to be updated, the expected timing metadata when this will happen was never set.
- There were a couple of cases where the safety intervals are added inappropriately, delaying key rollovers longer than necessary.
- If you have identical `keys` in your `dnssec-policy`, they may be retired inappropriately. Note that having keys with identical properties is discouraged in all cases.

Closes #5242

Merge branch '5242-several-keymgr-issues' into 'main'

See merge request isc-projects/bind9!10251
2025-03-20 10:13:22 +00:00
Matthijs Mekking 3e836a87e6 Update Retired and Removed if we update lifetime
If we are updating the lifetime, and it was not set before, also
set/update the Retired and Removed timing metadata.
2025-03-20 10:12:16 +00:00
Matthijs Mekking b93cb2e80e Fix a key generation issue in the tests
The dnssec-keygen command for the ZSK generation for the zone
multisigner-model2.kasp was wrong (no ZSK was generated in the setup
script, but when 'named' is started, the missing ZSK was created
anyway by 'dnssec-policy'.
2025-03-20 10:12:16 +00:00
Matthijs Mekking 6c6b8796d3 Fix keymgr bug wrt setting the next time
Only set the next time the keymgr should run if the value is non zero.
Otherwise we default back to one hour. This may happen if there is one
or more key with an unlimited lifetime.
2025-03-20 10:12:16 +00:00
Matthijs Mekking 8c9d2eb2bf keymgr: also set DeleteCDS when setting PublishCDS
The keymgr never set the expected timing metadata when CDS/CDNSKEY
records for the corresponding key will be removed from the zone. This
is not troublesome, as key states dictate when this happens, but with
the new pytest we use the timing metadata to determine if the CDS and/or
CDNSKEY for the given key needs to be published.
2025-03-20 10:12:16 +00:00
Matthijs Mekking 63edc4435f Fix wrong usage of safety intervals in keymgr
There are a couple of cases where the safety intervals are added
inappropriately:

1. When setting the PublishCDS/SyncPublish timing metadata, we don't
   need to add the publish-safety value if we are calculating the time
   when the zone is completely signed for the first time. This value
   is for when the DNSKEY has been published and we add a safety
   interval before considering the DNSKEY omnipresent.

2. The retire-safety value should only be added to ZSK rollovers if
   there is an actual rollover happening, similar to adding the sign
   delay.

3. The retire-safety value should only be added to KSK rollovers if
   there is an actual rollover happening. We consider the new DS
   omnipresent a bit later, so that we are forced to keep the old DS
   a bit longer.
2025-03-20 10:12:16 +00:00
Matthijs Mekking ef671919d5 Fix a small keymgr bug
While converting the kasp system test to pytest, I encountered a small
bug in the keymgr code. We retire keys when there is more than one
key matching a 'keys' line from the dnssec-policy. But if there are
multiple identical 'keys' lines, as is the case for the test zone
'checkds-doubleksk.kasp', we retire one of the two keys that have the
same properties.

Fix this by checking if there are double matches. This is not fool proof
because there may be many keys for a few identical 'keys' lines, but it
is good enough for now. In practice it makes no sense to have a policy
that dictates multiple keys with identical properties.
2025-03-20 10:12:16 +00:00
Mark Andrews 329a332708 fix: usr: Fix write after free in validator code
Raw integer pointers were being used for the validator's nvalidations
and nfails values but the memory holding them could be freed before
they ceased to be used.  Use reference counted counters instead.

Closes #5239

Merge branch '5239-use-counter-for-nvalidations-and-nfailss' into 'main'

See merge request isc-projects/bind9!10248
2025-03-20 01:30:11 +00:00
Mark Andrews bfbaacc9a0 Use reference counted counters for nfail and nvalidations
The fetch context that held these values could be freed while there
were still active pointers to the memory.  Using a reference counted
pointer avoids this.
2025-03-20 09:12:49 +11:00
Andoni Duarte Pintado 8dba96d71e Merge tag 'v9.21.6' 2025-03-19 17:36:14 +01:00
Michal Nowak 1d688e89b8 fix: test: Fix the log-report-channel zones check
The check looks for logs that are not present, fails to make the
possible failure visible, and fails to bump the check enumerator:

    I:checking that log-report-channel zones fail if '*._er/TXT' is missing (129)
    grep: test.out4.129: No such file or directory
    grep: test.out4.129: No such file or directory
    I:checking that raw zone with bad class is handled (129)

The issue appeared in #3659.

Merge branch 'mnowak/checkzone-test-fix' into 'main'

See merge request isc-projects/bind9!10286
2025-03-19 08:00:42 +00:00
Michal Nowak c2e60f9a5a Fix the log-report-channel zones check
The check looks for logs that are not present, fails to make the
possible failure visible, and fails to bump the check enumerator:

    I:checking that log-report-channel zones fail if '*._er/TXT' is missing (129)
    grep: test.out4.129: No such file or directory
    grep: test.out4.129: No such file or directory
    I:checking that raw zone with bad class is handled (129)
2025-03-19 08:00:19 +00:00
Mark Andrews ed727ee924 fix: test: Fix failing grep invocation on OpenBSD
Lines starting with A or NSEC are expected but not matched with the
OpenBSD grep. Extended regular expressions with direct use of
parentheses and the pipe symbol is more appropriate.

    I:checking RRSIG query from cache (154)
    I:failed

The issue appeared in #4805.

Merge branch 'mnowak/openbsd-grep-fix' into 'main'

See merge request isc-projects/bind9!10285
2025-03-19 00:04:39 +00:00
Michal NowakandMark Andrews 00584d6f29 Fix failing grep invocation on OpenBSD
Lines starting with A or NSEC are expected but not matched with the
OpenBSD grep. Extended regular expressions with direct use of
parentheses and the pipe symbol is more appropriate.

    I:checking RRSIG query from cache (154)
    I:failed
2025-03-18 23:29:22 +00:00
Arаm Sаrgsyаn d30b9eb46e fix: usr: Fix resolver statistics counters for timed out responses
When query responses timed out, the resolver could incorrectly increase the regular responses counters, even if no response was received. This has been fixed.

Closes #5193

Merge branch '5193-resolver-statistics-counters-fix' into 'main'

See merge request isc-projects/bind9!10227
2025-03-18 17:05:23 +00:00
Aram Sargsyan 0c7fa8d572 Test resolver statistics when responses time out
Add a test to check that the timed out responses do not skew the
normal responses statistics counters, and that they do update the
timeouts counter.
2025-03-18 16:20:59 +00:00
Aram Sargsyan 830e548111 Fix the resolvers RTT-ranged responses statistics counters
When a response times out the fctx_cancelquery() function
incorrectly calculates it in the 'dns_resstatscounter_queryrtt5'
counter (i.e. >=1600 ms). To avoid this, the rctx_timedout()
function should make sure that 'rctx->finish' is NULL. And in order
to adjust the RTT values for the timed out server, 'rctx->no_response'
should be true. Update the rctx_timedout() function to make those
changes.
2025-03-18 16:20:59 +00:00
Aram Sargsyan 12e7dfa397 Fix resolver responses statistics counter
The resquery_response() function increases the response counter without
checking if the response was successful. Increase the counter only when
the result indicates success.
2025-03-18 16:20:59 +00:00
Michał Kępień c6e5710846 chg: test: asyncserver.py: TCP improvements
This branch started off as `michal/upforwd-asyncserver`.  It quickly
turned out that the critical `asyncserver.py` change that was needed for
the `upforwd` system test was for the server to be able to read multiple
TCP queries on a single connection.  As currently present in `main`,
`asyncserver.py` closes every client connection after servicing a single
query.  Retaining that behavior would cause the `upforwd` system test to
fail and, in general, capturing all data sent by a client seems more
useful in tests than just closing connections quickly.  `asyncserver.py`
can always be extended in the future (e.g. by adding a new
`ResponseAction` that the networking code would react to) to reinstate
the original behavior, if it turns out to be necessary.

While working on changing that particular `asyncserver.py` behavior, I
noticed a couple of other deficiencies in the TCP connection handling
code, so I started addressing them.  One thing led to another and before
I noticed, enough changes were applied to be worth doing a separate
merge request, particularly given that the actual rewrite of
`upforwd/ans4/ans.pl` using `asyncserver.py` is trivial once the
required changes to `asyncserver.py` itself are applied.

Merge branch 'michal/asyncserver-tcp-improvements' into 'main'

See merge request isc-projects/bind9!10276
2025-03-18 15:30:35 +00:00
Michał Kępień 575a874582 Handle queries indefinitely on each TCP connection
Instead of closing every incoming TCP connection after handling a single
query, continue receiving queries on each TCP connection until the
client disconnects itself.  When coupled with response dropping, this
enables silently receiving all incoming data, simulating an unresponsive
server.
2025-03-18 16:28:18 +01:00
Michał Kępień 68fe9a5df5 Enable receiving chunked TCP DNS messages
A TCP DNS client may send its queries in chunks, causing
StreamReader.read() to return less data than previously declared by the
client as the DNS message length; even the two-octet DNS message length
itself may be split up into two single-octet transmissions.  Sending
data in chunks is valid client behavior that should not be treated as an
error.  Add a new helper method for reading TCP data in a loop, properly
distinguishing between chunked queries and client disconnections.  Use
the new method for reading all TCP data from clients.
2025-03-18 16:28:18 +01:00
Michał Kępień 8c3f673f37 Extend TCP logging
Emit more log messages from TCP connection handling code and extend
existing ones to improve debuggability of servers using asyncserver.py.
2025-03-18 16:28:18 +01:00
Michał Kępień 748ed4259b Handle connection resets during reading
A TCP peer may reset the connection at any point, but asyncserver.py
currently only handles connection resets when it is sending data to the
client.  Handle connection resets during reading in the same way.
2025-03-18 16:28:18 +01:00
Michał Kępień a956947fba Refactor AsyncDnsServer._handle_tcp()
Split up AsyncDnsServer._handle_tcp() into a set of smaller methods to
improve code readability.
2025-03-18 16:28:18 +01:00
Michał Kępień e4c3186a7c Gracefully handle TCP client disconnections
Prevent premature client disconnections during reading from triggering
unhandled exceptions in TCP connection handling code.
2025-03-18 16:28:18 +01:00
Michał Kępień 5764a9d660 Simplify peer address formatting
Add a helper class, Peer, which holds the <host, port> tuple of a
connection endpoint and gets pretty-printed when formatted as a string.
This enables passing instances of this new class directly to logging
functions, eliminating the need for the AsyncDnsServer._format_peer()
helper method.
2025-03-18 16:28:18 +01:00
Nicki Křížek a2042e603e chg: ci: Allow re-run of the shotgun jobs to reduce false positives
The false positive rate is about 10-20 % when evaluating shotgun results
from a single run. Attempt to reduce the false positive rate by allowing
a re-run of failed jobs.

Merge branch 'nicki/ci-shotgun-reduce-false-positives' into 'main'

See merge request isc-projects/bind9!10271
2025-03-18 09:29:10 +00:00
Nicki Křížek 5eab352478 Allow re-run of the shotgun jobs to reduce false positive
The false positive rate is about 10-20 % when evaluating shotgun results
from a single run. Attempt to reduce the false positive rate by allowing
a re-run of failed jobs.

While there is a slight risk that barely noticable decreases in
performance might slip by more easily in MRs, they'd still likely pop up
during nightly or pre-release testing.

Also increase the tolerance threshold for DoH latency comparisons, as
those tests often experience increased jitter in the tail end latencies.
2025-03-18 10:19:32 +01:00
Nicki Křížek 7f8226a039 Adjust the load factor for shotgun:tcp test
With the slightly decreased load for the TCP test, the results appear to
be a little bit more stable.
2025-03-18 10:19:32 +01:00
Michał Kępień 192627db10 chg: test: Use isctest.asyncserver in the "qmin" test
Replace custom DNS servers used in the "qmin" system test with new code
based on the isctest.asyncserver module.  The revised code employs zone
files and a limited amount of custom logic, which massively improves
test readability and maintainability, extends logging, and fixes
non-compliant replies sent by some of the custom servers in response to
certain queries (e.g. AA=0 in authoritative empty non-terminal
responses, non-glue address records in ADDITIONAL section).

Merge branch 'michal/qmin-asyncserver' into 'main'

See merge request isc-projects/bind9!10195
2025-03-18 05:55:17 +00:00
Michał Kępień dfd37918d6 Broaden vulture exclude glob for ans.py servers
The vulture tool seems to be unable to follow how the parent classes
defined in bin/tests/system/qmin/qmin_ans.py use mandatory properties
specified by child classes in bin/tests/system/qmin/ans*/ans.py.  Make
the tool ignore not just ans.py servers, but also *_ans.py utility
modules above the ansX/ subdirectories to prevent false positives about
unused code from causing CI pipeline failures.
2025-03-18 06:19:01 +01:00
Michał Kępień f413ddbe5f Ignore .hypothesis files created by system tests
Some versions of the Hypothesis Python library - notably the one
included in stock OS repositories for Ubuntu 20.04 Focal Fossa - cause a
.hypothesis file to be created in a Python script's working directory
when the hypothesis module is present in its import chain.  Ignore such
files by adding them to the list of expected test artifacts to prevent
pytest teardown checks from failing due to these files appearing in the
file system after running system tests.
2025-03-18 06:19:01 +01:00
Michał Kępień a799dd04ad Fix PYTHONPATH set for ans.py servers by start.pl
Commit 6c010a5644 caused the PYTHONPATH
environment variable to be set for ans.py servers started using
start.pl.  However, no system test has actually used the new
isctest.asyncserver module since that change was applied, so it has not
been noticed until now that including the source directory in PYTHONPATH
is only sufficient for in-tree builds.  Include the build directory
instead of the source directory in the PYTHONPATH environment variable
set for ans.py servers started by start.pl so that they work correctly
for both in-tree and out-of-tree builds.
2025-03-18 06:19:01 +01:00
Michał Kępień 7faa34c6ee Use isctest.asyncserver in the "qmin" test
Replace custom DNS servers used in the "qmin" system test with new code
based on the isctest.asyncserver module.  The revised code employs zone
files and a limited amount of custom logic, which massively improves
test readability and maintainability, extends logging, and fixes
non-compliant replies sent by some of the custom servers in response to
certain queries (e.g. AA=0 in authoritative empty non-terminal
responses, non-glue address records in ADDITIONAL section).
2025-03-18 06:19:01 +01:00
Ondřej Surý 575a2e5f11 rem: dev: Cleanup BIND 8 compatibility code
There was some code in dns_resolver unit meant to keep compatibility with BIND 8 breaking the DNS protocol.  These should not be needed anymore.

Merge branch 'ondrej/resolver-bind-8-cleanup' into 'main'

See merge request isc-projects/bind9!10270
2025-03-18 00:12:31 +00:00
Ondřej SurýandEvan Hunt 0d9f58b745 Remove a kludge to process non-authoritative CNAME response
A BIND 8 server could return a non-authoritative answer when a CNAME is
followed.  This is no longer handled as a valid answer.
2025-03-17 23:23:24 +00:00
Ondřej SurýandEvan Hunt 05d6542e6d Remove the kludges for records in the bad sections
There were kludges to help process responses from authoritative servers
giving RRs in wrong sections (mentioning BIND 8).  These should just go
away and such responses should not be processed.
2025-03-17 23:23:24 +00:00
Ondřej SurýandEvan Hunt ff73d37f69 Small cleanup in dns_adb unit 2025-03-17 23:23:24 +00:00
Michal Nowak 9d9e9d9cb1 chg: ci: Disable linkcheck on dl.acm.org
The check fails with the following error for some time:

    403 Client Error: Forbidden for url: https://dl.acm.org/doi/10.1145/1315245.1315298

Merge branch 'mnowak/linkcheck-disable-dl-acm-org' into 'main'

See merge request isc-projects/bind9!10272
2025-03-17 17:07:40 +00:00
Michal Nowak 1ab889ee21 Disable linkcheck on dl.acm.org
The check fails with the following error for some time:

    403 Client Error: Forbidden for url: https://dl.acm.org/doi/10.1145/1315245.1315298
2025-03-17 17:39:36 +01:00
Arаm Sаrgsyаn ae2fd7ef15 new: dev: Implement -T cookiealwaysvalid
When `-T cookiealwaysvalid` is passed to `named`, DNS cookie checks for
the incoming queries always pass, given they are structurally correct.

Merge branch 'aram/new-named-minus-T-option-of-cookiealwaysvalid' into 'main'

See merge request isc-projects/bind9!10232
2025-03-17 11:36:57 +00:00
Aram Sargsyan 4e75a20b6a Test -T cookiealwaysvalid
Add a check in the "cookie" system test to make sure that the new
'-T cookiealwaysvalid' option works.
2025-03-17 10:42:47 +00:00
Aram Sargsyan 807ef8545d Implement -T cookiealwaysvalid
When -T cookiealwaysvalid is passed to named, DNS cookie checks for
the incoming queries always pass, given they are structurally correct.
2025-03-17 10:42:47 +00:00
Mark Andrews 06427720f7 fix: dev: Add missing locks when returning addresses
Add missing locks in dns_zone_getxfrsource4 et al.  Addresses CID 468706, 468708, 468741, 468742, 468785, and 468778.

Cleanup dns_zone_setxfrsource4 et al to now return void.

Remove double copies with dns_zone_getprimaryaddr and dns_zone_getsourceaddr.

Closes #4933

Merge branch '4933-add-missing-locks-when-returning-addresses' into 'main'

See merge request isc-projects/bind9!9485
2025-03-15 06:04:34 +00:00
Mark Andrews d0a59277fb Add missing locks when returning addresses
Add missing locks in dns_zone_getxfrsource4 et al. Addresses CID
468706, 468708, 468741, 468742, 468785 and 468778.

Cleanup dns_zone_setxfrsource4 et al to now return void.

Remove double copies with dns_zone_getprimaryaddr and dns_zone_getsourceaddr.
2025-03-15 04:51:59 +00:00
Evan Hunt a8dd267bd0 fix: nil: Add new convenience functions to classify rdata types
- `dns_rdatatype_ismulti()` returns true if a given type can have
  multiple answers: ANY, RRSIG, or SIG.
- `dns_rdatatype_issig()` returns true for a signature: RRSIG or SIG.
- `dns_rdatatype_isaddr()` returns true for an address: A or AAAA.
- `dns_rdatatype_isalias()` returns true for an alias: CNAME or DNAME.

Code has been modified to use these functions where applicable.

These and all similar functions (e.g., `dns_rdatatype_ismeta()`, `dns_rdatatype_issingleton()`, etc) are now `static inline` functions defined in `rdata.h`.

Merge branch 'each-rdatatype-functions' into 'main'

See merge request isc-projects/bind9!10216
2025-03-15 01:26:35 +00:00
Evan Hunt 606d30796e use new dns_rdatatype classification functions
modify code to use dns_rdatatype_ismulti(), dns_rdatatype_issig(),
dns_rdatatype_isaddr(), and dns_rdatatype_isalias() where applicable.
2025-03-15 00:27:54 +00:00
Evan Hunt 37ff0aa9c0 convert rdatatype classification routines to inline
turn the dns_rdatatype_is*() functions into static inline
functions in rdata.h.
2025-03-15 00:27:54 +00:00
Evan Hunt 1c51d44d82 add new functions to classify rdata types
- dns_rdatatype_ismulti() returns true if a given type can have
  multiple answers: ANY, RRSIG, or SIG.
- dns_rdatatype_issig() returns true for a signature: RRSIG or SIG.
- dns_rdatatype_isaddr() returns true for an address: A or AAAA.
- dns_rdatatype_isalias() returns true for an alias: CNAME or DNAME.
2025-03-15 00:27:54 +00:00
Evan Hunt 3b0b658a52 fix: dev: step() could ignore rollbacks
The `step()` function (used for stepping to the prececessor or successor of a database node) could overlook a node if there was an rdataset that was marked IGNORE because it had been rolled back, covering an active rdataset under it.

Closes #5170

Merge branch '5170-step-ignores-rollback' into 'main'

See merge request isc-projects/bind9!10103
2025-03-14 23:19:36 +00:00
Evan Hunt ecde0ea2d7 add a unit test with an empty node
the db_test unit test now looks up an empty nonterminal node
to exercise the behavior of the step() function in qpzone.
2025-03-14 23:19:17 +00:00
Evan Hunt 7d98aba3ac add a unit test to check database rollback
check that a database rollback works and the correct
(original) data is found on lookup.
2025-03-14 23:19:17 +00:00
Evan Hunt 24eaff7adc qpzone.c:step() could ignore rollbacks
the step() function (used for stepping to the prececessor or
successor of a database node) could overlook a node because
there was an rdataset marked IGNORE because it had been rolled
back, covering an active rdataset under it.
2025-03-14 23:19:17 +00:00
Evan Hunt 025ef4d7b8 fix: dev: Fix handling of revoked keys
When a key is revoked, its key ID changes due to the inclusion of the "revoked" flag. A collision between this changed key ID
and an unrelated public-only key could cause a crash in `dnssec-signzone`.

Closes #5231

Merge branch '5231-fix-keyid-collision' into 'main'

See merge request isc-projects/bind9!10233
2025-03-14 22:26:36 +00:00
Evan Hunt 9cfe9f5eb7 fix handling of revoked keys
when a key is revoked its key ID changes, due to the inclusion
of the "revoke" flag. a collision between this changed key ID and
that of an unrelated public-only key could cause a crash in
dnssec-signzone.
2025-03-14 22:25:44 +00:00
Mark Andrews e6c07b3386 fix: test: Tune many types tests in reclimit test
The `I:checking that lifting the limit will allow everything to get
cached (20)` test was failing due to the TTL of the records being
too short for the elapsed time of the test.  Raise the TTL to fix
this and adjust other tests as needed.

Closes #5206

Merge branch '5206-tune-last-sub-test-of-reclimit' into 'main'

See merge request isc-projects/bind9!10177
2025-03-14 05:28:01 +00:00
Mark Andrews 1a58bd2113 Tune many types tests in reclimit test
The 'I:checking that lifting the limit will allow everything to get
cached (20)' test was failing due to the TTL of the records being
too short for the elapsed time of the test.  Raise the TTL to fix
this and adjust other tests as needed.
2025-03-14 02:03:50 +00:00
Mark Andrews 42799ae81f fix: usr: QNAME minimization could leak the query type
When performing QNAME minimization, `named` now sends an NS query for the original query name, before sending the final query. This prevents the parent zone from learning the original query type, in the event that the query name is a delegation point.

For example, when looking up an address record for `example.com`, NS queries are now sent to the servers for both `com` and `example.com`, before the address query is sent to the servers for `example.com`.  Previously, an address query would have been sent to the servers for `com`.

Closes #4805

Merge branch '4805-missing-qname-ns-query-when-using-qname-minimisation' into 'main'

See merge request isc-projects/bind9!9155
2025-03-14 02:02:52 +00:00
Mark Andrews de519cd1c9 Don't leak the original QTYPE to parent zone
When performing QNAME minimization, named now sends an NS
query for the original QNAME, to prevent the parent zone from
receiving the QTYPE.

For example, when looking up example.com/A, we now send NS queries
for both com and example.com before sending the A query to the
servers for example.com.  Previously, an A query for example.com
would have been sent to the servers for com.

Several system tests needed to be adjusted for the new query pattern:

- Some queries in the serve-stale test were sent to the wrong server.
- The synthfromdnssec test could fail due to timing issues; this
  has been addressed by adding a 1-second delay.
- The cookie test could fail due to the a change in the count of
  TSIG records received in the "check that missing COOKIE with a
  valid TSIG signed response does not trigger TCP fallback" test case.
- The GL #4652 regression test case in the chain system test depends
  on a particular query order, which no longer occurs when QNAME
  minimization is active. We now disable qname-minimization
  for that test.
2025-03-14 01:01:26 +00:00
Mark Andrews 496f7963cd Fix handling of ISC_R_TIMEOUT in resume_qmin()
If a timeout occurs when sending a QMIN query, QNAME
minimization should be disabled. This now causes a hard
failure in strict mode, or a fallback to non-minimized queries
in relaxed mode.
2025-03-14 01:01:26 +00:00
Mark Andrews 98fc14dc75 Exempt QNAME minimization queries from fetches-per-zone
The calling fetch has already called fcount_incr() for this zone;
calling it again for a QMIN query results in double counting.

When resuming after a QMIN query is answered, however, we do now
ensure before continuing that the fetches-per-zone limit has not
been exceeded.
2025-03-14 01:01:26 +00:00
Mark Andrews 3397212df3 new: usr: dig can now display the received BADVERS message during negotiation
Dig +showbadvers now displays the received BADVERS message and 
continues the EDNS version negotiation.  Previously to see the
BADVERS message +noednsneg had to be specified which terminated the
EDNS negotiation.  Additionally the specified EDNS value (+edns=value)
is now used when making all the initial queries with +trace. i.e EDNS
version negotiation will be performed with each server when performing
the trace.

Closes #5234

Merge branch '5234-have-dig-display-the-badvers-message' into 'main'

See merge request isc-projects/bind9!10234
2025-03-14 00:45:20 +00:00
Mark Andrews 947ca25663 check that dig +showbadvers works 2025-03-13 21:36:14 +00:00
Mark Andrews 6c271f6328 Add "+showbadvers" to dig and reset EDNS version
Add "+showbadvers" to display the BADVERS response similarly
to "+showbadcookie".  Additionally reset the EDNS version to
the requested version in "dig +trace" so that EDNS version
negotiation can be tested at all levels of the trace rather
that just when requesting the root nameservers.
2025-03-13 21:36:14 +00:00
Matthijs Mekking 6ac4cfb948 fix: usr: Ensure max-clients-per-query is at least clients-per-query
If the `max-clients-per-query` option is set to a lower value than `clients-per-query`, the value is adjusted to match `clients-per-query`.

Closes #5224

Merge branch '5224-raise-max-clients-per-query-to-be-at-least' into 'main'

See merge request isc-projects/bind9!10241
2025-03-13 13:02:48 +00:00
Matthijs Mekking f6f9645ed1 Raise max-clients-per-query to be at least
In the case where 'clients-per-query' is larger than
'max-clients-per-query', raise 'max-clients-per-query' so that
'clients-per-query' equals 'max-clients-per-query' and log a warning
that this is what happened.
2025-03-13 13:02:28 +00:00
Matthijs Mekking 1f674ef42e Test new max-clients-per-query log warning
Make sure the new warning is logged.
2025-03-13 13:02:28 +00:00
Matthijs Mekking f50753f303 Update max-clients-per-query documentation
The new intended behavior is that 'max-clients-per-query' value is
raised to equal 'clients-per-query' if it is lower.
2025-03-13 13:02:28 +00:00
Colin Vidal 45ee3715e1 new: usr: Add support for EDE 20 (Not Authoritative)
Support was added for EDE codes 20 (Not Authoritative) when client requests recursion (RD) but the server has recursion disabled.

RFC 8914 mention EDE 20 should also be returned if the client doesn't have the RD bit set (and recursion is needed) but it doesn't apply for
BIND as BIND would try to resolve from the "deepest" referral in AUTHORITY section. For example, if the client asks for "www.isc.org/A" but the server only knows the root domain, it will return NOERROR but no answer for "www.isc.og/A", just the list of other servers to ask.

See #1836

Merge branch '1836-not-authoritative' into 'main'

See merge request isc-projects/bind9!10228
2025-03-13 11:56:37 +00:00
Colin Vidal 7f613c207f add system test covering EDE 20
Add system test to cover extended DNS error 20 (Not authoritative).
2025-03-13 11:16:01 +01:00
Colin Vidal 24ffbdcfea add support for EDE 20 (Not Authoritative)
Extended DNS Error message EDE 20 (Not Authoritative) is now sent when
client request recursion (RD) but the server has recursion disabled.

RFC 8914 mention EDE 20 should also be returned if the client doesn't
have the RD bit set (and recursion is needed) but it doesn't apply for
BIND as BIND would try to resolve from the "deepest" referral in
AUTHORITY section. For example, if the client asks for "www.isc.org/A"
but the server only knows the root domain, it will returns NOERROR but
no answer for "www.isc.og/A", just the list of other servers to ask.
2025-03-13 11:16:01 +01:00
Colin Vidal e66dc07c68 new: usr: Add support for EDE 7 and EDE 8
Support was added for EDE codes 7 (Signature Expired) and 8 (Signature Not Yet Valid) which might occur during DNSSEC validation.

See #2715

Merge branch '2715-expired-future-keys' into 'main'

See merge request isc-projects/bind9!10225
2025-03-13 10:13:36 +00:00
Colin Vidal e763d6637f add system tests covering EDE 7 and 8
Add DNSSEC system tests to cover extended DNS error 7 (Signature
Expired) and 8 (Signature Not Yet Valid).
2025-03-13 09:57:09 +01:00
Colin Vidal 334ea1269f add support for EDE 7 and 8
Extended DNS Error messages EDE 7 (expired key) and EDE 8 (validity
period of the key not yet started) are now sent in case of such DNSSEC
validation failures.

Refactor the existing validator extended error APIs in order to make it
easy to have a consisdent extra info (with domain/type) in the various
use case (i.e. when the EDE depends on validator state,
validate_extendederror or when the EDE doesn't depend of any state but
can be called directly in a specific flow).
2025-03-13 09:57:09 +01:00
Matthijs Mekking 3309863c97 fix: test: Take into account key collisions in ksr system test
Closes #5229

Merge branch '5229-ksr-system-test-can-fail-on-key-collision' into 'main'

See merge request isc-projects/bind9!10238
2025-03-13 08:19:05 +00:00
Matthijs Mekking 8b3d2e5633 ksr: Take into account key collisions
When generating new key pairs, one test checks if existing keys that
match the time bundle are selected, rather than extra keys being
generated. Part of the test is to check the verbose output, counting
the number of "Selecting" and "Generating" occurences. But if there
is a key collision, the ksr tool will output that the key already
exists and includes the substring "already exists, or might collide
with another key upon revokation.  Generating a new key".

So substract by one the generated counter if there is a "collide"
occurrence.
2025-03-13 08:18:50 +00:00
Matthijs Mekking 3973c2e8c3 fix: dev: Fix CID 544147: Code maintainability issues (UNUSED_VALUE)
Assigning value "NULL" to "newstr", but that stored value is overwritten
before it can be used.

Setting "newstr" to NULL does not have any effect, so the line can
safely be removed.

Closes #5227

Merge branch '5227-cid-544147' into 'main'

See merge request isc-projects/bind9!10239
2025-03-13 08:18:35 +00:00
Matthijs Mekking ecef45bf18 Fix CID 544147
Assigning value "NULL" to "newstr", but that stored value is overwritten
before it can be used.

Setting "newstr" to NULL does not have any effect, so the line can
safely be removed.
2025-03-12 16:39:36 +01:00
Andoni Duarte 33a0cc9823 chg: doc: Set up version for BIND 9.21.7
Merge branch 'andoni/set-up-version-for-bind-9.21.7' into 'main'

See merge request isc-projects/bind9!10237
2025-03-12 13:07:00 +00:00
Andoni Duarte Pintado bd711bb839 Update BIND version to 9.21.7-dev 2025-03-12 12:09:35 +01:00
Andoni Duarte Pintado 21ca763bca Update BIND version for release 2025-03-11 11:37:59 +01:00
Andoni Duarte 474b7a04f1 new: doc: Prepare documentation for BIND 9.21.6
Merge branch 'andoni/prepare-documentation-for-bind-9.21.6' into 'v9.21.6-release'

See merge request isc-private/bind9!784
2025-03-11 10:10:19 +00:00
Andoni Duarte Pintado 5dfcedd52d Tweak and reword relase notes 2025-03-11 10:46:21 +01:00
Andoni Duarte Pintado 7c308c2298 Prepare release notes for BIND 9.21.6 2025-03-11 10:46:21 +01:00
Andoni Duarte Pintado f0b5f0cbce Generate changelog for BIND 9.21.6 2025-03-11 10:46:21 +01:00
133 changed files with 4214 additions and 3404 deletions
+6 -6
View File
@@ -383,6 +383,9 @@ stages:
SHOTGUN_ROUNDS: 3
- &shotgun_rule_other
if: '$CI_PIPELINE_SOURCE =~ /^(api|pipeline|schedule|trigger|web)$/'
# when using data from a single run, the overall instability of the results
# causes quite high false positive rate, rerun the test to attemp to reduce those
retry: 1
script:
- if [ -z "$BASELINE" ]; then export BASELINE=$BIND_BASELINE_VERSION; fi # this dotenv variable can't be set in the rules section, because rules are evaluated before any jobs run
- PIPELINE_ID=$(curl -s -X POST --fail
@@ -584,7 +587,7 @@ vulture:
<<: *precheck_job
needs: []
script:
- vulture --exclude "*/ans*/ans.py,conftest.py,isctest" --ignore-names "pytestmark" bin/tests/system/
- vulture --exclude "*ans.py,conftest.py,isctest" --ignore-names "pytestmark" bin/tests/system/
ci-variables:
stage: precheck
@@ -1688,9 +1691,6 @@ respdiff-third-party:
# Performance tests
# Run shotgun:udp right away, but delay other shotgun jobs sligthly in order to
# allow re-use of the built container image. Otherwise, the jobs would do the
# same builds in parallel rather than re-use the already built image.
shotgun:udp:
<<: *shotgun_job
variables:
@@ -1701,7 +1701,7 @@ shotgun:tcp:
<<: *shotgun_job
variables:
SHOTGUN_SCENARIO: tcp
SHOTGUN_TRAFFIC_MULTIPLIER: 13
SHOTGUN_TRAFFIC_MULTIPLIER: 12
shotgun:dot:
<<: *shotgun_job
@@ -1722,7 +1722,7 @@ shotgun:doh-get:
variables:
SHOTGUN_SCENARIO: doh-get
SHOTGUN_TRAFFIC_MULTIPLIER: 3
SHOTGUN_EVAL_THRESHOLD_LATENCY_PCTL_MAX: 0.3 # bump from the default due to increased tail-end jitter
SHOTGUN_EVAL_THRESHOLD_LATENCY_PCTL_MAX: 0.4 # bump from the default due to increased tail-end jitter
rules: *shotgun_rules_manual_mr
.stress-test: &stress_test
+33 -5
View File
@@ -289,6 +289,7 @@ help(void) {
" form of answers - global "
"option)\n"
" +[no]showbadcookie (Show BADCOOKIE message)\n"
" +[no]showbadvers (Show BADVERS message)\n"
" +[no]showsearch (Search with intermediate "
"results)\n"
" +[no]split=## (Split hex/base64 fields "
@@ -327,6 +328,7 @@ help(void) {
" +[no]yaml (Present the results as "
"YAML)\n"
" +[no]zflag (Set Z flag in query)\n"
" +[no]zoneversion (Request zone version)\n"
" global d-opts and servers (before host name) affect "
"all "
"queries.\n"
@@ -1772,6 +1774,8 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
FULLCHECK("edns");
if (!state) {
lookup->edns = -1;
lookup->original_edns =
-1;
break;
}
if (value == NULL) {
@@ -1788,6 +1792,7 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
goto exit_or_usage;
}
lookup->edns = num;
lookup->original_edns = num;
break;
case 'f':
FULLCHECK("ednsflags");
@@ -2306,8 +2311,18 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
case 'w': /* showsearch */
switch (cmd[4]) {
case 'b':
FULLCHECK("showbadcookie");
lookup->showbadcookie = state;
switch (cmd[7]) {
case 'c':
FULLCHECK("showbadcookie");
lookup->showbadcookie = state;
break;
case 'v':
FULLCHECK("showbadvers");
lookup->showbadvers = state;
break;
default:
goto invalid_option;
}
break;
case 's':
FULLCHECK("showsearch");
@@ -2560,9 +2575,22 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
lookup->rrcomments = -1;
}
break;
case 'z': /* zflag */
FULLCHECK("zflag");
lookup->zflag = state;
case 'z':
switch (cmd[1]) {
case 'f': /* zflag */
FULLCHECK("zflag");
lookup->zflag = state;
break;
case 'o': /* zoneversion */
FULLCHECK("zoneversion");
if (state && lookup->edns == -1) {
lookup->edns = DEFAULT_EDNS_VERSION;
}
lookup->zoneversion = state;
break;
default:
goto invalid_option;
}
break;
default:
invalid_option:
+10
View File
@@ -614,6 +614,12 @@ abbreviation is unambiguous; for example, :option:`+cd` is equivalent to
BADCOOKIE rcode before retrying the request or not. The default
is to not show the messages.
.. option:: +showbadvers, +noshowbadvers
This option toggles whether to show the message containing the
BADVERS rcode before retrying the request or not. The default
is to not show the messages.
.. option:: +showsearch, +noshowsearch
This option performs [or does not perform] a search showing intermediate results.
@@ -751,6 +757,10 @@ abbreviation is unambiguous; for example, :option:`+cd` is equivalent to
This option sets [or does not set] the last unassigned DNS header flag in a DNS query.
This flag is off by default.
.. option:: +zoneversion, +nozoneversion
When enabled, this option includes an EDNS Zone Version request when sending a query.
Multiple Queries
~~~~~~~~~~~~~~~~
+20 -1
View File
@@ -605,6 +605,7 @@ make_empty_lookup(void) {
.idnout = idnout,
.udpsize = -1,
.edns = -1,
.original_edns = -1,
.recurse = true,
.retries = tries,
.comments = true,
@@ -704,6 +705,7 @@ clone_lookup(dig_lookup_t *lookold, bool servers) {
looknew->opcode = lookold->opcode;
looknew->expire = lookold->expire;
looknew->nsid = lookold->nsid;
looknew->zoneversion = lookold->zoneversion;
looknew->tcp_keepalive = lookold->tcp_keepalive;
looknew->header_only = lookold->header_only;
looknew->https_mode = lookold->https_mode;
@@ -738,6 +740,7 @@ clone_lookup(dig_lookup_t *lookold, bool servers) {
}
looknew->showbadcookie = lookold->showbadcookie;
looknew->showbadvers = lookold->showbadvers;
looknew->sendcookie = lookold->sendcookie;
looknew->seenbadcookie = lookold->seenbadcookie;
looknew->badcookie = lookold->badcookie;
@@ -764,6 +767,7 @@ clone_lookup(dig_lookup_t *lookold, bool servers) {
looknew->idnout = lookold->idnout;
looknew->udpsize = lookold->udpsize;
looknew->edns = lookold->edns;
looknew->original_edns = lookold->original_edns;
looknew->recurse = lookold->recurse;
looknew->aaonly = lookold->aaonly;
looknew->adflag = lookold->adflag;
@@ -1938,6 +1942,7 @@ followup_lookup(dns_message_t *msg, dig_query_t *query, dns_section_t section) {
}
domain = dns_fixedname_name(&lookup->fdomain);
dns_name_copy(name, domain);
lookup->edns = lookup->original_edns;
}
debug("adding server %s", namestr);
num = getaddresses(lookup, namestr, &lresult);
@@ -2456,7 +2461,8 @@ setup_lookup(dig_lookup_t *lookup) {
lookup->udpsize = DEFAULT_EDNS_BUFSIZE;
}
if (lookup->edns < 0) {
lookup->edns = DEFAULT_EDNS_VERSION;
lookup->original_edns = lookup->edns =
DEFAULT_EDNS_VERSION;
}
if (lookup->nsid) {
@@ -2586,6 +2592,14 @@ setup_lookup(dig_lookup_t *lookup) {
i++;
}
if (lookup->zoneversion) {
INSIST(i < MAXOPTS);
opts[i].code = DNS_OPT_ZONEVERSION;
opts[i].length = 0;
opts[i].value = NULL;
i++;
}
if (lookup->ednsoptscnt != 0) {
INSIST(i + lookup->ednsoptscnt <= MAXOPTS);
memmove(&opts[i], lookup->ednsopts,
@@ -4300,6 +4314,11 @@ recv_done(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
if (msg->rcode == dns_rcode_badvers && msg->opt != NULL &&
(newedns = ednsvers(msg->opt)) < l->edns && l->ednsneg)
{
if (l->showbadvers) {
dighost_printmessage(query, &b, msg, true);
dighost_received(isc_buffer_usedlength(&b), &peer,
query);
}
/*
* Add minimum EDNS version required checks here if needed.
*/
+5 -4
View File
@@ -117,11 +117,11 @@ struct dig_lookup {
section_answer, section_authority, section_question,
seenbadcookie, sendcookie, servfail_stops,
setqid, /*% use a speciied query ID */
showbadcookie, stats, tcflag, tcp_keepalive, tcp_mode,
tcp_mode_set, tls_mode, /*% connect using TLS */
trace, /*% dig +trace */
showbadcookie, showbadvers, stats, tcflag, tcp_keepalive,
tcp_mode, tcp_mode_set, tls_mode, /*% connect using TLS */
trace, /*% dig +trace */
trace_root, /*% initial query for either +trace or +nssearch */
ttlunits, use_usec, waiting_connect, zflag;
ttlunits, use_usec, waiting_connect, zflag, zoneversion;
char textname[MXNAME]; /*% Name we're going to be looking up */
char cmdline[MXNAME];
dns_rdatatype_t rdtype;
@@ -148,6 +148,7 @@ struct dig_lookup {
int nsfound;
int16_t udpsize;
int16_t edns;
int16_t original_edns;
int16_t padding;
uint32_t ixfr_serial;
isc_buffer_t rdatabuf;
+1 -2
View File
@@ -246,8 +246,7 @@ printsection(dns_message_t *msg, dns_section_t sectionid,
(list_type == dns_rdatatype_any ||
rdataset->type == list_type)) ||
(list_addresses &&
(rdataset->type == dns_rdatatype_a ||
rdataset->type == dns_rdatatype_aaaa ||
(dns_rdatatype_isaddr(rdataset->type) ||
rdataset->type == dns_rdatatype_ns ||
rdataset->type == dns_rdatatype_ptr))))
{
+3
View File
@@ -99,6 +99,7 @@ options {\n\
recursing-file \"named.recursing\";\n\
recursive-clients 1000;\n\
request-nsid false;\n\
request-zoneversion false;\n\
resolver-query-timeout 10;\n\
# responselog <boolean>;\n\
rrset-order { order random; };\n\
@@ -239,6 +240,7 @@ options {\n\
notify yes;\n\
notify-delay 5;\n\
notify-to-soa no;\n\
provide-zoneversion yes;\n\
send-report-channel .;\n\
serial-update-method increment;\n\
sig-signing-nodes 100;\n\
@@ -260,6 +262,7 @@ view \"_bind\" chaos {\n\
notify no;\n\
allow-new-zones no;\n\
max-cache-size 2M;\n\
provide-zoneversion no;\n\
\n\
# Prevent use of this zone in DNS amplified reflection DoS attacks\n\
rate-limit {\n\
-1
View File
@@ -736,7 +736,6 @@ controlkeylist_fromcfg(const cfg_obj_t *keylist, isc_mem_t *mctx,
key->secret.length = 0;
ISC_LINK_INIT(key, link);
ISC_LIST_APPEND(*keyids, key, link);
newstr = NULL;
}
}
+7 -1
View File
@@ -129,6 +129,7 @@ static int maxudp = 0;
/*
* -T options:
*/
static bool cookiealwaysvalid = false;
static bool dropedns = false;
static bool ednsformerr = false;
static bool ednsnotimp = false;
@@ -652,7 +653,9 @@ parse_T_opt(char *option) {
* force the server to behave (or misbehave) in
* specified ways for testing purposes.
*/
if (!strcmp(option, "dropedns")) {
if (!strcmp(option, "cookiealwaysvalid")) {
cookiealwaysvalid = true;
} else if (!strcmp(option, "dropedns")) {
dropedns = true;
} else if (!strcmp(option, "ednsformerr")) {
ednsformerr = true;
@@ -1220,6 +1223,9 @@ setup(void) {
/*
* Modify server context according to command line options
*/
if (cookiealwaysvalid) {
ns_server_setoption(sctx, NS_SERVER_COOKIEALWAYSVALID, true);
}
if (disable4) {
ns_server_setoption(sctx, NS_SERVER_DISABLE4, true);
}
+29 -5
View File
@@ -1391,6 +1391,13 @@ configure_peer(const cfg_obj_t *cpeer, isc_mem_t *mctx, dns_peer_t **peerp) {
CHECK(dns_peer_setrequestnsid(peer, cfg_obj_asboolean(obj)));
}
obj = NULL;
(void)cfg_map_get(cpeer, "request-zoneversion", &obj);
if (obj != NULL) {
CHECK(dns_peer_setrequestzoneversion(peer,
cfg_obj_asboolean(obj)));
}
obj = NULL;
(void)cfg_map_get(cpeer, "send-cookie", &obj);
if (obj != NULL) {
@@ -3246,6 +3253,7 @@ create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view,
dns_zone_setoption(zone, ~DNS_ZONEOPT_NOCHECKNS, false);
dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true);
dns_zone_setoption(zone, DNS_ZONEOPT_ZONEVERSION, false);
dns_zone_setcheckdstype(zone, dns_checkdstype_no);
dns_zone_setnotifytype(zone, dns_notifytype_no);
dns_zone_setautomatic(zone, true);
@@ -3762,7 +3770,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
uint32_t maxbits;
unsigned int resopts = 0;
dns_zone_t *zone = NULL;
uint32_t max_clients_per_query;
uint32_t clients_per_query, max_clients_per_query;
bool empty_zones_enable;
const cfg_obj_t *disablelist = NULL;
isc_stats_t *resstats = NULL;
@@ -5132,6 +5140,11 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
INSIST(result == ISC_R_SUCCESS);
view->requestnsid = cfg_obj_asboolean(obj);
obj = NULL;
result = named_config_get(maps, "request-zoneversion", &obj);
INSIST(result == ISC_R_SUCCESS);
view->requestzoneversion = cfg_obj_asboolean(obj);
obj = NULL;
result = named_config_get(maps, "send-cookie", &obj);
INSIST(result == ISC_R_SUCCESS);
@@ -5168,15 +5181,26 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
INSIST(result == ISC_R_SUCCESS);
view->v6bias = cfg_obj_asuint32(obj) * 1000;
obj = NULL;
result = named_config_get(maps, "clients-per-query", &obj);
INSIST(result == ISC_R_SUCCESS);
clients_per_query = cfg_obj_asuint32(obj);
obj = NULL;
result = named_config_get(maps, "max-clients-per-query", &obj);
INSIST(result == ISC_R_SUCCESS);
max_clients_per_query = cfg_obj_asuint32(obj);
obj = NULL;
result = named_config_get(maps, "clients-per-query", &obj);
INSIST(result == ISC_R_SUCCESS);
dns_resolver_setclientsperquery(view->resolver, cfg_obj_asuint32(obj),
if (max_clients_per_query < clients_per_query) {
cfg_obj_log(obj, ISC_LOG_WARNING,
"configured clients-per-query (%u) exceeds "
"max-clients-per-query (%u); automatically "
"adjusting max-clients-per-query to (%u)",
clients_per_query, max_clients_per_query,
clients_per_query);
max_clients_per_query = clients_per_query;
}
dns_resolver_setclientsperquery(view->resolver, clients_per_query,
max_clients_per_query);
/*
+4 -2
View File
@@ -358,6 +358,8 @@ init_desc(void) {
SET_NSSTATDESC(expireopt, "Expire option received", "ExpireOpt");
SET_NSSTATDESC(keepaliveopt, "EDNS TCP keepalive option received",
"KeepAliveOpt");
SET_NSSTATDESC(zoneversionopt, "ZONEVERSION option received",
"ZoneVersionOpt");
SET_NSSTATDESC(padopt, "EDNS padding option received", "PadOpt");
SET_NSSTATDESC(otheropt, "Other EDNS option received", "OtherOpt");
SET_NSSTATDESC(cookiein, "COOKIE option received", "CookieIn");
@@ -1603,7 +1605,7 @@ xfrin_xmlrender(dns_zone_t *zone, void *arg) {
isc_sockaddr_format(addrp, addr_buf, sizeof(addr_buf));
TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR addr_buf));
} else if (is_presoa) {
addr = dns_zone_getsourceaddr(zone);
dns_zone_getsourceaddr(zone, &addr);
isc_sockaddr_format(&addr, addr_buf, sizeof(addr_buf));
TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR addr_buf));
} else {
@@ -2660,7 +2662,7 @@ xfrin_jsonrender(dns_zone_t *zone, void *arg) {
json_object_object_add(xfrinobj, "localaddr",
json_object_new_string(addr_buf));
} else if (is_presoa) {
addr = dns_zone_getsourceaddr(zone);
dns_zone_getsourceaddr(zone, &addr);
isc_sockaddr_format(&addr, addr_buf, sizeof(addr_buf));
json_object_object_add(xfrinobj, "localaddr",
json_object_new_string(addr_buf));
+12 -8
View File
@@ -1227,6 +1227,12 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
dns_zone_setkasp(zone, NULL);
}
obj = NULL;
result = named_config_get(maps, "provide-zoneversion", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
dns_zone_setoption(zone, DNS_ZONEOPT_ZONEVERSION,
cfg_obj_asboolean(obj));
obj = NULL;
result = named_config_get(maps, "notify", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
@@ -1279,22 +1285,22 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
obj = NULL;
result = named_config_get(maps, "parental-source", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
CHECK(dns_zone_setparentalsrc4(zone, cfg_obj_assockaddr(obj)));
dns_zone_setparentalsrc4(zone, cfg_obj_assockaddr(obj));
obj = NULL;
result = named_config_get(maps, "parental-source-v6", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
CHECK(dns_zone_setparentalsrc6(zone, cfg_obj_assockaddr(obj)));
dns_zone_setparentalsrc6(zone, cfg_obj_assockaddr(obj));
obj = NULL;
result = named_config_get(maps, "notify-source", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
CHECK(dns_zone_setnotifysrc4(zone, cfg_obj_assockaddr(obj)));
dns_zone_setnotifysrc4(zone, cfg_obj_assockaddr(obj));
obj = NULL;
result = named_config_get(maps, "notify-source-v6", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
CHECK(dns_zone_setnotifysrc6(zone, cfg_obj_assockaddr(obj)));
dns_zone_setnotifysrc6(zone, cfg_obj_assockaddr(obj));
obj = NULL;
result = named_config_get(maps, "notify-to-soa", &obj);
@@ -1938,14 +1944,12 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
obj = NULL;
result = named_config_get(maps, "transfer-source", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
CHECK(dns_zone_setxfrsource4(mayberaw,
cfg_obj_assockaddr(obj)));
dns_zone_setxfrsource4(mayberaw, cfg_obj_assockaddr(obj));
obj = NULL;
result = named_config_get(maps, "transfer-source-v6", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
CHECK(dns_zone_setxfrsource6(mayberaw,
cfg_obj_assockaddr(obj)));
dns_zone_setxfrsource6(mayberaw, cfg_obj_assockaddr(obj));
obj = NULL;
(void)named_config_get(maps, "try-tcp-refresh", &obj);
+1
View File
@@ -28,6 +28,7 @@ options {
} except-from {
"example";
};
qname-minimization disabled; // Regression test for GL #4652
};
trust-anchors { };
@@ -31,6 +31,7 @@ server 0.0.0.0 {
request-ixfr no;
request-ixfr-max-diffs 0;
request-nsid no;
request-zoneversion no;
require-cookie no;
send-cookie no;
tcp-keepalive no;
@@ -55,6 +56,7 @@ server :: {
request-ixfr no;
request-ixfr-max-diffs 0;
request-nsid no;
request-zoneversion no;
require-cookie no;
send-cookie no;
tcp-keepalive no;
+4 -2
View File
@@ -131,11 +131,13 @@ status=$((status + ret))
echo_i "checking that log-report-channel zones fail if '*._er/TXT' is missing ($n)"
ret=0
$CHECKZONE -R fail example zones/er.db >test.out2.$n 2>&1 || ret=1
grep -F "no '*._er/TXT' wildcard found" test.out4.$n >/dev/null && ret=1
grep -F "no '*._er/TXT' wildcard found" test.out2.$n >/dev/null && ret=1
$CHECKZONE example zones/er-missing.db >test.out3.$n 2>&1 || ret=1
grep -F "no '*._er/TXT' wildcard found" test.out4.$n >/dev/null && ret=1
grep -F "no '*._er/TXT' wildcard found" test.out3.$n >/dev/null && ret=1
$CHECKZONE -R fail example zones/er-missing.db >test.out4.$n 2>&1 && ret=1
grep -F "no '*._er/TXT' wildcard found" test.out4.$n >/dev/null || ret=1
n=$((n + 1))
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "checking that raw zone with bad class is handled ($n)"
+1
View File
@@ -308,6 +308,7 @@ def logger(request, system_test_name):
@pytest.fixture(scope="module")
def expected_artifacts(request):
common_artifacts = [
"*/.hypothesis", # drop after Ubuntu 20.04 Focal Fossa gets removed from CI
".libs/*", # possible build artifacts, see GL #5055
"ns*/named.conf",
"ns*/named.memstats",
+26 -4
View File
@@ -361,6 +361,23 @@ grep "status: NOERROR," dig.out.test$n >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "Restart NS4 with -T cookiealwaysvalid ($n)"
stop_server ns4
touch ns4/named.cookiealwaysvalid
start_server --noclean --restart --port ${PORT} ns4 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "test NS6 cookie on NS4 with -T cookiealwaysvalid (expect success) ($n)"
ret=0
$DIG $DIGOPTS +cookie=$ns6cookie -b 10.53.0.4 +nobadcookie soa . @10.53.0.4 >dig.out.test$n || ret=1
grep "; COOKIE:.*(good)" dig.out.test$n >/dev/null || ret=1
grep "status: NOERROR," dig.out.test$n >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "check that test server is correctly configured ($n)"
ret=0
@@ -552,16 +569,21 @@ sys.exit(1)'; then
$DIG $DIGOPTS @10.53.0.1 tsig. >dig.out.test$n.1 || ret=1
grep "status: NOERROR" dig.out.test$n.1 >/dev/null || ret=1
rndc_dumpdb ns1
# prime cache with NS response for QNAME minimisation
grep "$pat" ns1/named_dump.db.test$n >/dev/null || ret=1
$DIG $DIGOPTS @10.53.0.1 NS nocookie.tsig >dig.out.test$n.2 || ret=1
grep "status: NOERROR" dig.out.test$n.2 >/dev/null || ret=1
# check the disabled server response
nextpart ns1/named.run >/dev/null
$DIG $DIGOPTS @10.53.0.1 nocookie.tsig >dig.out.test$n.2 || ret=1
grep "status: NOERROR" dig.out.test$n.2 >/dev/null || ret=1
grep 'A.10\.53\.0\.9' dig.out.test$n.2 >/dev/null || ret=1
grep 'A.10\.53\.0\.10' dig.out.test$n.2 >/dev/null || ret=1
$DIG $DIGOPTS @10.53.0.1 nocookie.tsig >dig.out.test$n.3 || ret=1
grep "status: NOERROR" dig.out.test$n.3 >/dev/null || ret=1
grep 'A.10\.53\.0\.9' dig.out.test$n.3 >/dev/null || ret=1
grep 'A.10\.53\.0\.10' dig.out.test$n.3 >/dev/null || ret=1
nextpart ns1/named.run >named.run.test$n
count=$(grep -c ') [0-9][0-9]* NOERROR 0' named.run.test$n)
test $count -eq 2 || ret=1
count=$(grep -c '^; COOKIE: ................................' named.run.test$n)
test $count -eq 1 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
fi
@@ -19,6 +19,7 @@ pytestmark = pytest.mark.extra_artifacts(
"ans*/ans.run",
"ans*/query.log",
"ns1/named_dump.db*",
"ns4/named.cookiealwaysvalid",
]
)
@@ -36,4 +36,5 @@ zone "example" {
zone "example.tld" {
type primary;
file "example.tld.db";
provide-zoneversion no;
};
+77
View File
@@ -801,6 +801,73 @@ if [ -x "$DIG" ]; then
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking dig +zoneversion to a authoritative server ($n)"
ret=0
dig_with_opts @10.53.0.2 +zoneversion a.example >dig.out.test$n 2>&1 || ret=1
pat="; ZONEVERSION: ZONE: example, SOA-SERIAL: 2000042407"
grep "$pat" dig.out.test$n >/dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking dig +zoneversion to a authoritative server with zoneversion disabled ($n)"
ret=0
dig_with_opts @10.53.0.2 +zoneversion a.example.tld >dig.out.test$n 2>&1 || ret=1
grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1
grep "; ZONEVERSION:" dig.out.test$n >/dev/null && ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
if [ $HAS_PYYAML -ne 0 ]; then
n=$((n + 1))
echo_i "checking dig +yaml +zoneversion to a authoritative server ($n)"
ret=0
dig_with_opts @10.53.0.2 +yaml +zoneversion a.example >dig.out.test$n 2>&1 || ret=1
$PYTHON yamlget.py dig.out.test$n 0 message response_message_data OPT_PSEUDOSECTION EDNS ZONEVERSION >yamlget.out.test$n 2>&1 || ret=1
read -r value <yamlget.out.test$n
expected="{'ZONE': 'example', 'SOA-SERIAL': 2000042407}"
[ "$value" = "$expected" ] || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
fi
n=$((n + 1))
echo_i "checking dig +ednsopt=ZONEVERSION:<answer> to a authoritative server ($n)"
ret=0
dig_with_opts @10.53.0.2 +ednsopt=ZONEVERSION:0100000007DA a.example >dig.out.test$n 2>&1 || ret=1
grep "status: FORMERR" dig.out.test$n >/dev/null || ret=1
grep "; ZONEVERSION:" dig.out.test$n >/dev/null && ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking dig +zoneversion to a recursive server ($n)"
ret=0
dig_with_opts @10.53.0.3 +zoneversion a.example >dig.out.test$n 2>&1 || ret=1
grep '; ZONEVERSION:' dig.out.test$n >/dev/null && ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking display of non serial type zoneversion ($n)"
ret=0
dig_with_opts @10.53.0.2 +qr +ednsopt=ZONEVERSION:0100000007DA a.example >dig.out.test$n 2>&1 || ret=1
grep '; ZONEVERSION: LABELS: 1, TYPE: 0, VALUE: 000007da ("....")' dig.out.test$n >/dev/null && ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
if [ $HAS_PYYAML -ne 0 ]; then
n=$((n + 1))
echo_i "checking display of non serial type zoneversion +yaml ($n)"
ret=0
dig_with_opts @10.53.0.2 +qr +ednsopt=ZONEVERSION:0100000007DA a.example +yaml >dig.out.test$n 2>&1 || ret=1
$PYTHON yamlget.py dig.out.test$n 0 message query_message_data OPT_PSEUDOSECTION EDNS ZONEVERSION >yamlget.out.test$n 2>&1 || ret=1
expected="{'ZONE': 'example', 'TYPE': 1, 'VALUE': 000007da, PVALUE: '....'}"
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
fi
n=$((n + 1))
echo_i "check that dig gracefully handles bad escape in domain name ($n)"
ret=0
@@ -1135,6 +1202,16 @@ if [ -x "$DIG" ]; then
grep "; EDNS: version: 0, flags:; udp: 1232" dig.out.test$n >/dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "check that dig +showbadvers works ($n)"
dig_with_opts @10.53.0.3 +edns=1 +qr +showbadvers a.example >dig.out.test$n 2>&1 || ret=1
grep "; EDNS: version: 1, flags:; udp: 1232" dig.out.test$n >/dev/null || ret=1
grep "; EDNS: version: 0, flags:; udp: 1232" dig.out.test$n >/dev/null || ret=1
grep -F "status: BADVERS" dig.out.test$n >/dev/null || ret=1
grep -F "status: NOERROR" dig.out.test$n >/dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
else
echo_i "$DIG is needed, so skipping these dig tests"
fi
+1 -1
View File
@@ -64,7 +64,7 @@ for subdomain in digest-alg-unsupported ds-unsupported secure badds \
kskonly update-nsec3 auto-nsec auto-nsec3 secure.below-cname \
ttlpatch split-dnssec split-smart expired expiring upper lower \
dnskey-unknown dnskey-unsupported dnskey-unsupported-2 \
dnskey-nsec3-unknown managed-future revkey \
dnskey-nsec3-unknown managed-future future revkey \
dname-at-apex-nsec3 occluded rsasha1 rsasha1-1024; do
cp "../ns3/dsset-$subdomain.example." .
done
@@ -0,0 +1,6 @@
; This is a key-signing key, keyid 23640, for .
; Created: 20250310185208 (Mon Mar 10 18:52:08 2025)
; Publish: 20250310185208 (Mon Mar 10 18:52:08 2025)
; Activate: 20250310185208 (Mon Mar 10 18:52:08 2025)
; Revoke: 20250310185208 (Mon Mar 10 18:52:08 2025)
. IN DNSKEY 257 3 13 uKwpRtMH+9iuUk/Xj6LciIP5ZckaBtXaUqxUxzJYexXjvxGZGX4470Jv hq2NCI3HBZQNaCCP/h9sluhIzRGPTA==
@@ -0,0 +1,7 @@
Private-key-format: v1.3
Algorithm: 13 (ECDSAP256SHA256)
PrivateKey: m5udfGNSijISQ8Tfp4kx09O1em4PErLUw/mCj3SKmqw=
Created: 20250310185208
Publish: 20250310185208
Activate: 20250310185208
Revoke: 20250310185208
@@ -0,0 +1,5 @@
; This is a zone-signing key, keyid 23768, for .
; Created: 20250310185208 (Mon Mar 10 18:52:08 2025)
; Publish: 20250310185208 (Mon Mar 10 18:52:08 2025)
; Activate: 20250310185208 (Mon Mar 10 18:52:08 2025)
. IN DNSKEY 256 3 13 TFelYtTRBWeA9A307vvuWIcaNwW4txW4RgSELtsi46ZQs24ncRxmxtFf uJuPyVXePNiE4HNI9CIowGUsn5WuBw==
@@ -0,0 +1,37 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
; This is a zone which has two DNSKEY records, both of which have
; existing private key files available. They should be loaded automatically
; and the zone correctly signed.
;
$TTL 30 ; 30 seconds
. IN SOA a.root.servers.nil. each.isc.org. (
2000042101 ; serial
600 ; refresh (10 minutes)
600 ; retry (10 minutes)
1200 ; expire (20 minutes)
600 ; minimum (10 minutes)
)
NS a.root-servers.nil.
DNSKEY 256 3 13 (
TFelYtTRBWeA9A307vvuWIcaNwW4txW4RgSELtsi46ZQ
s24ncRxmxtFfuJuPyVXePNiE4HNI9CIowGUsn5WuBw==
) ; ZSK; alg = ECDSAP256SHA256 ; key id = 23768
DNSKEY 257 3 13 (
OSmhpULEDCUzHCBeDU5uJXzkCcGuW2qrkQznKRPGhRZN
j7ZUIGInGzM5Um5m02ULWt8tKbi55NJUeifKWegQ0g==
) ; KSK; alg = ECDSAP256SHA256 ; key id = 22255
DNSKEY 385 3 13 (
uKwpRtMH+9iuUk/Xj6LciIP5ZckaBtXaUqxUxzJYexXj
vxGZGX4470Jvhq2NCI3HBZQNaCCP/h9sluhIzRGPTA==
) ; revoked KSK; alg = ECDSAP256SHA256 ; key id = 23768
a.root-servers.nil. A 10.53.0.1
+27 -2
View File
@@ -1564,6 +1564,18 @@ n=$((n + 1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
echo_ic "revoked KSK ID collides with ZSK ($n)"
ret=0
# signing should fail, but should not coredump
(
cd signer/general || exit 0
rm -f signed.zone
$SIGNER -S -f signed.zone -o . test12.zone >signer.out.$n
) && ret=1
n=$((n + 1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
echo_ic "check that dnssec-signzone rejects excessive NSEC3 iterations ($n)"
ret=0
(
@@ -2179,7 +2191,7 @@ echo_i "checking RRSIG query from cache ($n)"
ret=0
dig_with_opts normalthenrrsig.secure.example. @10.53.0.4 a >/dev/null || ret=1
ans=$(dig_with_opts +short normalthenrrsig.secure.example. @10.53.0.4 rrsig) || ret=1
expect=$(dig_with_opts +short normalthenrrsig.secure.example. @10.53.0.3 rrsig | grep '^A') || ret=1
expect=$(dig_with_opts +short normalthenrrsig.secure.example. @10.53.0.3 rrsig | grep -E '^(A|NSEC)') || ret=1
test "$ans" = "$expect" || ret=1
# also check that RA is set
dig_with_opts normalthenrrsig.secure.example. @10.53.0.4 rrsig >dig.out.ns4.test$n || ret=1
@@ -2859,6 +2871,19 @@ dig_with_opts +noauth expired.example. +dnssec @10.53.0.4 soa >dig.out.ns4.test$
grep "SERVFAIL" dig.out.ns4.test$n >/dev/null || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1
grep "expired.example/.*: RRSIG has expired" ns4/named.run >/dev/null || ret=1
grep "; EDE: 7 (Signature Expired): (expired.example/DNSKEY)" dig.out.ns4.test$n >/dev/null || ret=1
n=$((n + 1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
status=$((status + ret))
echo_i "checking signatures in the future do not validate ($n)"
ret=0
dig_with_opts +noauth future.example. +dnssec @10.53.0.4 soa >dig.out.ns4.test$n || ret=1
grep "SERVFAIL" dig.out.ns4.test$n >/dev/null || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1
grep "future.example/.*: RRSIG validity period has not begun" ns4/named.run >/dev/null || ret=1
grep "; EDE: 8 (Signature Not Yet Valid): (future.example/DNSKEY)" dig.out.ns4.test$n >/dev/null || ret=1
n=$((n + 1))
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
@@ -3755,7 +3780,7 @@ status=$((status + ret))
echo_i "checking EDE code 1 for bad alg mnemonic ($n)"
ret=0
dig_with_opts @10.53.0.4 badalg.secure.example >dig.out.ns4.test$n || ret=1
grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (ECDSAP256SHA256 badalg.secure.example/A)" dig.out.ns4.test$n >/dev/null || ret=1
grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (ECDSAP256SHA256 badalg.secure.example/NSEC)" dig.out.ns4.test$n >/dev/null || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1
n=$((n + 1))
test "$ret" -eq 0 || echo_i "failed"
+2 -6
View File
@@ -232,9 +232,7 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
dns_fixedname_init(&name);
CHECK(dns__db_addrdataset(sampledb->db, node, version, now, rdataset,
options, addedrdataset DNS__DB_FLARG_PASS));
if (rdataset->type == dns_rdatatype_a ||
rdataset->type == dns_rdatatype_aaaa)
{
if (dns_rdatatype_isaddr(rdataset->type)) {
CHECK(dns_db_nodefullname(sampledb->db, node,
dns_fixedname_name(&name)));
CHECK(syncptrs(sampledb->inst, dns_fixedname_name(&name),
@@ -263,9 +261,7 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
goto cleanup;
}
if (rdataset->type == dns_rdatatype_a ||
rdataset->type == dns_rdatatype_aaaa)
{
if (dns_rdatatype_isaddr(rdataset->type)) {
CHECK(dns_db_nodefullname(sampledb->db, node,
dns_fixedname_name(&name)));
CHECK(syncptrs(sampledb->inst, dns_fixedname_name(&name),
@@ -0,0 +1,52 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
options {
query-source address 10.53.0.5;
notify-source 10.53.0.5;
transfer-source 10.53.0.5;
port @PORT@;
directory ".";
pid-file "named.pid";
listen-on { 10.53.0.5; };
listen-on-v6 { none; };
recursion yes;
dnssec-validation yes;
notify yes;
stale-answer-enable yes;
stale-cache-enable yes;
stale-answer-client-timeout 0;
/* max-clients-per-query < clients-per-query */
clients-per-query 10;
max-clients-per-query 5;
};
trust-anchors { };
server 10.53.0.4 {
edns no;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.5 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "root.hint";
};
+9
View File
@@ -328,5 +328,14 @@ echo_i "$zspill clients spilled (expected $expected)"
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking a warning is logged if max-clients-per-query < clients-per-query ($n)"
ret=0
copy_setports ns5/named3.conf.in ns5/named.conf
rndc_reconfig ns5 10.53.0.5
wait_for_message ns5/named.run "configured clients-per-query (10) exceeds max-clients-per-query (5); automatically adjusting max-clients-per-query to (10)" || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1
+100 -32
View File
@@ -224,6 +224,20 @@ class DnsProtocol(enum.Enum):
TCP = enum.auto()
@dataclass(frozen=True)
class Peer:
"""
Pretty-printed connection endpoint.
"""
host: str
port: int
def __str__(self) -> str:
host = f"[{self.host}]" if ":" in self.host else self.host
return f"{host}:{self.port}"
@dataclass
class QueryContext:
"""
@@ -232,7 +246,7 @@ class QueryContext:
query: dns.message.Message
response: dns.message.Message
peer: Tuple[str, int]
peer: Peer
protocol: DnsProtocol
zone: Optional[dns.zone.Zone] = None
soa: Optional[dns.rrset.RRset] = None
@@ -513,56 +527,110 @@ class AsyncDnsServer(AsyncServer):
self._zone_tree.add(zone)
async def _handle_udp(
self, wire: bytes, peer: Tuple[str, int], transport: asyncio.DatagramTransport
self, wire: bytes, addr: Tuple[str, int], transport: asyncio.DatagramTransport
) -> None:
logging.debug("Received UDP message: %s", wire.hex())
peer = Peer(addr[0], addr[1])
responses = self._handle_query(wire, peer, DnsProtocol.UDP)
async for response in responses:
transport.sendto(response, peer)
transport.sendto(response, addr)
async def _handle_tcp(
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
) -> None:
wire_length_bytes = await reader.read(2)
(wire_length,) = struct.unpack("!H", wire_length_bytes)
logging.debug("Receiving TCP message (%d octets)...", wire_length)
peer_info = writer.get_extra_info("peername")
peer = Peer(peer_info[0], peer_info[1])
logging.debug("Accepted TCP connection from %s", peer)
wire = await reader.read(wire_length)
full_message = wire_length_bytes + wire
logging.debug("Received complete TCP message: %s", full_message.hex())
peer = writer.get_extra_info("peername")
responses = self._handle_query(wire, peer, DnsProtocol.TCP)
async for response in responses:
writer.write(response)
while True:
try:
await writer.drain()
wire = await self._read_tcp_query(reader, peer)
if not wire:
break
await self._send_tcp_response(writer, peer, wire)
except ConnectionResetError:
logging.error(
"TCP connection from %s reset by peer", self._format_peer(peer)
)
logging.error("TCP connection from %s reset by peer", peer)
return
logging.debug("Closing TCP connection from %s", peer)
writer.close()
await writer.wait_closed()
def _format_peer(self, peer: Tuple[str, int]) -> str:
host = peer[0]
port = peer[1]
if "::" in host:
host = f"[{host}]"
return f"{host}:{port}"
async def _read_tcp_query(
self, reader: asyncio.StreamReader, peer: Peer
) -> Optional[bytes]:
wire_length = await self._read_tcp_query_wire_length(reader, peer)
if not wire_length:
return None
def _log_query(
self, qctx: QueryContext, peer: Tuple[str, int], protocol: DnsProtocol
return await self._read_tcp_query_wire(reader, peer, wire_length)
async def _read_tcp_query_wire_length(
self, reader: asyncio.StreamReader, peer: Peer
) -> Optional[int]:
logging.debug("Receiving TCP message length from %s...", peer)
wire_length_bytes = await self._read_tcp_octets(reader, peer, 2)
if not wire_length_bytes:
return None
(wire_length,) = struct.unpack("!H", wire_length_bytes)
return wire_length
async def _read_tcp_query_wire(
self, reader: asyncio.StreamReader, peer: Peer, wire_length: int
) -> Optional[bytes]:
logging.debug("Receiving TCP message (%d octets) from %s...", wire_length, peer)
wire = await self._read_tcp_octets(reader, peer, wire_length)
if not wire:
return None
logging.debug("Received complete TCP message from %s: %s", peer, wire.hex())
return wire
async def _read_tcp_octets(
self, reader: asyncio.StreamReader, peer: Peer, expected: int
) -> Optional[bytes]:
buffer = b""
while len(buffer) < expected:
chunk = await reader.read(expected - len(buffer))
if not chunk:
if buffer:
logging.debug(
"Received short TCP message (%d octets) from %s: %s",
len(buffer),
peer,
buffer.hex(),
)
else:
logging.debug("Received disconnect from %s", peer)
return None
logging.debug("Received %d TCP octets from %s", len(chunk), peer)
buffer += chunk
return buffer
async def _send_tcp_response(
self, writer: asyncio.StreamWriter, peer: Peer, wire: bytes
) -> None:
responses = self._handle_query(wire, peer, DnsProtocol.TCP)
async for response in responses:
writer.write(response)
await writer.drain()
def _log_query(self, qctx: QueryContext, peer: Peer, protocol: DnsProtocol) -> None:
logging.info(
"Received %s/%s/%s (ID=%d) query from %s (%s)",
qctx.qname.to_text(omit_final_dot=True),
dns.rdataclass.to_text(qctx.qclass),
dns.rdatatype.to_text(qctx.qtype),
qctx.query.id,
self._format_peer(peer),
peer,
protocol.name,
)
logging.debug(
@@ -573,14 +641,14 @@ class AsyncDnsServer(AsyncServer):
self,
qctx: QueryContext,
response: Optional[Union[dns.message.Message, bytes]],
peer: Tuple[str, int],
peer: Peer,
protocol: DnsProtocol,
) -> None:
if not response:
logging.info(
"Not sending a response to query (ID=%d) from %s (%s)",
qctx.query.id,
self._format_peer(peer),
peer,
protocol.name,
)
return
@@ -606,7 +674,7 @@ class AsyncDnsServer(AsyncServer):
len(response.authority),
len(response.additional),
qctx.query.id,
self._format_peer(peer),
peer,
protocol.name,
)
logging.debug(
@@ -618,13 +686,13 @@ class AsyncDnsServer(AsyncServer):
"Sending response (%d bytes) to a query (ID=%d) from %s (%s)",
len(response),
qctx.query.id,
self._format_peer(peer),
peer,
protocol.name,
)
logging.debug("[OUT] %s", response.hex())
async def _handle_query(
self, wire: bytes, peer: Tuple[str, int], protocol: DnsProtocol
self, wire: bytes, peer: Peer, protocol: DnsProtocol
) -> AsyncGenerator[bytes, None]:
"""
Yield wire data to send as a response over the established transport.
-52
View File
@@ -9,7 +9,6 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
import difflib
import shutil
from typing import Optional
@@ -98,28 +97,6 @@ def zones_equal(
assert found_rdataset.ttl == rdataset.ttl
def zone_contains(
zone: dns.zone.Zone, rrset: dns.rrset.RRset, compare_ttl=False
) -> bool:
"""Check if a zone contains RRset"""
def compare_rrs(rr1, rrset):
rr2 = next((other_rr for other_rr in rrset if rr1 == other_rr), None)
if rr2 is None:
return False
if compare_ttl:
return rr1.ttl == rr2.ttl
return True
for _, node in zone.nodes.items():
for rdataset in node:
for rr in rdataset:
if compare_rrs(rr, rrset):
return True
return False
def is_executable(cmd: str, errmsg: str) -> None:
executable = shutil.which(cmd)
assert executable is not None, errmsg
@@ -151,32 +128,3 @@ def is_response_to(response: dns.message.Message, query: dns.message.Message) ->
single_question(response)
single_question(query)
assert query.is_response(response), str(response)
def file_contents_contain(file, substr):
with open(file, "r", encoding="utf-8") as fp:
for line in fp:
if f"{substr}" in line:
return True
return False
def file_contents_equal(file1, file2):
def normalize_line(line):
# remove trailing&leading whitespace and replace multiple whitespaces
return " ".join(line.split())
def read_lines(file_path):
with open(file_path, "r", encoding="utf-8") as file:
return [normalize_line(line) for line in file.readlines()]
lines1 = read_lines(file1)
lines2 = read_lines(file2)
differ = difflib.Differ()
diff = differ.compare(lines1, lines2)
for line in diff:
assert not line.startswith("+ ") and not line.startswith(
"- "
), f'file contents of "{file1}" and "{file2}" differ'
+27 -628
View File
@@ -10,34 +10,24 @@
# information regarding copyright ownership.
from functools import total_ordering
import glob
import os
from pathlib import Path
import re
import subprocess
import time
from typing import List, Optional, Union
from typing import Optional, Union
from datetime import datetime, timedelta, timezone
import dns
import dns.tsig
import isctest.log
import isctest.query
DEFAULT_TTL = 300
NEXT_KEY_EVENT_THRESHOLD = 100
def _query(server, qname, qtype, tsig=None):
def _query(server, qname, qtype):
query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
if tsig is not None:
tsigkey = tsig.split(":")
keyring = dns.tsig.Key(tsigkey[1], tsigkey[2], tsigkey[0])
query.use_tsig(keyring)
try:
response = isctest.query.tcp(query, server.ip, server.ports.dns, timeout=3)
except dns.exception.Timeout:
@@ -47,158 +37,6 @@ def _query(server, qname, qtype, tsig=None):
return response
class KeyProperties:
"""
Represent the (expected) properties a key should have.
"""
def __init__(self, name: str, properties: dict, metadata: dict, timing: dict):
self.name = name
self.key = None
self.properties = properties
self.metadata = metadata
self.timing = timing
def __repr__(self):
return self.name
def __str__(self) -> str:
return self.name
@staticmethod
def default(with_state=True) -> "KeyProperties":
result = KeyProperties.__new__(KeyProperties)
result.name = "DEFAULT"
result.key = None
result.timing = {}
result.properties = {
"expect": True,
"private": True,
"legacy": False,
"role": "csk",
"role_full": "key-signing",
"dnskey_ttl": 3600,
"flags": 257,
}
result.metadata = {
"Algorithm": 13, # ECDSAP256SHA256
"Length": 256,
"Lifetime": 0,
"KSK": "yes",
"ZSK": "yes",
}
if with_state:
result.metadata["GoalState"] = "omnipresent"
result.metadata["DNSKEYState"] = "rumoured"
result.metadata["KRRSIGState"] = "rumoured"
result.metadata["ZRRSIGState"] = "rumoured"
result.metadata["DSState"] = "hidden"
return result
def Ipub(self, config):
ipub = timedelta(0)
if self.key.get_metadata("Predecessor", must_exist=False) != "undefined":
# Ipub = Dprp + TTLkey
ipub = (
config["dnskey-ttl"]
+ config["zone-propagation-delay"]
+ config["publish-safety"]
)
self.timing["Active"] = self.timing["Published"] + ipub
def IpubC(self, config):
if not self.key.is_ksk():
return
ttl1 = config["dnskey-ttl"] + config["publish-safety"]
ttl2 = timedelta(0)
if self.key.get_metadata("Predecessor", must_exist=False) == "undefined":
# If this is the first key, we also need to wait until the zone
# signatures are omnipresent. Use max-zone-ttl instead of
# dnskey-ttl, and no publish-safety (because we are looking at
# signatures here, not the public key).
ttl2 = config["max-zone-ttl"]
# IpubC = DprpC + TTLkey
ipubc = config["zone-propagation-delay"] + max(ttl1, ttl2)
self.timing["PublishCDS"] = self.timing["Published"] + ipubc
if self.metadata["Lifetime"] != 0:
self.timing["DeleteCDS"] = self.timing["PublishCDS"] + int(
self.metadata["Lifetime"]
)
def Iret(self, config):
if self.metadata["Lifetime"] == 0:
return
sign_delay = config["signatures-validity"] - config["signatures-refresh"]
safety_interval = config["retire-safety"]
iretKSK = timedelta(0)
iretZSK = timedelta(0)
if self.key.is_ksk():
# Iret = DprpP + TTLds
iretKSK = (
config["parent-propagation-delay"] + config["ds-ttl"] + safety_interval
)
if self.key.is_zsk():
# Iret = Dsgn + Dprp + TTLsig
iretZSK = (
sign_delay
+ config["zone-propagation-delay"]
+ config["max-zone-ttl"]
+ safety_interval
)
self.timing["Removed"] = self.timing["Retired"] + max(iretKSK, iretZSK)
def set_expected_keytimes(self, config, offset=None, pregenerated=False):
if self.key is None:
raise ValueError("KeyProperties must be attached to a Key")
if self.properties["legacy"]:
return
if offset is None:
offset = self.properties["offset"]
self.timing["Generated"] = self.key.get_timing("Created")
self.timing["Published"] = self.timing["Generated"]
if pregenerated:
self.timing["Published"] = self.key.get_timing("Publish")
self.timing["Published"] = self.timing["Published"] + offset
self.Ipub(config)
# Set Retired timing metadata if key has lifetime.
if self.metadata["Lifetime"] != 0:
self.timing["Retired"] = self.timing["Active"] + int(
self.metadata["Lifetime"]
)
self.IpubC(config)
self.Iret(config)
# Key state change times must exist, but since we cannot reliably tell
# when named made the actual state change, we don't care what the
# value is. Set it to None will verify that the metadata exists, but
# without actual checking the value.
self.timing["DNSKEYChange"] = None
if self.key.is_ksk():
self.timing["DSChange"] = None
self.timing["KRRSIGChange"] = None
if self.key.is_zsk():
self.timing["ZRRSIGChange"] = None
@total_ordering
class KeyTimingMetadata:
"""
@@ -279,7 +117,6 @@ class Key:
else:
self.keydir = Path(keydir)
self.path = str(self.keydir / name)
self.privatefile = f"{self.path}.private"
self.keyfile = f"{self.path}.key"
self.statefile = f"{self.path}.state"
self.tag = int(self.name[-5:])
@@ -302,43 +139,21 @@ class Key:
)
return None
def get_metadata(
self, metadata: str, file=None, comment=False, must_exist=True
) -> str:
if file is None:
file = self.statefile
def get_metadata(self, metadata: str, must_exist=True) -> str:
value = "undefined"
regex = rf"{metadata}:\s+(\S+).*"
if comment:
# The expected metadata is prefixed with a ';'.
regex = rf";\s+{metadata}:\s+(\S+).*"
with open(file, "r", encoding="utf-8") as fp:
for line in fp:
regex = rf"{metadata}:\s+(.*)"
with open(self.statefile, "r", encoding="utf-8") as file:
for line in file:
match = re.match(regex, line)
if match is not None:
value = match.group(1)
break
if must_exist and value == "undefined":
raise ValueError(
f'metadata "{metadata}" for key "{self.name}" in file "{file}" undefined'
'state metadata "{metadata}" for key "{self.name}" undefined'
)
return value
def ttl(self) -> int:
with open(self.keyfile, "r", encoding="utf-8") as file:
for line in file:
if line.startswith(";"):
continue
return int(line.split()[1])
return 0
def dnskey(self):
with open(self.keyfile, "r", encoding="utf-8") as file:
for line in file:
if "DNSKEY" in line:
return line.strip()
return "undefined"
def is_ksk(self) -> bool:
return self.get_metadata("KSK") == "yes"
@@ -372,7 +187,7 @@ class Key:
dsfromkey_command = [
os.environ.get("DSFROMKEY"),
"-T",
str(self.ttl()),
"3600",
"-a",
alg,
"-C",
@@ -401,152 +216,6 @@ class Key:
return digest_fromfile == digest_fromwire
def has_metadata(self, key, metadata):
# If 'key' exists in 'metadata' then it must also exist in the state
# meta data. Otherwise, it must not exist in the state meta data.
if key in metadata:
return self.get_metadata(key) != "undefined"
value = self.get_metadata(key, must_exist=False)
if value != "undefined":
isctest.log.debug(f"{self.name} {key} METADATA UNEXPECTED: {value}")
return value == "undefined"
def match_metadata(self, key, metadata):
# If 'key' exists in 'metadata' then it must match the value in the
# state meta data. Otherwise, it must also not exist in the state meta
# data.
if key in metadata:
value = self.get_metadata(key)
if value != f"{metadata[key]}":
isctest.log.debug(
f"{self.name} {key} METADATA MISMATCH: {value} - {metadata[key]}"
)
return value == f"{metadata[key]}"
value = self.get_metadata(key, must_exist=False)
if value != "undefined":
isctest.log.debug(f"{self.name} {key} METADATA UNEXPECTED: {value}")
return value == "undefined"
def match_timing(self, key, timing, file, comment=False):
# If 'key' exists in 'timing' then it must match the value in the
# state timing data. Otherwise, it must also not exist in the state timing
# data.
if key in timing:
value = self.get_metadata(key, file=file, comment=comment)
if value != str(timing[key]):
isctest.log.debug(
f"{self.name} {key} TIMING MISMATCH: {value} - {timing[key]}"
)
return value == str(timing[key])
value = self.get_metadata(key, file=file, comment=comment, must_exist=False)
if value != "undefined":
isctest.log.debug(f"{self.name} {key} TIMING UNEXPECTED: {value}")
return value == "undefined"
def match_properties(self, zone, properties):
# Check the key with given properties.
if not properties.properties["expect"]:
return False
# Check file existence.
# Noop. If file is missing then the get_metadata calls will fail.
# Check the public key file.
role = properties.properties["role_full"]
comment = f"This is a {role} key, keyid {self.tag}, for {zone}."
if not isctest.check.file_contents_contain(self.keyfile, comment):
isctest.log.debug(f"{self.name} COMMENT MISMATCH: expected '{comment}'")
return False
ttl = properties.properties["dnskey_ttl"]
flags = properties.properties["flags"]
alg = properties.metadata["Algorithm"]
dnskey = f"{zone}. {ttl} IN DNSKEY {flags} 3 {alg}"
if not isctest.check.file_contents_contain(self.keyfile, dnskey):
isctest.log.debug(f"{self.name} DNSKEY MISMATCH: expected '{dnskey}'")
return False
# Now check the private key file.
if properties.properties["private"]:
# Retrieve creation date.
created = self.get_metadata("Generated")
pval = self.get_metadata("Created", file=self.privatefile)
if pval != created:
isctest.log.debug(
f"{self.name} Created METADATA MISMATCH: {pval} - {created}"
)
return False
pval = self.get_metadata("Private-key-format", file=self.privatefile)
if pval != "v1.3":
isctest.log.debug(
f"{self.name} Private-key-format METADATA MISMATCH: {pval} - v1.3"
)
return False
pval = self.get_metadata("Algorithm", file=self.privatefile)
if pval != f"{alg}":
isctest.log.debug(
f"{self.name} Algorithm METADATA MISMATCH: {pval} - {alg}"
)
return False
# Now check the key state file.
if properties.properties["legacy"]:
return True
comment = f"This is the state of key {self.tag}, for {zone}."
if not isctest.check.file_contents_contain(self.statefile, comment):
isctest.log.debug(f"{self.name} COMMENT MISMATCH: expected '{comment}'")
return False
attributes = [
"Lifetime",
"Algorithm",
"Length",
"KSK",
"ZSK",
"GoalState",
"DNSKEYState",
"KRRSIGState",
"ZRRSIGState",
"DSState",
]
for key in attributes:
if not self.match_metadata(key, properties.metadata):
return False
# A match is found.
return True
def match_timingmetadata(self, timings, file=None, comment=False):
if file is None:
file = self.statefile
attributes = [
"Generated",
"Created",
"Published",
"Publish",
"PublishCDS",
"SyncPublish",
"Active",
"Activate",
"Retired",
"Inactive",
"Revoked",
"Removed",
"Delete",
]
for key in attributes:
if not self.match_timing(key, timings, file, comment=comment):
isctest.log.debug(f"{self.name} TIMING METADATA MISMATCH: {key}")
return False
return True
def __lt__(self, other: "Key"):
return self.name < other.name
@@ -557,14 +226,14 @@ class Key:
return self.path
def check_zone_is_signed(server, zone, tsig=None):
def check_zone_is_signed(server, zone):
addr = server.ip
fqdn = f"{zone}."
# wait until zone is fully signed
signed = False
for _ in range(10):
response = _query(server, fqdn, dns.rdatatype.NSEC, tsig=tsig)
response = _query(server, fqdn, dns.rdatatype.NSEC)
if not isinstance(response, dns.message.Message):
isctest.log.debug(f"no response for {fqdn} NSEC from {addr}")
elif response.rcode() != dns.rcode.NOERROR:
@@ -608,111 +277,13 @@ def check_zone_is_signed(server, zone, tsig=None):
assert signed
def check_keys(zone, keys, expected):
# Checks keys for a configured zone. This verifies:
# 1. The expected number of keys exist in 'keys'.
# 2. The keys match the expected properties.
def _check_keys():
# check number of keys matches expected.
if len(keys) != len(expected):
return False
if len(keys) == 0:
return True
for expect in expected:
expect.key = None
for key in keys:
found = False
i = 0
while not found and i < len(expected):
if expected[i].key is None:
found = key.match_properties(zone, expected[i])
if found:
key.external = expected[i].properties["legacy"]
expected[i].key = key
i += 1
if not found:
return False
return True
isctest.run.retry_with_timeout(_check_keys, timeout=10)
def check_keytimes(keys, expected):
# Check the key timing metadata for all keys in 'keys'.
assert len(keys) == len(expected)
if len(keys) == 0:
return
for key in keys:
for expect in expected:
if expect.properties["legacy"]:
continue
if not key is expect.key:
continue
synonyms = {}
if "Generated" in expect.timing:
synonyms["Created"] = expect.timing["Generated"]
if "Published" in expect.timing:
synonyms["Publish"] = expect.timing["Published"]
if "PublishCDS" in expect.timing:
synonyms["SyncPublish"] = expect.timing["PublishCDS"]
if "Active" in expect.timing:
synonyms["Activate"] = expect.timing["Active"]
if "Retired" in expect.timing:
synonyms["Inactive"] = expect.timing["Retired"]
if "DeleteCDS" in expect.timing:
synonyms["SyncDelete"] = expect.timing["DeleteCDS"]
if "Revoked" in expect.timing:
synonyms["Revoked"] = expect.timing["Revoked"]
if "Removed" in expect.timing:
synonyms["Delete"] = expect.timing["Removed"]
assert key.match_timingmetadata(synonyms, file=key.keyfile, comment=True)
if expect.properties["private"]:
assert key.match_timingmetadata(synonyms, file=key.privatefile)
if not expect.properties["legacy"]:
assert key.match_timingmetadata(expect.timing)
state_changes = [
"DNSKEYChange",
"KRRSIGChange",
"ZRRSIGChange",
"DSChange",
]
for change in state_changes:
assert key.has_metadata(change, expect.timing)
def check_keyrelationships(keys, expected):
# Check the key relationships (Successor and Predecessor metadata).
for key in keys:
for expect in expected:
if expect.properties["legacy"]:
continue
if not key is expect.key:
continue
relationship_status = ["Predecessor", "Successor"]
for status in relationship_status:
assert key.match_metadata(status, expect.metadata)
def check_dnssec_verify(server, zone, tsig=None):
def check_dnssec_verify(server, zone):
# Check if zone if DNSSEC valid with dnssec-verify.
fqdn = f"{zone}."
verified = False
for _ in range(10):
transfer = _query(server, fqdn, dns.rdatatype.AXFR, tsig=tsig)
transfer = _query(server, fqdn, dns.rdatatype.AXFR)
if not isinstance(transfer, dns.message.Message):
isctest.log.debug(f"no response for {fqdn} AXFR from {server.ip}")
elif transfer.rcode() != dns.rcode.NOERROR:
@@ -844,9 +415,9 @@ def _check_dnskeys(dnskeys, keys, cdnskey=False):
delete_md = f"Sync{delete_md}"
for key in keys:
publish = key.get_timing(publish_md, must_exist=False)
publish = key.get_timing(publish_md)
delete = key.get_timing(delete_md, must_exist=False)
published = publish is not None and now >= publish
published = now >= publish
removed = delete is not None and delete <= now
if not published or removed:
@@ -931,8 +502,8 @@ def check_cds(rrset, keys):
assert numcds == len(cdss)
def _query_rrset(server, fqdn, qtype, tsig=None):
response = _query(server, fqdn, qtype, tsig=tsig)
def _query_rrset(server, fqdn, qtype):
response = _query(server, fqdn, qtype)
assert response.rcode() == dns.rcode.NOERROR
rrs = []
@@ -952,43 +523,46 @@ def _query_rrset(server, fqdn, qtype, tsig=None):
return rrs, rrsigs
def check_apex(server, zone, ksks, zsks, tsig=None):
def check_apex(server, zone, ksks, zsks):
# Test the apex of a zone. This checks that the SOA and DNSKEY RRsets
# are signed correctly and with the appropriate keys.
fqdn = f"{zone}."
# test dnskey query
dnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.DNSKEY, tsig=tsig)
dnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.DNSKEY)
assert len(dnskeys) > 0
check_dnskeys(dnskeys, ksks, zsks)
assert len(rrsigs) > 0
check_signatures(rrsigs, dns.rdatatype.DNSKEY, fqdn, ksks, zsks)
# test soa query
soa, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.SOA, tsig=tsig)
soa, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.SOA)
assert len(soa) == 1
assert f"{zone}. {DEFAULT_TTL} IN SOA" in soa[0].to_text()
assert len(rrsigs) > 0
check_signatures(rrsigs, dns.rdatatype.SOA, fqdn, ksks, zsks)
# test cdnskey query
cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY, tsig=tsig)
cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY)
check_dnskeys(cdnskeys, ksks, zsks, cdnskey=True)
if len(cdnskeys) > 0:
assert len(rrsigs) > 0
check_signatures(rrsigs, dns.rdatatype.CDNSKEY, fqdn, ksks, zsks)
# test cds query
cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS, tsig=tsig)
cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS)
check_cds(cds, ksks)
if len(cds) > 0:
assert len(rrsigs) > 0
check_signatures(rrsigs, dns.rdatatype.CDS, fqdn, ksks, zsks)
def check_subdomain(server, zone, ksks, zsks, tsig=None):
def check_subdomain(server, zone, ksks, zsks):
# Test an RRset below the apex and verify it is signed correctly.
fqdn = f"{zone}."
qname = f"a.{zone}."
qtype = dns.rdatatype.A
response = _query(server, qname, qtype, tsig=tsig)
response = _query(server, qname, qtype)
assert response.rcode() == dns.rcode.NOERROR
match = f"{qname} {DEFAULT_TTL} IN A 10.0.0.1"
@@ -1001,180 +575,5 @@ def check_subdomain(server, zone, ksks, zsks, tsig=None):
else:
assert match in rrset.to_text()
assert len(rrsigs) > 0
check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
def check_update_is_signed(server, fqdn, qname, qtype, rdata, ksks, zsks, tsig=None):
# Test an RRset below the apex and verify it is updated and signed correctly.
response = _query(server, qname, qtype, tsig=tsig)
if response.rcode() != dns.rcode.NOERROR:
return False
rrtype = dns.rdatatype.to_text(qtype)
match = f"{qname} {DEFAULT_TTL} IN {rrtype} {rdata}"
rrsigs = []
for rrset in response.answer:
if rrset.match(
dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype
):
rrsigs.append(rrset)
elif not match in rrset.to_text():
return False
if len(rrsigs) == 0:
return False
# Zone is updated, ready to verify the signatures.
check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
return True
def check_next_key_event(server, zone, next_event):
if next_event is None:
# No next key event check.
return True
val = int(next_event.total_seconds())
if val == 3600:
waitfor = rf".*zone {zone}.*: next key event in (.*) seconds"
else:
# Don't want default loadkeys interval.
waitfor = rf".*zone {zone}.*: next key event in (?!3600$)(.*) seconds"
with server.watch_log_from_start() as watcher:
watcher.wait_for_line(re.compile(waitfor))
next_found = False
minval = val - NEXT_KEY_EVENT_THRESHOLD
maxval = val + NEXT_KEY_EVENT_THRESHOLD
with open(f"{server.identifier}/named.run", "r", encoding="utf-8") as fp:
for line in fp:
match = re.match(waitfor, line)
if match is not None:
nextval = int(match.group(1))
if minval <= nextval <= maxval:
next_found = True
break
isctest.log.debug(
f"check next key event: expected {val} in: {line.strip()}"
)
return next_found
def keydir_to_keylist(
zone: str, keydir: Optional[str] = None, in_use: Optional[bool] = False
) -> List[Key]:
# Retrieve all keys from the key files in a directory. If 'zone' is None,
# retrieve all keys in the directory, otherwise only those matching the
# zone name. If 'keydir' is None, search the current directory.
if zone is None:
zone = ""
all_keys = []
if keydir is None:
regex = rf"(K{zone}\.\+.*\+.*)\.key"
for filename in glob.glob(f"K{zone}.+*+*.key"):
match = re.match(regex, filename)
if match is not None:
all_keys.append(Key(match.group(1)))
else:
regex = rf"{keydir}/(K{zone}\.\+.*\+.*)\.key"
for filename in glob.glob(f"{keydir}/K{zone}.+*+*.key"):
match = re.match(regex, filename)
if match is not None:
all_keys.append(Key(match.group(1), keydir))
states = ["GoalState", "DNSKEYState", "KRRSIGState", "ZRRSIGState", "DSState"]
def used(kk):
if not in_use:
return True
for state in states:
val = kk.get_metadata(state, must_exist=False)
if val not in ["undefined", "hidden"]:
isctest.log.debug(f"key {kk} in use")
return True
return False
return [k for k in all_keys if used(k)]
def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
return [Key(name, keydir) for name in keystr.split()]
def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]:
# Get the policies from a list of specially formatted strings.
# The splitted line should result in the following items:
# line[0]: Role
# line[1]: Lifetime
# line[2]: Algorithm
# line[3]: Length
# Then, optional data for specific tests may follow:
# - "goal", "dnskey", "krrsig", "zrrsig", "ds", followed by a value,
# sets the given state to the specific value
# - "offset", an offset for testing key rollover timings
proplist = []
count = 0
for key in keys:
count += 1
line = key.split()
keyprop = KeyProperties(f"KEY{count}", {}, {}, {})
keyprop.properties["expect"] = True
keyprop.properties["private"] = True
keyprop.properties["legacy"] = False
keyprop.properties["offset"] = timedelta(0)
keyprop.properties["role"] = line[0]
if line[0] == "zsk":
keyprop.properties["role_full"] = "zone-signing"
keyprop.properties["flags"] = 256
keyprop.metadata["ZSK"] = "yes"
keyprop.metadata["KSK"] = "no"
else:
keyprop.properties["role_full"] = "key-signing"
keyprop.properties["flags"] = 257
keyprop.metadata["ZSK"] = "yes" if line[0] == "csk" else "no"
keyprop.metadata["KSK"] = "yes"
keyprop.properties["dnskey_ttl"] = ttl
keyprop.metadata["Algorithm"] = line[2]
keyprop.metadata["Length"] = line[3]
keyprop.metadata["Lifetime"] = 0
if line[1] != "unlimited":
keyprop.metadata["Lifetime"] = int(line[1])
if len(line) > 4:
i = 4
while i < len(line):
if line[i].startswith("goal:"):
keyval = line[i].split(":")
keyprop.metadata["GoalState"] = keyval[1]
elif line[i].startswith("dnskey:"):
keyval = line[i].split(":")
keyprop.metadata["DNSKEYState"] = keyval[1]
elif line[i].startswith("krrsig:"):
keyval = line[i].split(":")
keyprop.metadata["KRRSIGState"] = keyval[1]
elif line[i].startswith("zrrsig:"):
keyval = line[i].split(":")
keyprop.metadata["ZRRSIGState"] = keyval[1]
elif line[i].startswith("ds:"):
keyval = line[i].split(":")
keyprop.metadata["DSState"] = keyval[1]
elif line[i].startswith("offset:"):
keyval = line[i].split(":")
keyprop.properties["offset"] = timedelta(seconds=int(keyval[1]))
else:
assert False, f"undefined optional data {line[i]}"
i += 1
proplist.append(keyprop)
return proplist
-28
View File
@@ -1,28 +0,0 @@
#!/bin/sh
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
. ../conf.sh
if test -n "$PYTHON"; then
if $PYTHON -c "from dns.update import UpdateMessage" 2>/dev/null; then
:
else
echo_i "This test requires the dnspython >= 2.0.0 module." >&2
exit 1
fi
else
echo_i "This test requires Python and the dnspython module." >&2
exit 1
fi
exit 0
+463 -5
View File
@@ -54,6 +54,178 @@ next_key_event_threshold=100
# Tests #
###############################################################################
#
# dnssec-keygen
#
set_zone "kasp"
set_policy "kasp" "4" "200"
set_server "keys" "10.53.0.1"
n=$((n + 1))
echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)"
ret=0
$KEYGEN -K keys -k "$POLICY" -l kasp.conf "$ZONE" >"keygen.out.$POLICY.test$n" 2>/dev/null || ret=1
lines=$(wc -l <"keygen.out.$POLICY.test$n")
test "$lines" -eq $NUM_KEYS || log_error "wrong number of keys created for policy kasp: $lines"
# Temporarily don't log errors because we are searching multiple files.
disable_logerror
# Key properties.
set_keyrole "KEY1" "csk"
set_keylifetime "KEY1" "31536000"
set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
set_keysigning "KEY1" "yes"
set_zonesigning "KEY1" "yes"
set_keyrole "KEY2" "ksk"
set_keylifetime "KEY2" "31536000"
set_keyalgorithm "KEY2" "8" "RSASHA256" "2048"
set_keysigning "KEY2" "yes"
set_zonesigning "KEY2" "no"
set_keyrole "KEY3" "zsk"
set_keylifetime "KEY3" "2592000"
set_keyalgorithm "KEY3" "8" "RSASHA256" "2048"
set_keysigning "KEY3" "no"
set_zonesigning "KEY3" "yes"
set_keyrole "KEY4" "zsk"
set_keylifetime "KEY4" "16070400"
set_keyalgorithm "KEY4" "8" "RSASHA256" "3072"
set_keysigning "KEY4" "no"
set_zonesigning "KEY4" "yes"
lines=$(get_keyids "$DIR" "$ZONE" | wc -l)
test "$lines" -eq $NUM_KEYS || log_error "bad number of key ids"
status=$((status + ret))
ids=$(get_keyids "$DIR" "$ZONE")
for id in $ids; do
# There are four key files with the same algorithm.
# Check them until a match is found.
ret=0 && check_key "KEY1" "$id"
test "$ret" -eq 0 && continue
ret=0 && check_key "KEY2" "$id"
test "$ret" -eq 0 && continue
ret=0 && check_key "KEY3" "$id"
test "$ret" -eq 0 && continue
ret=0 && check_key "KEY4" "$id"
# If ret is still non-zero, non of the files matched.
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
done
# Turn error logs on again.
enable_logerror
n=$((n + 1))
echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)"
ret=0
set_zone "kasp"
set_policy "default" "1" "3600"
set_server "." "10.53.0.1"
# Key properties.
key_clear "KEY1"
set_keyrole "KEY1" "csk"
set_keylifetime "KEY1" "0"
set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
set_keysigning "KEY1" "yes"
set_zonesigning "KEY1" "yes"
key_clear "KEY2"
key_clear "KEY3"
key_clear "KEY4"
$KEYGEN -G -k "$POLICY" "$ZONE" >"keygen.out.$POLICY.test$n" 2>/dev/null || ret=1
lines=$(wc -l <"keygen.out.$POLICY.test$n")
test "$lines" -eq $NUM_KEYS || log_error "wrong number of keys created for policy default: $lines"
# Temporarily adjust max search depth for this test
MAXDEPTH=1
ids=$(get_keyids "$DIR" "$ZONE")
MAXDEPTH=3
echo_i "found in dir $DIR for zone $ZONE the following keytags: $ids"
for id in $ids; do
check_key "KEY1" "$id"
test "$ret" -eq 0 && key_save KEY1
check_keytimes
done
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
#
# dnssec-settime
#
# These test builds upon the latest created key with dnssec-keygen and uses the
# environment variables BASE_FILE, KEY_FILE, PRIVATE_FILE and STATE_FILE.
CMP_FILE="${BASE_FILE}.cmp"
n=$((n + 1))
echo_i "check that 'dnssec-settime' by default does not edit key state file ($n)"
ret=0
cp "$STATE_FILE" "$CMP_FILE"
$SETTIME -P +3600 "$BASE_FILE" >/dev/null || log_error "settime failed"
grep "; Publish: " "$KEY_FILE" >/dev/null || log_error "mismatch published in $KEY_FILE"
grep "Publish: " "$PRIVATE_FILE" >/dev/null || log_error "mismatch published in $PRIVATE_FILE"
diff "$CMP_FILE" "$STATE_FILE" || log_error "unexpected file change in $STATE_FILE"
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
n=$((n + 1))
echo_i "check that 'dnssec-settime -s' also sets publish time metadata and states in key state file ($n)"
ret=0
cp "$STATE_FILE" "$CMP_FILE"
now=$(date +%Y%m%d%H%M%S)
$SETTIME -s -P "$now" -g "omnipresent" -k "rumoured" "$now" -z "omnipresent" "$now" -r "rumoured" "$now" -d "hidden" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
set_keystate "KEY1" "GOAL" "omnipresent"
set_keystate "KEY1" "STATE_DNSKEY" "rumoured"
set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
set_keystate "KEY1" "STATE_ZRRSIG" "omnipresent"
set_keystate "KEY1" "STATE_DS" "hidden"
check_key "KEY1" "$id"
test "$ret" -eq 0 && key_save KEY1
set_keytime "KEY1" "PUBLISHED" "${now}"
check_keytimes
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
n=$((n + 1))
echo_i "check that 'dnssec-settime -s' also unsets publish time metadata and states in key state file ($n)"
ret=0
cp "$STATE_FILE" "$CMP_FILE"
$SETTIME -s -P "none" -g "none" -k "none" "$now" -z "none" "$now" -r "none" "$now" -d "none" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
set_keystate "KEY1" "GOAL" "none"
set_keystate "KEY1" "STATE_DNSKEY" "none"
set_keystate "KEY1" "STATE_KRRSIG" "none"
set_keystate "KEY1" "STATE_ZRRSIG" "none"
set_keystate "KEY1" "STATE_DS" "none"
check_key "KEY1" "$id"
test "$ret" -eq 0 && key_save KEY1
set_keytime "KEY1" "PUBLISHED" "none"
check_keytimes
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
n=$((n + 1))
echo_i "check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase) ($n)"
ret=0
cp "$STATE_FILE" "$CMP_FILE"
now=$(date +%Y%m%d%H%M%S)
$SETTIME -s -A "$now" -g "HIDDEN" -k "UNRETENTIVE" "$now" -z "UNRETENTIVE" "$now" -r "OMNIPRESENT" "$now" -d "OMNIPRESENT" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
set_keystate "KEY1" "GOAL" "hidden"
set_keystate "KEY1" "STATE_DNSKEY" "unretentive"
set_keystate "KEY1" "STATE_KRRSIG" "omnipresent"
set_keystate "KEY1" "STATE_ZRRSIG" "unretentive"
set_keystate "KEY1" "STATE_DS" "omnipresent"
check_key "KEY1" "$id"
test "$ret" -eq 0 && key_save KEY1
set_keytime "KEY1" "ACTIVE" "${now}"
check_keytimes
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
#
# named
#
@@ -64,7 +236,6 @@ next_key_event_threshold=100
# infinite loops if there is an error.
n=$((n + 1))
echo_i "waiting for kasp signing changes to take effect ($n)"
ret=0
_wait_for_done_apexnsec() {
while read -r zone; do
@@ -85,6 +256,18 @@ retry_quiet 30 _wait_for_done_apexnsec || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
# Test max-zone-ttl rejects zones with too high TTL.
n=$((n + 1))
echo_i "check that max-zone-ttl rejects zones with too high TTL ($n)"
ret=0
set_zone "max-zone-ttl.kasp"
grep "loading from master file ${ZONE}.db failed: out of range" "ns3/named.run" >/dev/null || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
#
# Zone: default.kasp.
#
set_keytimes_csk_policy() {
# The first key is immediately published and activated.
created=$(key_get KEY1 CREATED)
@@ -97,6 +280,10 @@ set_keytimes_csk_policy() {
# Key lifetime is unlimited, so not setting RETIRED and REMOVED.
}
# Check the zone with default kasp policy has loaded and is signed.
set_zone "default.kasp"
set_policy "default" "1" "3600"
set_server "ns3" "10.53.0.3"
# Key properties.
set_keyrole "KEY1" "csk"
set_keylifetime "KEY1" "0"
@@ -110,6 +297,240 @@ set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
set_keystate "KEY1" "STATE_ZRRSIG" "rumoured"
set_keystate "KEY1" "STATE_DS" "hidden"
check_keys
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
check_subdomain
dnssec_verify
# Trigger a keymgr run. Make sure the key files are not touched if there are
# no modifications to the key metadata.
n=$((n + 1))
echo_i "make sure key files are untouched if metadata does not change ($n)"
ret=0
basefile=$(key_get KEY1 BASEFILE)
privkey_stat=$(key_get KEY1 PRIVKEY_STAT)
pubkey_stat=$(key_get KEY1 PUBKEY_STAT)
state_stat=$(key_get KEY1 STATE_STAT)
nextpart $DIR/named.run >/dev/null
rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
wait_for_log 3 "keymgr: $ZONE done" $DIR/named.run || ret=1
privkey_stat2=$(key_stat "${basefile}.private")
pubkey_stat2=$(key_stat "${basefile}.key")
state_stat2=$(key_stat "${basefile}.state")
test "$privkey_stat" = "$privkey_stat2" || log_error "wrong private key file stat (expected $privkey_stat got $privkey_stat2)"
test "$pubkey_stat" = "$pubkey_stat2" || log_error "wrong public key file stat (expected $pubkey_stat got $pubkey_stat2)"
test "$state_stat" = "$state_stat2" || log_error "wrong state file stat (expected $state_stat got $state_stat2)"
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
n=$((n + 1))
echo_i "again ($n)"
ret=0
nextpart $DIR/named.run >/dev/null
rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
wait_for_log 3 "keymgr: $ZONE done" $DIR/named.run || ret=1
privkey_stat2=$(key_stat "${basefile}.private")
pubkey_stat2=$(key_stat "${basefile}.key")
state_stat2=$(key_stat "${basefile}.state")
test "$privkey_stat" = "$privkey_stat2" || log_error "wrong private key file stat (expected $privkey_stat got $privkey_stat2)"
test "$pubkey_stat" = "$pubkey_stat2" || log_error "wrong public key file stat (expected $pubkey_stat got $pubkey_stat2)"
test "$state_stat" = "$state_stat2" || log_error "wrong state file stat (expected $state_stat got $state_stat2)"
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
# Update zone.
n=$((n + 1))
echo_i "modify unsigned zone file and check that new record is signed for zone ${ZONE} ($n)"
ret=0
cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db"
rndccmd 10.53.0.3 reload "$ZONE" >/dev/null || log_error "rndc reload zone ${ZONE} failed"
update_is_signed() {
ip_a=$1
ip_d=$2
if [ "$ip_a" != "-" ]; then
dig_with_opts "a.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n.a" || return 1
grep "status: NOERROR" "dig.out.$DIR.test$n.a" >/dev/null || return 1
grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*${ip_a}" "dig.out.$DIR.test$n.a" >/dev/null || return 1
lines=$(get_keys_which_signed A 0 "dig.out.$DIR.test$n.a" | wc -l)
test "$lines" -eq 1 || return 1
get_keys_which_signed A 0 "dig.out.$DIR.test$n.a" | grep "^${KEY_ID}$" >/dev/null || return 1
fi
if [ "$ip_d" != "-" ]; then
dig_with_opts "d.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n".d || return 1
grep "status: NOERROR" "dig.out.$DIR.test$n".d >/dev/null || return 1
grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*${ip_d}" "dig.out.$DIR.test$n".d >/dev/null || return 1
lines=$(get_keys_which_signed A 0 "dig.out.$DIR.test$n".d | wc -l)
test "$lines" -eq 1 || return 1
get_keys_which_signed A 0 "dig.out.$DIR.test$n".d | grep "^${KEY_ID}$" >/dev/null || return 1
fi
}
retry_quiet 10 update_is_signed "10.0.0.11" "10.0.0.44" || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
# Move the private key file, a rekey event should not introduce replacement
# keys.
ret=0
echo_i "test that if private key files are inaccessible this doesn't trigger a rollover ($n)"
basefile=$(key_get KEY1 BASEFILE)
mv "${basefile}.private" "${basefile}.offline"
rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
wait_for_log 3 "zone $ZONE/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" $DIR/named.run || ret=1
mv "${basefile}.offline" "${basefile}.private"
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
# Nothing has changed.
check_keys
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
check_subdomain
dnssec_verify
#
# A zone with special characters.
#
set_zone "i-am.\":\;?&[]\@!\$*+,|=\.\(\)special.kasp."
set_policy "default" "1" "3600"
set_server "ns3" "10.53.0.3"
# It is non-trivial to adapt the tests to deal with all possible different
# escaping characters, so we will just try to verify the zone.
dnssec_verify
#
# Zone: dynamic.kasp
#
set_zone "dynamic.kasp"
set_dynamic
set_policy "default" "1" "3600"
set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
check_subdomain
dnssec_verify
# Update zone with nsupdate.
n=$((n + 1))
echo_i "nsupdate zone and check that new record is signed for zone ${ZONE} ($n)"
ret=0
(
echo zone ${ZONE}
echo server 10.53.0.3 "$PORT"
echo update del "a.${ZONE}" 300 A 10.0.0.1
echo update add "a.${ZONE}" 300 A 10.0.0.101
echo update add "d.${ZONE}" 300 A 10.0.0.4
echo send
) | $NSUPDATE
retry_quiet 10 update_is_signed "10.0.0.101" "10.0.0.4" || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
# Update zone with nsupdate (reverting the above change).
n=$((n + 1))
echo_i "nsupdate zone and check that new record is signed for zone ${ZONE} ($n)"
ret=0
(
echo zone ${ZONE}
echo server 10.53.0.3 "$PORT"
echo update add "a.${ZONE}" 300 A 10.0.0.1
echo update del "a.${ZONE}" 300 A 10.0.0.101
echo update del "d.${ZONE}" 300 A 10.0.0.4
echo send
) | $NSUPDATE
retry_quiet 10 update_is_signed "10.0.0.1" "-" || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
# Update zone with freeze/thaw.
n=$((n + 1))
echo_i "modify zone file and check that new record is signed for zone ${ZONE} ($n)"
ret=0
rndccmd 10.53.0.3 freeze "$ZONE" >/dev/null || log_error "rndc freeze zone ${ZONE} failed"
sleep 1
echo "d.${ZONE}. 300 A 10.0.0.44" >>"${DIR}/${ZONE}.db"
rndccmd 10.53.0.3 thaw "$ZONE" >/dev/null || log_error "rndc thaw zone ${ZONE} failed"
retry_quiet 10 update_is_signed "10.0.0.1" "10.0.0.44" || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
#
# Zone: dynamic-inline-signing.kasp
#
set_zone "dynamic-inline-signing.kasp"
set_dynamic
set_policy "default" "1" "3600"
set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
check_subdomain
dnssec_verify
# Update zone with freeze/thaw.
n=$((n + 1))
echo_i "modify unsigned zone file and check that new record is signed for zone ${ZONE} ($n)"
ret=0
rndccmd 10.53.0.3 freeze "$ZONE" >/dev/null || log_error "rndc freeze zone ${ZONE} failed"
sleep 1
cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db"
rndccmd 10.53.0.3 thaw "$ZONE" >/dev/null || log_error "rndc thaw zone ${ZONE} failed"
retry_quiet 10 update_is_signed || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
#
# Zone: dynamic-signed-inline-signing.kasp
#
set_zone "dynamic-signed-inline-signing.kasp"
set_dynamic
set_policy "default" "1" "3600"
set_server "ns3" "10.53.0.3"
dnssec_verify
# Ensure no zone_resigninc for the unsigned version of the zone is triggered.
n=$((n + 1))
echo_i "check if resigning the raw version of the zone is prevented for zone ${ZONE} ($n)"
ret=0
grep "zone_resigninc: zone $ZONE/IN (unsigned): enter" $DIR/named.run && ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
#
# Zone: inline-signing.kasp
#
set_zone "inline-signing.kasp"
set_policy "default" "1" "3600"
set_server "ns3" "10.53.0.3"
# Key properties, timings and states same as above.
check_keys
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
set_keytimes_csk_policy
check_keytimes
check_apex
check_subdomain
dnssec_verify
#
# Zone: checkds-ksk.kasp.
#
@@ -455,16 +876,53 @@ if [ $RSASHA1_SUPPORTED = 1 ]; then
dnssec_verify
fi
#
# Zone: unsigned.kasp.
#
set_zone "unsigned.kasp"
set_policy "none" "0" "0"
set_server "ns3" "10.53.0.3"
key_clear "KEY1"
key_clear "KEY2"
key_clear "KEY3"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
check_apex
check_subdomain
# Make sure the zone file is untouched.
n=$((n + 1))
echo_i "Make sure the zonefile for zone ${ZONE} is not edited ($n)"
ret=0
diff "${DIR}/${ZONE}.db.infile" "${DIR}/${ZONE}.db" || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
#
# Zone: insecure.kasp.
#
set_zone "insecure.kasp"
set_policy "insecure" "0" "0"
set_server "ns3" "10.53.0.3"
key_clear "KEY1"
key_clear "KEY2"
key_clear "KEY3"
key_clear "KEY4"
check_keys
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
check_apex
check_subdomain
#
# Zone: unlimited.kasp.
#
set_zone "unlimited.kasp"
set_policy "unlimited" "1" "1234"
set_server "ns3" "10.53.0.3"
key_clear "KEY1"
key_clear "KEY2"
key_clear "KEY3"
key_clear "KEY4"
# Key properties.
set_keyrole "KEY1" "csk"
set_keylifetime "KEY1" "0"
-569
View File
@@ -1,569 +0,0 @@
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
import os
import shutil
import time
from datetime import timedelta
import dns
import dns.update
import pytest
import isctest
from isctest.kasp import (
KeyProperties,
KeyTimingMetadata,
)
pytestmark = pytest.mark.extra_artifacts(
[
"K*.private",
"K*.backup",
"K*.cmp",
"K*.key",
"K*.state",
"*.axfr",
"*.created",
"dig.out*",
"keyevent.out.*",
"keygen.out.*",
"keys",
"published.test*",
"python.out.*",
"retired.test*",
"rndc.dnssec.*.out.*",
"rndc.zonestatus.out.*",
"rrsig.out.*",
"created.key-*",
"unused.key-*",
"verify.out.*",
"zone.out.*",
"ns*/K*.key",
"ns*/K*.offline",
"ns*/K*.private",
"ns*/K*.state",
"ns*/*.db",
"ns*/*.db.infile",
"ns*/*.db.signed",
"ns*/*.db.signed.tmp",
"ns*/*.jbk",
"ns*/*.jnl",
"ns*/*.zsk1",
"ns*/*.zsk2",
"ns*/dsset-*",
"ns*/keygen.out.*",
"ns*/keys",
"ns*/ksk",
"ns*/ksk/K*",
"ns*/zsk",
"ns*/zsk",
"ns*/zsk/K*",
"ns*/named-fips.conf",
"ns*/settime.out.*",
"ns*/signer.out.*",
"ns*/zones",
"ns*/policies/*.conf",
"ns3/legacy-keys.*",
"ns3/dynamic-signed-inline-signing.kasp.db.signed.signed",
]
)
def check_all(server, zone, policy, ksks, zsks, tsig=None):
isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy)
isctest.kasp.check_apex(server, zone, ksks, zsks, tsig=tsig)
isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig)
isctest.kasp.check_dnssec_verify(server, zone)
def set_keytimes_default_policy(kp):
# The first key is immediately published and activated.
kp.timing["Generated"] = kp.key.get_timing("Created")
kp.timing["Published"] = kp.timing["Generated"]
kp.timing["Active"] = kp.timing["Generated"]
# The DS can be published if the DNSKEY and RRSIG records are
# OMNIPRESENT. This happens after max-zone-ttl (1d) plus
# plus zone-propagation-delay (300s).
kp.timing["PublishCDS"] = kp.timing["Published"] + timedelta(days=1, seconds=300)
# Key lifetime is unlimited, so not setting 'Retired' nor 'Removed'.
kp.timing["DNSKEYChange"] = kp.timing["Published"]
kp.timing["DSChange"] = kp.timing["Published"]
kp.timing["KRRSIGChange"] = kp.timing["Active"]
kp.timing["ZRRSIGChange"] = kp.timing["Active"]
def test_kasp_default(servers):
server = servers["ns3"]
# check the zone with default kasp policy has loaded and is signed.
isctest.log.info("check a zone with the default policy is signed")
zone = "default.kasp"
policy = "default"
# Key properties.
# DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait.
keyprops = [
"csk 0 13 256 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden",
]
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
isctest.kasp.check_zone_is_signed(server, zone)
isctest.kasp.check_keys(zone, keys, expected)
set_keytimes_default_policy(expected[0])
isctest.kasp.check_keytimes(keys, expected)
check_all(server, zone, policy, keys, [])
# Trigger a keymgr run. Make sure the key files are not touched if there
# are no modifications to the key metadata.
isctest.log.info(
"check that key files are untouched if there are no metadata changes"
)
key = keys[0]
privkey_stat = os.stat(key.privatefile)
pubkey_stat = os.stat(key.keyfile)
state_stat = os.stat(key.statefile)
with server.watch_log_from_here() as watcher:
server.rndc(f"loadkeys {zone}", log=False)
watcher.wait_for_line(f"keymgr: {zone} done")
assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime
assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime
assert state_stat.st_mtime == os.stat(key.statefile).st_mtime
# again
with server.watch_log_from_here() as watcher:
server.rndc(f"loadkeys {zone}", log=False)
watcher.wait_for_line(f"keymgr: {zone} done")
assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime
assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime
assert state_stat.st_mtime == os.stat(key.statefile).st_mtime
# modify unsigned zone file and check that new record is signed.
isctest.log.info("check that an updated zone signs the new record")
shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
server.rndc(f"reload {zone}", log=False)
def update_is_signed():
parts = update.split()
qname = parts[0]
qtype = dns.rdatatype.from_text(parts[1])
rdata = parts[2]
return isctest.kasp.check_update_is_signed(
server, zone, qname, qtype, rdata, keys, []
)
expected_updates = [f"a.{zone}. A 10.0.0.11", f"d.{zone}. A 10.0.0.44"]
for update in expected_updates:
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
# Move the private key file, a rekey event should not introduce
# replacement keys.
isctest.log.info("check that missing private key doesn't trigger rollover")
shutil.move(f"{key.privatefile}", f"{key.path}.offline")
expectmsg = "zone_rekey:zone_verifykeys failed: some key files are missing"
with server.watch_log_from_here() as watcher:
server.rndc(f"loadkeys {zone}", log=False)
watcher.wait_for_line(f"zone {zone}/IN (signed): {expectmsg}")
# Nothing has changed.
expected[0].properties["private"] = False
isctest.kasp.check_keys(zone, keys, expected)
isctest.kasp.check_keytimes(keys, expected)
check_all(server, zone, policy, keys, [])
# A zone that uses inline-signing.
isctest.log.info("check an inline-signed zone with the default policy is signed")
zone = "inline-signing.kasp"
# Key properties.
key1 = KeyProperties.default()
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
expected = [key1]
isctest.kasp.check_zone_is_signed(server, zone)
isctest.kasp.check_keys(zone, keys, expected)
set_keytimes_default_policy(key1)
isctest.kasp.check_keytimes(keys, expected)
check_all(server, zone, policy, keys, [])
def test_kasp_dynamic(servers):
# Dynamic update test cases.
server = servers["ns3"]
# Standard dynamic zone.
isctest.log.info("check dynamic zone is updated and signed after update")
zone = "dynamic.kasp"
policy = "default"
# Key properties.
key1 = KeyProperties.default()
expected = [key1]
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
isctest.kasp.check_zone_is_signed(server, zone)
isctest.kasp.check_keys(zone, keys, expected)
set_keytimes_default_policy(key1)
expected = [key1]
isctest.kasp.check_keytimes(keys, expected)
check_all(server, zone, policy, keys, [])
# Update zone with nsupdate.
def nsupdate():
message = dns.update.UpdateMessage(zone)
for update in updates:
if update[0] == 0:
message.delete(update[1], update[2], update[3])
else:
message.add(update[1], update[2], update[3], update[4])
try:
response = isctest.query.udp(
message, server.ip, server.ports.dns, timeout=3
)
assert response.rcode() == dns.rcode.NOERROR
except dns.exception.Timeout:
isctest.log.info(f"error: update timeout for {zone}")
isctest.log.debug(f"update of zone {zone} to server {server.ip} successful")
def update_is_signed():
parts = update.split()
qname = parts[0]
qtype = dns.rdatatype.from_text(parts[1])
rdata = parts[2]
return isctest.kasp.check_update_is_signed(
server, zone, qname, qtype, rdata, keys, []
)
updates = [
[0, f"a.{zone}.", "A", "10.0.0.1"],
[1, f"a.{zone}.", 300, "A", "10.0.0.101"],
[1, f"d.{zone}.", 300, "A", "10.0.0.4"],
]
nsupdate()
expected_updates = [f"a.{zone}. A 10.0.0.101", f"d.{zone}. A 10.0.0.4"]
for update in expected_updates:
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
# Update zone with nsupdate (reverting the above change).
updates = [
[1, f"a.{zone}.", 300, "A", "10.0.0.1"],
[0, f"a.{zone}.", "A", "10.0.0.101"],
[0, f"d.{zone}.", "A", "10.0.0.4"],
]
nsupdate()
update = f"a.{zone}. A 10.0.0.1"
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
# Update zone with freeze/thaw.
isctest.log.info("check dynamic zone is updated and signed after freeze and thaw")
with server.watch_log_from_here() as watcher:
server.rndc(f"freeze {zone}", log=False)
watcher.wait_for_line(f"freezing zone '{zone}/IN': success")
time.sleep(1)
with open(f"ns3/{zone}.db", "a", encoding="utf-8") as zonefile:
zonefile.write(f"d.{zone}. 300 A 10.0.0.44\n")
time.sleep(1)
with server.watch_log_from_here() as watcher:
server.rndc(f"thaw {zone}", log=False)
watcher.wait_for_line(f"thawing zone '{zone}/IN': success")
expected_updates = [f"a.{zone}. A 10.0.0.1", f"d.{zone}. A 10.0.0.44"]
for update in expected_updates:
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
# Dynamic, and inline-signing.
zone = "dynamic-inline-signing.kasp"
# Key properties.
key1 = KeyProperties.default()
expected = [key1]
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
isctest.kasp.check_zone_is_signed(server, zone)
isctest.kasp.check_keys(zone, keys, expected)
set_keytimes_default_policy(key1)
expected = [key1]
isctest.kasp.check_keytimes(keys, expected)
check_all(server, zone, policy, keys, [])
# Update zone with freeze/thaw.
isctest.log.info(
"check dynamic inline-signed zone is updated and signed after freeze and thaw"
)
with server.watch_log_from_here() as watcher:
server.rndc(f"freeze {zone}", log=False)
watcher.wait_for_line(f"freezing zone '{zone}/IN': success")
time.sleep(1)
shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
time.sleep(1)
with server.watch_log_from_here() as watcher:
server.rndc(f"thaw {zone}", log=False)
watcher.wait_for_line(f"thawing zone '{zone}/IN': success")
expected_updates = [f"a.{zone}. A 10.0.0.11", f"d.{zone}. A 10.0.0.44"]
for update in expected_updates:
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
# Dynamic, signed, and inline-signing.
isctest.log.info("check dynamic signed, and inline-signed zone")
zone = "dynamic-signed-inline-signing.kasp"
# Key properties.
key1 = KeyProperties.default()
# The ns3/setup.sh script sets all states to omnipresent.
key1.metadata["DNSKEYState"] = "omnipresent"
key1.metadata["KRRSIGState"] = "omnipresent"
key1.metadata["ZRRSIGState"] = "omnipresent"
key1.metadata["DSState"] = "omnipresent"
expected = [key1]
keys = isctest.kasp.keydir_to_keylist(zone, "ns3/keys")
isctest.kasp.check_zone_is_signed(server, zone)
isctest.kasp.check_keys(zone, keys, expected)
check_all(server, zone, policy, keys, [])
# Ensure no zone_resigninc for the unsigned version of the zone is triggered.
assert f"zone_resigninc: zone {zone}/IN (unsigned): enter" not in "ns3/named.run"
def test_kasp_special_cases(servers):
server = servers["ns3"]
# Insecure zones.
isctest.log.info("check insecure zones")
zone = "insecure.kasp"
expected = []
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
isctest.kasp.check_keys(zone, keys, expected)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy="insecure")
isctest.kasp.check_apex(server, zone, keys, [])
isctest.kasp.check_subdomain(server, zone, keys, [])
zone = "unsigned.kasp"
expected = []
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
isctest.kasp.check_keys(zone, keys, expected)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=None)
isctest.kasp.check_apex(server, zone, keys, [])
isctest.kasp.check_subdomain(server, zone, keys, [])
# Make sure the zone file is untouched.
isctest.check.file_contents_equal(f"ns3/{zone}.db.infile", f"ns3/{zone}.db")
# A zone with special characters.
isctest.log.info("check special characters")
zone = r'i-am.":\;?&[]\@!\$*+,|=\.\(\)special.kasp'
# It is non-trivial to adapt the tests to deal with all possible different
# escaping characters, so we will just try to verify the zone.
isctest.kasp.check_dnssec_verify(server, zone)
# check that max-zone-ttl rejects zones with too high TTL.
isctest.log.info("check max-zone-ttl rejects zones with too high TTL")
zone = "max-zone-ttl.kasp"
assert f"loading from master file {zone}.db failed: out of range" in server.log
def test_kasp_dnssec_keygen():
def keygen(zone, policy, keydir=None):
if keydir is None:
keydir = "."
keygen_command = [
os.environ.get("KEYGEN"),
"-K",
keydir,
"-k",
policy,
"-l",
"kasp.conf",
zone,
]
return isctest.run.cmd(keygen_command, log_stdout=True).stdout.decode("utf-8")
# check that 'dnssec-keygen -k' (configured policy) creates valid files.
lifetime = {
"P1Y": int(timedelta(days=365).total_seconds()),
"P30D": int(timedelta(days=30).total_seconds()),
"P6M": int(timedelta(days=31 * 6).total_seconds()),
}
keyprops = [
f"csk {lifetime['P1Y']} 13 256",
f"ksk {lifetime['P1Y']} 8 2048",
f"zsk {lifetime['P30D']} 8 2048",
f"zsk {lifetime['P6M']} 8 3072",
]
keydir = "keys"
out = keygen("kasp", "kasp", keydir)
keys = isctest.kasp.keystr_to_keylist(out, keydir)
expected = isctest.kasp.policy_to_properties(ttl=200, keys=keyprops)
isctest.kasp.check_keys("kasp", keys, expected)
# check that 'dnssec-keygen -k' (default policy) creates valid files.
keyprops = ["csk 0 13 256"]
out = keygen("kasp", "default")
keys = isctest.kasp.keystr_to_keylist(out)
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
isctest.kasp.check_keys("kasp", keys, expected)
# check that 'dnssec-settime' by default does not edit key state file.
key = keys[0]
privatefile = f"{key.path}.private"
keyfile = f"{key.path}.key"
statefile = f"{key.path}.state"
shutil.copyfile(privatefile, f"{privatefile}.backup")
shutil.copyfile(keyfile, f"{keyfile}.backup")
shutil.copyfile(statefile, f"{statefile}.backup")
created = key.get_timing("Created")
publish = key.get_timing("Publish") + timedelta(hours=1)
settime = [
os.environ.get("SETTIME"),
"-P",
str(publish),
key.path,
]
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
isctest.check.file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
assert key.get_metadata("Publish", file=key.privatefile) == str(publish)
assert key.get_metadata("Publish", file=key.keyfile, comment=True) == str(publish)
# check that 'dnssec-settime -s' also sets publish time metadata and
# states in key state file.
now = KeyTimingMetadata.now()
goal = "omnipresent"
dnskey = "rumoured"
krrsig = "rumoured"
zrrsig = "omnipresent"
ds = "hidden"
keyprops = [
f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
]
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
expected[0].timing = {
"Generated": created,
"Published": now,
"Active": created,
"DNSKEYChange": now,
"KRRSIGChange": now,
"ZRRSIGChange": now,
"DSChange": now,
}
settime = [
os.environ.get("SETTIME"),
"-s",
"-P",
str(now),
"-g",
goal,
"-k",
dnskey,
str(now),
"-r",
krrsig,
str(now),
"-z",
zrrsig,
str(now),
"-d",
ds,
str(now),
key.path,
]
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
isctest.kasp.check_keys("kasp", keys, expected)
isctest.kasp.check_keytimes(keys, expected)
# check that 'dnssec-settime -s' also unsets publish time metadata and
# states in key state file.
now = KeyTimingMetadata.now()
keyprops = ["csk 0 13 256"]
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
expected[0].timing = {
"Generated": created,
"Active": created,
}
settime = [
os.environ.get("SETTIME"),
"-s",
"-P",
"none",
"-g",
"none",
"-k",
"none",
str(now),
"-z",
"none",
str(now),
"-r",
"none",
str(now),
"-d",
"none",
str(now),
key.path,
]
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
isctest.kasp.check_keys("kasp", keys, expected)
isctest.kasp.check_keytimes(keys, expected)
# check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase)
soon = now + timedelta(hours=2)
goal = "hidden"
dnskey = "unretentive"
krrsig = "omnipresent"
zrrsig = "unretentive"
ds = "omnipresent"
keyprops = [
f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
]
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
expected[0].timing = {
"Generated": created,
"Active": soon,
"DNSKEYChange": soon,
"KRRSIGChange": soon,
"ZRRSIGChange": soon,
"DSChange": soon,
}
settime = [
os.environ.get("SETTIME"),
"-s",
"-A",
str(soon),
"-g",
"HIDDEN",
"-k",
"UNRETENTIVE",
str(soon),
"-z",
"UNRETENTIVE",
str(soon),
"-r",
"OMNIPRESENT",
str(soon),
"-d",
"OMNIPRESENT",
str(soon),
key.path,
]
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
isctest.kasp.check_keys("kasp", keys, expected)
isctest.kasp.check_keytimes(keys, expected)
+59 -33
View File
@@ -10,14 +10,19 @@
# information regarding copyright ownership.
from datetime import timedelta
import difflib
import os
import shutil
import time
from typing import List, Optional
import pytest
import isctest
from isctest.kasp import KeyTimingMetadata
from isctest.kasp import (
Key,
KeyTimingMetadata,
)
pytestmark = pytest.mark.extra_artifacts(
[
@@ -84,6 +89,31 @@ def between(value, start, end):
return start < value < end
def check_file_contents_equal(file1, file2):
def normalize_line(line):
# remove trailing&leading whitespace and replace multiple whitespaces
return " ".join(line.split())
def read_lines(file_path):
with open(file_path, "r", encoding="utf-8") as file:
return [normalize_line(line) for line in file.readlines()]
lines1 = read_lines(file1)
lines2 = read_lines(file2)
differ = difflib.Differ()
diff = differ.compare(lines1, lines2)
for line in diff:
assert not line.startswith("+ ") and not line.startswith(
"- "
), f'file contents of "{file1}" and "{file2}" differ'
def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
return [Key(name, keydir) for name in keystr.split()]
def ksr(zone, policy, action, options="", raise_on_exception=True):
ksr_command = [
os.environ.get("KSR"),
@@ -485,14 +515,14 @@ def test_ksr_common(servers):
# create ksk
kskdir = "ns1/offline"
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
check_keys(ksks, None)
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
out, _ = ksr(zone, policy, "keygen", options="-i now -e +1y")
zsks = isctest.kasp.keystr_to_keylist(out)
zsks = keystr_to_keylist(out)
assert len(zsks) == 2
lifetime = timedelta(days=31 * 6)
@@ -502,7 +532,7 @@ def test_ksr_common(servers):
# in the given key directory
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
zsks = keystr_to_keylist(out, zskdir)
assert len(zsks) == 2
lifetime = timedelta(days=31 * 6)
@@ -545,22 +575,18 @@ def test_ksr_common(servers):
# check that 'dnssec-ksr keygen' selects pregenerated keys for
# the same time bundle
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +1y")
selected_zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
selected_zsks = keystr_to_keylist(out, zskdir)
assert len(selected_zsks) == 2
for index, key in enumerate(selected_zsks):
assert zsks[index] == key
isctest.check.file_contents_equal(
f"{key.path}.private", f"{key.path}.private.backup"
)
isctest.check.file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
isctest.check.file_contents_equal(
f"{key.path}.state", f"{key.path}.state.backup"
)
check_file_contents_equal(f"{key.path}.private", f"{key.path}.private.backup")
check_file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
check_file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
# check that 'dnssec-ksr keygen' generates only necessary keys for
# overlapping time bundle
out, err = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +2y -v 1")
overlapping_zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
overlapping_zsks = keystr_to_keylist(out, zskdir)
assert len(overlapping_zsks) == 4
verbose = err.split()
@@ -571,24 +597,24 @@ def test_ksr_common(servers):
selected += 1
if "Generating" in output:
generated += 1
# Subtract if there was a key collision.
if "collide" in output:
generated -= 1
assert selected == 2
assert generated == 2
for index, key in enumerate(overlapping_zsks):
if index < 2:
assert zsks[index] == key
isctest.check.file_contents_equal(
check_file_contents_equal(
f"{key.path}.private", f"{key.path}.private.backup"
)
isctest.check.file_contents_equal(
f"{key.path}.key", f"{key.path}.key.backup"
)
isctest.check.file_contents_equal(
f"{key.path}.state", f"{key.path}.state.backup"
)
check_file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
check_file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
# run 'dnssec-ksr keygen' again with verbosity 0
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +2y")
overlapping_zsks2 = isctest.kasp.keystr_to_keylist(out, zskdir)
overlapping_zsks2 = keystr_to_keylist(out, zskdir)
assert len(overlapping_zsks2) == 4
check_keys(overlapping_zsks2, lifetime)
for index, key in enumerate(overlapping_zsks2):
@@ -683,7 +709,7 @@ def test_ksr_lastbundle(servers):
kskdir = "ns1/offline"
offset = -timedelta(days=365)
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1d -o")
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
check_keys(ksks, None, offset=offset)
@@ -691,7 +717,7 @@ def test_ksr_lastbundle(servers):
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1d")
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
zsks = keystr_to_keylist(out, zskdir)
assert len(zsks) == 2
lifetime = timedelta(days=31 * 6)
@@ -762,7 +788,7 @@ def test_ksr_inthemiddle(servers):
kskdir = "ns1/offline"
offset = -timedelta(days=365)
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1y -o")
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
check_keys(ksks, None, offset=offset)
@@ -770,7 +796,7 @@ def test_ksr_inthemiddle(servers):
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1y")
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
zsks = keystr_to_keylist(out, zskdir)
assert len(zsks) == 4
lifetime = timedelta(days=31 * 6)
@@ -842,13 +868,13 @@ def check_ksr_rekey_logs_error(server, zone, policy, offset, end):
then = now + offset
until = now + end
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i {then} -e {until} -o")
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
# key generation
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {then} -e {until}")
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
zsks = keystr_to_keylist(out, zskdir)
assert len(zsks) == 2
# create request
@@ -915,7 +941,7 @@ def test_ksr_unlimited(servers):
# create ksk
kskdir = "ns1/offline"
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +2y -o")
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
check_keys(ksks, None)
@@ -923,7 +949,7 @@ def test_ksr_unlimited(servers):
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +2y")
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
zsks = keystr_to_keylist(out, zskdir)
assert len(zsks) == 1
lifetime = None
@@ -1032,7 +1058,7 @@ def test_ksr_twotone(servers):
# create ksk
kskdir = "ns1/offline"
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 2
ksks_defalg = []
@@ -1056,7 +1082,7 @@ def test_ksr_twotone(servers):
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
zsks = keystr_to_keylist(out, zskdir)
# First algorithm keys have a lifetime of 3 months, so there should
# be 4 created keys. Second algorithm keys have a lifetime of 5
# months, so there should be 3 created keys. While only two time
@@ -1150,7 +1176,7 @@ def test_ksr_kskroll(servers):
# create ksk
kskdir = "ns1/offline"
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 2
lifetime = timedelta(days=31 * 6)
@@ -1159,7 +1185,7 @@ def test_ksr_kskroll(servers):
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
zsks = keystr_to_keylist(out, zskdir)
assert len(zsks) == 1
check_keys(zsks, None)
+3 -3
View File
@@ -385,7 +385,7 @@ $DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A >dig.out.ns3.test$n.1 2>&1
grep "NOERROR" dig.out.ns3.test$n.1 >/dev/null || ret=1
grep "flags:.* ad" dig.out.ns3.test$n.1 >/dev/null || ret=1
# Sanity check: the authoritative server should have been queried.
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" >/dev/null || ret=1
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/NS/IN'" >/dev/null || ret=1
# Reconfigure ns2 so that the zone can be mirrored on ns3.
sed '/^zone "initially-unavailable" {$/,/^};$/ {
s/10.53.0.254/10.53.0.3/
@@ -403,7 +403,7 @@ $DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A >dig.out.ns3.test$n.2 2>&1
grep "NOERROR" dig.out.ns3.test$n.2 >/dev/null || ret=1
grep "flags:.* ad" dig.out.ns3.test$n.2 >/dev/null || ret=1
# Ensure the authoritative server was not queried.
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" >/dev/null && ret=1
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/NS/IN'" >/dev/null && ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@@ -434,7 +434,7 @@ $DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A >dig.out.ns3.test$n 2>&1 |
grep "NOERROR" dig.out.ns3.test$n >/dev/null || ret=1
grep "flags:.* ad" dig.out.ns3.test$n >/dev/null || ret=1
# Sanity check: the authoritative server should have been queried.
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" >/dev/null || ret=1
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/NS/IN'" >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@@ -0,0 +1,17 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 30 SOA ns2.good. hostmaster.arpa. 2018050100 1 1 1 1
@ 30 NS ns2.good.
8.2.6.0 60 NS ns3.good.
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0 1 PTR nee.com.
+91 -436
View File
@@ -1,456 +1,111 @@
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
"""
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
from __future__ import print_function
import os
import sys
import signal
import socket
import select
from datetime import datetime, timedelta
import time
import functools
SPDX-License-Identifier: MPL-2.0
import dns, dns.message, dns.query, dns.flags
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
from dns.name import *
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, you can obtain one at https://mozilla.org/MPL/2.0/.
See the COPYRIGHT file distributed with this work for additional
information regarding copyright ownership.
"""
from typing import AsyncGenerator
import dns.message
import dns.name
import dns.rcode
import dns.rdataclass
import dns.rdatatype
from isctest.asyncserver import (
AsyncDnsServer,
DnsResponseSend,
DomainHandler,
QueryContext,
ResponseAction,
)
from qmin_ans import (
DelayedResponseHandler,
EntRcodeChanger,
QueryLogHandler,
log_query,
)
# Log query to file
def logquery(type, qname):
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
class QueryLogger(QueryLogHandler):
domains = ["1.0.0.2.ip6.arpa.", "fwd.", "good."]
def endswith(domain, labels):
return domain.endswith("." + labels) or domain == labels
class BadHandler(EntRcodeChanger):
domains = ["bad."]
rcode = dns.rcode.NXDOMAIN
############################################################################
# Respond to a DNS query.
# For good. it serves:
# ns2.good. IN A 10.53.0.2
# zoop.boing.good. NS ns3.good.
# ns3.good. IN A 10.53.0.3
# too.many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.good. A 192.0.2.2
# it responds properly (with NODATA empty response) to non-empty terminals
#
# For slow. it works the same as for good., but each response is delayed by 400 milliseconds
#
# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals
#
# For ugly. it works the same as for good., but returns garbage to non-empty terminals
#
# For 1.0.0.2.ip6.arpa it serves
# 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa. IN PTR nee.com.
# 8.2.6.0.1.0.0.2.ip6.arpa IN NS ns3.good
# 1.0.0.2.ip6.arpa. IN NS ns2.good
# ip6.arpa. IN NS ns2.good
#
# For stale. it serves:
# a.b. NS ns.a.b.stale.
# ns.a.b.stale. IN A 10.53.0.3
# b. NS ns.b.stale.
# ns.b.stale. IN A 10.53.0.4
############################################################################
def create_response(msg):
m = dns.message.from_wire(msg)
qname = m.question[0].name.to_text()
lqname = qname.lower()
labels = lqname.split(".")
# get qtype
rrtype = m.question[0].rdtype
typename = dns.rdatatype.to_text(rrtype)
if typename == "A" or typename == "AAAA":
typename = "ADDR"
bad = False
ugly = False
slow = False
# log this query
with open("query.log", "a") as f:
f.write("%s %s\n" % (typename, lqname))
print("%s %s" % (typename, lqname), end=" ")
r = dns.message.make_response(m)
r.set_rcode(NOERROR)
if endswith(lqname, "1.0.0.2.ip6.arpa."):
# Direct query - give direct answer
if endswith(lqname, "8.2.6.0.1.0.0.2.ip6.arpa."):
# Delegate to ns3
r.authority.append(
dns.rrset.from_text(
"8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns3.good."
)
)
r.additional.append(
dns.rrset.from_text("ns3.good.", 60, IN, A, "10.53.0.3")
)
elif (
lqname
== "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa."
and rrtype == PTR
):
# Direct query - give direct answer
r.answer.append(
dns.rrset.from_text(
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.",
1,
IN,
PTR,
"nee.com.",
)
)
r.flags |= dns.flags.AA
elif lqname == "1.0.0.2.ip6.arpa." and rrtype == NS:
# NS query at the apex
r.answer.append(
dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, NS, "ns2.good.")
)
r.flags |= dns.flags.AA
elif endswith(
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.",
lqname,
):
# NODATA answer
r.authority.append(
dns.rrset.from_text(
"1.0.0.2.ip6.arpa.",
30,
IN,
SOA,
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
)
)
else:
# NXDOMAIN
r.authority.append(
dns.rrset.from_text(
"1.0.0.2.ip6.arpa.",
30,
IN,
SOA,
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
)
)
r.set_rcode(NXDOMAIN)
return r
elif endswith(lqname, "ip6.arpa."):
if lqname == "ip6.arpa." and rrtype == NS:
# NS query at the apex
r.answer.append(dns.rrset.from_text("ip6.arpa.", 30, IN, NS, "ns2.good."))
r.flags |= dns.flags.AA
elif endswith("1.0.0.2.ip6.arpa.", lqname):
# NODATA answer
r.authority.append(
dns.rrset.from_text(
"ip6.arpa.",
30,
IN,
SOA,
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
)
)
else:
# NXDOMAIN
r.authority.append(
dns.rrset.from_text(
"ip6.arpa.",
30,
IN,
SOA,
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
)
)
r.set_rcode(NXDOMAIN)
return r
elif endswith(lqname, "stale."):
if endswith(lqname, "a.b.stale."):
# Delegate to ns.a.b.stale.
r.authority.append(
dns.rrset.from_text("a.b.stale.", 2, IN, NS, "ns.a.b.stale.")
)
r.additional.append(
dns.rrset.from_text("ns.a.b.stale.", 2, IN, A, "10.53.0.3")
)
elif endswith(lqname, "b.stale."):
# Delegate to ns.b.stale.
r.authority.append(
dns.rrset.from_text("b.stale.", 2, IN, NS, "ns.b.stale.")
)
r.additional.append(
dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.4")
)
elif lqname == "stale." and rrtype == NS:
# NS query at the apex.
r.answer.append(dns.rrset.from_text("stale.", 2, IN, NS, "ns2.stale."))
r.flags |= dns.flags.AA
elif lqname == "stale." and rrtype == SOA:
# SOA query at the apex.
r.answer.append(
dns.rrset.from_text(
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.stale. 1 2 3 4 5"
)
)
r.flags |= dns.flags.AA
elif lqname == "stale.":
# NODATA answer
r.authority.append(
dns.rrset.from_text(
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
)
)
r.flags |= dns.flags.AA
elif lqname == "ns2.stale.":
if rrtype == A:
r.additional.append(
dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.2")
)
else:
r.authority.append(
dns.rrset.from_text(
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
)
)
r.flags |= dns.flags.AA
else:
# NXDOMAIN
r.authority.append(
dns.rrset.from_text(
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
)
)
r.set_rcode(NXDOMAIN)
return r
elif endswith(lqname, "bad."):
bad = True
suffix = "bad."
lqname = lqname[:-4]
elif endswith(lqname, "ugly."):
ugly = True
suffix = "ugly."
lqname = lqname[:-5]
elif endswith(lqname, "good."):
suffix = "good."
lqname = lqname[:-5]
elif endswith(lqname, "slow."):
slow = True
suffix = "slow."
lqname = lqname[:-5]
elif endswith(lqname, "fwd."):
suffix = "fwd."
lqname = lqname[:-4]
else:
r.set_rcode(REFUSED)
return r
# Good/bad/ugly differs only in how we treat non-empty terminals
if endswith(lqname, "zoop.boing."):
r.authority.append(
dns.rrset.from_text("zoop.boing." + suffix, 1, IN, NS, "ns3." + suffix)
)
elif (
lqname == "many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z."
and rrtype == A
):
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2"))
r.flags |= dns.flags.AA
elif lqname == "" and rrtype == NS:
r.answer.append(dns.rrset.from_text(suffix, 30, IN, NS, "ns2." + suffix))
r.flags |= dns.flags.AA
elif lqname == "ns2.":
r.flags |= dns.flags.AA
if rrtype == A:
r.answer.append(
dns.rrset.from_text("ns2." + suffix, 30, IN, A, "10.53.0.2")
)
elif rrtype == AAAA:
r.answer.append(
dns.rrset.from_text(
"ns2." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::2"
)
)
else:
r.authority.append(
dns.rrset.from_text(
suffix,
30,
IN,
SOA,
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
)
)
elif lqname == "ns3.":
r.flags |= dns.flags.AA
if rrtype == A:
r.answer.append(
dns.rrset.from_text("ns3." + suffix, 30, IN, A, "10.53.0.3")
)
elif lqname == "ns3." and rrtype == AAAA:
r.answer.append(
dns.rrset.from_text(
"ns3." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::3"
)
)
else:
r.authority.append(
dns.rrset.from_text(
suffix,
30,
IN,
SOA,
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
)
)
elif lqname == "ns4.":
r.flags |= dns.flags.AA
if rrtype == A:
r.answer.append(
dns.rrset.from_text("ns4." + suffix, 30, IN, A, "10.53.0.4")
)
elif rrtype == AAAA:
r.answer.append(
dns.rrset.from_text(
"ns4." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::4"
)
)
else:
r.authority.append(
dns.rrset.from_text(
suffix,
30,
IN,
SOA,
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
)
)
elif lqname == "a.bit.longer.ns.name." and rrtype == A:
r.answer.append(
dns.rrset.from_text("a.bit.longer.ns.name." + suffix, 1, IN, A, "10.53.0.4")
)
r.flags |= dns.flags.AA
elif lqname == "a.bit.longer.ns.name." and rrtype == AAAA:
r.answer.append(
dns.rrset.from_text(
"a.bit.longer.ns.name." + suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::4"
)
)
r.flags |= dns.flags.AA
else:
r.authority.append(
dns.rrset.from_text(
suffix,
1,
IN,
SOA,
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
)
)
if bad or not (
endswith("icky.icky.icky.ptang.zoop.boing.", lqname)
or endswith(
"many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.",
lqname,
)
or endswith("a.bit.longer.ns.name.", lqname)
):
r.set_rcode(NXDOMAIN)
if ugly:
r.set_rcode(FORMERR)
if slow:
time.sleep(0.2)
return r
class UglyHandler(EntRcodeChanger):
domains = ["ugly."]
rcode = dns.rcode.FORMERR
def sigterm(signum, frame):
print("Shutting down now...")
os.remove("ans.pid")
running = False
sys.exit(0)
class SlowHandler(DelayedResponseHandler):
domains = ["slow."]
delay = 0.2
############################################################################
# Main
#
# Set up responder and control channel, open the pid file, and start
# the main loop, listening for queries on the query channel or commands
# on the control channel and acting on them.
############################################################################
ip4 = "10.53.0.2"
ip6 = "fd92:7065:b8e:ffff::2"
def send_delegation(
qctx: QueryContext, zone_cut: dns.name.Name, target_addr: str
) -> ResponseAction:
"""
Delegate `zone_cut` to a single in-bailiwick name server, `ns.<zone_cut>`,
with a single IPv4 glue record (provided in `target_addr`) included in the
ADDITIONAL section.
"""
ns_name = "ns." + zone_cut.to_text()
ns_rrset = dns.rrset.from_text(
zone_cut, 2, dns.rdataclass.IN, dns.rdatatype.NS, ns_name
)
a_rrset = dns.rrset.from_text(
ns_name, 2, dns.rdataclass.IN, dns.rdatatype.A, target_addr
)
try:
port = int(os.environ["PORT"])
except:
port = 5300
response = dns.message.make_response(qctx.query)
response.set_rcode(dns.rcode.NOERROR)
response.authority.append(ns_rrset)
response.additional.append(a_rrset)
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_socket.bind((ip4, port))
return DnsResponseSend(response, authoritative=False)
havev6 = True
try:
query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
try:
query6_socket.bind((ip6, port))
except:
query6_socket.close()
havev6 = False
except:
havev6 = False
signal.signal(signal.SIGTERM, sigterm)
class StaleHandler(DomainHandler):
"""
`a.b.stale` is a subdomain of `b.stale` and these two subdomains need to be
delegated to different name servers. Therefore, their delegations cannot
be placed in the zone file because the zone cut at `b.stale` would occlude
the one at `a.b.stale`. Generate these delegations dynamically depending
on the QNAME.
"""
f = open("ans.pid", "w")
pid = os.getpid()
print(pid, file=f)
f.close()
domains = ["stale."]
running = True
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[ResponseAction, None]:
log_query(qctx)
a_b_stale = dns.name.from_text("a.b.stale.")
b_stale = dns.name.from_text("b.stale.")
if qctx.qname.is_subdomain(a_b_stale):
yield send_delegation(qctx, a_b_stale, "10.53.0.3")
elif qctx.qname.is_subdomain(b_stale):
yield send_delegation(qctx, b_stale, "10.53.0.4")
print("Listening on %s port %d" % (ip4, port))
if havev6:
print("Listening on %s port %d" % (ip6, port))
print("Ctrl-c to quit")
if havev6:
input = [query4_socket, query6_socket]
else:
input = [query4_socket]
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
except select.error as e:
break
except socket.error as e:
break
except KeyboardInterrupt:
break
for s in inputready:
if s == query4_socket or s == query6_socket:
print(
"Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
)
# Handle incoming queries
msg = s.recvfrom(65535)
rsp = create_response(msg[0])
if rsp:
print(dns.rcode.to_text(rsp.rcode()))
s.sendto(rsp.to_wire(), msg[1])
else:
print("NO RESPONSE")
if not running:
break
if __name__ == "__main__":
server = AsyncDnsServer()
server.install_response_handler(QueryLogger())
server.install_response_handler(BadHandler())
server.install_response_handler(UglyHandler())
server.install_response_handler(SlowHandler())
server.install_response_handler(StaleHandler())
server.run()
+1
View File
@@ -0,0 +1 @@
good.db
+1
View File
@@ -0,0 +1 @@
good.db
+26
View File
@@ -0,0 +1,26 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns2 hostmaster.arpa. 2018050100 1 1 1 1
@ 30 NS ns2
ns2 30 A 10.53.0.2
30 AAAA fd92:7065:b8e:ffff::2
zoop.boing 30 NS ns3
ns3 30 A 10.53.0.3
30 AAAA fd92:7065:b8e:ffff::3
ns4 30 A 10.53.0.4
30 AAAA fd92:7065:b8e:ffff::4
a.bit.longer.ns.name 1 A 10.53.0.4
1 AAAA fd92:7065:b8e:ffff::4
+1
View File
@@ -0,0 +1 @@
good.db
+15
View File
@@ -0,0 +1,15 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 2 SOA ns2 hostmaster.stale. 1 2 3 4 5
@ 2 NS ns2
ns2 2 A 10.53.0.2
2 AAAA fd92:7065:b8e:ffff::2
+1
View File
@@ -0,0 +1 @@
good.db
@@ -0,0 +1,15 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 30 SOA ns3.good. hostmaster.arpa. 2018050100 1 1 1 1
@ 30 NS ns3.good.
1.1.1.1 60 NS ns4.good.
+15
View File
@@ -0,0 +1,15 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns hostmaster.a.b.stale. 1 2 3 4 5
@ 1 NS ns
@ 1 TXT "peekaboo"
ns 1 A 10.53.0.3
+34 -273
View File
@@ -1,285 +1,46 @@
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
"""
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
from __future__ import print_function
import os
import sys
import signal
import socket
import select
from datetime import datetime, timedelta
import time
import functools
SPDX-License-Identifier: MPL-2.0
import dns, dns.message, dns.query, dns.flags
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
from dns.name import *
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, you can obtain one at https://mozilla.org/MPL/2.0/.
See the COPYRIGHT file distributed with this work for additional
information regarding copyright ownership.
"""
import dns.rcode
from isctest.asyncserver import AsyncDnsServer
from qmin_ans import DelayedResponseHandler, EntRcodeChanger, QueryLogHandler
# Log query to file
def logquery(type, qname):
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
class QueryLogger(QueryLogHandler):
domains = ["8.2.6.0.1.0.0.2.ip6.arpa.", "a.b.stale.", "zoop.boing.good."]
def endswith(domain, labels):
return domain.endswith("." + labels) or domain == labels
class ZoopBoingBadHandler(EntRcodeChanger):
domains = ["zoop.boing.bad."]
rcode = dns.rcode.NXDOMAIN
############################################################################
# Respond to a DNS query.
# For good. it serves:
# zoop.boing.good. NS ns3.good.
# icky.ptang.zoop.boing.good. NS a.bit.longer.ns.name.good.
# it responds properly (with NODATA empty response) to non-empty terminals
#
# For slow. it works the same as for good., but each response is delayed by 400 milliseconds
#
# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals
#
# For ugly. it works the same as for good., but returns garbage to non-empty terminals
#
# For stale. it serves:
# a.b.stale. IN TXT peekaboo (resolver did not do qname minimization)
############################################################################
def create_response(msg):
m = dns.message.from_wire(msg)
qname = m.question[0].name.to_text()
lqname = qname.lower()
labels = lqname.split(".")
suffix = ""
# get qtype
rrtype = m.question[0].rdtype
typename = dns.rdatatype.to_text(rrtype)
if typename == "A" or typename == "AAAA":
typename = "ADDR"
bad = False
ugly = False
slow = False
# log this query
with open("query.log", "a") as f:
f.write("%s %s\n" % (typename, lqname))
print("%s %s" % (typename, lqname), end=" ")
r = dns.message.make_response(m)
r.set_rcode(NOERROR)
ip6req = False
if endswith(lqname, "bad."):
bad = True
suffix = "bad."
lqname = lqname[:-4]
elif endswith(lqname, "ugly."):
ugly = True
suffix = "ugly."
lqname = lqname[:-5]
elif endswith(lqname, "good."):
suffix = "good."
lqname = lqname[:-5]
elif endswith(lqname, "slow."):
slow = True
suffix = "slow."
lqname = lqname[:-5]
elif endswith(lqname, "8.2.6.0.1.0.0.2.ip6.arpa."):
ip6req = True
elif endswith(lqname, "a.b.stale."):
if lqname == "a.b.stale.":
r.flags |= dns.flags.AA
if rrtype == TXT:
# Direct query.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, TXT, "peekaboo"))
elif rrtype == NS:
# NS a.b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.a.b.stale."))
r.additional.append(
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
)
elif rrtype == SOA:
# SOA a.b.
r.answer.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
)
)
else:
# NODATA.
r.authority.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
)
)
elif lqname == "ns.a.b.stale.":
r.flags |= dns.flags.AA
if rrtype == A:
r.answer.append(
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
)
else:
r.authority.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
)
)
else:
r.flags |= dns.flags.AA
r.authority.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
)
)
r.set_rcode(NXDOMAIN)
# NXDOMAIN.
return r
else:
r.set_rcode(REFUSED)
return r
# Good/bad differs only in how we treat non-empty terminals
if lqname == "zoop.boing." and rrtype == NS:
r.answer.append(
dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3." + suffix)
)
r.flags |= dns.flags.AA
elif endswith(lqname, "icky.ptang.zoop.boing."):
r.authority.append(
dns.rrset.from_text(
"icky.ptang.zoop.boing." + suffix,
1,
IN,
NS,
"a.bit.longer.ns.name." + suffix,
)
)
elif endswith("icky.ptang.zoop.boing.", lqname):
r.authority.append(
dns.rrset.from_text(
"zoop.boing." + suffix,
1,
IN,
SOA,
"ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
)
)
if bad:
r.set_rcode(NXDOMAIN)
if ugly:
r.set_rcode(FORMERR)
elif endswith(lqname, "zoop.boing."):
r.authority.append(
dns.rrset.from_text(
"zoop.boing." + suffix,
1,
IN,
SOA,
"ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
)
)
r.set_rcode(NXDOMAIN)
elif ip6req:
r.authority.append(
dns.rrset.from_text(
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns4.good."
)
)
r.additional.append(dns.rrset.from_text("ns4.good.", 60, IN, A, "10.53.0.4"))
else:
r.set_rcode(REFUSED)
if slow:
time.sleep(0.4)
return r
class ZoopBoingUglyHandler(EntRcodeChanger):
domains = ["zoop.boing.ugly."]
rcode = dns.rcode.FORMERR
def sigterm(signum, frame):
print("Shutting down now...")
os.remove("ans.pid")
running = False
sys.exit(0)
class ZoopBoingSlowHandler(DelayedResponseHandler):
domains = ["zoop.boing.slow."]
delay = 0.4
############################################################################
# Main
#
# Set up responder and control channel, open the pid file, and start
# the main loop, listening for queries on the query channel or commands
# on the control channel and acting on them.
############################################################################
ip4 = "10.53.0.3"
ip6 = "fd92:7065:b8e:ffff::3"
try:
port = int(os.environ["PORT"])
except:
port = 5300
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_socket.bind((ip4, port))
havev6 = True
try:
query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
try:
query6_socket.bind((ip6, port))
except:
query6_socket.close()
havev6 = False
except:
havev6 = False
signal.signal(signal.SIGTERM, sigterm)
f = open("ans.pid", "w")
pid = os.getpid()
print(pid, file=f)
f.close()
running = True
print("Listening on %s port %d" % (ip4, port))
if havev6:
print("Listening on %s port %d" % (ip6, port))
print("Ctrl-c to quit")
if havev6:
input = [query4_socket, query6_socket]
else:
input = [query4_socket]
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
except select.error as e:
break
except socket.error as e:
break
except KeyboardInterrupt:
break
for s in inputready:
if s == query4_socket or s == query6_socket:
print(
"Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
)
# Handle incoming queries
msg = s.recvfrom(65535)
rsp = create_response(msg[0])
if rsp:
print(dns.rcode.to_text(rsp.rcode()))
s.sendto(rsp.to_wire(), msg[1])
else:
print("NO RESPONSE")
if not running:
break
if __name__ == "__main__":
server = AsyncDnsServer()
server.install_response_handler(QueryLogger())
server.install_response_handler(ZoopBoingBadHandler())
server.install_response_handler(ZoopBoingUglyHandler())
server.install_response_handler(ZoopBoingSlowHandler())
server.run()
@@ -0,0 +1,14 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns3.bad. hostmaster.arpa. 2018050100 1 1 1 1
@ 1 NS ns3.bad.
icky.ptang 1 NS a.bit.longer.ns.name.bad.
@@ -0,0 +1,14 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns3.good. hostmaster.arpa. 2018050100 1 1 1 1
@ 1 NS ns3.good.
icky.ptang 1 NS a.bit.longer.ns.name.good.
@@ -0,0 +1,14 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns3.slow. hostmaster.arpa. 2018050100 1 1 1 1
@ 1 NS ns3.slow.
icky.ptang 1 NS a.bit.longer.ns.name.slow.
@@ -0,0 +1,14 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns3.ugly. hostmaster.arpa. 2018050100 1 1 1 1
@ 1 NS ns3.ugly.
icky.ptang 1 NS a.bit.longer.ns.name.ugly.
@@ -0,0 +1,15 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 30 SOA ns4.good. hostmaster.arpa. 2018050100 1 1 1 1
@ 30 NS ns4.good.
test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4 1 TXT "long_ip6_name"
+15
View File
@@ -0,0 +1,15 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns hostmaster.a.b.stale. 1 2 3 4 5
@ 1 NS ns
ns 1 A 10.53.0.4
@ 1 TXT "hooray"
+79 -330
View File
@@ -1,344 +1,93 @@
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
"""
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
from __future__ import print_function
import os
import sys
import signal
import socket
import select
from datetime import datetime, timedelta
import time
import functools
SPDX-License-Identifier: MPL-2.0
import dns, dns.message, dns.query, dns.flags
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
from dns.name import *
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, you can obtain one at https://mozilla.org/MPL/2.0/.
See the COPYRIGHT file distributed with this work for additional
information regarding copyright ownership.
"""
from typing import AsyncGenerator
import dns.rcode
from isctest.asyncserver import (
AsyncDnsServer,
DnsResponseSend,
DomainHandler,
QueryContext,
ResponseAction,
)
from qmin_ans import DelayedResponseHandler, EntRcodeChanger, QueryLogHandler, log_query
# Log query to file
def logquery(type, qname):
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
class QueryLogger(QueryLogHandler):
domains = [
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
"icky.ptang.zoop.boing.good.",
]
def endswith(domain, labels):
return domain.endswith("." + labels) or domain == labels
class StaleHandler(DomainHandler):
"""
The test code relies on this server returning non-minimal (i.e. including
address records in the ADDITIONAL section) responses to NS queries for
`b.stale` and `a.b.stale`. While this logic (returning non-minimal
responses to NS queries) could be implemented in AsyncDnsServer itself,
doing so breaks a lot of other checks in this system test. Therefore, only
these two zones behave in this particular way, thanks to a custom response
handler implemented below.
"""
domains = ["b.stale", "a.b.stale"]
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[ResponseAction, None]:
log_query(qctx)
if qctx.qtype == dns.rdatatype.NS:
assert qctx.zone
assert qctx.response.answer[0]
for nameserver in qctx.response.answer[0]:
if not nameserver.target.is_subdomain(qctx.response.answer[0].name):
continue
glue_a = qctx.zone.get_rrset(nameserver.target, dns.rdatatype.A)
if glue_a:
qctx.response.additional.append(glue_a)
glue_aaaa = qctx.zone.get_rrset(nameserver.target, dns.rdatatype.AAAA)
if glue_aaaa:
qctx.response.additional.append(glue_aaaa)
yield DnsResponseSend(qctx.response)
############################################################################
# Respond to a DNS query.
# For good. it serves:
# icky.ptang.zoop.boing.good. NS a.bit.longer.ns.name.
# icky.icky.icky.ptang.zoop.boing.good. A 192.0.2.1
# more.icky.icky.icky.ptang.zoop.boing.good. A 192.0.2.2
# it responds properly (with NODATA empty response) to non-empty terminals
#
# For slow. it works the same as for good., but each response is delayed by 400 milliseconds
#
# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals
#
# For ugly. it works the same as for good., but returns garbage to non-empty terminals
#
# For stale. it serves:
# a.b.stale. IN TXT hooray (resolver did do qname minimization)
############################################################################
def create_response(msg):
m = dns.message.from_wire(msg)
qname = m.question[0].name.to_text()
lqname = qname.lower()
labels = lqname.split(".")
suffix = ""
# get qtype
rrtype = m.question[0].rdtype
typename = dns.rdatatype.to_text(rrtype)
if typename == "A" or typename == "AAAA":
typename = "ADDR"
bad = False
slow = False
ugly = False
# log this query
with open("query.log", "a") as f:
f.write("%s %s\n" % (typename, lqname))
print("%s %s" % (typename, lqname), end=" ")
r = dns.message.make_response(m)
r.set_rcode(NOERROR)
ip6req = False
if endswith(lqname, "bad."):
bad = True
suffix = "bad."
lqname = lqname[:-4]
elif endswith(lqname, "ugly."):
ugly = True
suffix = "ugly."
lqname = lqname[:-5]
elif endswith(lqname, "good."):
suffix = "good."
lqname = lqname[:-5]
elif endswith(lqname, "slow."):
slow = True
suffix = "slow."
lqname = lqname[:-5]
elif endswith(lqname, "1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa."):
ip6req = True
elif endswith(lqname, "b.stale."):
if lqname == "a.b.stale.":
r.flags |= dns.flags.AA
if rrtype == TXT:
# Direct query.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, TXT, "hooray"))
elif rrtype == NS:
# NS a.b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.a.b.stale."))
r.additional.append(
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
)
elif rrtype == SOA:
# SOA a.b.
r.answer.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
)
)
else:
# NODATA.
r.authority.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
)
)
elif lqname == "ns.a.b.stale.":
r.flags |= dns.flags.AA
if rrtype == A:
r.answer.append(
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
)
else:
# NODATA.
r.authority.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
)
)
elif lqname == "b.stale.":
r.flags |= dns.flags.AA
if rrtype == NS:
# NS b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.b.stale."))
r.additional.append(
dns.rrset.from_text("ns.b.stale.", 1, IN, A, "10.53.0.4")
)
elif rrtype == SOA:
# SOA b.
r.answer.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
)
)
else:
# NODATA.
r.authority.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
)
)
elif lqname == "ns.b.stale.":
r.flags |= dns.flags.AA
if rrtype == A:
# SOA a.b.
r.answer.append(
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.4")
)
else:
# NODATA.
r.authority.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
)
)
else:
r.authority.append(
dns.rrset.from_text(
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
)
)
r.set_rcode(NXDOMAIN)
# NXDOMAIN.
return r
else:
r.set_rcode(REFUSED)
return r
# Good/bad differs only in how we treat non-empty terminals
if lqname == "icky.icky.icky.ptang.zoop.boing." and rrtype == A:
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.1"))
r.flags |= dns.flags.AA
elif lqname == "more.icky.icky.icky.ptang.zoop.boing." and rrtype == A:
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2"))
r.flags |= dns.flags.AA
elif lqname == "icky.ptang.zoop.boing." and rrtype == NS:
r.answer.append(
dns.rrset.from_text(
lqname + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix
)
)
r.flags |= dns.flags.AA
elif endswith(lqname, "icky.ptang.zoop.boing."):
r.authority.append(
dns.rrset.from_text(
"icky.ptang.zoop.boing." + suffix,
1,
IN,
SOA,
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
)
)
if bad or not endswith("more.icky.icky.icky.ptang.zoop.boing.", lqname):
r.set_rcode(NXDOMAIN)
if ugly:
r.set_rcode(FORMERR)
elif ip6req:
r.flags |= dns.flags.AA
if (
lqname
== "test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa."
and rrtype == TXT
):
r.answer.append(
dns.rrset.from_text(
"test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
1,
IN,
TXT,
"long_ip6_name",
)
)
elif endswith(
"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
lqname,
):
# NODATA answer
r.authority.append(
dns.rrset.from_text(
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
60,
IN,
SOA,
"ns4.good. hostmaster.arpa. 2018050100 120 30 320 16",
)
)
else:
# NXDOMAIN
r.authority.append(
dns.rrset.from_text(
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
60,
IN,
SOA,
"ns4.good. hostmaster.arpa. 2018050100 120 30 320 16",
)
)
r.set_rcode(NXDOMAIN)
else:
r.set_rcode(REFUSED)
if slow:
time.sleep(0.4)
return r
class IckyPtangZoopBoingBadHandler(EntRcodeChanger):
domains = ["icky.ptang.zoop.boing.bad."]
rcode = dns.rcode.NXDOMAIN
def sigterm(signum, frame):
print("Shutting down now...")
os.remove("ans.pid")
running = False
sys.exit(0)
class IckyPtangZoopBoingUglyHandler(EntRcodeChanger):
domains = ["icky.ptang.zoop.boing.ugly."]
rcode = dns.rcode.FORMERR
############################################################################
# Main
#
# Set up responder and control channel, open the pid file, and start
# the main loop, listening for queries on the query channel or commands
# on the control channel and acting on them.
############################################################################
ip4 = "10.53.0.4"
ip6 = "fd92:7065:b8e:ffff::4"
class IckyPtangZoopBoingSlowHandler(DelayedResponseHandler):
domains = ["icky.ptang.zoop.boing.slow."]
delay = 0.4
try:
port = int(os.environ["PORT"])
except:
port = 5300
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_socket.bind((ip4, port))
havev6 = True
try:
query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
try:
query6_socket.bind((ip6, port))
except:
query6_socket.close()
havev6 = False
except:
havev6 = False
signal.signal(signal.SIGTERM, sigterm)
f = open("ans.pid", "w")
pid = os.getpid()
print(pid, file=f)
f.close()
running = True
print("Listening on %s port %d" % (ip4, port))
if havev6:
print("Listening on %s port %d" % (ip6, port))
print("Ctrl-c to quit")
if havev6:
input = [query4_socket, query6_socket]
else:
input = [query4_socket]
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
except select.error as e:
break
except socket.error as e:
break
except KeyboardInterrupt:
break
for s in inputready:
if s == query4_socket or s == query6_socket:
print(
"Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
)
# Handle incoming queries
msg = s.recvfrom(65535)
rsp = create_response(msg[0])
if rsp:
print(dns.rcode.to_text(rsp.rcode()))
s.sendto(rsp.to_wire(), msg[1])
else:
print("NO RESPONSE")
if not running:
break
if __name__ == "__main__":
server = AsyncDnsServer()
server.install_response_handler(QueryLogger())
server.install_response_handler(StaleHandler())
server.install_response_handler(IckyPtangZoopBoingBadHandler())
server.install_response_handler(IckyPtangZoopBoingUglyHandler())
server.install_response_handler(IckyPtangZoopBoingSlowHandler())
server.run()
+16
View File
@@ -0,0 +1,16 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns hostmaster.b.stale. 1 2 3 4 5
@ 1 NS ns
ns 1 A 10.53.0.4
a 1 NS ns.a
ns.a 1 A 10.53.0.4
@@ -0,0 +1,15 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns4.bad. hostmaster.arpa. 2018050100 1 1 1 1
@ 1 NS a.bit.longer.ns.name.bad.
icky.icky 1 A 192.0.2.1
more.icky.icky 1 A 192.0.2.2
@@ -0,0 +1,15 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns4.good. hostmaster.arpa. 2018050100 1 1 1 1
@ 1 NS a.bit.longer.ns.name.good.
icky.icky 1 A 192.0.2.1
more.icky.icky 1 A 192.0.2.2
@@ -0,0 +1,15 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns4.slow. hostmaster.arpa. 2018050100 1 1 1 1
@ 1 NS a.bit.longer.ns.name.slow.
icky.icky 1 A 192.0.2.1
more.icky.icky 1 A 192.0.2.2
@@ -0,0 +1,15 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
@ 1 SOA ns4.ugly. hostmaster.arpa. 2018050100 1 1 1 1
@ 1 NS a.bit.longer.ns.name.ugly.
icky.icky 1 A 192.0.2.1
more.icky.icky 1 A 192.0.2.2
+107
View File
@@ -0,0 +1,107 @@
"""
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
SPDX-License-Identifier: MPL-2.0
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, you can obtain one at https://mozilla.org/MPL/2.0/.
See the COPYRIGHT file distributed with this work for additional
information regarding copyright ownership.
"""
from typing import AsyncGenerator
import abc
import dns.rcode
import dns.rdataclass
import dns.rdatatype
from isctest.asyncserver import (
DnsResponseSend,
DomainHandler,
QueryContext,
ResponseAction,
)
from isctest.compat import dns_rcode
def log_query(qctx: QueryContext) -> None:
"""
Log a received DNS query to a text file inspected by `tests.sh`. AAAA and
A queries are logged identically because the relative order in which they
are received does not matter.
"""
qname = qctx.qname.to_text()
qtype = dns.rdatatype.to_text(qctx.qtype)
if qtype in ("A", "AAAA"):
qtype = "ADDR"
with open("query.log", "a", encoding="utf-8") as query_log:
print(f"{qtype} {qname}", file=query_log)
class QueryLogHandler(DomainHandler):
"""
Log all received DNS queries to a text file. Use the zone file for
preparing responses.
"""
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[ResponseAction, None]:
log_query(qctx)
yield DnsResponseSend(qctx.response)
class EntRcodeChanger(DomainHandler):
"""
Log all received DNS queries to a text file. Use the zone file for
preparing responses, but override the RCODE returned for empty
non-terminals (ENTs) to the value specified by the child class. This
emulates broken authoritative servers.
"""
@property
@abc.abstractmethod
def rcode(self) -> dns_rcode:
raise NotImplementedError
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[ResponseAction, None]:
assert qctx.zone
log_query(qctx)
if (
qctx.response.rcode() == dns.rcode.NOERROR
and not qctx.response.answer
and qctx.response.authority
and qctx.response.authority[0].rdtype == dns.rdatatype.SOA
and not qctx.zone.get_node(qctx.qname)
):
qctx.response.set_rcode(self.rcode)
yield DnsResponseSend(qctx.response)
class DelayedResponseHandler(DomainHandler):
"""
Log all received DNS queries to a text file. Use the zone file for
preparing responses, but delay sending every answer by the amount of time
specified (in seconds) by the child class. This emulates network delays.
"""
@property
@abc.abstractmethod
def delay(self) -> float:
raise NotImplementedError
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[ResponseAction, None]:
log_query(qctx)
yield DnsResponseSend(qctx.response, delay=self.delay)
+23
View File
@@ -127,12 +127,14 @@ ADDR a.bit.longer.ns.name.good.
ADDR ns2.good.
ADDR ns3.good.
ADDR ns3.good.
NS a.bit.longer.ns.name.good.
NS bit.longer.ns.name.good.
NS boing.good.
NS good.
NS longer.ns.name.good.
NS name.good.
NS ns.name.good.
NS ns3.good.
NS zoop.boing.good.
__EOF
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
@@ -165,11 +167,13 @@ ADDR a.bit.longer.ns.name.good.
ADDR ns2.good.
ADDR ns3.good.
ADDR ns3.good.
NS a.bit.longer.ns.name.good.
NS bit.longer.ns.name.good.
NS boing.good.
NS longer.ns.name.good.
NS name.good.
NS ns.name.good.
NS ns3.good.
NS zoop.boing.good.
__EOF
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
@@ -221,6 +225,7 @@ ADDR ns3.bad.
ADDR ns3.bad.
NS boing.bad.
NS name.bad.
NS ns3.bad.
__EOF
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
ADDR icky.icky.icky.ptang.zoop.boing.bad.
@@ -271,6 +276,7 @@ ADDR ns3.ugly.
NS boing.ugly.
NS name.ugly.
NS name.ugly.
NS ns3.ugly.
__EOF
echo "ADDR icky.icky.icky.ptang.zoop.boing.ugly." | diff ans3/query.log - >/dev/null || ret=1
echo "ADDR icky.icky.icky.ptang.zoop.boing.ugly." | diff ans4/query.log - >/dev/null || ret=1
@@ -302,11 +308,13 @@ ADDR a.bit.longer.ns.name.slow.
ADDR ns2.slow.
ADDR ns3.slow.
ADDR ns3.slow.
NS a.bit.longer.ns.name.slow.
NS bit.longer.ns.name.slow.
NS boing.slow.
NS longer.ns.name.slow.
NS name.slow.
NS ns.name.slow.
NS ns3.slow.
NS slow.
NS zoop.boing.slow.
__EOF
@@ -340,6 +348,7 @@ NS 8.f.4.0.1.0.0.2.ip6.arpa.
NS 0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
NS 0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
NS 0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
NS 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
PTR 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
__EOF
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
@@ -362,12 +371,14 @@ ADDR a.bit.longer.ns.name.good.
ADDR ns2.good.
ADDR ns3.good.
ADDR ns3.good.
NS a.bit.longer.ns.name.good.
NS bit.longer.ns.name.good.
NS boing.good.
NS good.
NS longer.ns.name.good.
NS name.good.
NS ns.name.good.
NS ns3.good.
NS zoop.boing.good.
__EOF
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
@@ -449,6 +460,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
@@ -457,7 +469,9 @@ __EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
NS a.b.stale.
NS b.stale.
TXT a.b.stale.
__EOF
@@ -476,6 +490,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
@@ -483,7 +498,9 @@ __EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
NS a.b.stale.
TXT a.b.stale.
__EOF
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
@@ -519,6 +536,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
@@ -527,7 +545,9 @@ __EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
NS a.b.stale.
NS b.stale.
TXT a.b.stale.
__EOF
@@ -546,6 +566,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
@@ -553,7 +574,9 @@ __EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
NS a.b.stale.
TXT a.b.stale.
__EOF
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
+2 -2
View File
@@ -9,9 +9,9 @@
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
$TTL 60
$TTL 120
big. IN SOA ns.big. hostmaster.ns.big. 1 0 0 0 60
big. IN SOA ns.big. hostmaster.ns.big. 1 0 0 0 120
big. IN NS ns.big.
ns.big. IN A 10.53.0.1
+25 -25
View File
@@ -280,11 +280,11 @@ echo_i "checking that priority names under the max-types-per-name limit get cach
# Query for NXDOMAIN for items on our priority list - these should get cached
for rrtype in AAAA MX NS; do
check_manytypes 1 manytypes.big "${rrtype}" NOERROR big SOA 60 || ret=1
check_manytypes 1 manytypes.big "${rrtype}" NOERROR big SOA 120 || ret=1
done
# Wait at least 1 second
for rrtype in AAAA MX NS; do
check_manytypes 2 manytypes.big "${rrtype}" NOERROR big SOA "" 60 || ret=1
check_manytypes 2 manytypes.big "${rrtype}" NOERROR big SOA "" 120 || ret=1
done
if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -299,13 +299,13 @@ echo_i "checking that NXDOMAIN names under the max-types-per-name limit get cach
# Query for 10 NXDOMAIN types
for ntype in $(seq 65270 65279); do
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 60 || ret=1
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 120 || ret=1
done
# Wait at least 1 second
sleep 1
# Query for 10 NXDOMAIN types again - these should be cached
for ntype in $(seq 65270 65279); do
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA "" 60 || ret=1
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA "" 120 || ret=1
done
if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -318,13 +318,13 @@ echo_i "checking that existing names under the max-types-per-name limit get cach
# Limited to 10 types - these should be cached and the previous record should be evicted
for ntype in $(seq 65280 65289); do
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
done
# Wait at least one second
sleep 1
# Limited to 10 types - these should be cached
for ntype in $(seq 65280 65289); do
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 60 || ret=1
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 120 || ret=1
done
if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -356,11 +356,11 @@ echo_i "checking that priority NXDOMAIN names over the max-types-per-name limit
# Query for NXDOMAIN for items on our priority list - these should get cached
for rrtype in AAAA MX NS; do
check_manytypes 1 manytypes.big "${rrtype}" NOERROR big SOA 60 || ret=1
check_manytypes 1 manytypes.big "${rrtype}" NOERROR big SOA 120 || ret=1
done
# Wait at least 1 second
for rrtype in AAAA MX NS; do
check_manytypes 2 manytypes.big "${rrtype}" NOERROR big SOA "" 60 || ret=1
check_manytypes 2 manytypes.big "${rrtype}" NOERROR big SOA "" 120 || ret=1
done
if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -372,11 +372,11 @@ ret=0
echo_i "checking that priority name over the max-types-per-name get cached ($n)"
# Query for an item on our priority list - it should get cached
check_manytypes 1 manytypes.big "A" NOERROR manytypes.big A 60 || ret=1
check_manytypes 1 manytypes.big "A" NOERROR manytypes.big A 120 || ret=1
# Wait at least 1 second
sleep 1
# Query the same name again - it should be in the cache
check_manytypes 2 manytypes.big "A" NOERROR big manytypes.A "" 60 || ret=1
check_manytypes 2 manytypes.big "A" NOERROR big manytypes.A "" 120 || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@@ -389,7 +389,7 @@ ret=0
echo_i "checking that priority name over the max-types-per-name don't get evicted ($n)"
# Query for an item on our priority list - it should get cached
check_manytypes 1 manytypes.big "A" NOERROR manytypes.big A 60 || ret=1
check_manytypes 1 manytypes.big "A" NOERROR manytypes.big A 120 || ret=1
# Query for 10 more types - this should not evict A record
for ntype in $(seq 65280 65289); do
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big || ret=1
@@ -397,9 +397,9 @@ done
# Wait at least 1 second
sleep 1
# Query the same name again - it should be in the cache
check_manytypes 2 manytypes.big "A" NOERROR manytypes.big A "" 60 || ret=1
check_manytypes 2 manytypes.big "A" NOERROR manytypes.big A "" 120 || ret=1
# This one was first in the list and should have been evicted
check_manytypes 2 manytypes.big "TYPE65280" NOERROR manytypes.big TYPE65280 60 || ret=1
check_manytypes 2 manytypes.big "TYPE65280" NOERROR manytypes.big TYPE65280 120 || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@@ -413,21 +413,21 @@ echo_i "checking that non-priority types cause eviction ($n)"
# Everything on top of that will cause the cache eviction
for ntype in $(seq 65280 65299); do
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
done
# Wait at least one second
sleep 1
# These should have TTL != 60 now
# These should have TTL != 120 now
for ntype in $(seq 65290 65299); do
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 60 || ret=1
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 120 || ret=1
done
# These should have been evicted
for ntype in $(seq 65280 65289); do
check_manytypes 3 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
check_manytypes 3 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
done
# These should have been evicted by the previous block
for ntype in $(seq 65290 65299); do
check_manytypes 4 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
check_manytypes 4 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
done
if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -442,25 +442,25 @@ echo_i "checking that signed names under the max-types-per-name limit get cached
# Go through the 10 items, this should result in 20 items (type + rrsig(type))
for ntype in $(seq 65280 65289); do
check_manytypes 1 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 60 || ret=1
check_manytypes 1 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 120 || ret=1
done
# Wait at least one second
sleep 1
# These should have TTL != 60 now
# These should have TTL != 120 now
for ntype in $(seq 65285 65289); do
check_manytypes 2 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" "" 60 || ret=1
check_manytypes 2 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" "" 120 || ret=1
done
# These should have been evicted
for ntype in $(seq 65280 65284); do
check_manytypes 3 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 60 || ret=1
check_manytypes 3 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 120 || ret=1
done
# These should have been evicted by the previous block
for ntype in $(seq 65285 65289); do
check_manytypes 4 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 60 || ret=1
check_manytypes 4 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 120 || ret=1
done
if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -475,12 +475,12 @@ echo_i "checking that lifting the limit will allow everything to get cached ($n)
ns3_reset ns3/named6.conf.in
for ntype in $(seq 65280 65534); do
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
done
# Wait at least one second
sleep 1
for ntype in $(seq 65280 65534); do
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 60 || ret=1
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 120 || ret=1
done
if [ $ret -ne 0 ]; then echo_i "failed"; fi
+11
View File
@@ -20,6 +20,8 @@ use IO::Socket;
use Net::DNS;
use Net::DNS::Packet;
print "Using Net::DNS $Net::DNS::VERSION\n";
my $localport = int($ENV{'PORT'});
if (!$localport) { $localport = 5300; }
@@ -170,6 +172,15 @@ for (;;) {
$packet->push("authority",
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
}
} elsif ($qname eq "zoneversion") {
$packet->push("authority", new Net::DNS::RR(". 300 SOA . . 0 0 0 0 0"));
if ($Net::DNS::VERSION >= 1.49) {
$packet->edns->option('ZONEVERSION' => [0, 1, '01022304'] )
} elsif ($Net::DNS::VERSION >= 1.35) {
$packet->edns->option('19' => {'BASE16' => '000101022304'} )
} else {
$packet->edns->option('19' => pack 'H*', '000101022304')
}
} else {
# Data for the "bogus referrals" test
$packet->push("authority", new Net::DNS::RR("below.www.example.com 300 NS ns.below.www.example.com"));
@@ -31,6 +31,7 @@ options {
resolver-query-timeout 5000; # 5 seconds
attach-cache "globalcache";
max-recursion-queries 100;
request-zoneversion yes;
};
trust-anchors { };
@@ -4,26 +4,21 @@
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#pragma once
/*! \file dns/zonekey.h */
#include <stdbool.h>
#include <dns/types.h>
bool
dns_zonekey_iszonekey(dns_rdata_t *keyrdata);
/*%<
* Determines if the key record contained in the rdata is a zone key.
*
* Requires:
* 'keyrdata' is not NULL.
*/
options {
query-source address 10.53.0.11;
notify-source 10.53.0.11;
transfer-source 10.53.0.11;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.11; };
listen-on-v6 { none; };
recursion no;
dnssec-validation no;
};
@@ -26,6 +26,15 @@ options {
querylog yes;
prefetch 4 10;
responselog yes;
request-nsid yes;
request-zoneversion yes;
};
// Don't break tests which depend on ans10 by requesting
// zoneversion or nsid
server 10.53.0.10 {
request-nsid no;
request-zoneversion no;
};
include "trusted.conf";
+1
View File
@@ -24,5 +24,6 @@ copy_setports ns5/named.conf.in ns5/named.conf
copy_setports ns6/named.conf.in ns6/named.conf
copy_setports ns7/named1.conf.in ns7/named.conf
copy_setports ns9/named.conf.in ns9/named.conf
copy_setports ns11/named.conf.in ns11/named.conf
(cd ns6 && $SHELL keygen.sh)
+50 -4
View File
@@ -43,6 +43,12 @@ grep "status: NOERROR" dig.out.ns1.test${n} >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
rndccmd 10.53.0.1 stats || ret=1 # Get the responses, RTT and timeout statistics before the following timeout tests
grep -F 'responses received' ns1/named.stats >ns1/named.stats.responses-before || true
grep -F 'queries with RTT' ns1/named.stats >ns1/named.stats.rtt-before || true
grep -F 'query timeouts' ns1/named.stats >ns1/named.stats.timeouts-before || true
mv ns1/named.stats ns1/named.stats-before
# 'resolver-query-timeout' is set to 5 seconds in ns1, so dig with a lower
# timeout value should give up earlier than that.
n=$((n + 1))
@@ -66,6 +72,20 @@ grep -F "EDE: 22 (No Reachable Authority)" dig.out.ns1.test${n} >/dev/null || re
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking that the timeout didn't skew the resolver responses counters and did update the timeout counter ($n)"
ret=0
rndccmd 10.53.0.1 stats || ret=1
grep -F 'responses received' ns1/named.stats >ns1/named.stats.responses-after || true
grep -F 'queries with RTT' ns1/named.stats >ns1/named.stats.rtt-after || true
grep -F 'query timeouts' ns1/named.stats >ns1/named.stats.timeouts-after || true
mv ns1/named.stats ns1/named.stats-after
diff ns1/named.stats.responses-before ns1/named.stats.responses-after >/dev/null || ret=1
diff ns1/named.stats.rtt-before ns1/named.stats.rtt-after >/dev/null || ret=1
diff ns1/named.stats.timeouts-before ns1/named.stats.timeouts-after >/dev/null && ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
# 'resolver-query-timeout' is set to 5 seconds in ns1, so named should
# interrupt the non-responsive query and send a SERVFAIL answer before dig's
# own timeout fires, which is set to 7 seconds. This time, exampleudp.net is
@@ -729,10 +749,10 @@ if ${FEATURETEST} --enable-querytrace; then
grep "status: SERVFAIL" dig.ns5.out.${n} >/dev/null || ret=1
check_namedrun() {
nextpartpeek ns5/named.run >nextpart.out.${n}
grep 'resolving tcpalso.no-questions/A for [^:]*: empty question section, accepting it anyway as TC=1' nextpart.out.${n} >/dev/null || return 1
grep '(tcpalso.no-questions/A): connecting via TCP' nextpart.out.${n} >/dev/null || return 1
grep 'resolving tcpalso.no-questions/A for [^:]*: empty question section$' nextpart.out.${n} >/dev/null || return 1
grep '(tcpalso.no-questions/A): nextitem' nextpart.out.${n} >/dev/null || return 1
grep 'resolving tcpalso.no-questions/NS for [^:]*: empty question section, accepting it anyway as TC=1' nextpart.out.${n} >/dev/null || return 1
grep '(tcpalso.no-questions/NS): connecting via TCP' nextpart.out.${n} >/dev/null || return 1
grep 'resolving tcpalso.no-questions/NS for [^:]*: empty question section$' nextpart.out.${n} >/dev/null || return 1
grep '(tcpalso.no-questions/NS): nextitem' nextpart.out.${n} >/dev/null || return 1
return 0
}
retry_quiet 12 check_namedrun || ret=1
@@ -881,6 +901,23 @@ test ${lines:-1} -ne 0 && ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "check that received ZONEVERSION is logged ($n)"
ret=0
pat="received ZONEVERSION serial 2010 from 10.53.0.4#[0-9]* for mixedttl.tld/TXT zone tld"
grep "$pat" ns5/named.run >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "check that received ZONEVERSION is logged non serial ($n)"
ret=0
dig_with_opts +tcp @10.53.0.1 zoneversion >dig.out.${n} || ret=1
pat='received ZONEVERSION type 1 value 01022304 (\.\.#\.) from 10.53.0.2#[0-9]* for zoneversion/A zone \.'
grep "$pat" ns1/named.run >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "check resolver behavior when FORMERR for EDNS options happens (${n})"
ret=0
@@ -1015,5 +1052,14 @@ ttl=$(awk '{print $2}' dig.ns1.out.${n})
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "client requests recursion but it is disabled - expect EDE 20 code with REFUSED($n)"
ret=0
dig_with_opts +recurse www.isc.org @10.53.0.11 a >dig.out.ns11.test${n} || ret=1
grep "status: REFUSED" dig.out.ns11.test${n} >/dev/null || ret=1
grep -F "EDE: 20 (Not Authoritative)" dig.out.ns11.test${n} >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1
@@ -21,6 +21,7 @@ pytestmark = pytest.mark.extra_artifacts(
"nextpart.out.*",
"ans*/ans.run",
"ans*/query.log",
"ns1/named.stats*",
"ns4/tld.db",
"ns5/trusted.conf",
"ns6/K*",
+1 -1
View File
@@ -111,8 +111,8 @@ def test_rpz_passthru_logging():
expected_rcode=dns.rcode.NOERROR,
)
assert res_allowed_any.answer == [
dns.rrset.from_text("allowed.", 300, "IN", "NS", "ns1.allowed."),
dns.rrset.from_text("allowed.", 300, "IN", "A", "10.53.0.2"),
dns.rrset.from_text("allowed.", 300, "IN", "NS", "ns1.allowed."),
]
# The comparison above doesn't compare the TTL values, and we want to
# make sure that the "passthru" rpz doesn't cap the TTL with max-policy-ttl.
+12 -11
View File
@@ -115,10 +115,12 @@ sleep 2
# stale for somewhere between 3500-3599 seconds.
echo_i "check rndc dump stale data.example ($n)"
rndc_dumpdb ns1 || ret=1
awk '/; stale since [0-9]*/ { x=$0; getline; print x, $0}' ns1/named_dump.db.test$n \
# add in inherited owner names
awk '$1 ~ /^[0-9][0-9]*$/ { $0 = last " " $0 } $1 != ";" { last = $1 } { print }' ns1/named_dump.db.test$n >named_dump.db.test$n
awk '/; stale since [0-9]*/ { x=$0; getline; print x, $0}' named_dump.db.test$n \
| grep "; stale since [0-9]* data\.example.*3[56]...*TXT.*A text record with a 2 second ttl" >/dev/null 2>&1 || ret=1
# Also make sure the not expired data does not have a stale comment.
awk '/; authanswer/ { x=$0; getline; print x, $0}' ns1/named_dump.db.test$n \
awk '/; authanswer/ { x=$0; getline; print x, $0}' named_dump.db.test$n \
| grep "; authanswer longttl\.example.*[56]...*TXT.*A text record with a 600 second ttl" >/dev/null 2>&1 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@@ -1664,16 +1666,15 @@ status=$((status + ret))
# Check that expired records are dumped.
echo_i "check rndc dump expired data.example ($n)"
ret=0
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired (awaiting cleanup) data\.example\..*A text record with a 2 second ttl" >/dev/null 2>&1 || ret=1
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired (awaiting cleanup) nodata\.example\." >/dev/null 2>&1 || ret=1
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired (awaiting cleanup) nxdomain\.example\." >/dev/null 2>&1 || ret=1
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired (awaiting cleanup) othertype\.example\." >/dev/null 2>&1 || ret=1
# add in inherited owner names
awk '$1 ~ /^[0-9][0-9]*$/ { $0 = last " " $0 } $1 != ";" { last = $1 } { print }' ns5/named_dump.db.test$n >named_dump.db.test$n
# extract expired records
awk '/; expired/ { x=$0; getline; print x, $0}' named_dump.db.test$n >expired.test$n
grep "; expired (awaiting cleanup) data\.example\..*A text record with a 2 second ttl" expired.test$n >/dev/null 2>&1 || ret=1
grep "; expired (awaiting cleanup) nodata\.example\." expired.test$n >/dev/null 2>&1 || ret=1
grep "; expired (awaiting cleanup) nxdomain\.example\." expired.test$n >/dev/null 2>&1 || ret=1
# Also make sure the not expired data does not have an expired comment.
awk '/; authanswer/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
awk '/; authanswer/ { x=$0; getline; print x, $0}' named_dump.db.test$n \
| grep "; authanswer longttl\.example.*A text record with a 600 second ttl" >/dev/null 2>&1 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@@ -14,6 +14,8 @@ import pytest
pytestmark = pytest.mark.extra_artifacts(
[
"dig.out.*",
"expired.test*",
"named_dump.db.test*",
"rndc.out.*",
"ans*/ans.run",
"ns*/named.stats*",
+3 -2
View File
@@ -264,7 +264,8 @@ sub construct_ns_command {
foreach my $t_option(
"dropedns", "ednsformerr", "ednsnotimp", "ednsrefused",
"noaa", "noedns", "nosoa", "maxudp512", "maxudp1460",
"cookiealwaysvalid", "noaa", "noedns", "nosoa",
"maxudp512", "maxudp1460",
) {
if (-e "$testdir/$server/named.$t_option") {
$command .= "-T $t_option "
@@ -323,7 +324,7 @@ sub construct_ans_command {
}
if (-e "$testdir/$server/ans.py") {
$ENV{'PYTHONPATH'} = $testdir . ":" . $ENV{'srcdir'};
$ENV{'PYTHONPATH'} = $testdir . ":" . $builddir;
$command = "$PYTHON -u ans.py 10.53.0.$n $queryport";
} elsif (-e "$testdir/$server/ans.pl") {
$command = "$PERL ans.pl";
+4 -3
View File
@@ -414,10 +414,10 @@ for ns in 2 4 5 6; do
check_status NOERROR dig.out.ns${ns}.test$n || ret=1
if [ ${synth} = yes ]; then
check_synth_cname b.wild-cname.example. dig.out.ns${ns}.test$n || ret=1
nextpart ns1/named.run | grep b.wild-cname.example/A >/dev/null && ret=1
nextpart ns1/named.run | grep b.wild-cname.example/NS >/dev/null && ret=1
else
check_nosynth_cname b.wild-cname.example. dig.out.ns${ns}.test$n || ret=1
nextpart ns1/named.run | grep b.wild-cname.example/A >/dev/null || ret=1
nextpart ns1/named.run | grep b.wild-cname.example/NS >/dev/null || ret=1
fi
grep "ns1.example.*.IN.A" dig.out.ns${ns}.test$n >/dev/null || ret=1
digcomp wildcname.out dig.out.ns${ns}.test$n || ret=1
@@ -470,6 +470,7 @@ for ns in 2 4 5 6; do
check_nosynth_aaaa b.wild-2-nsec-afterdata.example. dig.out.a.ns${ns}.test$n || ret=1
#
nextpart ns1/named.run >/dev/null
sleep 1
dig_with_opts b.wild-2-nsec-afterdata.example. @10.53.0.${ns} TLSA >dig.out.ns${ns}.test$n || ret=1
check_ad_flag $ad dig.out.ns${ns}.test$n || ret=1
check_status NOERROR dig.out.ns${ns}.test$n || ret=1
@@ -531,7 +532,7 @@ for ns in 2 4 5 6; do
check_ad_flag no dig.out.ns${ns}.test$n || ret=1
check_status NOERROR dig.out.ns${ns}.test$n || ret=1
check_nosynth_cname b.wild-cname.insecure.example dig.out.ns${ns}.test$n || ret=1
nextpart ns1/named.run | grep b.wild-cname.insecure.example/A >/dev/null || ret=1
nextpart ns1/named.run | grep b.wild-cname.insecure.example/NS >/dev/null || ret=1
grep "ns1.insecure.example.*.IN.A" dig.out.ns${ns}.test$n >/dev/null || ret=1
digcomp insecure.wildcname.out dig.out.ns${ns}.test$n || ret=1
n=$((n + 1))
+1 -1
View File
@@ -16,7 +16,7 @@
#
m4_define([bind_VERSION_MAJOR], 9)dnl
m4_define([bind_VERSION_MINOR], 21)dnl
m4_define([bind_VERSION_PATCH], 6)dnl
m4_define([bind_VERSION_PATCH], 7)dnl
m4_define([bind_VERSION_EXTRA], -dev)dnl
m4_define([bind_DESCRIPTION], [(Development Release)])dnl
m4_define([bind_SRCID], [m4_esyscmd_s([git rev-parse --short HEAD | cut -b1-7])])dnl
+1
View File
@@ -18,6 +18,7 @@ Changelog
development. Regular users should refer to :ref:`Release Notes <relnotes>`
for changes relevant to them.
.. include:: ../changelog/changelog-9.21.6.rst
.. include:: ../changelog/changelog-9.21.5.rst
.. include:: ../changelog/changelog-9.21.4.rst
.. include:: ../changelog/changelog-9.21.3.rst
+1
View File
@@ -218,6 +218,7 @@ latex_logo = "isc-logo.pdf"
linkcheck_timeout = 10
linkcheck_ignore = [
"http://127.0.0.1",
"https://dl.acm.org",
"https://gitlab.isc.org",
"https://kb.isc.org",
"https://simpleicon.com/",
+4
View File
@@ -127,3 +127,7 @@
``zoneload``
Loading of zones and creation of automatic empty zones.
``zoneversion``
ZONEVERSION options received from upstream servers.
+1
View File
@@ -47,6 +47,7 @@ The list of known issues affecting the latest version in the 9.21 branch can be
found at
https://gitlab.isc.org/isc-projects/bind9/-/wikis/Known-Issues-in-BIND-9.21
.. include:: ../notes/notes-9.21.6.rst
.. include:: ../notes/notes-9.21.5.rst
.. include:: ../notes/notes-9.21.4.rst
.. include:: ../notes/notes-9.21.3.rst
+29 -3
View File
@@ -2165,6 +2165,14 @@ Boolean Options
ultimate primary should be set to still send NOTIFY messages to all the name servers
listed in the NS RRset.
.. namedconf:statement:: provide-zoneversion
:tags: transfer
:short: Controls the return EDNS ZONEVERSION answers.
If ``yes`` EDNS ZONEVERSION answers will be returned otherwise
not for primary, secondary and mirror zones. The default is
``yes``.
.. namedconf:statement:: recursion
:tags: query
:short: Defines whether recursion and caching are allowed.
@@ -2188,6 +2196,18 @@ Boolean Options
option in its response, then its contents are logged in the ``nsid``
category at level ``info``. The default is ``no``.
.. namedconf:statement:: request-zoneversion
:tags: query
:short: Controls whether an empty EDNS(0) ZONEVERSION option is sent with all queries to authoritative name servers during iterative resolution.
If ``yes``, then an empty EDNS(0) ZONEVERSION option is sent
with all queries to authoritative name servers during iterative
resolution. If the authoritative server returns an ZONEVERSION
option in its response, then its contents are logged in the
``zoneversion`` category at level ``info``. If the NSID has
also been requested and it is returned then that is appended to
the log message. The default is ``no``.
.. namedconf:statement:: require-cookie
:tags: query
:short: Controls whether responses without a server cookie are accepted.
@@ -3660,9 +3680,13 @@ system.
after 20 minutes if it has remained unchanged.
If :any:`max-clients-per-query` is set to zero, there is no upper bound, other
than that imposed by :any:`recursive-clients`. If :any:`clients-per-query` is
set to zero, :any:`max-clients-per-query` no longer applies and there is no
upper bound, other than that imposed by :any:`recursive-clients`.
than that imposed by :any:`recursive-clients`. If the option is set to a
lower value than :any:`clients-per-query`, the value is adjusted to
:any:`clients-per-query`.
If :any:`clients-per-query` is set to zero, :any:`max-clients-per-query` no
longer applies and there is no upper bound, other than that imposed by
:any:`recursive-clients`.
.. namedconf:statement:: max-validations-per-fetch
:tags: server
@@ -5597,11 +5621,13 @@ and :namedconf:ref:`options` blocks:
- :namedconf:ref:`notify-source-v6`
- :namedconf:ref:`notify-source`
- :namedconf:ref:`provide-ixfr`
- :namedconf:ref:`provide-zoneversion`
- :namedconf:ref:`query-source-v6`
- :namedconf:ref:`query-source`
- :namedconf:ref:`request-expire`
- :namedconf:ref:`request-ixfr`
- :namedconf:ref:`request-nsid`
- :namedconf:ref:`request-zoneversion`
- :namedconf:ref:`require-cookie`
- :namedconf:ref:`send-cookie`
- :namedconf:ref:`transfer-format`
+478
View File
@@ -0,0 +1,478 @@
.. Copyright (C) Internet Systems Consortium, Inc. ("ISC")
..
.. SPDX-License-Identifier: MPL-2.0
..
.. This Source Code Form is subject to the terms of the Mozilla Public
.. License, v. 2.0. If a copy of the MPL was not distributed with this
.. file, you can obtain one at https://mozilla.org/MPL/2.0/.
..
.. See the COPYRIGHT file distributed with this work for additional
.. information regarding copyright ownership.
BIND 9.21.6
-----------
New Features
~~~~~~~~~~~~
- Implement the min-transfer-rate-in configuration option.
``a282f1ba3f``
A new option 'min-transfer-rate-in <bytes> <minutes>' has been added
to the view and zone configurations. It can abort incoming zone
transfers which run very slowly due to network related issues, for
example. The default value is set to 10240 bytes in 5 minutes.
:gl:`#3914` :gl:`!9098`
- Add digest methods for SIG and RRSIG. ``fd48df20f3``
ZONEMD digests RRSIG records and potentially digests SIG record. Add
digests methods for both record types. :gl:`#5219` :gl:`!10217`
- Add HTTPS record query to host command line tool. ``d34414c47b``
The host command was extended to also query for the HTTPS RR type by
default. :gl:`!8642`
Removed Features
~~~~~~~~~~~~~~~~
- Clean up unnecessary code in qpcache. ``74c9ff384e``
Removed some code from the cache database implementation that was left
over from before it and the zone database implementation were
separated. :gl:`!9991`
- Cleanup isc/util.h header and friends. ``239712df16``
Cleanup short list macros from <isc/util.h>, remove two unused
headers, move locking macros to respective headers and use only the
C11 static assertion. :gl:`!10196`
- Remove check for the mandatory IPv6 support. ``daa9c17905``
IPv6 Advanced Socket API (:rfc:`3542`) is a hard requirement, remove
the autoconf check to speed up the ./configure run a little bit.
:gl:`!10201`
- Remove log initialization checks from named. ``1b3e7f52ec``
Logging initialization check is now redundant as there is a default
global log context created during libisc's constructor.
`isc_log` calls can safely be made at any time outside libisc's
constructor. :gl:`!10186`
Feature Changes
~~~~~~~~~~~~~~~
- Refactor and simplify isc_symtab. ``5559539eb0``
This commit does several changes to isc_symtab:
1. Rewrite the isc_symtab to internally use isc_hashmap instead of
hand-stiched hashtable.
2. Create a new isc_symtab_define_and_return() api, which returns
the already defined symvalue on ISC_R_EXISTS; this allows users of
the API to skip the isc_symtab_lookup()+isc_symtab_define() calls
and directly call isc_symtab_define_and_return().
3. Merge isccc_symtab into isc_symtab - the only missing function
was isccc_symtab_foreach() that was merged into isc_symtab API.
4. Add full set of unit tests for the isc_symtab API. :gl:`#5103`
:gl:`!9921`
- Drop malformed notify messages early instead of decompressing them.
``7fce7707db``
The DNS header shows if a message has multiple questions or invalid
NOTIFY sections. We can drop these messages early, right after parsing
the question. This matches RFC 9619 for multi-question messages and
Unbound's handling of NOTIFY. We still parse the question to include
it in our FORMERR response.
Add drop_msg_early() function to check for these conditions: -
Messages with more than one question, as required by RFC 9619 - NOTIFY
query messages containing answer sections (like Unbound) - NOTIFY
messages containing authority sections (like Unbound) :gl:`#5158`,
#3656 :gl:`!10056`
- Cleanup parts of the isc_mem API. ``4ba1ccfa2e``
This MR changes custom attach/detach implementation with refcount
macros, replaces isc_mem_destroy() with isc_mem_detach(), and does
various small cleanups. :gl:`!9456`
- Move the library initialization and shutdown to executables.
``6e0c1f151c``
Instead of relying on unreliable order of execution of the library
constructors and destructors, move them to individual binaries. The
advantage is that the execution time and order will remain constant
and will not depend on the dynamic load dependency solver.
:gl:`!10069`
- Reduce memory used to store DNS names. ``24db1b1a8a``
The memory used to internally store the DNS names has been reduced.
:gl:`!10140`
- Unify fips handling to isc_crypto and make the toggle one way.
``3de629d6b7``
Since algorithm fetching is handled purely in libisc, FIPS mode
toggling can be purely done in within the library instead of provider
fetching in the binary for OpenSSL >=3.0.
Disabling FIPS mode isn't a realistic requirement and isn't done
anywhere in the codebase. Make the FIPS mode toggle enable-only to
reflect the situation. :gl:`!9920`
Bug Fixes
~~~~~~~~~
- Prevent a reference leak when using plugins. ``5604d3a44e``
The `NS_QUERY_DONE_BEGIN` and `NS_QUERY_DONE_SEND` plugin hooks could
cause a reference leak if they returned `NS_HOOK_RETURN` without
cleaning up the query context properly. :gl:`#2094` :gl:`!9971`
- Fix isc_quota bug. ``742d379d88``
Running jobs which were entered into the isc_quota queue is the
responsibility of the isc_quota_release() function, which, when
releasing a previously acquired quota, checks whether the queue is
empty, and if it's not, it runs a job from the queue without touching
the 'quota->used' counter. This mechanism is susceptible to a possible
hangup of a newly queued job in case when between the time a decision
has been made to queue it (because used >= max) and the time it was
actually queued, the last quota was released. Since there is no more
quotas to be released (unless arriving in the future), the newly
entered job will be stuck in the queue.
Fix the issue by adding checks in both isc_quota_release() and
isc_quota_acquire_cb() to make sure that the described hangup does not
happen. Also see code comments. :gl:`#4965` :gl:`!10082`
- Fix dual-stack-servers configuration option. ``6af708f3b0``
The dual-stack-servers configuration option was not working as
expected; the specified servers were not being used when they should
have been, leading to resolution failures. This has been fixed.
:gl:`#5019` :gl:`!9708`
- Implement sig0key-checks-limit and sig0message-checks-limit.
``d78ebff861``
Previously a hard-coded limitation of maximum two key or message
verification checks were introduced when checking the message's SIG(0)
signature. It was done in order to protect against possible DoS
attacks. The logic behind choosing the number 2 was that more than a
single key should only be required during key rotations, and in that
case two keys are enough. But later it became apparent that there are
other use cases too where even more keys are required, see issue
number #5050 in GitLab.
This change introduces two new configuration options for the views,
`sig0key-checks-limit` and `sig0message-checks-limit`, which define
how many keys are allowed to be checked to find a matching key, and
how many message verifications are allowed to take place once a
matching key has been found. The latter protects against expensive
cryptographic operations when there are keys with colliding tags and
algorithm numbers, with default being 2, and the former protects
against a bit less expensive key parsing operations and defaults to
16. :gl:`#5050` :gl:`!9967`
- Fix the data race causing a permanent active client increase.
``479c366c2b``
Previously, a data race could cause a newly created fetch context for
a new client to be used before it had been fully initialized, which
would cause the query to become stuck; queries for the same data would
be either paused indefinitely or dropped because of the
`clients-per-query` limit. This has been fixed. :gl:`#5053`
:gl:`!10146`
- Fix deferred validation of unsigned DS and DNSKEY records.
``ebf1606f38``
When processing a query with the "checking disabled" bit set (CD=1),
`named` stores the unvalidated result in the cache, marked "pending".
When the same query is sent with CD=0, the cached data is validated,
and either accepted as an answer, or ejected from the cache as
invalid. This deferred validation was not attempted for DS and DNSKEY
records if they had no cached signatures, causing spurious validation
failures. We now complete the deferred validation in this scenario.
Also, if deferred validation fails, we now re-query the data to find
out whether the zone has been corrected since the invalid data was
cached. :gl:`#5066` :gl:`!10104`
- When recording an rr trace, use libtool. ``6320586df0``
When a system test is run with the `USE_RR` environment variable set
to 1, an `rr` trace is now correctly generated for each instance of
`named`. :gl:`#5079` :gl:`!10197`
- Do not cache signatures for rejected data. ``fc3a4d6f89``
The cache has been updated so that if new data is rejected - for
example, because there was already existing data at a higher trust
level - then its covering RRSIG will also be rejected. :gl:`#5132`
:gl:`!9999`
- Fix wrong logging severity in do_nsfetch() ``1f6a16e6d0``
ISC_LOG_WARNING was used while ISC_LOG_DEBUG(3) was implied.
:gl:`#5145` :gl:`!10017`
- Fix RPZ race condition during a reconfiguration. ``5ba811bea2``
With RPZ in use, `named` could terminate unexpectedly because of a
race condition when a reconfiguration command was received using
`rndc`. This has been fixed. :gl:`#5146` :gl:`!10079`
- "CNAME and other data check" not applied to all types. ``b694acbe45``
An incorrect optimization caused "CNAME and other data" errors not to
be detected if certain types were at the same node as a CNAME. This
has been fixed. :gl:`#5150` :gl:`!10033`
- Use named Service Parameter Keys (SvcParamKeys) by default.
``3f61a87be3``
When converting SVCB records to text representation `named` now uses
named `SvcParamKeys` values unless backward-compatible mode is
activated, in which case the values which were not defined initially
in RFC9460 and were added later (see [1]) are converted to opaque
"keyNNNN" syntax, like, for example, "key7" instead of "dohpath".
Also a new `+[no]svcparamkeycompat` option is implemented for `dig`,
which enables the backward-compatible mode and uses the opaque syntax,
if required for interoperability with other software or scripts. By
default, the compatibility mode is disabled.
[1] https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
:gl:`#5156` :gl:`!10085`
- Relax private DNSKEY and RRSIG constraints. ``1bc7016d7a``
DNSKEY, KEY, RRSIG and SIG constraints have been relaxed to allow
empty key and signature material after the algorithm identifier for
PRIVATEOID and PRIVATEDNS. It is arguable whether this falls within
the expected use of these types as no key material is shared and the
signatures are ineffective but these are private algorithms and they
can be totally insecure. :gl:`#5167` :gl:`!10083`
- Delete dead nodes when committing a new version. ``67255da4b3``
In the qpzone implementation of `dns_db_closeversion()`, if there are
changed nodes that have no remaining data, delete them. :gl:`#5169`
:gl:`!10089`
- Revert "Delete dead nodes when committing a new version"
``b652d5327c``
This reverts commit 67255da4b376f65138b299dcd5eb6a3b7f9735a9,
reversing changes made to 74c9ff384e695d1b27fa365d1fee84576f869d4c.
:gl:`#5169` :gl:`!10224`
- Fix dns_qp_insert() checks in qpzone. ``d6b63210a8``
Remove code in the QP zone database to handle failures of
`dns_qp_insert()` which can't actually happen. :gl:`#5171`
:gl:`!10088`
- Remove NSEC/DS/NSEC3 RRSIG check from dns_message_parse.
``f0785fedf1``
Previously, when parsing responses, named incorrectly rejected
responses without matching RRSIG records for NSEC/DS/NSEC3 records in
the authority section. This rejection, if appropriate, should have
been left for the validator to determine and has been fixed.
:gl:`#5185` :gl:`!10125`
- Fix TTL issue with ANY queries processed through RPZ "passthru"
``23c1fbc609``
Answers to an "ANY" query which were processed by the RPZ "passthru"
policy had the response-policy's `max-policy-ttl` value unexpectedly
applied. This has been fixed. :gl:`#5187` :gl:`!10176`
- Save time when creating a slab from another slab. ``cf981ab13b``
The `dns_rdataslab_fromrdataset()` function creates a slab from an
rdataset. If the source rdataset already uses a slab, then no
processing is necessary; we can just copy the existing slab to a new
location. :gl:`#5188` :gl:`!10162`
- Dnssec-signzone needs to check for a NULL key when setting offline.
``26f8ee7229``
dnssec-signzone could dereference a NULL key pointer when resigning a
zone. This has been fixed. :gl:`#5192` :gl:`!10161`
- Acquire the database reference before possibly last node release.
``c4868b5bd9``
Acquire the database reference in the detachnode() to prevent the last
reference to be release while the NODE_LOCK being locked. The
NODE_LOCK is locked/unlocked inside the RCU critical section, thus it
is most probably this should not pose a problem as the database uses
call_rcu memory reclamation, but this it is still safer to acquire the
reference before releasing the node. :gl:`#5194` :gl:`!10155`
- Fix a logic error in cache_name() ``02ef8ff01c``
A change in 6aba56ae8 (checking whether a rejected RRset was identical
to the data it would have replaced, so that we could still cache a
signature) inadvertently introduced cases where processing of a
response would continue when previously it would have been skipped.
:gl:`#5197` :gl:`!10157`
- Fix a bug in the statistics channel when querying zone transfers
information. ``e02d73e7e3``
When querying zone transfers information from the statistics channel
there was a rare possibility that `named` could terminate unexpectedly
if a zone transfer was in a state when transferring from all the
available primary servers had failed earlier. This has been fixed.
:gl:`#5198` :gl:`!10182`
- Fix assertion failure when dumping recursing clients. ``796b662b92``
Previously, if a new counter was added to the hashtable while dumping
recursing clients via the `rndc recursing` command, and
`fetches-per-zone` was enabled, an assertion failure could occur. This
has been fixed. :gl:`#5200` :gl:`!10164`
- Validating ADB fetches could cause a crash in import_rdataset()
``49ccbe857a``
Previously, in some cases, the resolver could return rdatasets of type
CNAME or DNAME without the result code being set to `DNS_R_CNAME` or
`DNS_R_DNAME`. This could trigger an assertion failure in the ADB. The
resolver error has been fixed. :gl:`#5201` :gl:`!10172`
- Call isc__iterated_hash_initialize in isc__work_cb. ``f3458fdf43``
isc_iterated_hash didn't work in offloaded threads as the per thread
initialisation has not been done. This has been fixed. :gl:`#5214`
:gl:`!10206`
- Fix a bug in get_request_transport_type() ``db5166ab99``
When `dns_remote_done()` is true, calling `dns_remote_curraddr()`
asserts. Add a `dns_remote_curraddr()` check before calling
`dns_remote_curraddr()`. :gl:`#5215` :gl:`!10222`
- Clean up dns_rdataslab module. ``948f8d7a98``
Rdata slabs used in the QP databases are usually prepended with a slab
header, but are sometimes "raw", containing only the rdata and no
header. Previously, to allow for them to be used both ways, functions
that operated on them took a `reservelen` argument, which would be set
to either the header length or to zero, and skipped over that many
bytes at the beginning of the buffer. Most such functions were never
used on the raw form. To make the code clearer, each of these
functions now operates on full slabs with headers, and an alternate
"raw" version of the function has been added in cases where that was
needed.
In addition, the `dns_rdataslab_merge()` and `_subtract()` functions
have been rewritten for clarity and efficiency, and a minor bug has
been fixed in `dns_rdataslab_equal()` and `_equalx()`, which could
cause an incorrect result if both slabs being compared had zero
length. :gl:`!10084`
- Dump the active resolver fetches from dns_resolver_dumpfetches()
``5d0c347e75``
Previously, active resolver fetches were only dumped when the
`fetches-per-zone` configuration option was enabled. Now, active
resolver fetches are dumped along with the number of
`clients-per-server` counters per resolver fetch. :gl:`!10107`
- Fix the foundname vs dcname madness in qpcache_findzonecut()
``4e68dbf194``
The qpcache_findzonecut() accepts two "foundnames": 'foundname' and
'dcname' could be NULL. Originally, when 'dcname' would be NULL, the
'dcname' would be set to 'foundname' which basically means that we
were copying the .ndata over itself for no apparent reason.
:gl:`!10049`
- Post [CVE-2024-12705] Performance Drop Fixes, Part 2. ``c8104daf8d``
This merge request addresses several key performance bottlenecks in
the DoH (DNS over HTTPS) implementation by introducing significant
optimizations and improvements.
### Key Improvements
1. **Simplification and Optimisation of `http_do_bio()` Function**:
- The code flow in the `http_do_bio()` function has been significantly
simplified. 2. **Flushing HTTP Write Buffer on Outgoing DNS
Messages**: - The buffer is flushed and a send operation is
performed when there is an outgoing DNS message. 3. **Bumping Active
Streams Processing Limit**: - The total number of active streams
has been increased to 60% of the total streams limit.
These changes collectively enhance the performance and reliability of
the DoH implementation, making it more efficient and robust for
handling high-load scenarios, particularly noticeable in long runs (>=
1h) of `stress:long:rpz:doh+udp:linux:*` tests. It improves perf. for
tests for BIND 9.18, but it likely will have a positive but less
pronounced effect on newer versions as well.
In essence, the merge request fixes three bottlenecks stacked upon
each other.
*It is a logical continuation of the merge requests !10109.* !10109,
unfortunately, did not completely [address the performance drop in
9.18](https://gitlab.isc.org/isc-projects/bind9/-/pipelines/221545)
for longer runs of the stress test. This merge request [addresses
that](https://gitlab.isc.org/isc-projects/bind9/-/pipelines/223661).
**P.S.**
The origin of the fixes is, in fact, the branch in !10193. So this MR
is a ... *forward port* of them. :gl:`!10192`
- Post [CVE-2024-12705] Performance Drop Fixes. ``3033d127d2``
This merge request fixes a [performance
drop](https://gitlab.isc.org/isc-projects/bind9/-/pipelines/216728)
after merging the fixes for #4795, in particular in 9.18.
The MR [fixes the
problem](https://gitlab.isc.org/isc-projects/bind9/-/pipelines/219825)
without affecting performance for the newer versions, in particular
for [the development version](https://gitlab.isc.org/isc-projects/bind
9/-/pipelines/220619). :gl:`!10109`
- Remove 'target' from dns_adb. ``764eb65cf6``
When a server name turns out to be a CNAME or DNAME, the ADB does not
use it, but the `dns_adbname` structure still stored a copy of the
target name. This is unnecessary and the code has been removed.
:gl:`!10149`
- Simplify some dns_name API calls. ``e16560a650``
Several functions in the `dns_name` module have had parameters
removed, that were rarely or never used: - `dns_name_fromtext()` and
`dns_name_concatenate()` no longer take a target buffer. -
`dns_name_towire()` no longer takes a compression offset pointer; this
is now part of the compression context. - `dns_name_towire()` with a
`NULL` compression context will copy name data directly into a buffer
with no processing. :gl:`!10152`
- Sync the TSAN CC, CFLAGS and LDFLAGS in the respdiff:tsan job.
``22b5442722``
:gl:`!10209`
+1
View File
@@ -33,6 +33,7 @@ zone <string> [ <class> ] {
notify-source ( <ipv4_address> | * );
notify-source-v6 ( <ipv6_address> | * );
primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
provide-zoneversion <boolean>;
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
+6
View File
@@ -226,6 +226,7 @@ options {
preferred-glue <string>;
prefetch <integer> [ <integer> ];
provide-ixfr <boolean>;
provide-zoneversion <boolean>;
qname-minimization ( strict | relaxed | disabled | off );
query-source [ address ] ( <ipv4_address> | * | none );
query-source-v6 [ address ] ( <ipv6_address> | * | none );
@@ -254,6 +255,7 @@ options {
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
request-zoneversion <boolean>;
require-server-cookie <boolean>;
resolver-query-timeout <integer>;
resolver-use-dns64 <boolean>;
@@ -343,6 +345,7 @@ server <netprefix> {
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
request-zoneversion <boolean>;
require-cookie <boolean>;
send-cookie <boolean>;
tcp-keepalive <boolean>;
@@ -509,6 +512,7 @@ view <string> [ <class> ] {
preferred-glue <string>;
prefetch <integer> [ <integer> ];
provide-ixfr <boolean>;
provide-zoneversion <boolean>;
qname-minimization ( strict | relaxed | disabled | off );
query-source [ address ] ( <ipv4_address> | * | none );
query-source-v6 [ address ] ( <ipv6_address> | * | none );
@@ -534,6 +538,7 @@ view <string> [ <class> ] {
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
request-zoneversion <boolean>;
require-server-cookie <boolean>;
resolver-query-timeout <integer>;
resolver-use-dns64 <boolean>;
@@ -561,6 +566,7 @@ view <string> [ <class> ] {
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
request-zoneversion <boolean>;
require-cookie <boolean>;
send-cookie <boolean>;
tcp-keepalive <boolean>;
+1
View File
@@ -51,6 +51,7 @@ zone <string> [ <class> ] {
parental-agents [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
parental-source ( <ipv4_address> | * );
parental-source-v6 ( <ipv6_address> | * );
provide-zoneversion <boolean>;
send-report-channel <string>;
serial-update-method ( date | increment | unixtime );
sig-signing-nodes <integer>;
+1
View File
@@ -50,6 +50,7 @@ zone <string> [ <class> ] {
parental-source ( <ipv4_address> | * );
parental-source-v6 ( <ipv6_address> | * );
primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
provide-zoneversion <boolean>;
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
+186
View File
@@ -0,0 +1,186 @@
.. Copyright (C) Internet Systems Consortium, Inc. ("ISC")
..
.. SPDX-License-Identifier: MPL-2.0
..
.. This Source Code Form is subject to the terms of the Mozilla Public
.. License, v. 2.0. If a copy of the MPL was not distributed with this
.. file, you can obtain one at https://mozilla.org/MPL/2.0/.
..
.. See the COPYRIGHT file distributed with this work for additional
.. information regarding copyright ownership.
Notes for BIND 9.21.6
---------------------
New Features
~~~~~~~~~~~~
- Implement the :any:`min-transfer-rate-in` configuration option.
A new option :any:`min-transfer-rate-in` has been added
to the view and zone configurations. It can abort incoming zone
transfers that run very slowly due to network-related issues, for
example. The default value is 10240 bytes in five minutes.
:gl:`#3914`
- Add HTTPS record query to :iscman:`host` command line tool.
The :iscman:`host` command was extended to also query for the HTTPS RR
type by default.
- Implement :any:`sig0key-checks-limit` and :any:`sig0message-checks-limit`.
Previously, a hard-coded limitation of a maximum of two key or message
verification checks was introduced when checking a message's ``SIG(0)``
signature, to protect against possible DoS
attacks. Two as a maximum was chosen so that more than a
single key should only be required during key rotations, and in that
case two keys are enough. It later became apparent that there are
other use cases where even more keys are required; see the related GitLab issue for examples.
This change introduces two new configuration options for the views:
:any:`sig0key-checks-limit` and :any:`sig0message-checks-limit`. They define
how many keys can be checked to find a matching key, and
how many message verifications are allowed to take place once a
matching key has been found. The former provides
slightly less "expensive" key parsing operations and defaults to
16. The latter protects against expensive
cryptographic operations when there are keys with colliding tags and
algorithm numbers; the default is 2. :gl:`#5050`
Feature Changes
~~~~~~~~~~~~~~~
- Drop malformed notify messages early instead of decompressing them.
The DNS header shows whether a message has multiple questions or invalid
NOTIFY sections. :iscman:`named` can now drop these messages early, right after parsing
the question, to match :rfc:`9619` for multi-question messages and
Unbound's handling of NOTIFY. Questions are still parsed to be included
in BIND's FORMERR response.
Add ``drop_msg_early()`` function to check for these conditions:
- Messages with more than one question, as required by :rfc:`9619`
- NOTIFY query messages containing answer sections (like Unbound)
:gl:`#5158`
- Reduce memory used to store DNS names.
The memory used to internally store the DNS names has been reduced
by no longer caching certain fields from an internal data structure.
Bug Fixes
~~~~~~~~~
- Fix :any:`dual-stack-servers` configuration option.
The :any:`dual-stack-servers` configuration option was not working as
expected; the specified servers were not being used when they should
have been, leading to resolution failures. This has been fixed.
:gl:`#5019`
- Fix a data race causing a permanent active client increase.
Previously, a data race could cause a newly created fetch context for
a new client to be used before it had been fully initialized, which
would cause the query to become stuck; queries for the same data would
be either paused indefinitely or dropped because of the
:any:`clients-per-query` limit. This has been fixed. :gl:`#5053`
- Fix deferred validation of unsigned DS and DNSKEY records.
When processing a query with the "checking disabled" bit set (CD=1),
:iscman:`named` stores the invalidated result in the cache, marked "pending".
When the same query is sent with CD=0, the cached data is validated
and either accepted as an answer, or ejected from the cache as
invalid. This deferred validation was not attempted for DS and DNSKEY
records if they had no cached signatures, causing spurious validation
failures. The deferred validation is now completed in this scenario.
Also, if deferred validation fails, the data is now re-queried to find
out whether the zone has been corrected since the invalid data was
cached. :gl:`#5066`
- Fix RPZ race condition during a reconfiguration.
With RPZ in use, :iscman:`named` could terminate unexpectedly because of a
race condition when a reconfiguration command was received using
:iscman:`rndc`. This has been fixed. :gl:`#5146`
- "CNAME and other data check" not applied to all types.
An incorrect optimization caused "CNAME and other data" errors not to
be detected if certain types were at the same node as a CNAME. This
has been fixed. :gl:`#5150`
- Use named Service Parameter Keys (``SvcParamKeys``) by default.
When converting SVCB records to text representation, :iscman:`named` now uses
named ``SvcParamKeys`` values unless backward-compatible mode is
activated. In that case, values which were not defined initially
in :rfc:`9460` and were added later (see [1]) are converted to opaque
"keyNNNN" syntax, e.g. "key7" instead of "dohpath".
Also a new ``+[no]svcparamkeycompat`` option is implemented for :iscman:`dig`,
which enables the backward-compatible mode and uses the opaque syntax,
if required for interoperability with other software or scripts. By
default, the compatibility mode is disabled.
[1] https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
:gl:`#5156`
- Relax private DNSKEY and RRSIG constraints.
DNSKEY, KEY, RRSIG, and SIG constraints have been relaxed to allow
empty key and signature material after the algorithm identifier for
PRIVATEOID and PRIVATEDNS. It is arguable whether this falls within
the expected use of these types, as no key material is shared and the
signatures are ineffective, but these are private algorithms and they
can be totally insecure. :gl:`#5167`
- Remove NSEC/DS/NSEC3 RRSIG check from ``dns_message_parse()``.
Previously, when parsing responses, :iscman:`named` incorrectly rejected
responses without matching RRSIG records for NSEC/DS/NSEC3 records in
the authority section. This rejection, if appropriate, should have
been left for the validator to determine and has been fixed.
:gl:`#5185`
- Fix TTL issue with ANY queries processed through RPZ "passthru".
Answers to an "ANY" query which were processed by the RPZ "passthru"
policy had the response-policy's ``max-policy-ttl`` value unexpectedly
applied. This has been fixed. :gl:`#5187`
- :iscman:`dnssec-signzone` needs to check for a NULL key when setting offline.
:iscman:`dnssec-signzone` could dereference a NULL key pointer when resigning
a zone. This has been fixed. :gl:`#5192`
- Fix a bug in the statistics channel when querying zone transfer
information.
When querying zone transfer information from the statistics channel,
there was a rare possibility that :iscman:`named` could terminate unexpectedly
if a zone transfer was in a state when transferring from all the
available primary servers had failed earlier. This has been fixed.
:gl:`#5198`
- Fix assertion failure when dumping recursing clients.
Previously, if a new counter was added to the hash table while dumping
recursing clients via the :option:`rndc recursing` command, and
:any:`fetches-per-zone` was enabled, an assertion failure could occur. This
has been fixed. :gl:`#5200`
- Dump the active resolver fetches from ``dns_resolver_dumpfetches()``
Previously, active resolver fetches were only dumped when the
:any:`fetches-per-zone` configuration option was enabled. Now, active
resolver fetches are dumped along with the number of
:any:`clients-per-query` counters per resolver fetch.
-2
View File
@@ -136,7 +136,6 @@ libdns_la_HEADERS = \
include/dns/view.h \
include/dns/xfrin.h \
include/dns/zone.h \
include/dns/zonekey.h \
include/dns/zoneverify.h \
include/dns/zt.h
@@ -255,7 +254,6 @@ libdns_la_SOURCES = \
zone.c \
zone_p.h \
zoneverify.c \
zonekey.c \
zt.c
if HAVE_GSSAPI
+7 -7
View File
@@ -567,7 +567,7 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
rdataset->ttl = ttlclamp(rdataset->ttl);
}
REQUIRE(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
REQUIRE(dns_rdatatype_isaddr(rdtype));
for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
result = dns_rdataset_next(rdataset))
@@ -1076,11 +1076,11 @@ new_adbfetch(dns_adb_t *adb) {
dns_adbfetch_t *fetch = NULL;
fetch = isc_mem_get(adb->hmctx, sizeof(*fetch));
*fetch = (dns_adbfetch_t){ 0 };
*fetch = (dns_adbfetch_t){
.magic = DNS_ADBFETCH_MAGIC,
};
dns_rdataset_init(&fetch->rdataset);
fetch->magic = DNS_ADBFETCH_MAGIC;
return fetch;
}
@@ -2557,7 +2557,7 @@ dbfind_name(dns_adbname_t *adbname, isc_stdtime_t now, dns_rdatatype_t rdtype) {
adb = adbname->adb;
REQUIRE(DNS_ADB_VALID(adb));
REQUIRE(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
REQUIRE(dns_rdatatype_isaddr(rdtype));
fname = dns_fixedname_initname(&foundname);
dns_rdataset_init(&rdataset);
@@ -2909,6 +2909,7 @@ fetch_name(dns_adbname_t *adbname, bool start_at_zone, bool no_validation,
* createfetch to find deepest cached name when we're providing
* domain and nameservers.
*/
dns_adbname_ref(adbname);
result = dns_resolver_createfetch(
adb->res, adbname->name, type, name, nameservers, NULL, NULL, 0,
options, depth, qc, gqc, isc_loop(), fetch_callback, adbname,
@@ -2916,11 +2917,10 @@ fetch_name(dns_adbname_t *adbname, bool start_at_zone, bool no_validation,
if (result != ISC_R_SUCCESS) {
DP(ENTER_LEVEL, "fetch_name: createfetch failed with %s",
isc_result_totext(result));
dns_adbname_unref(adbname);
goto cleanup;
}
dns_adbname_ref(adbname);
if (type == dns_rdatatype_a) {
adbname->fetch_a = fetch;
inc_resstats(adb, dns_resstatscounter_gluefetchv4);
+1 -2
View File
@@ -1516,8 +1516,7 @@ catz_process_primaries(dns_catz_zone_t *catz, dns_ipkeylist_t *ipkl,
}
/* else - 'simple' case - without labels */
if (value->type != dns_rdatatype_a && value->type != dns_rdatatype_aaaa)
{
if (!dns_rdatatype_isaddr(value->type)) {
return ISC_R_FAILURE;
}
+11
View File
@@ -1200,3 +1200,14 @@ dns__db_logtoomanyrecords(dns_db_t *db, const dns_name_t *name,
(db->attributes & DNS_DBATTR_CACHE) != 0 ? "cache" : "zone",
isc_result_totext(DNS_R_TOOMANYRECORDS), limit);
}
isc_result_t
dns_db_getzoneversion(dns_db_t *db, isc_buffer_t *b) {
REQUIRE(db != NULL);
REQUIRE(b != NULL);
if (db->methods->getzoneversion != NULL) {
return (db->methods->getzoneversion)(db, b);
}
return ISC_R_NOTIMPLEMENTED;
}
+53 -12
View File
@@ -1101,6 +1101,41 @@ dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name,
return false;
}
bool
dns_dnssec_iszonekey(dns_rdata_dnskey_t *key) {
return (key->flags & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE &&
(key->flags & DNS_KEYTYPE_NOAUTH) == 0 &&
(key->protocol == DNS_KEYPROTO_DNSSEC ||
key->protocol == DNS_KEYPROTO_ANY);
}
bool
dns_dnssec_haszonekey(dns_rdataset_t *keyset) {
isc_result_t result;
REQUIRE(keyset != NULL);
if (keyset->type != dns_rdatatype_dnskey) {
return false;
}
for (result = dns_rdataset_first(keyset); result == ISC_R_SUCCESS;
result = dns_rdataset_next(keyset))
{
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_dnskey_t key;
dns_rdataset_current(keyset, &rdata);
dns_rdata_tostruct(&rdata, &key, NULL); /* can't fail */
if (dns_dnssec_iszonekey(&key)) {
return true;
}
}
return false;
}
void
dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey,
dns_dnsseckey_t **dkp) {
@@ -1428,29 +1463,35 @@ addkey(dns_dnsseckeylist_t *keylist, dst_key_t **newkey, bool savekeys,
if (key != NULL) {
/*
* Found a match. If the old key was only public and the
* new key is private, replace the old one; otherwise
* leave it. But either way, mark the key as having
* been found in the zone.
* Found a match. If we already had a private key, then
* the new key can't be an improvement. If the existing
* key was public-only but the new key is too, then it's
* still not an improvement. Mark the old key as having
* been found in the zone and stop.
*/
if (dst_key_isprivate(key->key)) {
dst_key_free(newkey);
} else if (dst_key_isprivate(*newkey)) {
dst_key_free(&key->key);
key->key = *newkey;
if (dst_key_isprivate(key->key) || !dst_key_isprivate(*newkey))
{
key->source = dns_keysource_zoneapex;
return;
}
key->source = dns_keysource_zoneapex;
return;
/*
* However, if the old key was public-only, and the new key
* is private, then we're throwing away the old key.
*/
dst_key_free(&key->key);
ISC_LIST_UNLINK(*keylist, key, link);
dns_dnsseckey_destroy(mctx, &key);
}
/* Store the new key. */
dns_dnsseckey_create(mctx, newkey, &key);
key->source = dns_keysource_zoneapex;
key->pubkey = pubkey_only;
if (key->legacy || savekeys) {
key->force_publish = true;
key->force_sign = dst_key_isprivate(key->key);
}
key->source = dns_keysource_zoneapex;
ISC_LIST_APPEND(*keylist, key, link);
*newkey = NULL;
}
+8 -19
View File
@@ -162,8 +162,7 @@ computeid(dst_key_t *key);
static isc_result_t
frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
unsigned int protocol, dns_rdataclass_t rdclass,
isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
dst_key_t **keyp);
isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
static isc_result_t
algorithm_status(unsigned int alg);
@@ -721,13 +720,6 @@ dst_key_todns(const dst_key_t *key, isc_buffer_t *target) {
isc_result_t
dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
return dst_key_fromdns_ex(name, rdclass, source, mctx, false, keyp);
}
isc_result_t
dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass,
isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
dst_key_t **keyp) {
uint8_t alg, proto;
uint32_t flags, extflags;
dst_key_t *key = NULL;
@@ -756,7 +748,7 @@ dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass,
}
result = frombuffer(name, alg, flags, proto, rdclass, source, mctx,
no_rdata, &key);
&key);
if (result != ISC_R_SUCCESS) {
return result;
}
@@ -775,7 +767,7 @@ dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
isc_result_t result;
result = frombuffer(name, alg, flags, protocol, rdclass, source, mctx,
false, &key);
&key);
if (result != ISC_R_SUCCESS) {
return result;
}
@@ -2267,8 +2259,7 @@ computeid(dst_key_t *key) {
static isc_result_t
frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
unsigned int protocol, dns_rdataclass_t rdclass,
isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
dst_key_t **keyp) {
isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
dst_key_t *key;
isc_result_t ret;
@@ -2290,12 +2281,10 @@ frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
return DST_R_UNSUPPORTEDALG;
}
if (!no_rdata) {
ret = key->func->fromdns(key, source);
if (ret != ISC_R_SUCCESS) {
dst_key_free(&key);
return ret;
}
ret = key->func->fromdns(key, source);
if (ret != ISC_R_SUCCESS) {
dst_key_free(&key);
return ret;
}
}
+19
View File
@@ -182,6 +182,7 @@ typedef struct dns_dbmethods {
dns_name_t *name);
void (*setmaxrrperset)(dns_db_t *db, uint32_t value);
void (*setmaxtypepername)(dns_db_t *db, uint32_t value);
isc_result_t (*getzoneversion)(dns_db_t *db, isc_buffer_t *b);
} dns_dbmethods_t;
typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx,
@@ -1805,3 +1806,21 @@ dns_db_setmaxtypepername(dns_db_t *db, uint32_t value);
* stored at a given node, then any subsequent attempt to add an rdataset
* with a new RR type will return ISC_R_TOOMANYRECORDS.
*/
isc_result_t
dns_db_getzoneversion(dns_db_t *db, isc_buffer_t *b);
/*%<
* Provides a database specific EDNS ZONEVERSION option.
*
* Requires:
* \li 'db' is a valid database
* \li 'b' is a valid buffer
*
* Returns:
* \li ISC_R_SUCCESS when it has populated the buffer with the ZONEVERSION
* response (maybe empty implying no ZONEVERSION to be returned).
* \li ISC_R_NOSPACE if the buffer is too small.
* \li ISC_R_NOTIMPLEMENTED if there is not a database specific
* ZONEVERSION
* \li ISC_R_FAILURE other failures
*/
+18
View File
@@ -242,6 +242,24 @@ dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name,
* rrset. dns_dnssec_signs() works on any rrset.
*/
bool
dns_dnssec_iszonekey(dns_rdata_dnskey_t *key);
/*%<
* Verify that 'key' is a DNSSEC key with the DNS_KEYOWNER_ZONE flag set.
*
* Requires:
*\li 'key' is not NULL.
*/
bool
dns_dnssec_haszonekey(dns_rdataset_t *keyset);
/*%<
* Verify that 'keyset' includes at least one zone key.
*
* Requires:
*\li 'keyset' is not NULL.
*/
void
dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey,
dns_dnsseckey_t **dkp);
+1 -2
View File
@@ -102,7 +102,6 @@
#define DNS_MESSAGEEXTFLAG_DO 0x8000U
/*%< EDNS0 extended OPT codes */
#define DNS_OPT_LLQ 1 /*%< LLQ opt code */
#define DNS_OPT_UL 2 /*%< UL opt code */
#define DNS_OPT_NSID 3 /*%< NSID opt code */
@@ -129,7 +128,7 @@
* options we know about. Extended DNS Errors may occur multiple times, see
* DNS_EDE_MAX_ERRORS.
*/
#define DNS_EDNSOPTIONS 8 + DNS_EDE_MAX_ERRORS
#define DNS_EDNSOPTIONS 9 + DNS_EDE_MAX_ERRORS
#define DNS_MESSAGE_REPLYPRESERVE (DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD)
#define DNS_MESSAGEEXTFLAG_REPLYPRESERVE (DNS_MESSAGEEXTFLAG_DO)
+6
View File
@@ -115,6 +115,12 @@ dns_peer_setrequestnsid(dns_peer_t *peer, bool newval);
isc_result_t
dns_peer_getrequestnsid(dns_peer_t *peer, bool *retval);
isc_result_t
dns_peer_setrequestzoneversion(dns_peer_t *peer, bool newval);
isc_result_t
dns_peer_getrequestzoneversion(dns_peer_t *peer, bool *retval);
isc_result_t
dns_peer_setsendcookie(dns_peer_t *peer, bool newval);
+190 -117
View File
@@ -113,6 +113,36 @@ struct dns_rdata {
ISC_LINK(dns_rdata_t) link;
};
/*%
* Rdatatype attributes.
*/
enum {
/*% only one may exist for a name */
DNS_RDATATYPEATTR_SINGLETON = 1 << 0,
/*% requires no other data be present */
DNS_RDATATYPEATTR_EXCLUSIVE = 1 << 1,
/*% Is a meta type */
DNS_RDATATYPEATTR_META = 1 << 2,
/*% Is a DNSSEC type, like RRSIG or NSEC */
DNS_RDATATYPEATTR_DNSSEC = 1 << 3,
/*% Is a zone cut authority type */
DNS_RDATATYPEATTR_ZONECUTAUTH = 1 << 4,
/*% Is reserved (unusable) */
DNS_RDATATYPEATTR_RESERVED = 1 << 5,
/*% Is an unknown type */
DNS_RDATATYPEATTR_UNKNOWN = 1 << 6,
/*% Is META, and can only be in a question section */
DNS_RDATATYPEATTR_QUESTIONONLY = 1 << 7,
/*% Is META, and can NOT be in a question section */
DNS_RDATATYPEATTR_NOTQUESTION = 1 << 8,
/*% Is present at zone cuts in the parent, not the child */
DNS_RDATATYPEATTR_ATPARENT = 1 << 9,
/*% Can exist along side a CNAME */
DNS_RDATATYPEATTR_ATCNAME = 1 << 10,
/*% Follow additional */
DNS_RDATATYPEATTR_FOLLOWADDITIONAL = 1 << 11,
};
#define DNS_RDATA_INIT \
{ \
.data = NULL, \
@@ -530,16 +560,28 @@ dns_rdata_freestruct(void *source);
* dns_rdata_tostruct().
*/
bool
dns_rdatatype_ismeta(dns_rdatatype_t type);
unsigned int
dns_rdatatype_attributes(dns_rdatatype_t rdtype);
/*%<
* Return attributes for the given type.
*
* Requires:
*\li 'rdtype' are known.
*
* Returns:
*\li a bitmask of the rdatatype attribute flags, defined above.
*/
/*%
* Return true iff the rdata type 'type' is a meta-type
* like ANY or AXFR.
*/
static inline bool
dns_rdatatype_ismeta(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_META) != 0;
}
bool
dns_rdatatype_issingleton(dns_rdatatype_t type);
/*%<
/*%
* Return true iff the rdata type 'type' is a singleton type,
* like CNAME or SOA.
*
@@ -547,34 +589,108 @@ dns_rdatatype_issingleton(dns_rdatatype_t type);
* \li 'type' is a valid rdata type.
*
*/
static inline bool
dns_rdatatype_issingleton(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_SINGLETON) !=
0;
}
bool
dns_rdataclass_ismeta(dns_rdataclass_t rdclass);
/*%<
* Return true iff the rdata class 'rdclass' is a meta-class
* like ANY or NONE.
/*%
* Return true iff rdata of type 'type' can not appear in the question
* section of a properly formatted message.
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
static inline bool
dns_rdatatype_notquestion(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) &
DNS_RDATATYPEATTR_NOTQUESTION) != 0;
}
bool
dns_rdatatype_isdnssec(dns_rdatatype_t type);
/*%<
/*%
* Return true iff rdata of type 'type' can only appear in the question
* section of a properly formatted message.
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
static inline bool
dns_rdatatype_questiononly(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) &
DNS_RDATATYPEATTR_QUESTIONONLY) != 0;
}
/*%
* Return true iff rdata of type 'type' can appear beside a cname.
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
static inline bool
dns_rdatatype_atcname(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATCNAME) !=
0;
}
/*%
* Return true iff rdata of type 'type' should appear at the parent of
* a zone cut.
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
static inline bool
dns_rdatatype_atparent(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATPARENT) !=
0;
}
/*%
* Return true if adding a record of type 'type' to the ADDITIONAL section
* of a message can itself trigger the addition of still more data to the
* additional section.
*
* (For example: adding SRV to the ADDITIONAL section may trigger
* the addition of address records associated with that SRV.)
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
static inline bool
dns_rdatatype_followadditional(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) &
DNS_RDATATYPEATTR_FOLLOWADDITIONAL) != 0;
}
/*%
* Return true iff 'type' is one of the DNSSEC
* rdata types that may exist alongside a CNAME record.
*
* Requires:
* \li 'type' is a valid rdata type.
*/
static inline bool
dns_rdatatype_isdnssec(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_DNSSEC) != 0;
}
bool
dns_rdatatype_iskeymaterial(dns_rdatatype_t type);
/*%<
/*%
* Return true iff the rdata type 'type' is a DNSSEC key
* related type, like DNSKEY, CDNSKEY, or CDS.
*/
static inline bool
dns_rdatatype_iskeymaterial(dns_rdatatype_t type) {
return type == dns_rdatatype_dnskey || type == dns_rdatatype_cdnskey ||
type == dns_rdatatype_cds;
}
bool
dns_rdatatype_iszonecutauth(dns_rdatatype_t type);
/*%<
/*%
* Return true iff rdata of type 'type' is considered authoritative
* data (not glue) in the NSEC chain when it occurs in the parent zone
* at a zone cut.
@@ -583,16 +699,68 @@ dns_rdatatype_iszonecutauth(dns_rdatatype_t type);
* \li 'type' is a valid rdata type.
*
*/
static inline bool
dns_rdatatype_iszonecutauth(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) &
DNS_RDATATYPEATTR_ZONECUTAUTH) != 0;
}
bool
dns_rdatatype_isknown(dns_rdatatype_t type);
/*%<
/*%
* Return true iff the rdata type 'type' is known.
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
static inline bool
dns_rdatatype_isknown(dns_rdatatype_t type) {
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_UNKNOWN) ==
0;
}
/*%
* Return true iff a query for the rdata type can have multiple
* unrelated answers in a response: ANY, RRSIG, or SIG.
*/
static inline bool
dns_rdatatype_ismulti(dns_rdatatype_t type) {
return type == dns_rdatatype_any || type == dns_rdatatype_rrsig ||
type == dns_rdatatype_sig;
}
/*%
* Return true iff the rdata type is a signature: either RRSIG or SIG.
*/
static inline bool
dns_rdatatype_issig(dns_rdatatype_t type) {
return type == dns_rdatatype_rrsig || type == dns_rdatatype_sig;
}
/*%
* Return true iff the rdata type is an address: either A or AAAA.
*/
static inline bool
dns_rdatatype_isaddr(dns_rdatatype_t type) {
return type == dns_rdatatype_a || type == dns_rdatatype_aaaa;
}
/*%
* Return true iff the rdata type is an alias: either CNAME or DNAME.
*/
static inline bool
dns_rdatatype_isalias(dns_rdatatype_t type) {
return type == dns_rdatatype_cname || type == dns_rdatatype_dname;
}
/*%
* Return true iff the rdata class 'rdclass' is a meta-class
* like ANY or NONE.
*/
static inline bool
dns_rdataclass_ismeta(dns_rdataclass_t rdclass) {
return rdclass == dns_rdataclass_reserved0 ||
rdclass == dns_rdataclass_none || rdclass == dns_rdataclass_any;
}
isc_result_t
dns_rdata_additionaldata(dns_rdata_t *rdata, const dns_name_t *owner,
@@ -653,101 +821,6 @@ dns_rdata_digest(dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg);
*\li Many other results are possible if not successful.
*/
bool
dns_rdatatype_questiononly(dns_rdatatype_t type);
/*%<
* Return true iff rdata of type 'type' can only appear in the question
* section of a properly formatted message.
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
bool
dns_rdatatype_notquestion(dns_rdatatype_t type);
/*%<
* Return true iff rdata of type 'type' can not appear in the question
* section of a properly formatted message.
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
bool
dns_rdatatype_atparent(dns_rdatatype_t type);
/*%<
* Return true iff rdata of type 'type' should appear at the parent of
* a zone cut.
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
bool
dns_rdatatype_atcname(dns_rdatatype_t type);
/*%<
* Return true iff rdata of type 'type' can appear beside a cname.
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
bool
dns_rdatatype_followadditional(dns_rdatatype_t type);
/*%<
* Return true if adding a record of type 'type' to the ADDITIONAL section
* of a message can itself trigger the addition of still more data to the
* additional section.
*
* (For example: adding SRV to the ADDITIONAL section may trigger
* the addition of address records associated with that SRV.)
*
* Requires:
* \li 'type' is a valid rdata type.
*
*/
unsigned int
dns_rdatatype_attributes(dns_rdatatype_t rdtype);
/*%<
* Return attributes for the given type.
*
* Requires:
*\li 'rdtype' are known.
*
* Returns:
*\li a bitmask consisting of the following flags.
*/
/*% only one may exist for a name */
#define DNS_RDATATYPEATTR_SINGLETON 0x00000001U
/*% requires no other data be present */
#define DNS_RDATATYPEATTR_EXCLUSIVE 0x00000002U
/*% Is a meta type */
#define DNS_RDATATYPEATTR_META 0x00000004U
/*% Is a DNSSEC type, like RRSIG or NSEC */
#define DNS_RDATATYPEATTR_DNSSEC 0x00000008U
/*% Is a zone cut authority type */
#define DNS_RDATATYPEATTR_ZONECUTAUTH 0x00000010U
/*% Is reserved (unusable) */
#define DNS_RDATATYPEATTR_RESERVED 0x00000020U
/*% Is an unknown type */
#define DNS_RDATATYPEATTR_UNKNOWN 0x00000040U
/*% Is META, and can only be in a question section */
#define DNS_RDATATYPEATTR_QUESTIONONLY 0x00000080U
/*% Is META, and can NOT be in a question section */
#define DNS_RDATATYPEATTR_NOTQUESTION 0x00000100U
/*% Is present at zone cuts in the parent, not the child */
#define DNS_RDATATYPEATTR_ATPARENT 0x00000200U
/*% Can exist along side a CNAME */
#define DNS_RDATATYPEATTR_ATCNAME 0x00000400U
/*% Follow additional */
#define DNS_RDATATYPEATTR_FOLLOWADDITIONAL 0x00000800U
dns_rdatatype_t
dns_rdata_covers(dns_rdata_t *rdata);
/*%<
+26 -24
View File
@@ -105,30 +105,32 @@ typedef enum { dns_quotatype_zone = 0, dns_quotatype_server } dns_quotatype_t;
* Options that modify how a 'fetch' is done.
*/
enum {
DNS_FETCHOPT_TCP = 1 << 0, /*%< Use TCP. */
DNS_FETCHOPT_UNSHARED = 1 << 1, /*%< See below. */
DNS_FETCHOPT_RECURSIVE = 1 << 2, /*%< Set RD? */
DNS_FETCHOPT_NOEDNS0 = 1 << 3, /*%< Do not use EDNS. */
DNS_FETCHOPT_FORWARDONLY = 1 << 4, /*%< Only use forwarders. */
DNS_FETCHOPT_NOVALIDATE = 1 << 5, /*%< Disable validation. */
DNS_FETCHOPT_WANTNSID = 1 << 6, /*%< Request NSID */
DNS_FETCHOPT_PREFETCH = 1 << 7, /*%< Do prefetch */
DNS_FETCHOPT_NOCDFLAG = 1 << 8, /*%< Don't set CD flag. */
DNS_FETCHOPT_NONTA = 1 << 9, /*%< Ignore NTA table. */
DNS_FETCHOPT_NOCACHED = 1 << 10, /*%< Force cache update. */
DNS_FETCHOPT_QMINIMIZE = 1 << 11, /*%< Use qname minimization. */
DNS_FETCHOPT_NOFOLLOW = 1 << 12, /*%< Don't retrieve the NS RRset
* from the child zone when a
* delegation is returned in
* response to a NS query. */
DNS_FETCHOPT_QMIN_STRICT = 1 << 13, /*%< Do not work around servers
* that return errors on
* non-empty terminals. */
DNS_FETCHOPT_QMIN_SKIP_IP6A = 1 << 14, /*%< Skip some labels when
* doing qname minimization
* on ip6.arpa. */
DNS_FETCHOPT_NOFORWARD = 1 << 15, /*%< Do not use forwarders if
* possible. */
DNS_FETCHOPT_TCP = 1 << 0, /*%< Use TCP. */
DNS_FETCHOPT_UNSHARED = 1 << 1, /*%< See below. */
DNS_FETCHOPT_RECURSIVE = 1 << 2, /*%< Set RD? */
DNS_FETCHOPT_NOEDNS0 = 1 << 3, /*%< Do not use EDNS. */
DNS_FETCHOPT_FORWARDONLY = 1 << 4, /*%< Only use forwarders. */
DNS_FETCHOPT_NOVALIDATE = 1 << 5, /*%< Disable validation. */
DNS_FETCHOPT_WANTNSID = 1 << 6, /*%< Request NSID */
DNS_FETCHOPT_PREFETCH = 1 << 7, /*%< Do prefetch */
DNS_FETCHOPT_NOCDFLAG = 1 << 8, /*%< Don't set CD flag. */
DNS_FETCHOPT_NONTA = 1 << 9, /*%< Ignore NTA table. */
DNS_FETCHOPT_NOCACHED = 1 << 10, /*%< Force cache update. */
DNS_FETCHOPT_QMINIMIZE = 1 << 11, /*%< Use qname minimization. */
DNS_FETCHOPT_NOFOLLOW = 1 << 12, /*%< Don't retrieve the NS RRset
* from the child zone when a
* delegation is returned in
* response to a NS query. */
DNS_FETCHOPT_QMIN_STRICT = 1 << 13, /*%< Do not work around servers
* that return errors on
* non-empty terminals. */
DNS_FETCHOPT_QMIN_SKIP_IP6A = 1 << 14, /*%< Skip some labels when
* doing qname minimization
* on ip6.arpa. */
DNS_FETCHOPT_NOFORWARD = 1 << 15, /*%< Do not use forwarders if
* possible. */
DNS_FETCHOPT_QMINFETCH = 1 << 16, /*%< Qmin fetch */
DNS_FETCHOPT_WANTZONEVERSION = 1 << 17, /*%< Request ZONEVERSION */
/*% EDNS version bits: */
DNS_FETCHOPT_EDNSVERSIONSET = 1 << 23,

Some files were not shown because too many files have changed in this diff Show More