$PYTHON -c "import yaml" 2>/dev/null && HAS_PYYAML=1
if [ -x "$DIG" ]; then
- n=$((n + 1))
- echo_i "checking dig preserves origin on TCP retries ($n)"
- ret=0
- dig_with_opts -d +tcp @10.53.0.4 +retry=1 +time=1 +domain=bar foo >dig.out.test$n 2>&1 && ret=1
- test "$(grep -c "trying origin bar" dig.out.test$n)" -eq 2 || ret=1
- grep "using root origin" <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 -6 -4 ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.2 -4 -6 A a.example >dig.out.test$n 2>&1 && ret=1
- grep "only one of -4 and -6 allowed" <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 @IPv6addr -4 A a.example ($n)"
- if testsock6 fd92:7065:b8e:ffff::2 2>/dev/null; then
- ret=0
- dig_with_opts +tcp @fd92:7065:b8e:ffff::2 -4 A a.example >dig.out.test$n 2>&1 && ret=1
- grep "address family not supported" <dig.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 dig +tcp @IPv4addr -6 A a.example ($n)"
- if testsock6 fd92:7065:b8e:ffff::2 2>/dev/null; then
- ret=0
- dig_with_opts +tcp @10.53.0.2 -6 A a.example >dig.out.test$n 2>&1 || ret=1
- grep "SERVER: ::ffff:10.53.0.2#$PORT" <dig.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 dig +notcp @IPv4addr -6 A a.example ($n)"
- if testsock6 fd92:7065:b8e:ffff::2 2>/dev/null; then
- ret=0
- dig_with_opts +notcp @10.53.0.2 -6 A a.example >dig.out.test$n 2>&1 || ret=1
- grep "SERVER: ::ffff:10.53.0.2#$PORT" <dig.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))
-
- n=$((n + 1))
- echo_i "checking exit code for a retry upon TCP EOF (immediate -> immediate) ($n)"
- ret=0
- set_response_sequence no-response $n
- dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 && ret=1
- # Sanity check: ensure ans5 behaves as expected.
- [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 2 ] || 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 (partial AXFR -> partial AXFR) ($n)"
- ret=0
- set_response_sequence partial-axfr $n
- dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 && ret=1
- # Sanity check: ensure ans5 behaves as expected.
- [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 2 ] || 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 -> partial AXFR) ($n)"
- ret=0
- set_response_sequence no-response.partial-axfr $n
- dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 && ret=1
- # Sanity check: ensure ans5 behaves as expected.
- [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 2 ] || 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 (partial AXFR -> immediate) ($n)"
- ret=0
- set_response_sequence partial-axfr.no-response $n
- dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 && ret=1
- # Sanity check: ensure ans5 behaves as expected.
- [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 2 ] || 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 -> complete AXFR) ($n)"
- ret=0
- set_response_sequence no-response.complete-axfr $n
- dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 || ret=1
- # Sanity check: ensure ans5 behaves as expected.
- [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 1 ] || 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 (partial AXFR -> complete AXFR) ($n)"
- ret=0
- set_response_sequence partial-axfr.complete-axfr $n
- dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 || ret=1
- # Sanity check: ensure ans5 behaves as expected.
- [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 1 ] || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking +tries=1 won't retry twice upon TCP EOF ($n)"
- ret=0
- set_response_sequence no-response $n
- dig_with_opts @10.53.0.5 example AXFR +tries=1 >dig.out.test$n 2>&1 && ret=1
- # Sanity check: ensure ans5 behaves as expected.
- [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 1 ] || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- n=$((n + 1))
- echo_i "checking +retry=0 won't retry twice upon TCP EOF ($n)"
- ret=0
- dig_with_opts @10.53.0.5 example AXFR +retry=0 >dig.out.test$n 2>&1 && ret=1
- # Sanity check: ensure ans5 behaves as expected.
- [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 1 ] || 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)"
[ "$value" = "d.example. 300 IN AAAA fd92:7065:b8e:ffff::0" ] || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
-
- # When all servers fail (here: a UDP query that times out with no
- # response), dig must not emit the ";"-prefixed startup banner ahead of
- # the "- type: DIG_ERROR" block, as that would make the +yaml output
- # invalid YAML. The query name is deliberately placed before +yaml on the
- # command line: that is what makes dig build the banner (while +cmd is
- # still in effect) before switching to YAML output, which is the ordering
- # that regressed.
- n=$((n + 1))
- echo_i "check that dig +yaml produces valid YAML when no servers could be reached ($n)"
- ret=0
- dig_with_opts silent.example @10.53.0.7 +notcp +timeout=1 +tries=1 +yaml >dig.out.test$n 2>&1 && ret=1
- $PYTHON yamlget.py dig.out.test$n 0 type >yamlget.out.test$n 2>&1 || ret=1
- read -r value <yamlget.out.test$n
- [ "$value" = "DIG_ERROR" ] || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
fi
-
- # +nocmd placed after the query name must suppress the startup banner
- # ("<<>> DiG ..." lines), including on the error path. This regressed
- # because the banner was built as soon as the query name was seen, before
- # +nocmd had been parsed; it is now built after the whole command line has
- # been processed. The default (+cmd) case is checked first so the absence
- # check below is meaningful.
- n=$((n + 1))
- echo_i "check that dig prints the startup banner by default ($n)"
- ret=0
- dig_with_opts silent.example @10.53.0.7 +notcp +timeout=1 +tries=1 >dig.out.test$n 2>&1 && ret=1
- grep -F "<<>> DiG" dig.out.test$n >/dev/null || ret=1
- grep -F "no servers could be reached" 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 +nocmd after the query name suppresses the startup banner ($n)"
- ret=0
- dig_with_opts silent.example @10.53.0.7 +notcp +timeout=1 +tries=1 +nocmd >dig.out.test$n 2>&1 && ret=1
- grep -F "<<>> DiG" dig.out.test$n >/dev/null && ret=1
- grep -F "no servers could be reached" 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)"
- ret=0
- dig_with_opts +timeout=1 +nofail @10.53.0.7 silent-then-servfail.example >dig.out.test$n 2>&1 || ret=1
- grep -F "status: SERVFAIL" 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 handles TCP timeout followed by a SERVFAIL correctly ($n)"
- ret=0
- dig_with_opts +timeout=1 +nofail +tcp @10.53.0.7 silent-then-servfail.example >dig.out.test$n 2>&1 || ret=1
- grep -F "status: SERVFAIL" dig.out.test$n >/dev/null || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- # With +short, dig must not emit the ";; " progress/error comments (here:
- # the "Got SERVFAIL reply from ..." note printed while retrying). +short
- # normally turns comments off, but "+short +comments" re-enables them while
- # short form is still in effect; the comment output then belongs to the
- # verbose form and would corrupt the short output. The "+comments" case
- # (without +short) is checked first so the absence check below is meaningful.
- n=$((n + 1))
- echo_i "check that dig +comments emits the retry comment ($n)"
- ret=0
- dig_with_opts +timeout=1 +nofail +comments @10.53.0.7 silent-then-servfail.example >dig.out.test$n 2>&1 || ret=1
- grep -F ";; Got SERVFAIL reply from" 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 +comments suppresses the retry comment ($n)"
- ret=0
- dig_with_opts +timeout=1 +nofail +short +comments @10.53.0.7 silent-then-servfail.example >dig.out.test$n 2>&1 || ret=1
- grep -F ";; Got SERVFAIL reply from" 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 tries the next server after a UDP socket network unreachable error ($n)"
- ret=0
- dig_with_opts @192.0.2.128 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
- test $(grep -F -e "connection refused" -e "timed out" -e "network unreachable" -e "host unreachable" dig.out.test$n | wc -l) -eq 3 || 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))
-
- n=$((n + 1))
- echo_i "check that dig tries the next server after a TCP socket network unreachable error ($n)"
- ret=0
- dig_with_opts +tcp @192.0.2.128 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
- test $(grep -F -e "connection refused" -e "timed out" -e "network unreachable" -e "host unreachable" dig.out.test$n | wc -l) -eq 3 || 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))
-
- n=$((n + 1))
- echo_i "check that dig tries the next server after a UDP socket read error ($n)"
- ret=0
- dig_with_opts @10.53.0.99 @10.53.0.3 a.example >dig.out.test$n 2>&1 || 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))
-
- n=$((n + 1))
- echo_i "check that dig tries the next server after a TCP socket read error ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.7 @10.53.0.3 close.example >dig.out.test$n 2>&1 || 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))
-
- # Note that we combine TCP socket "connection error" and "timeout" cases in
- # one, because it is not trivial to simulate the timeout case in a system test
- # in Linux without a firewall, but the code which handles error cases during
- # the connection establishment time does not differentiate between timeout and
- # other types of errors (unlike during reading), so this one check should be
- # sufficient for both cases.
- n=$((n + 1))
- echo_i "check that dig tries the next server after a TCP socket connection error/timeout ($n)"
- ret=0
- dig_with_opts +tcp @10.53.0.99 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
- test $(grep -F -e "connection refused" -e "timed out" -e "network unreachable" -e "host unreachable" dig.out.test$n | wc -l) -eq 3 || 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))
-
- n=$((n + 1))
- echo_i "check that dig tries the next server after UDP socket read timeouts ($n)"
- ret=0
- dig_with_opts +timeout=1 @10.53.0.7 @10.53.0.3 silent.example >dig.out.test$n 2>&1 || 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))
-
- n=$((n + 1))
- echo_i "check that dig tries the next server after TCP socket read timeouts ($n)"
- ret=0
- dig_with_opts +timeout=1 +tcp @10.53.0.7 @10.53.0.3 silent.example >dig.out.test$n 2>&1 || 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))
-
- # See [GL #3248] for more information
- n=$((n + 1))
- echo_i "check that dig correctly refuses to use a server with a IPv4 mapped IPv6 address after failing with a regular IP address ($n)"
- ret=0
- dig_with_opts @10.53.0.7 @::ffff:10.53.0.7 silent.example >dig.out.test$n 2>&1 || ret=1
- grep -F ";; Skipping mapped address" dig.out.test$n >/dev/null || ret=1
- grep -F ";; No acceptable nameservers" dig.out.test$n >/dev/null || ret=1
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
-
- # See [GL #3244] for more information
- n=$((n + 1))
- echo_i "check that dig handles printing query information with +qr and +y when multiple queries are involved (including a failed query) ($n)"
- ret=0
- dig_with_opts +timeout=1 +qr +y @127.0.0.1 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
- grep -F "IN A 10.0.0.1" dig.out.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)"
- ret=0
- dig_with_opts @localhost example -b 10.53.0.1 >dig.out.test$n 2>&1 || ret=1
- # We only care about an assertion failure, otherwise reset 'ret' to 0, because
- # @localhost is't really expected to have an answer for our query.
- grep -F "core dumped" dig.out.test$n >/dev/null || ret=0
- if [ $ret -ne 0 ]; then echo_i "failed"; fi
- status=$((status + ret))
else
echo_i "$DIG is needed, so skipping these dig tests"
fi
from isctest.util import param
import isctest
+import isctest.mark
pytestmark = [
pytest.mark.skipif(
result = dig(f"+qr +tcp @{ns2.ip} +ednsopt=8:00030000 A a.example")
assert "status: FORMERR" in result.out
assert len(result.out.grep("CLIENT-SUBNET: 00 03 00 00")) == 1
+
+
+def test_origin_preserved_on_tcp_retries(dig, ans4):
+ """Check that dig preserves the search origin when retrying over
+ TCP."""
+ result = dig(
+ f"-d +tcp @{ans4.ip} +retry=1 +time=1 +domain=bar foo",
+ raise_on_exception=False,
+ )
+ assert result.rc != 0
+ assert len(result.err.grep("trying origin bar")) == 2
+ assert "using root origin" not in result.err
+
+
+def test_4_and_6_mutually_exclusive(dig, ns2):
+ """Check that dig rejects -4 combined with -6."""
+ result = dig(f"+tcp @{ns2.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
+
+
+@isctest.mark.with_ipv6
+def test_ipv6_server_with_ipv4_only(dig):
+ """Check that dig -4 rejects an IPv6 server address."""
+ result = dig("+tcp @fd92:7065:b8e:ffff::2 -4 A a.example", raise_on_exception=False)
+ assert result.rc != 0
+ assert "address family not supported" in result.err
+
+
+@isctest.mark.with_ipv6
+@pytest.mark.parametrize("option", ["+tcp", "+notcp"])
+def test_ipv4_server_with_ipv6_only(dig, ns2, option):
+ """Check that dig -6 does not use a mapped form of an IPv4 server
+ address."""
+ result = dig(f"{option} @{ns2.ip} -6 A a.example")
+ assert f"SERVER: ::ffff:{ns2.ip}#" not in result.out
+
+
+@pytest.fixture(name="set_response_sequence")
+def set_response_sequence_fixture(dig, ans5):
+ """Arm the sequence of AXFR responses served by ans5."""
+
+ def _set(sequence):
+ dig(f"@{ans5.ip} {sequence}.response-sequence._control TXT")
+
+ return _set
+
+
+@pytest.mark.parametrize(
+ "sequence,tries,expect_failure,eof_errors",
+ [
+ param("no-response", 2, True, 2, id="immediate-immediate"),
+ param("partial-axfr", 2, True, 2, id="partial-partial"),
+ param("no-response.partial-axfr", 2, True, 2, id="immediate-partial"),
+ param("partial-axfr.no-response", 2, True, 2, id="partial-immediate"),
+ param("no-response.complete-axfr", 2, False, 1, id="immediate-complete"),
+ param("partial-axfr.complete-axfr", 2, False, 1, id="partial-complete"),
+ param("no-response", 1, True, 1, id="tries-1-no-second-retry"),
+ ],
+)
+def test_axfr_retry_upon_tcp_eof(
+ dig, ans5, set_response_sequence, sequence, tries, expect_failure, eof_errors
+):
+ """Check the exit code and the number of retries for an AXFR retried
+ upon TCP EOF."""
+ set_response_sequence(sequence)
+ result = dig(f"@{ans5.ip} example AXFR +tries={tries}", raise_on_exception=False)
+ assert (result.rc != 0) == expect_failure
+ # Sanity check: ensure ans5 behaves as expected.
+ eof_pattern = Re("communications error.*end of file")
+ assert len(result.out.grep(eof_pattern)) == eof_errors
+
+
+def test_axfr_no_retry_with_retry_0(dig, ans5, set_response_sequence):
+ """Check that +retry=0 does not retry upon TCP EOF."""
+ set_response_sequence("no-response")
+ result = dig(f"@{ans5.ip} example AXFR +retry=0", raise_on_exception=False)
+ assert result.rc != 0
+ # Sanity check: ensure ans5 behaves as expected.
+ eof_pattern = Re("communications error.*end of file")
+ assert len(result.out.grep(eof_pattern)) == 1
+
+
+@pytest.mark.parametrize(
+ "option",
+ [
+ param("", id="udp"),
+ param("+tcp", id="tcp"),
+ ],
+)
+def test_timeout_then_servfail(dig, ans7, option):
+ """Check that dig handles a timeout followed by a SERVFAIL
+ correctly. See GL #3020 for more information."""
+ result = dig(f"+timeout=1 +nofail {option} @{ans7.ip} silent-then-servfail.example")
+ assert "status: SERVFAIL" in result.out
+
+
+def test_comments_retry_comment(dig, ans7):
+ """Check that dig +comments emits the retry comment."""
+ result = dig(
+ f"+timeout=1 +nofail +comments @{ans7.ip} silent-then-servfail.example"
+ )
+ assert ";; Got SERVFAIL reply from" in result.out
+
+
+def test_short_comments_suppresses_retry_comment(dig, ans7):
+ """Check that dig +short +comments does not leak the ";; " comments
+ into the short-form output. +short normally turns comments off, but
+ "+short +comments" re-enables them while short form is still in
+ effect; the comment output then belongs to the verbose form and
+ would corrupt the short output."""
+ result = dig(
+ f"+timeout=1 +nofail +short +comments @{ans7.ip} silent-then-servfail.example"
+ )
+ assert ";; Got SERVFAIL reply from" not in result.out
+
+
+ERROR_PATTERN = Re("connection refused|timed out|network unreachable|host unreachable")
+
+
+@pytest.mark.parametrize(
+ "option",
+ [
+ param("", id="udp"),
+ param("+tcp", id="tcp"),
+ ],
+)
+def test_next_server_after_network_unreachable(dig, ns3, option):
+ """Check that dig tries the next server after a socket network
+ unreachable error."""
+ result = dig(f"{option} @192.0.2.128 @{ns3.ip} a.example")
+ assert len(result.out.grep(ERROR_PATTERN)) == 3
+ assert "status: NOERROR" in result.out
+
+
+def test_next_server_after_udp_read_error(dig, ns3):
+ """Check that dig tries the next server after a UDP socket read
+ error."""
+ result = dig(f"@10.53.0.99 @{ns3.ip} a.example")
+ assert "status: NOERROR" in result.out
+
+
+def test_next_server_after_tcp_read_error(dig, ans7, ns3):
+ """Check that dig tries the next server after a TCP socket read
+ error."""
+ result = dig(f"+tcp @{ans7.ip} @{ns3.ip} close.example")
+ assert "status: NOERROR" in result.out
+
+
+def test_next_server_after_tcp_connection_error(dig, ns3):
+ """Check that dig tries the next server after a TCP socket connection
+ error/timeout. The connection error and timeout cases are combined,
+ because it is not trivial to simulate the timeout case in a system
+ test in Linux without a firewall, but the code which handles error
+ cases during connection establishment does not differentiate between
+ timeout and other types of errors (unlike during reading), so this
+ one check should be sufficient for both cases."""
+ result = dig(f"+tcp @10.53.0.99 @{ns3.ip} a.example")
+ assert len(result.out.grep(ERROR_PATTERN)) == 3
+ assert "status: NOERROR" in result.out
+
+
+@pytest.mark.parametrize(
+ "option",
+ [
+ param("", id="udp"),
+ param("+tcp", id="tcp"),
+ ],
+)
+def test_next_server_after_read_timeout(dig, ans7, ns3, option):
+ """Check that dig tries the next server after socket read timeouts."""
+ result = dig(f"+timeout=1 {option} @{ans7.ip} @{ns3.ip} silent.example")
+ assert "status: NOERROR" in result.out
+
+
+def test_mapped_ipv6_server_refused(dig, ans7):
+ """Check that dig refuses to use a server with an IPv4-mapped IPv6
+ address after failing with the regular IP address. See GL #3248
+ for more information."""
+ result = dig(f"@{ans7.ip} @::ffff:{ans7.ip} silent.example")
+ assert ";; Skipping mapped address" in result.out
+ assert ";; No acceptable nameservers" in result.out
+
+
+def test_qr_and_y_with_failed_query(dig, ns3):
+ """Check that dig handles printing query information with +qr and +y
+ when multiple queries are involved, including a failed one. See
+ GL #3244 for more information."""
+ result = dig(f"+timeout=1 +qr +y @127.0.0.1 @{ns3.ip} a.example")
+ assert "IN A 10.0.0.1" in result.out
+
+
+def test_startup_banner_default(dig, ans7):
+ """Check that dig prints the startup banner by default, including on
+ the error path. This makes the absence check with +nocmd
+ meaningful."""
+ result = dig(
+ f"silent.example @{ans7.ip} +notcp +timeout=1 +tries=1",
+ raise_on_exception=False,
+ )
+ assert result.rc != 0
+ assert "<<>> DiG" in result.out
+ assert "no servers could be reached" in result.out
+
+
+def test_nocmd_after_query_name(dig, ans7):
+ """Check that +nocmd placed after the query name suppresses the
+ startup banner, including on the error path. This regressed because
+ the banner was built as soon as the query name was seen, before
+ +nocmd had been parsed."""
+ result = dig(
+ f"silent.example @{ans7.ip} +notcp +timeout=1 +tries=1 +nocmd",
+ raise_on_exception=False,
+ )
+ assert result.rc != 0
+ assert "<<>> DiG" not in result.out
+ assert "no servers could be reached" in result.out
+
+
+@needs_pyyaml
+def test_yaml_valid_when_no_server_reached(dig, ans7):
+ """Check that dig +yaml produces valid YAML when no servers could be
+ reached; the ";"-prefixed startup banner must not precede the
+ DIG_ERROR block. The query name is deliberately placed before +yaml
+ on the command line: that is what makes dig build the banner (while
+ +cmd is still in effect) before switching to YAML output, which is
+ the ordering that regressed."""
+ result = dig(
+ f"silent.example @{ans7.ip} +notcp +timeout=1 +tries=1 +yaml",
+ raise_on_exception=False,
+ )
+ assert result.rc != 0
+ assert parse_yaml(result.out)[0]["type"] == "DIG_ERROR"
+
+
+def test_source_address_both_families_no_crash(dig, ns1):
+ """Check that dig with an IPv4 source address and a server with both
+ IPv4 and IPv6 addresses does not crash. @localhost is not really
+ expected to have an answer for the query; only a crash (termination
+ by a signal) is an error. See GL #5609 for more information."""
+ result = dig(f"@localhost example -b {ns1.ip}", raise_on_exception=False)
+ assert result.rc >= 0