"""rustdesk-server — rendezvous/relay port presence (the recipe's whole purpose). The stock compose publishes hbbs's and hbbr's ports mode:host on the cc-ci node, so these assertions run on-host against 127.0.0.1. A TCP accept on each port is the minimal real signal that the corresponding listener is bound; the protocol layers above them are asserted by the websocket-handshake and punch-hole tests. """ from __future__ import annotations import socket import time # service -> port map from the stock compose (hbbs: 21115/21116(+udp)/21118, hbbr: 21117/21119) PORTS = { 21115: "hbbs NAT-test", 21116: "hbbs rendezvous (tcp)", 21117: "hbbr relay", 21118: "hbbs websocket", 21119: "hbbr websocket", } def _connect(port: int, timeout: float = 5.0) -> bool: try: with socket.create_connection(("127.0.0.1", port), timeout=timeout): return True except OSError: return False def test_all_ports_listening(meta): """Every stock host-published port accepts a TCP connection (poll: freshly rolled tasks rebind host-mode ports with a short window — mumble's READY_PROBE pattern, §5).""" deadline = time.time() + meta.HTTP_TIMEOUT pending = dict(PORTS) while pending and time.time() < deadline: for port in list(pending): if _connect(port): del pending[port] if pending: time.sleep(3) assert not pending, ( f"ports not listening on 127.0.0.1: {pending} " f"(waited {meta.HTTP_TIMEOUT}s; hbbs/hbbr host-mode binds)" ) def test_rendezvous_port_stably_listening(): """The core rendezvous port 21116 holds 3 consecutive accepts (no flapping rebind).""" for _ in range(3): assert _connect(21116), "hbbs rendezvous 21116 dropped a consecutive connect" time.sleep(1)