From: Štěpán Balážik Date: Thu, 28 Aug 2025 16:10:19 +0000 (+0200) Subject: Enable ignoring TCP connections X-Git-Tag: v9.21.15~44^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4042b805ff77f115b44ed659f8eed1793bc41720;p=thirdparty%2Fbind9.git Enable ignoring TCP connections Add a TCP connection handler, IgnoreAllConnections that allows establishing TCP connection but not reading anything from it. This re-uses the horrible hack from ConnectionReset handler and might break at any point in the future. See the comments and e4078885073a6c5b59729f4313108e3e7637efdb for more details. --- diff --git a/bin/tests/system/isctest/asyncserver.py b/bin/tests/system/isctest/asyncserver.py index 01d79e23511..f25ca4192b0 100644 --- a/bin/tests/system/isctest/asyncserver.py +++ b/bin/tests/system/isctest/asyncserver.py @@ -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(