Commit graph

26 commits

Author SHA1 Message Date
Michał Kępień
e407888507 Enable resetting TCP connections
Add a TCP connection handler, ConnectionReset, which enables closing TCP
connections without emptying the client socket buffer, causing the
kernel to send an RST segment to the client.  This relies on a horrible
asyncio hack that can break at any point in the future due to abusing
implementation details in the Python Standard Library.  Despite the eye
bleeding this code may cause, the approach it takes was still deemed
preferable to implementing an asyncio transport from scratch just to
enable triggering connection resets.
2025-07-24 13:09:49 +02:00
Štěpán Balážik
b4d53e7287 Add support for TCP connection handlers
Add a new abstract class, ConnectionHandler, instances of which can be
installed on AsyncDnsServer to manipulate TCP connections upon
accepting.
2025-07-24 13:09:49 +02:00
Michał Kępień
06b0800df8 Enable requesting TCP connections to be closed
In response to client queries, AsyncDnsServer users can currently only
make the server either send a reply or silently ignore the query.  In
the case of TCP queries, neither of these actions causes the client's
connection to be closed - the onus of doing that is on the client.
However, in some cases the server may be required to close the
connection on its own, so AsyncDnsServer users need to have some way of
requesting such an action.

Add a new ResponseAction subclass, ResponseDropAndCloseConnection, which
enables AsyncDnsServer users to conveniently request TCP connections to
be closed.  Instead of returning the response to send,
ResponseDropAndCloseConnection raises a custom exception that
AsyncDnsServer._handle_tcp() handles accordingly.
2025-07-24 13:09:49 +02:00
Štěpán Balážik
72ac1fe234 Let queries with TSIG parse in isctest.asyncserver.AsyncDnsServer
Previously, upon receiving a query with TSIG, the server would log
an error and timeout. As there is no way to set up the keyring in the
class anyway (and I believe we don't need it), this commit lets such
queries parse but logs the fact that the query has TSIG.

However, there is a bug [1] in dnspython, which causes `make_response`
and `to_wire` to crash on messages constructed by `from_wire` with
`keyring=False`, so the hack with `message.__class__` is needed to work
around this.

This makes just enough changes for the tsig system test to work with
dnspython >= 2.0.0. On older version the server gives up.

[1] https://github.com/rthalley/dnspython/issues/1205
2025-07-13 10:57:04 +02:00
Michał Kępień
8a562526f6
Force manual DNAME handling to be acknowledged
Adding proper DNAME support to AsyncDnsServer would add complexity to
its code for little gain: DNAME use in custom system test servers is
limited to crafting responses that attempt to trigger bugs in named.

This fact will not be obvious to AsyncDnsServer users as it
automatically loads all zone files it finds and handles CNAME records
like a normal authoritative DNS server would.

Therefore, to prevent surprises:

  - raise an exception whenever DNAME records are found in any of the
    zone files loaded by AsyncDnsServer,

  - add a new optional argument to the AsyncDnsServer constructor that
    enables suppressing this new behavior, enabling zones with DNAME
    records to be loaded anyway.

This enables response handlers to use the DNAME records present in zone
files in arbitrary ways without complicating the "base" code.
2025-05-30 18:08:54 +02:00
Michał Kępień
5110278008
Drop unused AsyncDnsServer constructor argument
The constructor for the AsyncDnsServer class takes a 'load_zones'
argument that is not used anywhere and is not expected to be useful in
the future: zone files are not required for an AsyncDnsServer instance
to start and, if necessary, zone-based answers can be suppressed or
modified by installing a custom response handler.
2025-05-30 18:08:54 +02:00
Michał Kępień
1b8ceec580
Properly handle CNAMEs when preparing responses
dnspython does not treat CNAME records in zone files in any special way;
they are just RRsets belonging to zone nodes.  Process CNAMEs when
preparing zone-based responses just like a normal authoritative DNS
server would.
2025-05-30 18:08:54 +02:00
Michał Kępień
2a9c74546d
Add debug logs for outgoing DNS messages
Since AsyncDnsServer logs incoming DNS messages as seen on the wire, do
the same for the responses sent by the server.
2025-05-30 18:08:54 +02:00
Michał Kępień
92b39f8352
Add control command for toggling response dropping
Implement a reusable control command that makes it possible to
dynamically disable/enable sending responses to clients.  This is a
typical use case for custom DNS servers employed in various BIND 9
system tests.
2025-04-11 09:14:57 -05:00
Michał Kępień
a7e1de716b
Implement control query handling
Some BIND 9 system tests need to dynamically change custom server
behavior at runtime.  Existing custom servers typically use a separate
TCP socket for listening to control commands, which mimics what named
does, but adds extra complexity to the custom server's networking code
for no gain (given the purpose at hand).  There is also no common way of
performing typical runtime actions (like toggling response dropping)
across all custom servers.

Instead of listening on a separate TCP socket in asyncserver.py, make it
detect DNS queries to a "magic" domain ("_control.") on the same port as
the one it uses for receiving "production" DNS traffic.  This enables
query/response logging code to be reused for control traffic, clearly
denotes behavior changes in packet captures, facilitates implementing
commonly used features as reusable chunks of code (by making them "own"
distinct subdomains of the control domain), voids the need for separate
tools sending control commands, and enables using DNS facilities for
returning information to the user (e.g. RCODE for status codes, TXT
records for additional information, etc.).
2025-04-11 09:14:57 -05:00
Michał Kępień
5e71fd081e
Add debug logs for response handler matching
With multiple and/or dynamically managed response handlers at play, it
becomes useful for debugging purposes to know which handler (if any) was
used for preparing each response sent by the server.  Add debug logs
providing that information.  Make class name the default string
representation of each response handler to prettify logs.
2025-04-11 09:14:57 -05:00
Michał Kępień
92b072bff4
Make response handler management more flexible
Extend AsyncDnsServer.install_response_handler() so that the provided
response handler can be inserted at the beginning of the handler list.
This enables installing a response handler that takes priority over all
previously installed handlers.

Add a new method, AsyncDnsServer.uninstall_response_handler(), which
enables removing a previously installed response handler.

Together, these two methods provide full control over the response
handler list at runtime.
2025-04-11 09:14:57 -05:00
Michał Kępień
fd0290c919
Gracefully handle invalid queries
Prevent custom servers based on asyncserver.py from exiting prematurely
due to unhandled exceptions raised as a result of attempting to parse
invalid queries sent by clients.
2025-04-11 09:14:57 -05:00
Michał Kępień
715bd1b667
Fix Python 3.6 StreamWriter compatibility issue
The StreamWriter.wait_closed() method was introduced in Python 3.7, so
attempting to use it with Python 3.6 raises an exception.  This has not
been noticed before because awaiting StreamWriter.wait_closed() is the
last action taken for each TCP connection and unhandled exceptions were
not causing the scripts based on AsyncServer to exit prematurely until
the previous commit.

As per Python documentation [1], awaiting StreamWriter.wait_closed()
after calling StreamWriter.close() is recommended, but not mandatory, so
try to use it if it is available, without taking any fallback action in
case it isn't.

[1] https://docs.python.org/3.13/library/asyncio-stream.html#asyncio.StreamWriter.close
2025-04-11 09:14:57 -05:00
Michał Kępień
ec4c92d9d5
Ensure uncaught exceptions kill custom servers
Uncaught exceptions raised by tasks running on event loops are not
handled by Python's default exception handler, so they do not cause
scripts to die immediately with a non-zero exit code.  Set up an
exception handler for AsyncServer code that makes any uncaught exception
the result of the Future that the top-level coroutine awaits.  This
ensures that any uncaught exceptions cause scripts based on AsyncServer
to immediately exit with an error, enabling the system test framework to
fail tests in which custom servers encounter unforeseen problems.
2025-04-11 09:14:57 -05:00
Michał Kępień
f24a534ff1
Add a response handler for ignoring all queries
Dropping all incoming queries is a typical use case for a custom server
used in BIND 9 system tests.  Add a response handler implementing that
behavior so that it can be reused.
2025-03-25 05:01:34 +01:00
Michał Kępień
75567f86ca
Make response handlers global by default
Instead of requiring each class inheriting from ResponseHandler to
define its match() method, make the latter non-abstract and default to
returning True for all queries.  This will reduce the amount of
boilerplate code in custom servers.
2025-03-25 05:01:34 +01:00
Michał Kępień
575a874582
Handle queries indefinitely on each TCP connection
Instead of closing every incoming TCP connection after handling a single
query, continue receiving queries on each TCP connection until the
client disconnects itself.  When coupled with response dropping, this
enables silently receiving all incoming data, simulating an unresponsive
server.
2025-03-18 16:28:18 +01:00
Michał Kępień
68fe9a5df5
Enable receiving chunked TCP DNS messages
A TCP DNS client may send its queries in chunks, causing
StreamReader.read() to return less data than previously declared by the
client as the DNS message length; even the two-octet DNS message length
itself may be split up into two single-octet transmissions.  Sending
data in chunks is valid client behavior that should not be treated as an
error.  Add a new helper method for reading TCP data in a loop, properly
distinguishing between chunked queries and client disconnections.  Use
the new method for reading all TCP data from clients.
2025-03-18 16:28:18 +01:00
Michał Kępień
8c3f673f37
Extend TCP logging
Emit more log messages from TCP connection handling code and extend
existing ones to improve debuggability of servers using asyncserver.py.
2025-03-18 16:28:18 +01:00
Michał Kępień
748ed4259b
Handle connection resets during reading
A TCP peer may reset the connection at any point, but asyncserver.py
currently only handles connection resets when it is sending data to the
client.  Handle connection resets during reading in the same way.
2025-03-18 16:28:18 +01:00
Michał Kępień
a956947fba
Refactor AsyncDnsServer._handle_tcp()
Split up AsyncDnsServer._handle_tcp() into a set of smaller methods to
improve code readability.
2025-03-18 16:28:18 +01:00
Michał Kępień
e4c3186a7c
Gracefully handle TCP client disconnections
Prevent premature client disconnections during reading from triggering
unhandled exceptions in TCP connection handling code.
2025-03-18 16:28:18 +01:00
Michał Kępień
5764a9d660
Simplify peer address formatting
Add a helper class, Peer, which holds the <host, port> tuple of a
connection endpoint and gets pretty-printed when formatted as a string.
This enables passing instances of this new class directly to logging
functions, eliminating the need for the AsyncDnsServer._format_peer()
helper method.
2025-03-18 16:28:18 +01:00
Nicki Křížek
7639c58c48 Disable too-many/too-few pylint checks
Enforcing pylint standards and default for our test code seems
counter-productive. Since most of the newly added code are tests or is
test-related, encountering these checks rarely make us refactor the code
in other ways and we just disable these checks individually. Code that
is too complex or convoluted will be pointed out in reviews anyways.
2024-10-17 11:01:44 +02:00
Michał Kępień
6c010a5644
Add an async DNS server for use in system tests
Implement a new Python class, AsyncDnsServer, which can be used by
ans.py scripts placed in ansX/ system test subdirectories.  This enables
conveniently starting a feature-limited, non-standards-compliant, custom
DNS server instance.  It can read and serve zone files, but it is also
able to evaluate any user-provided query-processing logic, allowing
query responses to be changed, delayed, or dropped altogether.  These
are all actions commonly taken by custom DNS servers written in Python
that are used in BIND 9 system tests.  Having a single "base"
implementation of such a custom DNS server reduces code duplication,
improving test maintainability.

Co-authored-by: Tom Krizek <tkrizek@isc.org>
2024-03-20 09:22:36 +01:00