status=0
n=0
-# Check if response in file $1 has the correct TTL range.
-# The response record must have RRtype $2 and class IN (CLASS1).
-# Maximum TTL is given by $3. This works in most cases where TTL is
-# the second word on the line. TTL position can be adjusted with
-# setting the position $4, but that requires updating this function.
-check_ttl_range() {
- file=$1
- pos=$4
-
- case "$pos" in
- "3")
- {
- awk -v rrtype="$2" -v ttl="$3" '($4 == "IN" || $4 == "CLASS1" ) && $5 == rrtype { if ($3 <= ttl) { ok=1 } } END { exit(ok?0:1) }' <$file
- result=$?
- } || true
- ;;
- *)
- {
- awk -v rrtype="$2" -v ttl="$3" '($3 == "IN" || $3 == "CLASS1" ) && $4 == rrtype { if ($2 <= ttl) { ok=1 } } END { exit(ok?0:1) }' <$file
- result=$?
- } || true
- ;;
- esac
-
- [ $result -eq 0 ] || echo_i "ttl check failed"
- return $result
-}
-
# use delv insecure mode by default, as we're mostly not testing dnssec
delv_with_opts() {
"$DELV" +noroot -p "$PORT" "$@"
}
-KEYID="$(cat ns2/keyid)"
-KEYDATA="$(sed <ns2/keydata -e 's/+/[+]/g')"
-NOSPLIT="$(sed <ns2/keydata -e 's/+/[+]/g' -e 's/ //g')"
-
-HAS_PYYAML=0
-$PYTHON -c "import yaml" 2>/dev/null && HAS_PYYAML=1
-
if [ -x "$DELV" ]; then
- n=$((n + 1))
- echo_i "checking delv short form works ($n)"
- ret=0
- delv_with_opts @10.53.0.3 +short a a.example >delv.out.test$n || ret=1
- test "$(wc -l <delv.out.test$n)" -eq 1 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +cookie works ($n)"
- ret=0
- delv_with_opts @10.53.0.3 +cookie a a.example -d 10 >delv.out.test$n 2>&1 || ret=1
- grep "; COOKIE:" delv.out.test$n >/dev/null || ret=1
- grep "; answer not validated" delv.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 delv +nocookie works ($n)"
- ret=0
- delv_with_opts @10.53.0.3 +nocookie +strace a a.example -d 10 >delv.out.test$n 2>&1 || ret=1
- grep "; COOKIE:" delv.out.test$n >/dev/null && ret=1
- grep "; answer not validated" delv.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 delv split width works ($n)"
- ret=0
- delv_with_opts @10.53.0.3 +split=4 -t sshfp foo.example >delv.out.test$n || ret=1
- grep " 9ABC DEF6 7890 " <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n "SSHFP" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +unknownformat works ($n)"
- ret=0
- delv_with_opts @10.53.0.3 +unknownformat a a.example >delv.out.test$n || ret=1
- grep "CLASS1[ ][ ]*TYPE1[ ][ ]*\\\\# 4 0A000001" <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n "TYPE1" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv -4 -6 ($n)"
- ret=0
- delv_with_opts @10.53.0.3 -4 -6 A a.example >delv.out.test$n 2>&1 && ret=1
- grep "only one of -4 and -6 allowed" <delv.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 delv exits cleanly on malformed query name ($n)"
- ret=0
- longlabel="$(printf 'a%.0s' $(seq 1 64))"
- delv_with_opts @10.53.0.3 -t a "$longlabel.example.com" >delv.out.test$n 2>&1
- rc=$?
- # Pre-fix: SIGABRT (exit 134) from dns_client_detach(NULL) in run_resolve cleanup.
- [ $rc -eq 134 ] && ret=1
- grep "label too long" delv.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 delv with IPv6 on IPv4 does not work ($n)"
- if testsock6 fd92:7065:b8e:ffff::3 2>/dev/null; then
- ret=0
- # following should fail because @IPv4 overrides earlier @IPv6 above
- # and -6 forces IPv6 so this should fail, with a message
- # "Use of IPv4 disabled by -6"
- delv_with_opts @fd92:7065:b8e:ffff::3 @10.53.0.3 -6 -t txt foo.example >delv.out.test$n 2>&1 && ret=1
- # it should have no results but error output
- grep "testing" <delv.out.test$n >/dev/null && ret=1
- grep "Use of IPv4 disabled by -6" delv.out.test$n >/dev/null || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
- else
- echo_i "IPv6 unavailable; skipping"
- fi
-
- n=$((n + 1))
- echo_i "checking delv with IPv4 on IPv6 does not work ($n)"
- if testsock6 fd92:7065:b8e:ffff::3 2>/dev/null; then
- ret=0
- # following should fail because @IPv6 overrides earlier @IPv4 above
- # and -4 forces IPv4 so this should fail, with a message
- # "Use of IPv6 disabled by -4"
- delv_with_opts @10.53.0.3 @fd92:7065:b8e:ffff::3 -4 -t txt foo.example >delv.out.test$n 2>&1 && ret=1
- # it should have no results but error output
- grep "testing" delv.out.test$n >/dev/null && ret=1
- grep "Use of IPv6 disabled by -4" delv.out.test$n >/dev/null || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
- else
- echo_i "IPv6 unavailable; skipping"
- fi
-
- n=$((n + 1))
- echo_i "checking delv with reverse lookup works ($n)"
- ret=0
- delv_with_opts @10.53.0.3 -x 127.0.0.1 >delv.out.test$n 2>&1 || ret=1
- # doesn't matter if has answer
- grep -i "127\\.in-addr\\.arpa\\." <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n '\\-ANY' 10800 3 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv over TCP works ($n)"
- ret=0
- delv_with_opts +tcp @10.53.0.3 a a.example >delv.out.test$n || ret=1
- grep "10\\.0\\.0\\.1$" <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n "A" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +multi +norrcomments works for DNSKEY (when default is rrcomments)($n)"
- ret=0
- delv_with_opts +tcp @10.53.0.3 +multi +norrcomments DNSKEY example >delv.out.test$n || ret=1
- grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" <delv.out.test$n >/dev/null && ret=1
- check_ttl_range delv.out.test$n "DNSKEY" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +multi +norrcomments works for SOA (when default is rrcomments)($n)"
- ret=0
- delv_with_opts +tcp @10.53.0.3 +multi +norrcomments SOA example >delv.out.test$n || ret=1
- grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" <delv.out.test$n >/dev/null && ret=1
- check_ttl_range delv.out.test$n "SOA" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +rrcomments works for DNSKEY($n)"
- ret=0
- delv_with_opts +tcp @10.53.0.3 +rrcomments DNSKEY example >delv.out.test$n || ret=1
- grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n "DNSKEY" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +short +rrcomments works for DNSKEY ($n)"
- ret=0
- delv_with_opts +tcp @10.53.0.3 +short +rrcomments DNSKEY example >delv.out.test$n || ret=1
- grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" <delv.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 delv +short +rrcomments works ($n)"
- ret=0
- delv_with_opts +tcp @10.53.0.3 +short +rrcomments DNSKEY example >delv.out.test$n || ret=1
- grep -q "$KEYDATA ; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" <delv.out.test$n || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +short +nosplit works ($n)"
- ret=0
- delv_with_opts +tcp @10.53.0.3 +short +nosplit DNSKEY example >delv.out.test$n || ret=1
- grep -q "$NOSPLIT" <delv.out.test$n || ret=1
- test "$(wc -l <delv.out.test$n)" -eq 1 || ret=1
- test "$(awk '{print NF}' <delv.out.test$n)" -eq 14 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +short +nosplit +norrcomments works ($n)"
- ret=0
- delv_with_opts +tcp @10.53.0.3 +short +nosplit +norrcomments DNSKEY example >delv.out.test$n || ret=1
- grep -q "$NOSPLIT\$" <delv.out.test$n || ret=1
- test "$(wc -l <delv.out.test$n)" -eq 1 || ret=1
- test "$(awk '{print NF}' <delv.out.test$n)" -eq 4 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +sp works as an abbriviated form of split ($n)"
- ret=0
- delv_with_opts @10.53.0.3 +sp=4 -t sshfp foo.example >delv.out.test$n || ret=1
- grep " 9ABC DEF6 7890 " <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n "SSHFP" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv +sh works as an abbriviated form of short ($n)"
- ret=0
- delv_with_opts @10.53.0.3 +sh a a.example >delv.out.test$n || ret=1
- test "$(wc -l <delv.out.test$n)" -eq 1 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv -c IN works ($n)"
- ret=0
- delv_with_opts @10.53.0.3 -c IN -t a a.example >delv.out.test$n || ret=1
- grep "a.example." <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n "A" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv -c CH is ignored, and treated like IN ($n)"
- ret=0
- delv_with_opts @10.53.0.3 -c CH -t a a.example >delv.out.test$n || ret=1
- grep "a.example." <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n "A" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking delv -c CH is ignored, and treated like IN ($n)"
- ret=0
- delv_with_opts @10.53.0.3 -c CH -t a a.example >delv.out.test$n || ret=1
- grep "a.example." <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n "A" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "check that delv -q -m works ($n)"
- ret=0
- delv_with_opts @10.53.0.3 -q -m >delv.out.test$n 2>&1 || ret=1
- grep '^; -m\..*[0-9]*.*IN.*ANY.*;' delv.out.test$n >/dev/null || ret=1
- grep "^add " delv.out.test$n >/dev/null && ret=1
- grep "^del " delv.out.test$n >/dev/null && ret=1
- check_ttl_range delv.out.test$n '\\-ANY' 300 3 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "check that delv -t ANY works ($n)"
- ret=0
- delv_with_opts @10.53.0.3 -t ANY example >delv.out.test$n 2>&1 || ret=1
- grep "^example." <delv.out.test$n >/dev/null || ret=1
- check_ttl_range delv.out.test$n NS 300 || ret=1
- check_ttl_range delv.out.test$n SOA 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "check that delv loads key-style trust anchors ($n)"
- ret=0
- delv_with_opts -a ns3/anchor.dnskey +root=example @10.53.0.3 -t DNSKEY example >delv.out.test$n 2>&1 || ret=1
- grep "fully validated" delv.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 delv loads DS-style trust anchors ($n)"
- ret=0
- delv_with_opts -a ns3/anchor.ds +root=example @10.53.0.3 -t DNSKEY example >delv.out.test$n 2>&1 || ret=1
- grep "fully validated" delv.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 "check delv +yaml ANY output ($n)"
- ret=0
- delv_with_opts +yaml @10.53.0.3 any ns2.example >delv.out.test$n || ret=1
- $PYTHON yamlget.py delv.out.test$n status >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- [ "$value" = "success" ] || ret=1
- $PYTHON yamlget.py delv.out.test$n query_name >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- [ "$value" = "ns2.example" ] || ret=1
- $PYTHON yamlget.py delv.out.test$n records 0 answer_not_validated 0 >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- count=$(echo $value | wc -w)
- [ ${count:-0} -eq 5 ] || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "check delv +yaml NODATA output ($n)"
- ret=0
- delv_with_opts +yaml @10.53.0.3 type500 ns2.example >delv.out.test$n || ret=1
- $PYTHON yamlget.py delv.out.test$n status >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- [ "$value" = "ncache nxrrset" ] || ret=1
- $PYTHON yamlget.py delv.out.test$n query_name >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- [ "$value" = "ns2.example" ] || ret=1
- $PYTHON yamlget.py delv.out.test$n records 0 negative_response_answer_not_validated 0 >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- count=$(echo $value | wc -w)
- [ ${count:-0} -eq 5 ] || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "check delv +yaml NXDOMAIN output ($n)"
- ret=0
- delv_with_opts +yaml @10.53.0.3 a this-does-not-exist.ns2.example >delv.out.test$n || ret=1
- $PYTHON yamlget.py delv.out.test$n status >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- [ "$value" = "ncache nxdomain" ] || ret=1
- $PYTHON yamlget.py delv.out.test$n query_name >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- [ "$value" = "this-does-not-exist.ns2.example" ] || ret=1
- $PYTHON yamlget.py delv.out.test$n records 0 negative_response_answer_not_validated 0 >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- count=$(echo $value | wc -w)
- [ ${count:-0} -eq 5 ] || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
- fi
-
- n=$((n + 1))
- echo_i "check that delv handles REFUSED when chasing DS records ($n)"
- ret=0
- delv_with_opts @10.53.0.2 +root xxx.example.tld A >delv.out.test$n 2>&1 || ret=1
- grep ";; resolution failed: broken trust chain" delv.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 NS output from delv +ns ($n)"
ret=0
--- /dev/null
+# 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.
+
+"""
+Tests for the delv tool.
+"""
+
+from re import compile as Re
+
+import os
+import re
+
+import pytest
+
+from digdelv.common import ARTIFACTS, check_ttl_range, needs_pyyaml, parse_yaml
+from isctest.util import param
+
+import isctest
+import isctest.mark
+
+pytestmark = [
+ pytest.mark.skipif(
+ not os.access(os.environ.get("DELV", ""), os.X_OK),
+ reason="delv executable not available",
+ ),
+ pytest.mark.extra_artifacts(ARTIFACTS),
+]
+
+
+@pytest.fixture(name="delv")
+def delv_fixture(named_port):
+ # use delv insecure mode by default, as we're mostly not testing dnssec
+ return isctest.run.EnvCmd("DELV", f"+noroot -p {named_port}")
+
+
+@pytest.mark.parametrize("option", ["+short", "+sh"])
+def test_short(delv, ns3, option):
+ """Check that delv +short (and its +sh abbreviation) returns a
+ single-line answer."""
+ result = delv(f"@{ns3.ip} {option} a a.example")
+ assert len(result.out.splitlines()) == 1
+
+
+def test_cookie(delv, ns3):
+ """Check that delv +cookie sends a DNS COOKIE option."""
+ result = delv(f"@{ns3.ip} +cookie a a.example -d 10")
+ assert "; COOKIE:" in result.err
+ assert "; answer not validated" in result.out
+
+
+def test_nocookie(delv, ns3):
+ """Check that delv +nocookie does not send a DNS COOKIE option."""
+ result = delv(f"@{ns3.ip} +nocookie +strace a a.example -d 10")
+ assert "; COOKIE:" not in result.err
+ assert "; answer not validated" in result.out
+
+
+@pytest.mark.parametrize("option", ["+split=4", "+sp=4"])
+def test_split_width(delv, ns3, option):
+ """Check that delv +split (and its +sp abbreviation) splits hex data
+ into fields of the requested width."""
+ result = delv(f"@{ns3.ip} {option} -t sshfp foo.example")
+ assert " 9ABC DEF6 7890 " in result.out
+ assert check_ttl_range(result.out, "SSHFP", 300)
+
+
+def test_unknownformat(delv, ns3):
+ """Check that delv +unknownformat prints RFC 3597 format."""
+ result = delv(f"@{ns3.ip} +unknownformat a a.example")
+ assert Re(r"CLASS1\s+TYPE1\s+\\# 4 0A000001") in result.out
+ assert check_ttl_range(result.out, "TYPE1", 300)
+
+
+def test_4_and_6_mutually_exclusive(delv, ns3):
+ """Check that delv rejects -4 combined with -6."""
+ result = delv(f"@{ns3.ip} -4 -6 A a.example", raise_on_exception=False)
+ assert result.rc != 0
+ assert "only one of -4 and -6 allowed" in result.err
+
+
+def test_malformed_query_name(delv, ns3):
+ """Check that delv exits cleanly on a malformed query name instead of
+ aborting in the dns_client_detach(NULL) cleanup path."""
+ longlabel = "a" * 64
+ result = delv(f"@{ns3.ip} -t a {longlabel}.example.com", raise_on_exception=False)
+ assert result.rc >= 0
+ assert "label too long" in result.err
+
+
+@isctest.mark.with_ipv6
+@pytest.mark.parametrize(
+ "server_args,message",
+ [
+ param(
+ "@fd92:7065:b8e:ffff::3 @{ns3} -6",
+ "Use of IPv4 disabled by -6",
+ id="ipv4-server-with-6",
+ ),
+ param(
+ "@{ns3} @fd92:7065:b8e:ffff::3 -4",
+ "Use of IPv6 disabled by -4",
+ id="ipv6-server-with-4",
+ ),
+ ],
+)
+def test_address_family_mismatch(delv, ns3, server_args, message):
+ """Check that the last @server option overrides earlier ones and that
+ the forced address family makes such a lookup fail."""
+ result = delv(
+ server_args.format(ns3=ns3.ip) + " -t txt foo.example",
+ raise_on_exception=False,
+ )
+ assert result.rc != 0
+ # it should have no results but error output
+ assert "testing" not in result.out
+ assert message in result.err
+
+
+def test_reverse_lookup(delv, ns3):
+ """Check that delv -x works."""
+ result = delv(f"@{ns3.ip} -x 127.0.0.1")
+ # doesn't matter if has answer
+ assert Re(r"127\.in-addr\.arpa\.", re.IGNORECASE) in result.out
+ assert check_ttl_range(result.out, r"\-ANY", 10800)
+
+
+def test_tcp(delv, ns3):
+ """Check that delv over TCP works."""
+ result = delv(f"+tcp @{ns3.ip} a a.example")
+ assert Re(r"10\.0\.0\.1$") in result.out
+ assert check_ttl_range(result.out, "A", 300)
+
+
+@pytest.mark.parametrize(
+ "args,expect_rrcomment,ttl_rrtype",
+ [
+ param(
+ "+multi +norrcomments DNSKEY example",
+ False,
+ "DNSKEY",
+ id="multi-norrcomments-dnskey",
+ ),
+ param(
+ "+multi +norrcomments SOA example",
+ False,
+ "SOA",
+ id="multi-norrcomments-soa",
+ ),
+ param("+rrcomments DNSKEY example", True, "DNSKEY", id="rrcomments"),
+ param("+short +rrcomments DNSKEY example", True, None, id="short-rrcomments"),
+ ],
+)
+def test_rrcomments(delv, ns3, zsk, args, expect_rrcomment, ttl_rrtype):
+ """Check that +[no]rrcomments controls the DNSKEY comment
+ (the default is rrcomments, even with +multi)."""
+ result = delv(f"+tcp @{ns3.ip} {args}")
+ assert (zsk.rrcomment in result.out) == expect_rrcomment
+ if ttl_rrtype:
+ assert check_ttl_range(result.out, ttl_rrtype, 300)
+
+
+def test_short_rrcomments_line(delv, ns3, zsk):
+ """Check the exact delv +short +rrcomments output line."""
+ result = delv(f"+tcp @{ns3.ip} +short +rrcomments DNSKEY example")
+ assert f"{zsk.keydata} {zsk.rrcomment}" in result.out
+
+
+def test_short_nosplit(delv, ns3, zsk):
+ """Check that delv +short +nosplit does not split the key data."""
+ result = delv(f"+tcp @{ns3.ip} +short +nosplit DNSKEY example")
+ assert zsk.keydata.replace(" ", "") in result.out
+ assert len(result.out.splitlines()) == 1
+ assert len(result.out.split()) == 14
+
+
+def test_short_nosplit_norrcomments(delv, ns3, zsk):
+ """Check that delv +short +nosplit +norrcomments prints the bare
+ unsplit rdata."""
+ result = delv(f"+tcp @{ns3.ip} +short +nosplit +norrcomments DNSKEY example")
+ nosplit = zsk.keydata.replace(" ", "")
+ assert Re(re.escape(nosplit) + "$") in result.out
+ assert len(result.out.splitlines()) == 1
+ assert len(result.out.split()) == 4
+
+
+@pytest.mark.parametrize(
+ "qclass",
+ [
+ param("IN", id="in"),
+ param("CH", id="ch-ignored"),
+ ],
+)
+def test_class_option(delv, ns3, qclass):
+ """Check that delv -c IN works and that -c CH is ignored and treated
+ like IN."""
+ result = delv(f"@{ns3.ip} -c {qclass} -t a a.example")
+ assert "a.example." in result.out
+ assert check_ttl_range(result.out, "A", 300)
+
+
+def test_q_m(delv, ns3):
+ """Check that -q -m treats -m as a query name, not as the memory
+ debugging flag."""
+ result = delv(f"@{ns3.ip} -q -m")
+ assert Re(r"^; -m\..*\d*.*IN.*ANY.*;") in result.out
+ for stream in (result.out, result.err):
+ assert Re(r"^add ") not in stream
+ assert Re(r"^del ") not in stream
+ assert check_ttl_range(result.out, r"\-ANY", 300)
+
+
+def test_any_query(delv, ns3):
+ """Check that delv -t ANY works."""
+ result = delv(f"@{ns3.ip} -t ANY example")
+ assert Re(r"^example\.") in result.out
+ assert check_ttl_range(result.out, "NS", 300)
+ assert check_ttl_range(result.out, "SOA", 300)
+
+
+@pytest.mark.parametrize(
+ "anchor",
+ [
+ param("anchor.dnskey", id="key-style"),
+ param("anchor.ds", id="ds-style"),
+ ],
+)
+def test_trust_anchors(delv, ns3, anchor):
+ """Check that delv loads key-style and DS-style trust anchors and
+ validates with them."""
+ result = delv(f"-a ns3/{anchor} +root=example @{ns3.ip} -t DNSKEY example")
+ assert "fully validated" in result.out
+
+
+def test_refused_chasing_ds(delv, ns2):
+ """Check that delv handles REFUSED when chasing DS records."""
+ result = delv(f"@{ns2.ip} +root xxx.example.tld A")
+ assert ";; resolution failed: broken trust chain" in result.err
+
+
+@needs_pyyaml
+def test_yaml_any(delv, ns3):
+ """Check the structure of delv +yaml output."""
+ result = delv(f"+yaml @{ns3.ip} any ns2.example")
+ data = parse_yaml(result.out)
+ assert data["status"] == "success"
+ assert data["query_name"] == "ns2.example"
+ answer = data["records"][0]["answer_not_validated"][0]
+ assert len(str(answer).split()) == 5
+
+
+@needs_pyyaml
+@pytest.mark.parametrize(
+ "qtype,qname,status",
+ [
+ param("type500", "ns2.example", "ncache nxrrset", id="nodata"),
+ param("a", "this-does-not-exist.ns2.example", "ncache nxdomain", id="nxdomain"),
+ ],
+)
+def test_yaml_negative(delv, ns3, qtype, qname, status):
+ """Check the structure of delv +yaml output for negative responses."""
+ result = delv(f"+yaml @{ns3.ip} {qtype} {qname}")
+ data = parse_yaml(result.out)
+ assert data["status"] == status
+ assert data["query_name"] == qname
+ answer = data["records"][0]["negative_response_answer_not_validated"][0]
+ assert len(str(answer).split()) == 5