From dd79d5022a6abfda0ac3fc886134afb32b4f1b7f Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Fri, 30 Oct 2026 16:53:53 +0100 Subject: Fix expiry tests under the new accept filters --- tests/accept_filter_probe.py | 110 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 tests/accept_filter_probe.py diff --git a/tests/accept_filter_probe.py b/tests/accept_filter_probe.py new file mode 100644 index 0000000..22f585a --- /dev/null +++ b/tests/accept_filter_probe.py @@ -0,0 +1,110 @@ +"""What a deferred accept does to connections that never send anything. + +Those connections never reach opentracker, so its own client timeout cannot +clean them up: whatever bound exists is the kernel's. This measures it, and +then asks the question that follows from it -- whether enough of them keep +the tracker from answering anybody else. + + make opentracker + python3 tests/accept_filter_probe.py + +On FreeBSD the filter does nothing unless accf_data is loaded, so run it both +ways to see whether the numbers differ: + + kldload accf_data +""" + +import os +import socket +import signal +import subprocess +import sys +import tempfile +import time +import urllib.error +import urllib.request + +TRACKER = os.environ.get("OPENTRACKER", "./opentracker") +IDLE = int(os.environ.get("IDLE", "300")) +PATIENCE = float(os.environ.get("PATIENCE", "90")) +HASH = "".join("%%%02x" % b for b in range(1, 21)) + + +def start(port): + directory = tempfile.mkdtemp(prefix="ot-accf-") + with open(os.path.join(directory, "conf"), "w") as handle: + handle.write("listen.tcp 127.0.0.1:%d\nlisten.udp 127.0.0.1:%d\n" % (port, port)) + log = open(os.path.join(directory, "log"), "w+b") + tracker = subprocess.Popen([TRACKER, "-f", os.path.join(directory, "conf")], stdout=log, stderr=log) + for _ in range(300): + try: + socket.create_connection(("127.0.0.1", port), timeout=0.2).close() + return tracker, log + except OSError: + time.sleep(0.02) + tracker.kill() + raise SystemExit("tracker never listened on %d" % port) + + +def announce(port, timeout=5): + """One ordinary request, so we can see whether the tracker still serves.""" + started = time.time() + try: + urllib.request.urlopen( + "http://127.0.0.1:%d/announce?info_hash=%s&peer_id=-OT0001-probe0000001&port=6881&left=0" % (port, HASH), + timeout=timeout, + ).read() + return time.time() - started, None + except Exception as problem: # noqa: BLE001 - reported, not swallowed + return time.time() - started, problem + + +def main(): + port = int(os.environ.get("PORT", "28000")) + tracker, log = start(port) + try: + print(" how long a connection that says nothing is held") + quiet = socket.create_connection(("127.0.0.1", port), timeout=5) + quiet.settimeout(PATIENCE) + started = time.time() + try: + held = "dropped after %.1f s" % (time.time() - started) if quiet.recv(1) == b"" else "sent us something" + except socket.timeout: + held = "still open after %.0f s" % PATIENCE + except OSError as problem: + held = "ended after %.1f s: %s" % (time.time() - started, problem) + quiet.close() + print(" %s" % held) + + print(" and whether %d of them keep anybody else out" % IDLE) + before, problem = announce(port) + print(" a request before: %.0f ms%s" % (before * 1000, "" if problem is None else " (%s)" % problem)) + + idlers = [] + for _ in range(IDLE): + try: + idlers.append(socket.create_connection(("127.0.0.1", port), timeout=2)) + except OSError as problem: + print(" could only open %d of them: %s" % (len(idlers), problem)) + break + print(" holding %d connections that have sent nothing" % len(idlers)) + + during, problem = announce(port, timeout=10) + print(" a request during: %.0f ms%s" % (during * 1000, "" if problem is None else " REFUSED: %s" % problem)) + + for idler in idlers: + idler.close() + after, problem = announce(port) + print(" a request after: %.0f ms%s" % (after * 1000, "" if problem is None else " (%s)" % problem)) + finally: + tracker.send_signal(signal.SIGINT) + try: + tracker.wait(timeout=10) + except subprocess.TimeoutExpired: + tracker.kill() + tracker.wait() + log.close() + + +if __name__ == "__main__": + main() -- cgit v1.3