$PYTHON -c "import yaml" 2>/dev/null && HAS_PYYAML=1
if [ -x "$DIG" ]; then
- n=$((n + 1))
- echo_i "check dig handles UPDATE response ($n)"
- ret=0
- dig_with_opts @10.53.0.6 +tries=1 +timeout=1 cname foo.bar >dig.out.test$n 2>&1 && ret=1
- grep "Opcode mismatch" 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 short form works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 +short a a.example >dig.out.test$n || ret=1
- test "$(wc -l <dig.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 dig split width works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 +split=4 -t sshfp foo.example >dig.out.test$n || ret=1
- grep " 9ABC DEF6 7890 " <dig.out.test$n >/dev/null || ret=1
- check_ttl_range dig.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 dig +unknownformat works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 +unknownformat a a.example >dig.out.test$n || ret=1
- grep "CLASS1[ ][ ]*TYPE1[ ][ ]*\\\\# 4 0A000001" <dig.out.test$n >/dev/null || ret=1
- check_ttl_range dig.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 dig with reverse lookup works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 -x 127.0.0.1 >dig.out.test$n 2>&1 || ret=1
- # doesn't matter if has answer
- grep -i "127\\.in-addr\\.arpa\\." <dig.out.test$n >/dev/null || ret=1
- check_ttl_range dig.out.test$n "SOA" 86400 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking dig -x rejects deeply nested input cleanly ($n)"
- ret=0
- longinput="$(printf '1.%.0s' $(seq 1 6400))1"
- # Pre-fix: SIGSEGV (139) or ASan abort (134) from unbounded recursion in
- # reverse_octets() on the dots. Post-fix: structured rejection via
- # DNS_R_NAMETOOLONG -> "Invalid IP address" -> exit 1.
- rc=0
- dig_with_opts -x "$longinput" >dig.out.test$n 2>&1 || rc=$?
- [ $rc -ge 128 ] && ret=1
- grep "Invalid IP address" 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 over TCP works ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 a a.example >dig.out.test$n || ret=1
- grep "10\\.0\\.0\\.1$" <dig.out.test$n >/dev/null || ret=1
- check_ttl_range dig.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 dig +multi +norrcomments works for DNSKEY (when default is rrcomments)($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +multi +norrcomments -t DNSKEY example >dig.out.test$n || ret=1
- grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" dig.out.test$n >/dev/null && ret=1
- check_ttl_range dig.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 dig +multi +norrcomments works for SOA (when default is rrcomments)($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +multi +norrcomments -t SOA example >dig.out.test$n || ret=1
- grep "; serial" dig.out.test$n >/dev/null && ret=1
- check_ttl_range dig.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 dig +rrcomments works for DNSKEY($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +rrcomments DNSKEY example >dig.out.test$n || ret=1
- grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" <dig.out.test$n >/dev/null || ret=1
- check_ttl_range dig.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 dig +short +rrcomments works for DNSKEY ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +short +rrcomments DNSKEY example >dig.out.test$n || ret=1
- grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" <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 +short +nosplit works($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +short +nosplit DNSKEY example >dig.out.test$n || ret=1
- grep "$NOSPLIT" <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 +short +rrcomments works($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +short +rrcomments DNSKEY example >dig.out.test$n || ret=1
- grep -q "$KEYDATA ; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID\$" <dig.out.test$n || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking dig multi flag is local($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 -t DNSKEY example +nomulti example +nomulti >dig.out.nn.$n || ret=1
- dig_with_opts +tcp @10.53.0.3 -t DNSKEY example +multi example +nomulti >dig.out.mn.$n || ret=1
- dig_with_opts +tcp @10.53.0.3 -t DNSKEY example +nomulti example +multi >dig.out.nm.$n || ret=1
- dig_with_opts +tcp @10.53.0.3 -t DNSKEY example +multi example +multi >dig.out.mm.$n || ret=1
- lcnn=$(wc -l <dig.out.nn.$n)
- lcmn=$(wc -l <dig.out.mn.$n)
- lcnm=$(wc -l <dig.out.nm.$n)
- lcmm=$(wc -l <dig.out.mm.$n)
- test "$lcmm" -ge "$lcnm" || ret=1
- test "$lcmm" -ge "$lcmn" || ret=1
- test "$lcnm" -ge "$lcnn" || ret=1
- test "$lcmn" -ge "$lcnn" || ret=1
- check_ttl_range dig.out.nn.$n "DNSKEY" 300 || ret=1
- check_ttl_range dig.out.mn.$n "DNSKEY" 300 || ret=1
- check_ttl_range dig.out.nm.$n "DNSKEY" 300 || ret=1
- check_ttl_range dig.out.mm.$n "DNSKEY" 300 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking dig +noheader-only works ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +noheader-only A example >dig.out.test$n || ret=1
- grep "Got answer:" <dig.out.test$n >/dev/null || ret=1
- check_ttl_range dig.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 dig +short +rrcomments works($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +short +rrcomments DNSKEY example >dig.out.test$n || ret=1
- grep -q "$KEYDATA ; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID\$" <dig.out.test$n || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking dig +header-only works ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +header-only example >dig.out.test$n || ret=1
- grep "^;; flags: qr rd; QUERY: 0, ANSWER: 0," <dig.out.test$n >/dev/null || ret=1
- grep "^;; QUESTION SECTION:" <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 +coflag works ($n)"
ret=0
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
- n=$((n + 1))
- echo_i "checking dig +header-only works (with class and type set) ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.3 +header-only -c IN -t A example >dig.out.test$n || ret=1
- grep "^;; flags: qr rd; QUERY: 0, ANSWER: 0," <dig.out.test$n >/dev/null || ret=1
- grep "^;; QUESTION SECTION:" <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 +zflag works, and that BIND properly ignores it ($n)"
ret=0
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
- n=$((n + 1))
- echo_i "checking dig +ttlunits works ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.2 +ttlunits A weeks.example >dig.out.test$n || ret=1
- grep "^weeks.example. 3w" <dig.out.test$n >/dev/null || ret=1
- dig_with_opts +tcp @10.53.0.2 +ttlunits A days.example >dig.out.test$n || ret=1
- grep "^days.example. 3d" <dig.out.test$n >/dev/null || ret=1
- dig_with_opts +tcp @10.53.0.2 +ttlunits A hours.example >dig.out.test$n || ret=1
- grep "^hours.example. 3h" <dig.out.test$n >/dev/null || ret=1
- dig_with_opts +tcp @10.53.0.2 +ttlunits A minutes.example >dig.out.test$n || ret=1
- grep "^minutes.example. 45m" <dig.out.test$n >/dev/null || ret=1
- dig_with_opts +tcp @10.53.0.2 +ttlunits A seconds.example >dig.out.test$n || ret=1
- grep "^seconds.example. 45s" <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 respects precedence of options with +ttlunits ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.2 +ttlunits +nottlid A weeks.example >dig.out.test$n || ret=1
- grep "^weeks.example. IN" <dig.out.test$n >/dev/null || ret=1
- dig_with_opts +tcp @10.53.0.2 +nottlid +ttlunits A weeks.example >dig.out.test$n || ret=1
- grep "^weeks.example. 3w" <dig.out.test$n >/dev/null || ret=1
- dig_with_opts +tcp @10.53.0.2 +nottlid +nottlunits A weeks.example >dig.out.test$n || ret=1
- grep "^weeks.example. 1814400" <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 preserves origin on TCP retries ($n)"
ret=0
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
- n=$((n + 1))
- echo_i "checking dig +sp works as an abbreviated form of split ($n)"
- ret=0
- dig_with_opts @10.53.0.3 +sp=4 -t sshfp foo.example >dig.out.test$n || ret=1
- grep " 9ABC DEF6 7890 " <dig.out.test$n >/dev/null || ret=1
- check_ttl_range dig.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 dig -c works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 -c CHAOS -t txt version.bind >dig.out.test$n || ret=1
- grep "version.bind. 0 CH TXT" <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 +ednsopt with option number ($n)"
ret=0
status=$((status + ret))
fi
- n=$((n + 1))
- echo_i "checking that dig warns about .local queries ($n)"
- ret=0
- dig_with_opts @10.53.0.3 local soa >dig.out.test$n 2>&1 || ret=1
- grep ";; WARNING: .local is reserved for Multicast DNS" 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 processes +ednsopt=key-tag and FORMERR is returned ($n)"
ret=0
status=$((status + ret))
fi
- n=$((n + 1))
- echo_i "check that dig gracefully handles bad escape in domain name ($n)"
- ret=0
- digstatus=0
- dig_with_opts @10.53.0.3 '\0.' >dig.out.test$n 2>&1 || digstatus=$?
- echo digstatus=$digstatus >>dig.out.test$n
- test $digstatus -eq 10 || ret=1
- grep REQUIRE dig.out.test$n >/dev/null && ret=1
- grep "is not a legal name (bad escape)" 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 -q -m works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 -q -m >dig.out.test$n 2>&1
- pat='^;-m\..*IN.*A$'
- grep "$pat" dig.out.test$n >/dev/null || ret=1
- grep "Dump of all outstanding memory allocations" 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 exit code for a retry upon TCP EOF (immediate -> immediate) ($n)"
ret=0
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
- n=$((n + 1))
- echo_i "check that dig +expandaaaa works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 +expandaaaa AAAA ns2.example >dig.out.test$n 2>&1 || ret=1
- grep "ns2.example.*fd92:7065:0b8e:ffff:0000:0000:0000:0002" 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 +noexpandaaaa works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 +noexpandaaaa AAAA ns2.example >dig.out.test$n 2>&1 || ret=1
- grep "ns2.example.*fd92:7065:b8e:ffff::2" 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 default for +[no]expandaaa (+noexpandaaaa) works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 AAAA ns2.example >dig.out.test$n 2>&1 || ret=1
- grep "ns2.example.*fd92:7065:b8e:ffff::2" 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 +short +expandaaaa works ($n)"
- ret=0
- dig_with_opts @10.53.0.3 +short +expandaaaa AAAA ns2.example >dig.out.test$n 2>&1 || ret=1
- pat='^fd92:7065:0b8e:ffff:0000:0000:0000:0002$'
- grep "$pat" 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 "check dig +yaml ANY output ($n)"
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
- n=$((n + 1))
- echo_i "check that dig +bufsize=0 just sets the buffer size to 0 ($n)"
- ret=0
- dig_with_opts @10.53.0.3 a.example +bufsize=0 +qr >dig.out.test$n 2>&1 || ret=1
- grep "EDNS:" 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 +bufsize restores default bufsize ($n)"
- ret=0
- dig_with_opts @10.53.0.3 a.example +bufsize=0 +bufsize +qr >dig.out.test$n 2>&1 || ret=1
- lines=$(grep "EDNS:.* udp:" dig.out.test$n | wc -l)
- lines1232=$(grep "EDNS:.* udp: 1232" dig.out.test$n | wc -l)
- test $lines -eq 2 || ret=1
- test $lines1232 -eq 2 || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "check that dig without -u displays 'Query time' in millseconds ($n)"
- ret=0
- dig_with_opts @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
- grep ';; Query time: [0-9][0-9]* msec' 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 -u displays 'Query time' in microseconds ($n)"
- ret=0
- dig_with_opts -u @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
- grep ';; Query time: [0-9][0-9]* usec' 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 +yaml without -u displays timestamps in milliseconds ($n)"
- ret=0
- dig_with_opts +yaml @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
- grep 'query_time: !!timestamp ....-..-..T..:..:..\....Z' dig.out.test$n >/dev/null || ret=1
- grep 'response_time: !!timestamp ....-..-..T..:..:..\....Z' 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 -u +yaml displays timestamps in microseconds ($n)"
- ret=0
- dig_with_opts -u +yaml @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
- grep 'query_time: !!timestamp ....-..-..T..:..:..\.......Z' dig.out.test$n >/dev/null || ret=1
- grep 'response_time: !!timestamp ....-..-..T..:..:..\.......Z' dig.out.test$n >/dev/null || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
# See [GL #3020] for more information
n=$((n + 1))
echo_i "check that dig handles UDP timeout followed by a SERVFAIL correctly ($n)"
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
- n=$((n + 1))
- echo_i "check dig's +nocrypto flag ($n)"
- ret=0
- dig_with_opts +dnssec +norec +nocrypto DNSKEY . @10.53.0.1 >dig.out.dnskey.test$n || ret=1
- grep -E "256 [0-9]+ $DEFAULT_ALGORITHM_NUMBER \\[key id = [1-9][0-9]*]" dig.out.dnskey.test$n >/dev/null || ret=1
- grep -E "RRSIG.* \\[omitted]" dig.out.dnskey.test$n >/dev/null || ret=1
- dig_with_opts +norec +nocrypto DS example \
- @10.53.0.1 >dig.out.ds.test$n || ret=1
- grep -E "DS.* [0-9]+ [12] \[omitted]" dig.out.ds.test$n >/dev/null || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
# See GL#5609
n=$((n + 1))
echo_i "check dig with a IPv4 source address and a server with both IPv4 and IPv6 addresses doesn't crash ($n)"
--- /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 dig tool.
+"""
+
+from re import compile as Re
+
+import os
+import re
+
+import pytest
+
+from digdelv.common import ARTIFACTS, check_ttl_range
+from isctest.util import param
+
+import isctest
+
+pytestmark = [
+ pytest.mark.skipif(
+ not os.access(os.environ.get("DIG", ""), os.X_OK),
+ reason="dig executable not available",
+ ),
+ pytest.mark.extra_artifacts(ARTIFACTS),
+]
+
+
+@pytest.fixture(name="dig")
+def dig_fixture(named_port):
+ return isctest.run.EnvCmd("DIG", f"-p {named_port}")
+
+
+def test_update_response(dig, ans6):
+ """Check that dig rejects a response with the UPDATE opcode."""
+ result = dig(
+ f"@{ans6.ip} +tries=1 +timeout=1 cname foo.bar", raise_on_exception=False
+ )
+ assert result.rc != 0
+ assert "Opcode mismatch" in result.out
+
+
+def test_short(dig, ns3):
+ """Check that dig +short returns a single-line answer."""
+ result = dig(f"@{ns3.ip} +short a a.example")
+ assert len(result.out.splitlines()) == 1
+
+
+@pytest.mark.parametrize("option", ["+split=4", "+sp=4"])
+def test_split_width(dig, ns3, option):
+ """Check that dig +split (and its +sp abbreviation) splits hex data
+ into fields of the requested width."""
+ result = dig(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(dig, ns3):
+ """Check that dig +unknownformat prints RFC 3597 format."""
+ result = dig(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_reverse_lookup(dig, ns3):
+ """Check that dig -x works."""
+ result = dig(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, "SOA", 86400)
+
+
+def test_reverse_lookup_deeply_nested(dig):
+ """Check that dig -x rejects deeply nested input cleanly instead of
+ crashing on unbounded recursion in reverse_octets()."""
+ longinput = "1." * 6400 + "1"
+ result = dig(f"-x {longinput}", raise_on_exception=False)
+ assert result.rc == 1
+ assert "Invalid IP address" in result.err
+
+
+def test_tcp(dig, ns3):
+ """Check that dig over TCP works."""
+ result = dig(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",
+ [
+ param("+multi +norrcomments -t DNSKEY example", False, id="multi-norrcomments"),
+ param("+rrcomments DNSKEY example", True, id="rrcomments"),
+ param("+short +rrcomments DNSKEY example", True, id="short-rrcomments"),
+ ],
+)
+def test_dnskey_rrcomments(dig, ns3, zsk, args, expect_rrcomment):
+ """Check that +[no]rrcomments controls the DNSKEY comment
+ (the default is rrcomments, even with +multi)."""
+ result = dig(f"+tcp @{ns3.ip} {args}")
+ assert (zsk.rrcomment in result.out) == expect_rrcomment
+ if "+short" not in args:
+ assert check_ttl_range(result.out, "DNSKEY", 300)
+
+
+def test_soa_norrcomments(dig, ns3):
+ """Check that +multi +norrcomments suppresses the SOA field comments."""
+ result = dig(f"+tcp @{ns3.ip} +multi +norrcomments -t SOA example")
+ assert "; serial" not in result.out
+ assert check_ttl_range(result.out, "SOA", 300)
+
+
+def test_short_nosplit(dig, ns3, zsk):
+ """Check that dig +short +nosplit does not split the key data."""
+ result = dig(f"+tcp @{ns3.ip} +short +nosplit DNSKEY example")
+ assert zsk.keydata.replace(" ", "") in result.out
+
+
+def test_short_rrcomments_line(dig, ns3, zsk):
+ """Check the exact dig +short +rrcomments output line."""
+ result = dig(f"+tcp @{ns3.ip} +short +rrcomments DNSKEY example")
+ expected = re.escape(f"{zsk.keydata} {zsk.rrcomment}")
+ assert Re(expected + "$") in result.out
+
+
+def test_multi_flag_is_local(dig, ns3):
+ """Check that +[no]multi applies to a single lookup only."""
+ lines = {}
+ for flags in [
+ ("nomulti", "nomulti"),
+ ("multi", "nomulti"),
+ ("nomulti", "multi"),
+ ("multi", "multi"),
+ ]:
+ first, second = flags
+ result = dig(f"+tcp @{ns3.ip} -t DNSKEY example +{first} example +{second}")
+ assert check_ttl_range(result.out, "DNSKEY", 300)
+ lines[flags] = len(result.out.splitlines())
+ assert lines[("multi", "multi")] >= lines[("nomulti", "multi")]
+ assert lines[("multi", "multi")] >= lines[("multi", "nomulti")]
+ assert lines[("nomulti", "multi")] >= lines[("nomulti", "nomulti")]
+ assert lines[("multi", "nomulti")] >= lines[("nomulti", "nomulti")]
+
+
+def test_noheader_only(dig, ns3):
+ """Check that dig +noheader-only sends a full query."""
+ result = dig(f"+tcp @{ns3.ip} +noheader-only A example")
+ assert "Got answer:" in result.out
+ assert check_ttl_range(result.out, "SOA", 300)
+
+
+@pytest.mark.parametrize(
+ "class_type",
+ [
+ param("", id="default"),
+ param("-c IN -t A", id="with-class-and-type"),
+ ],
+)
+def test_header_only(dig, ns3, class_type):
+ """Check that dig +header-only sends a query without a question."""
+ result = dig(f"+tcp @{ns3.ip} +header-only {class_type} example")
+ assert Re(r"^;; flags: qr rd; QUERY: 0, ANSWER: 0,") in result.out
+ assert Re(r"^;; QUESTION SECTION:") not in result.out
+
+
+@pytest.mark.parametrize(
+ "qname,ttl",
+ [
+ param("weeks", "3w"),
+ param("days", "3d"),
+ param("hours", "3h"),
+ param("minutes", "45m"),
+ param("seconds", "45s"),
+ ],
+)
+def test_ttl_units(dig, ns2, qname, ttl):
+ """Check that dig +ttlunits prints TTLs in time units."""
+ result = dig(f"+tcp @{ns2.ip} +ttlunits A {qname}.example")
+ assert Re(rf"^{qname}\.example\.\s+{ttl}\s") in result.out
+
+
+@pytest.mark.parametrize(
+ "options,field",
+ [
+ param("+ttlunits +nottlid", "IN", id="nottlid-wins"),
+ param("+nottlid +ttlunits", "3w", id="ttlunits-wins"),
+ param("+nottlid +nottlunits", "1814400", id="plain-seconds"),
+ ],
+)
+def test_ttl_units_precedence(dig, ns2, options, field):
+ """Check that the last of the +ttlid/+ttlunits options wins."""
+ result = dig(f"+tcp @{ns2.ip} {options} A weeks.example")
+ assert Re(rf"^weeks\.example\.\s+{re.escape(field)}\s") in result.out
+
+
+def test_class_chaos(dig, ns3):
+ """Check that dig -c CHAOS works."""
+ result = dig(f"@{ns3.ip} -c CHAOS -t txt version.bind")
+ assert "version.bind.\t\t0\tCH\tTXT" in result.out
+
+
+def test_bad_escape(dig, ns3):
+ """Check that dig gracefully rejects a bad escape in the domain name."""
+ result = dig(rf"@{ns3.ip} \0.", raise_on_exception=False)
+ assert result.rc == 10
+ assert "REQUIRE" not in result.err
+ assert "is not a legal name (bad escape)" in result.err
+
+
+def test_q_m(dig, ns3):
+ """Check that -q -m treats -m as a query name, not as the memory
+ debugging flag."""
+ result = dig(f"@{ns3.ip} -q -m", raise_on_exception=False)
+ assert Re(r"^;-m\..*IN.*A$") in result.out
+ assert "Dump of all outstanding memory allocations" not in result.out
+
+
+@pytest.mark.parametrize(
+ "options,pattern",
+ [
+ param(
+ "+expandaaaa",
+ r"ns2\.example.*fd92:7065:0b8e:ffff:0000:0000:0000:0002",
+ id="expandaaaa",
+ ),
+ param(
+ "+noexpandaaaa", r"ns2\.example.*fd92:7065:b8e:ffff::2", id="noexpandaaaa"
+ ),
+ param("", r"ns2\.example.*fd92:7065:b8e:ffff::2", id="default"),
+ param(
+ "+short +expandaaaa",
+ r"^fd92:7065:0b8e:ffff:0000:0000:0000:0002$",
+ id="short-expandaaaa",
+ ),
+ ],
+)
+def test_expandaaaa(dig, ns3, options, pattern):
+ """Check that +[no]expandaaaa controls AAAA address formatting
+ (the default is +noexpandaaaa)."""
+ result = dig(f"@{ns3.ip} {options} AAAA ns2.example")
+ assert Re(pattern) in result.out
+
+
+def test_bufsize_zero(dig, ns3):
+ """Check that +bufsize=0 just sets the advertised buffer size to 0
+ instead of disabling EDNS."""
+ result = dig(f"@{ns3.ip} a.example +bufsize=0 +qr")
+ assert "EDNS:" in result.out
+
+
+def test_bufsize_restores_default(dig, ns3):
+ """Check that a later +bufsize restores the default buffer size."""
+ result = dig(f"@{ns3.ip} a.example +bufsize=0 +bufsize +qr")
+ assert len(result.out.grep(Re(r"EDNS:.* udp:"))) == 2
+ assert len(result.out.grep(Re(r"EDNS:.* udp: 1232"))) == 2
+
+
+@pytest.mark.parametrize(
+ "options,unit",
+ [
+ param("", "msec", id="msec"),
+ param("-u", "usec", id="usec"),
+ ],
+)
+def test_query_time_units(dig, ns3, options, unit):
+ """Check that Query time is in milliseconds, or in microseconds
+ with -u."""
+ result = dig(f"{options} @{ns3.ip} a.example")
+ assert Re(rf";; Query time: \d+ {unit}") in result.out
+
+
+@pytest.mark.parametrize(
+ "options,digits",
+ [
+ param("+yaml", 3, id="msec"),
+ param("-u +yaml", 6, id="usec"),
+ ],
+)
+def test_yaml_timestamp_precision(dig, ns3, options, digits):
+ """Check that +yaml timestamps have millisecond precision, or
+ microsecond precision with -u."""
+ result = dig(f"{options} @{ns3.ip} a.example")
+ for field in ("query_time", "response_time"):
+ pattern = (
+ rf"{field}: !!timestamp \d{{4}}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{{{digits}}}Z"
+ )
+ assert Re(pattern) in result.out
+
+
+def test_local_reserved_warning(dig, ns3):
+ """Check that dig warns about .local queries."""
+ result = dig(f"@{ns3.ip} local soa")
+ assert ";; WARNING: .local is reserved for Multicast DNS" in result.out
+
+
+def test_nocrypto(dig, ns1):
+ """Check that +nocrypto omits the key and signature data."""
+ alg_num = os.environ["DEFAULT_ALGORITHM_NUMBER"]
+ result = dig(f"+dnssec +norec +nocrypto DNSKEY . @{ns1.ip}")
+ assert Re(rf"256 \d+ {alg_num} \[key id = [1-9]\d*]") in result.out
+ assert Re(r"RRSIG.* \[omitted]") in result.out
+ result = dig(f"+norec +nocrypto DS example @{ns1.ip}")
+ assert Re(r"DS.* \d+ [12] \[omitted]") in result.out