[9.20] chg: test: Replace digdelv ans.pl with AsyncDnsServer

Rewrite ans servers in digdelv test to use AsyncDnsServer.

Backport of MR !11308

Merge branch 'backport-nicki/asyncdnsserver-digdelv-9.20' into 'bind-9.20'

See merge request isc-projects/bind9!11448
This commit is contained in:
Nicki Křížek 2026-01-12 13:21:42 +01:00
commit 8b0d752db2
13 changed files with 305 additions and 625 deletions

View file

@ -0,0 +1,25 @@
# 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 isctest.asyncserver import (
AsyncDnsServer,
IgnoreAllQueries,
)
def main() -> None:
server = AsyncDnsServer()
server.install_response_handler(IgnoreAllQueries())
server.run()
if __name__ == "__main__":
main()

View file

@ -1 +0,0 @@

View file

@ -1,176 +0,0 @@
#!/usr/bin/perl
# 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 TCP-only DNS server whose aim is to facilitate testing how dig
# copes with prematurely closed TCP connections.
#
# This server can be configured (through a separate control socket) with a
# series of responses to send for subsequent incoming TCP DNS queries. Only
# one query is handled before closing each connection. In order to keep things
# simple, the server is not equipped with any mechanism for handling malformed
# queries.
#
# Available response types are defined in the %response_types hash in the
# getAnswerSection() function below. Each RR returned is generated dynamically
# based on the QNAME found in the incoming query.
use IO::File;
use Net::DNS;
use Net::DNS::Packet;
use strict;
# Ignore SIGPIPE so we won't fail if peer closes a TCP socket early
local $SIG{PIPE} = 'IGNORE';
# Flush logged output after every line
local $| = 1;
my $server_addr = "10.53.0.5";
if (@ARGV > 0) {
$server_addr = @ARGV[0];
}
my $mainport = int($ENV{'PORT'});
if (!$mainport) { $mainport = 5300; }
my $ctrlport = int($ENV{'EXTRAPORT1'});
if (!$ctrlport) { $ctrlport = 5301; }
my $ctlsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
LocalPort => $ctrlport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
LocalPort => $mainport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
print $pidf "$$\n" or die "cannot write pid file: $!";
$pidf->close or die "cannot close pid file: $!";;
sub rmpid { unlink "ans.pid"; exit 1; };
$SIG{INT} = \&rmpid;
$SIG{TERM} = \&rmpid;
my @response_sequence = ("complete_axfr");
my $connection_counter = 0;
# Return the next answer type to send, incrementing the connection counter and
# making sure the latter does not exceed the size of the array holding the
# configured response sequence.
sub getNextResponseType {
my $response_type = $response_sequence[$connection_counter];
$connection_counter++;
$connection_counter %= scalar(@response_sequence);
return $response_type;
}
# Return an array of resource records comprising the answer section of a given
# response type.
sub getAnswerSection {
my ($response_type, $qname) = @_;
my %response_types = (
no_response => [],
partial_axfr => [
Net::DNS::RR->new("$qname 300 IN SOA . . 0 0 0 0 300"),
Net::DNS::RR->new("$qname NS ."),
],
complete_axfr => [
Net::DNS::RR->new("$qname 300 IN SOA . . 0 0 0 0 300"),
Net::DNS::RR->new("$qname NS ."),
Net::DNS::RR->new("$qname 300 IN SOA . . 0 0 0 0 300"),
],
);
return $response_types{$response_type};
}
# Generate a Net::DNS::Packet containing the response to send on the current
# TCP connection. If the answer section of the response is determined to be
# empty, no data will be sent on the connection at all (immediate EOF).
sub generateResponse {
my ($buf) = @_;
my $request;
if ($Net::DNS::VERSION > 0.68) {
$request = new Net::DNS::Packet(\$buf, 0);
$@ and die $@;
} else {
my $err;
($request, $err) = new Net::DNS::Packet(\$buf, 0);
$err and die $err;
}
my @questions = $request->question;
my $qname = $questions[0]->qname;
my $qtype = $questions[0]->qtype;
my $qclass = $questions[0]->qclass;
my $id = $request->header->id;
my $packet = new Net::DNS::Packet($qname, $qtype, $qclass);
$packet->header->qr(1);
$packet->header->aa(1);
$packet->header->id($id);
my $response_type = getNextResponseType();
my $answers = getAnswerSection($response_type, $qname);
for my $rr (@$answers) {
$packet->push("answer", $rr);
}
print " Sending \"$response_type\" response\n";
return $packet->data if @$answers;
}
my $rin;
my $rout;
for (;;) {
$rin = '';
vec($rin, fileno($ctlsock), 1) = 1;
vec($rin, fileno($tcpsock), 1) = 1;
select($rout = $rin, undef, undef, undef);
if (vec($rout, fileno($ctlsock), 1)) {
my $conn = $ctlsock->accept;
@response_sequence = split(' ', $conn->getline);
$connection_counter = 0;
print "Response sequence set to: @response_sequence\n";
$conn->close;
} elsif (vec($rout, fileno($tcpsock), 1)) {
my $buf;
my $lenbuf;
my $conn = $tcpsock->accept;
my $n = $conn->sysread($lenbuf, 2);
die unless $n == 2;
my $len = unpack("n", $lenbuf);
$n = $conn->sysread($buf, $len);
die unless $n == $len;
print "TCP request\n";
my $response = generateResponse($buf);
if ($response) {
$len = length($response);
$n = $conn->syswrite(pack("n", $len), 2);
$n = $conn->syswrite($response, $len);
print " Sent: $n chars via TCP\n";
} else {
print " No response sent\n";
}
$conn->close;
}
}

View file

@ -0,0 +1,104 @@
# 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 logging
from typing import AsyncGenerator, List, Optional
import dns.rcode
import dns.rdatatype
import dns.rrset
from isctest.asyncserver import (
CloseConnection,
ControlCommand,
ControllableAsyncDnsServer,
DnsResponseSend,
QueryContext,
ResponseAction,
ResponseHandler,
)
class ErraticAxfrHandler(ResponseHandler):
allowed_actions = ["no-response", "partial-axfr", "complete-axfr"]
def __init__(self, actions: List[str]) -> None:
self.actions = actions
self.counter = 0
for action in actions:
assert action in self.allowed_actions
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[ResponseAction, None]:
action = self.actions[self.counter % len(self.actions)]
self.counter += 1
logging.info("current response action: %s", action)
if action == "no-response":
yield CloseConnection()
return
soa_rr = dns.rrset.from_text(
qctx.qname, 300, qctx.qclass, dns.rdatatype.SOA, ". . 0 0 0 0 300"
)
ns_rr = dns.rrset.from_text(qctx.qname, 300, qctx.qclass, dns.rdatatype.NS, ".")
qctx.response.answer.append(soa_rr)
qctx.response.answer.append(ns_rr)
if action == "partial-axfr":
yield DnsResponseSend(qctx.response)
elif action == "complete-axfr":
qctx.response.answer.append(soa_rr)
yield DnsResponseSend(qctx.response)
yield CloseConnection()
class ResponseSequenceCommand(ControlCommand):
control_subdomain = "response-sequence"
def __init__(self) -> None:
self._current_handler: Optional[ResponseHandler] = None
def handle(
self, args: List[str], server: ControllableAsyncDnsServer, qctx: QueryContext
) -> str:
for action in args:
if action not in ErraticAxfrHandler.allowed_actions:
logging.error("invalid %s action '%s'", self, action)
qctx.response.set_rcode(dns.rcode.SERVFAIL)
return f"invalid action '{action}'; must be one of {ErraticAxfrHandler.allowed_actions}"
actions = args
if self._current_handler is not None:
server.uninstall_response_handler(self._current_handler)
self._current_handler = ErraticAxfrHandler(actions)
server.install_response_handler(self._current_handler)
msg = f"reponse sequence set to {actions}"
logging.info(msg)
return msg
def main() -> None:
server = ControllableAsyncDnsServer(
default_aa=True, default_rcode=dns.rcode.NOERROR
)
server.install_control_command(ResponseSequenceCommand())
server.run()
if __name__ == "__main__":
main()

View file

@ -1,84 +0,0 @@
#!/usr/bin/perl -w
# 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.
use IO::File;
use IO::Socket;
use Net::DNS;
use Net::DNS::Packet;
my $localport = int($ENV{'PORT'});
if (!$localport) { $localport = 5300; }
my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.6",
LocalPort => $localport, Proto => "udp") or die "$!";
my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
print $pidf "$$\n" or die "cannot write pid file: $!";
$pidf->close or die "cannot close pid file: $!";
sub rmpid { unlink "ans.pid"; exit 1; };
$SIG{INT} = \&rmpid;
$SIG{TERM} = \&rmpid;
for (;;) {
$sock->recv($buf, 512);
print "**** request from " , $sock->peerhost, " port ", $sock->peerport, "\n";
my $packet;
if ($Net::DNS::VERSION > 0.68) {
$packet = new Net::DNS::Packet(\$buf, 0);
$@ and die $@;
} else {
my $err;
($packet, $err) = new Net::DNS::Packet(\$buf, 0);
$err and die $err;
}
print "REQUEST:\n";
$packet->print;
$packet->header->qr(1);
my @questions = $packet->question;
my $qname = $questions[0]->qname;
my $qtype = $questions[0]->qtype;
my $donotrespond = 0;
$packet->header->aa(1);
if ($qtype eq "A") {
$packet->push("answer",
new Net::DNS::RR($qname . " 300 A 10.53.0.5"));
} else {
$donotrespond = 1;
}
if ($donotrespond == 0) {
my $sendsock =
IO::Socket::INET->new(LocalAddr => "10.53.1.2",
PeerAddr => $sock->peerhost,
PeerPort => $sock->peerport,
Proto => "udp") or die "$!";
print "**** response from ", $sendsock->sockhost, " to " ,
$sendsock->peerhost, " port ", $sendsock->peerport, "\n";
$sendsock->send($packet->data);
$sendsock->close;
print "RESPONSE:\n";
$packet->print;
print "\n";
} else {
print "DROP:\n";
}
}

View file

@ -0,0 +1,40 @@
# 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 dns.opcode
import dns.rcode
from isctest.asyncserver import (
AsyncDnsServer,
DnsResponseSend,
ResponseHandler,
QueryContext,
)
class ReplyUpdateHandler(ResponseHandler):
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[DnsResponseSend, None]:
qctx.response.set_opcode(dns.opcode.UPDATE)
yield DnsResponseSend(qctx.response)
def main() -> None:
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
server.install_response_handler(ReplyUpdateHandler())
server.run()
if __name__ == "__main__":
main()

View file

@ -1,68 +0,0 @@
#!/usr/bin/perl -w
# 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.
use IO::File;
use IO::Socket;
use Net::DNS;
use Net::DNS::Packet;
my $localport = int($ENV{'PORT'});
if (!$localport) { $localport = 5300; }
my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.7",
LocalPort => $localport, Proto => "udp") or die "$!";
my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
print $pidf "$$\n" or die "cannot write pid file: $!";
$pidf->close or die "cannot close pid file: $!";
sub rmpid { unlink "ans.pid"; exit 1; };
$SIG{INT} = \&rmpid;
$SIG{TERM} = \&rmpid;
STDOUT->autoflush(1);
print "Net::DNS::VERSION => $Net::DNS::VERSION\n";
for (;;) {
$sock->recv($buf, 512);
print "**** request from " , $sock->peerhost, " port ", $sock->peerport, "\n";
my $packet;
if ($Net::DNS::VERSION > 0.68) {
$packet = new Net::DNS::Packet(\$buf, 0);
$@ and die $@;
} else {
my $err;
($packet, $err) = new Net::DNS::Packet(\$buf, 0);
$err and die $err;
}
print "REQUEST:\n";
$packet->print;
$packet->header->qr(1);
$packet->header->opcode(5);
my @questions = $packet->question;
my $qname = $questions[0]->qname;
my $qtype = $questions[0]->qtype;
$packet->push("update", rr_del("$qname SOA"));
print "RESPONSE:\n";
$packet->print;
$sock->send($packet->data);
}

View file

@ -0,0 +1,76 @@
# 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 dns
import dns.rcode
from isctest.asyncserver import (
AsyncDnsServer,
CloseConnection,
DnsResponseSend,
DomainHandler,
IgnoreAllQueries,
QueryContext,
ResponseAction,
ResponseDrop,
)
class SilentHandler(DomainHandler, IgnoreAllQueries):
"""Handler that doesn't respond."""
domains = ["silent.example"]
class CloseHandler(DomainHandler):
"""Handler that doesn't respond and closes TCP connection."""
domains = ["close.example"]
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[ResponseAction, None]:
yield CloseConnection()
class SilentThenServfailHandler(DomainHandler):
"""Handler that drops one query and response to the next one with SERVFAIL."""
domains = ["silent-then-servfail.example"]
counter = 0
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[ResponseAction, None]:
if self.counter % 2 == 0:
yield ResponseDrop()
else:
qctx.response.set_rcode(dns.rcode.SERVFAIL)
yield DnsResponseSend(qctx.response, authoritative=False)
self.counter += 1
def main() -> None:
server = AsyncDnsServer()
server.install_response_handlers(
[
CloseHandler(),
SilentHandler(),
SilentThenServfailHandler(),
]
)
server.run()
if __name__ == "__main__":
main()

View file

@ -1,202 +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.
from __future__ import print_function
import os
import sys
import signal
import socket
import select
import struct
import dns, dns.message
from dns.rcode import *
modes = [
b"silent", # Do not respond
b"close", # UDP: same as silent; TCP: also close the connection
b"servfail", # Always respond with SERVFAIL
b"unstable", # Constantly switch between "silent" and "servfail"
]
mode = modes[0]
n = 0
def ctrl_channel(msg):
global modes, mode, n
msg = msg.splitlines().pop(0)
print("Received control message: %s" % msg)
if msg in modes:
mode = msg
n = 0
print("New mode: %s" % str(mode))
def create_servfail(msg):
m = dns.message.from_wire(msg)
qname = m.question[0].name.to_text()
rrtype = m.question[0].rdtype
typename = dns.rdatatype.to_text(rrtype)
with open("query.log", "a") as f:
f.write("%s %s\n" % (typename, qname))
print("%s %s" % (typename, qname), end=" ")
r = dns.message.make_response(m)
r.set_rcode(SERVFAIL)
return r
def sigterm(signum, frame):
print("Shutting down now...")
os.remove("ans.pid")
running = False
sys.exit(0)
ip4 = "10.53.0.8"
try:
port = int(os.environ["PORT"])
except:
port = 5300
try:
ctrlport = int(os.environ["EXTRAPORT1"])
except:
ctrlport = 5300
query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_udp.bind((ip4, port))
query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
query4_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
query4_tcp.bind((ip4, port))
query4_tcp.listen(100)
ctrl4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ctrl4_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ctrl4_tcp.bind((ip4, ctrlport))
ctrl4_tcp.listen(100)
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))
print("Listening on %s port %d" % (ip4, ctrlport))
print("Ctrl-c to quit")
input = [query4_udp, query4_tcp, ctrl4_tcp]
hung_conns = []
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_udp:
n = n + 1
print("UDP query received on %s" % ip4, end=" ")
msg = s.recvfrom(65535)
if (
mode == b"silent"
or mode == b"close"
or (mode == b"unstable" and n % 2 == 1)
):
# Do not respond.
print("NO RESPONSE (%s)" % str(mode))
continue
elif mode == b"servfail" or (mode == b"unstable" and n % 2 == 0):
rsp = create_servfail(msg[0])
if rsp:
print(dns.rcode.to_text(rsp.rcode()))
s.sendto(rsp.to_wire(), msg[1])
else:
print("NO RESPONSE (can not create a response)")
else:
raise (Exception("unsupported mode: %s" % mode))
elif s == query4_tcp:
n = n + 1
print("TCP query received on %s" % ip4, end=" ")
conn = None
try:
if mode == b"silent" or (mode == b"unstable" and n % 2 == 1):
conn, addr = s.accept()
# Do not respond and hang the connection.
print("NO RESPONSE (%s)" % str(mode))
hung_conns.append(conn)
continue
elif mode == b"close":
conn, addr = s.accept()
# Do not respond and close the connection.
print("NO RESPONSE (%s)" % str(mode))
conn.close()
continue
elif mode == b"servfail" or (mode == b"unstable" and n % 2 == 0):
conn, addr = s.accept()
# get TCP message length
msg = conn.recv(2)
if len(msg) != 2:
print("NO RESPONSE (can not read the message length)")
conn.close()
continue
length = struct.unpack(">H", msg[:2])[0]
msg = conn.recv(length)
if len(msg) != length:
print("NO RESPONSE (can not read the message)")
conn.close()
continue
rsp = create_servfail(msg)
if rsp:
print(dns.rcode.to_text(rsp.rcode()))
wire = rsp.to_wire()
conn.send(struct.pack(">H", len(wire)))
conn.send(wire)
else:
print("NO RESPONSE (can not create a response)")
else:
raise (Exception("unsupported mode: %s" % mode))
except socket.error as e:
print("NO RESPONSE (error: %s)" % str(e))
if conn:
conn.close()
elif s == ctrl4_tcp:
print("Control channel connected")
conn = None
try:
# Handle control channel input
conn, addr = s.accept()
msg = conn.recv(1024)
if msg:
ctrl_channel(msg)
conn.close()
except s.timeout:
pass
if conn:
conn.close()
if not running:
break

View file

@ -33,6 +33,9 @@ c AAAA fd92:7065:b8e:ffff::3
d A 10.0.0.0
d AAAA fd92:7065:b8e:ffff::
silent A 10.0.0.1
close A 10.0.0.1
xn--caf-dma A 10.1.2.3
foo TXT "testing"

View file

@ -19,10 +19,6 @@ set -e
status=0
n=0
sendcmd() {
send "${1}" "$EXTRAPORT1"
}
dig_with_opts() {
"$DIG" -p "$PORT" "$@"
}
@ -31,6 +27,12 @@ mdig_with_opts() {
"$MDIG" -p "$PORT" "$@"
}
set_response_sequence() {
SEQUENCE="${1}"
LOGID="${2}"
dig_with_opts @10.53.0.5 "${SEQUENCE}.response-sequence._control" TXT >dig.out.control${LOGID} 2>&1 || ret=1
}
# 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
@ -71,72 +73,44 @@ NOSPLIT="$(sed <ns2/keydata -e 's/+/[+]/g' -e 's/ //g')"
HAS_PYYAML=0
$PYTHON -c "import yaml" 2>/dev/null && HAS_PYYAML=1
#
# test whether ans7/ans.pl will be able to send a UPDATE response.
# if it can't, we will log that below.
#
if "$PERL" -e 'use Net::DNS; use Net::DNS::Packet; my $p = new Net::DNS::Packet; $p->header->opcode(5);' >/dev/null 2>&1; then
checkupdate=1
else
checkupdate=0
fi
n=$((n + 1))
echo_i "check nslookup handles UPDATE response ($n)"
ret=0
"$NSLOOKUP" -q=CNAME -timeout=1 "-port=$PORT" foo.bar 10.53.0.6 >nslookup.out.test$n 2>&1 && ret=1
grep "Opcode mismatch" nslookup.out.test$n >/dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
if [ -x "$NSLOOKUP" -a $checkupdate -eq 1 ]; then
n=$((n + 1))
echo_i "check host handles UPDATE response ($n)"
ret=0
"$HOST" -W 1 -t CNAME -p $PORT foo.bar 10.53.0.6 >host.out.test$n 2>&1 && ret=1
grep "Opcode mismatch" host.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 nslookup handles UPDATE response ($n)"
ret=0
"$NSLOOKUP" -q=CNAME -timeout=1 "-port=$PORT" foo.bar 10.53.0.7 >nslookup.out.test$n 2>&1 && ret=1
grep "Opcode mismatch" nslookup.out.test$n >/dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
fi
if [ -x "$HOST" -a $checkupdate -eq 1 ]; then
n=$((n + 1))
echo_i "check host handles UPDATE response ($n)"
ret=0
"$HOST" -W 1 -t CNAME -p $PORT foo.bar 10.53.0.7 >host.out.test$n 2>&1 && ret=1
grep "Opcode mismatch" host.out.test$n >/dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
fi
if [ -x "$NSUPDATE" -a $checkupdate -eq 1 ]; then
n=$((n + 1))
echo_i "check nsupdate handles UPDATE response to QUERY ($n)"
ret=0
res=0
$NSUPDATE <<EOF >nsupdate.out.test$n 2>&1 || res=$?
server 10.53.0.7 ${PORT}
n=$((n + 1))
echo_i "check nsupdate handles UPDATE response to QUERY ($n)"
ret=0
res=0
$NSUPDATE <<EOF >nsupdate.out.test$n 2>&1 || res=$?
server 10.53.0.6 ${PORT}
add x.example.com 300 in a 1.2.3.4
send
EOF
test $res -eq 1 || ret=1
grep "invalid OPCODE in response to SOA query" nsupdate.out.test$n >/dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
fi
test $res -eq 1 || ret=1
grep "invalid OPCODE in response to SOA query" nsupdate.out.test$n >/dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
if [ -x "$DIG" ]; then
if [ $checkupdate -eq 1 ]; then
n=$((n + 1))
echo_i "check dig handles UPDATE response ($n)"
ret=0
dig_with_opts @10.53.0.7 +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))
else
echo_i "Skipped UPDATE handling test"
fi
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)"
@ -382,8 +356,6 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "checking dig preserves origin on TCP retries ($n)"
ret=0
# Ask ans4 to still accept TCP connections, but not respond to queries
echo "//" | sendcmd 10.53.0.4
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
@ -1042,7 +1014,7 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "checking exit code for a retry upon TCP EOF (immediate -> immediate) ($n)"
ret=0
echo "no_response no_response" | sendcmd 10.53.0.5
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
@ -1052,7 +1024,7 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "checking exit code for a retry upon TCP EOF (partial AXFR -> partial AXFR) ($n)"
ret=0
echo "partial_axfr partial_axfr" | sendcmd 10.53.0.5
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
@ -1062,7 +1034,7 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "checking exit code for a retry upon TCP EOF (immediate -> partial AXFR) ($n)"
ret=0
echo "no_response partial_axfr" | sendcmd 10.53.0.5
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
@ -1072,7 +1044,7 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "checking exit code for a retry upon TCP EOF (partial AXFR -> immediate) ($n)"
ret=0
echo "partial_axfr no_response" | sendcmd 10.53.0.5
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
@ -1082,7 +1054,7 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "checking exit code for a retry upon TCP EOF (immediate -> complete AXFR) ($n)"
ret=0
echo "no_response complete_axfr" | sendcmd 10.53.0.5
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
@ -1092,7 +1064,7 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "checking exit code for a retry upon TCP EOF (partial AXFR -> complete AXFR) ($n)"
ret=0
echo "partial_axfr complete_axfr" | sendcmd 10.53.0.5
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
@ -1102,7 +1074,7 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "checking +tries=1 won't retry twice upon TCP EOF ($n)"
ret=0
echo "no_response no_response" | sendcmd 10.53.0.5
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
@ -1236,20 +1208,16 @@ if [ -x "$DIG" ]; then
# See [GL #3020] for more information
n=$((n + 1))
echo_i "check that dig handles UDP timeout followed by a SERVFAIL correctly ($n)"
# Ask ans8 to be in "unstable" mode (switching between "silent" and "servfail" modes)
echo "unstable" | sendcmd 10.53.0.8
ret=0
dig_with_opts +timeout=1 +nofail @10.53.0.8 a.example >dig.out.test$n 2>&1 || ret=1
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)"
# Ask ans8 to be in "unstable" mode (switching between "silent" and "servfail" modes)
echo "unstable" | sendcmd 10.53.0.8
ret=0
dig_with_opts +timeout=1 +nofail +tcp @10.53.0.8 a.example >dig.out.test$n 2>&1 || ret=1
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))
@ -1282,10 +1250,8 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "check that dig tries the next server after a TCP socket read error ($n)"
# Ask ans8 to be in "close" mode, which closes the connection after accepting it
echo "close" | sendcmd 10.53.0.8
ret=0
dig_with_opts +tcp @10.53.0.8 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
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))
@ -1307,20 +1273,16 @@ if [ -x "$DIG" ]; then
n=$((n + 1))
echo_i "check that dig tries the next server after UDP socket read timeouts ($n)"
# Ask ans8 to be in "silent" mode
echo "silent" | sendcmd 10.53.0.8
ret=0
dig_with_opts +timeout=1 @10.53.0.8 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
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)"
# Ask ans8 to be in "silent" mode
echo "silent" | sendcmd 10.53.0.8
ret=0
dig_with_opts +timeout=1 +tcp @10.53.0.8 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1
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))
@ -1329,7 +1291,7 @@ if [ -x "$DIG" ]; then
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.8 @::ffff:10.53.0.8 a.example >dig.out.test$n 2>&1 || ret=1
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

View file

@ -11,6 +11,9 @@
import pytest
# isctest.asyncserver requires dnspython >= 2.0.0
pytest.importorskip("dns", minversion="2.0.0")
pytestmark = pytest.mark.extra_artifacts(
[
"delv.out.*",
@ -20,7 +23,6 @@ pytestmark = pytest.mark.extra_artifacts(
"nsupdate.out.*",
"yamlget.out.*",
"ans*/ans.run",
"ans*/query.log",
"ns*/anchor.*",
"ns*/dsset-*",
"ns*/keydata",

View file

@ -429,10 +429,9 @@ class _ConnectionTeardownRequested(Exception):
@dataclass
class ResponseDropAndCloseConnection(ResponseAction):
class CloseConnection(ResponseAction):
"""
Action which makes the server close the connection after the DNS query is
received by the server (TCP only).
Action which makes the server close the connection (TCP only).
The connection may be closed with a delay if requested.
"""
@ -555,8 +554,8 @@ class ConnectionReset(ConnectionHandler):
make the server send an RST segment; this happens when the server closes a
client's socket while there is still unread data in that socket's buffer.
If closing the connection _after_ the query is read by the server is enough
for a given use case, the ResponseDropAndCloseConnection response handler
should be used instead.
for a given use case, the CloseConnection response handler should be used
instead.
"""
delay: float = 0.0