mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
[9.20] chg: test: Use isctest.asyncserver in the "nsupdate" test
Reimplement the custom server written in Perl in Python using the AsyncDnsServer class. Backport of MR !10915 Merge branch 'backport-stepan/nsupdate-asyncserver-9.20' into 'bind-9.20' See merge request isc-projects/bind9!11140
This commit is contained in:
commit
14c5e84477
3 changed files with 90 additions and 105 deletions
|
|
@ -414,6 +414,68 @@ class ConnectionHandler(abc.ABC):
|
|||
raise NotImplementedError
|
||||
|
||||
|
||||
def block_reading(peer: Peer, writer_not_the_reader: asyncio.StreamWriter) -> None:
|
||||
"""
|
||||
Block reads for the reader associated with the provided writer.
|
||||
|
||||
Yes, pass the writer, not the reader. See the comments below for details.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Python >= 3.7
|
||||
loop = asyncio.get_running_loop()
|
||||
except AttributeError:
|
||||
# Python < 3.7
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
logging.info("Blocking reads from %s", peer)
|
||||
|
||||
# This is Michał's submission for the Ugliest Hack of the Year contest.
|
||||
# (The alternative was implementing an asyncio transport from scratch.)
|
||||
#
|
||||
# In order to prevent the client socket from being read from, simply
|
||||
# not calling `reader.read()` is not enough, because asyncio buffers
|
||||
# incoming data itself on the transport level. However, `StreamReader`
|
||||
# does not expose the underlying transport as a property. Therefore,
|
||||
# cheat by extracting it from `StreamWriter` as it is the same
|
||||
# bidirectional transport as for the read side (a `Transport`, which is
|
||||
# a subclass of both `ReadTransport` and `WriteTransport`) and call
|
||||
# `ReadTransport.pause_reading()` to remove the underlying socket from
|
||||
# the set of descriptors monitored by the selector, thereby preventing
|
||||
# any reads from happening on the client socket. However...
|
||||
loop.call_soon(writer_not_the_reader.transport.pause_reading) # type: ignore
|
||||
|
||||
# ...due to `AsyncDnsServer._handle_tcp()` being a coroutine, by the
|
||||
# time it gets executed, asyncio transport code will already have added
|
||||
# the client socket to the set of descriptors monitored by the
|
||||
# selector. Therefore, if the client starts sending data immediately,
|
||||
# a read from the socket will have already been scheduled by the time
|
||||
# this handler gets executed. There is no way to prevent that from
|
||||
# happening, so work around it by abusing the fact that the transport
|
||||
# at hand is specifically an instance of `_SelectorSocketTransport`
|
||||
# (from asyncio.selector_events) and set the size of its read buffer to
|
||||
# just a single byte. This does give asyncio enough time to read that
|
||||
# single byte from the client socket's buffer before that socket is
|
||||
# removed from the set of monitored descriptors, but prevents the
|
||||
# one-off read from emptying the client socket buffer _entirely_, which
|
||||
# is enough to trigger sending an RST segment when the connection is
|
||||
# closed shortly afterwards.
|
||||
writer_not_the_reader.transport.max_size = 1 # type: ignore
|
||||
|
||||
|
||||
@dataclass
|
||||
class IgnoreAllConnections(ConnectionHandler):
|
||||
"""
|
||||
A connection handler that makes the server not read anything from the
|
||||
client socket, effectively ignoring all incoming connections.
|
||||
"""
|
||||
|
||||
async def handle(
|
||||
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, peer: Peer
|
||||
) -> None:
|
||||
block_reading(peer, writer)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConnectionReset(ConnectionHandler):
|
||||
"""
|
||||
|
|
@ -435,46 +497,7 @@ class ConnectionReset(ConnectionHandler):
|
|||
async def handle(
|
||||
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, peer: Peer
|
||||
) -> None:
|
||||
try:
|
||||
# Python >= 3.7
|
||||
loop = asyncio.get_running_loop()
|
||||
except AttributeError:
|
||||
# Python < 3.7
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
logging.info("Blocking reads from %s", peer)
|
||||
|
||||
# This is Michał's submission for the Ugliest Hack of the Year contest.
|
||||
# (The alternative was implementing an asyncio transport from scratch.)
|
||||
#
|
||||
# In order to prevent the client socket from being read from, simply
|
||||
# not calling `reader.read()` is not enough, because asyncio buffers
|
||||
# incoming data itself on the transport level. However, `StreamReader`
|
||||
# does not expose the underlying transport as a property. Therefore,
|
||||
# cheat by extracting it from `StreamWriter` as it is the same
|
||||
# bidirectional transport as for the read side (a `Transport`, which is
|
||||
# a subclass of both `ReadTransport` and `WriteTransport`) and call
|
||||
# `ReadTransport.pause_reading()` to remove the underlying socket from
|
||||
# the set of descriptors monitored by the selector, thereby preventing
|
||||
# any reads from happening on the client socket. However...
|
||||
loop.call_soon(writer.transport.pause_reading) # type: ignore
|
||||
|
||||
# ...due to `AsyncDnsServer._handle_tcp()` being a coroutine, by the
|
||||
# time it gets executed, asyncio transport code will already have added
|
||||
# the client socket to the set of descriptors monitored by the
|
||||
# selector. Therefore, if the client starts sending data immediately,
|
||||
# a read from the socket will have already been scheduled by the time
|
||||
# this handler gets executed. There is no way to prevent that from
|
||||
# happening, so work around it by abusing the fact that the transport
|
||||
# at hand is specifically an instance of `_SelectorSocketTransport`
|
||||
# (from asyncio.selector_events) and set the size of its read buffer to
|
||||
# just a single byte. This does give asyncio enough time to read that
|
||||
# single byte from the client socket's buffer before that socket is
|
||||
# removed from the set of monitored descriptors, but prevents the
|
||||
# one-off read from emptying the client socket buffer _entirely_, which
|
||||
# is enough to trigger sending an RST segment when the connection is
|
||||
# closed shortly afterwards.
|
||||
writer.transport.max_size = 1 # type: ignore
|
||||
block_reading(peer, writer)
|
||||
|
||||
if self.delay > 0:
|
||||
logging.info(
|
||||
|
|
|
|||
|
|
@ -1,65 +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.
|
||||
|
||||
use IO::Socket;
|
||||
use IO::File;
|
||||
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.4";
|
||||
if (@ARGV > 0) {
|
||||
$server_addr = @ARGV[0];
|
||||
}
|
||||
|
||||
my $localport = int($ENV{'PORT'});
|
||||
if (!$localport) { $localport = 5300; }
|
||||
|
||||
my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
|
||||
LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!";
|
||||
my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
|
||||
LocalPort => $localport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
|
||||
|
||||
print "listening on $server_addr:$localport.\n";
|
||||
|
||||
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;
|
||||
|
||||
# Main
|
||||
for (;;) {
|
||||
my $rin;
|
||||
my $rout;
|
||||
|
||||
$rin = '';
|
||||
vec($rin, fileno($udpsock), 1) = 1;
|
||||
vec($rin, fileno($tcpsock), 1) = 1;
|
||||
|
||||
select($rout = $rin, undef, undef, undef);
|
||||
|
||||
if (vec($rout, fileno($udpsock), 1)) {
|
||||
printf "UDP request\n";
|
||||
my $buf;
|
||||
$udpsock->recv($buf, 512);
|
||||
} elsif (vec($rout, fileno($tcpsock), 1)) {
|
||||
printf "TCP request\n";
|
||||
}
|
||||
}
|
||||
27
bin/tests/system/nsupdate/ans4/ans.py
Normal file
27
bin/tests/system/nsupdate/ans4/ans.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# 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,
|
||||
IgnoreAllConnections,
|
||||
IgnoreAllQueries,
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer()
|
||||
server.install_connection_handler(IgnoreAllConnections())
|
||||
server.install_response_handler(IgnoreAllQueries())
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue