fix: harden tcp bridge reconnect handling
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Simple TCP debug server for validating raw payload forwarding.
|
||||
|
||||
Usage examples:
|
||||
python tools/tcp_debug_server.py --host 0.0.0.0 --port 8081
|
||||
python tools/tcp_debug_server.py --port 8081 --echo
|
||||
|
||||
Features:
|
||||
- Listen as a plain TCP server
|
||||
- Print connect/disconnect events
|
||||
- Print received payload as text and hex
|
||||
- Optional echo mode
|
||||
- Optional stdin -> socket sender for manual testing
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import select
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def ts() -> str:
|
||||
return datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
||||
|
||||
|
||||
def hex_bytes(data: bytes) -> str:
|
||||
return " ".join(f"{b:02X}" for b in data)
|
||||
|
||||
|
||||
def text_view(data: bytes) -> str:
|
||||
return "".join(chr(b) if 32 <= b < 127 else "." for b in data)
|
||||
|
||||
|
||||
def sender_loop(conn: socket.socket, stop_event: threading.Event) -> None:
|
||||
print(f"[{ts()}] stdin sender ready. Type text and press Enter to send.")
|
||||
while not stop_event.is_set():
|
||||
line = sys.stdin.readline()
|
||||
if line == "":
|
||||
stop_event.set()
|
||||
break
|
||||
payload = line.encode("utf-8", errors="replace")
|
||||
try:
|
||||
conn.sendall(payload)
|
||||
print(
|
||||
f"[{ts()}] TX {len(payload)} bytes | text={payload!r} | hex={hex_bytes(payload)}"
|
||||
)
|
||||
except OSError as exc:
|
||||
print(f"[{ts()}] TX failed: {exc}")
|
||||
stop_event.set()
|
||||
break
|
||||
|
||||
|
||||
def serve(host: str, port: int, echo: bool, no_stdin: bool) -> int:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind((host, port))
|
||||
server.listen(1)
|
||||
print(f"[{ts()}] Listening on {host}:{port}")
|
||||
|
||||
conn, addr = server.accept()
|
||||
with conn:
|
||||
print(f"[{ts()}] Client connected from {addr[0]}:{addr[1]}")
|
||||
stop_event = threading.Event()
|
||||
sender_thread = None
|
||||
|
||||
if not no_stdin:
|
||||
sender_thread = threading.Thread(
|
||||
target=sender_loop, args=(conn, stop_event), daemon=True
|
||||
)
|
||||
sender_thread.start()
|
||||
|
||||
conn.setblocking(False)
|
||||
|
||||
while not stop_event.is_set():
|
||||
ready, _, _ = select.select([conn], [], [], 0.2)
|
||||
if not ready:
|
||||
continue
|
||||
|
||||
try:
|
||||
data = conn.recv(4096)
|
||||
except BlockingIOError:
|
||||
continue
|
||||
except OSError as exc:
|
||||
print(f"[{ts()}] RX failed: {exc}")
|
||||
break
|
||||
|
||||
if not data:
|
||||
print(f"[{ts()}] Client disconnected")
|
||||
break
|
||||
|
||||
print(
|
||||
f"[{ts()}] RX {len(data)} bytes | text={text_view(data)} | hex={hex_bytes(data)}"
|
||||
)
|
||||
|
||||
if echo:
|
||||
try:
|
||||
conn.sendall(data)
|
||||
print(f"[{ts()}] ECHO {len(data)} bytes")
|
||||
except OSError as exc:
|
||||
print(f"[{ts()}] Echo failed: {exc}")
|
||||
break
|
||||
|
||||
stop_event.set()
|
||||
if sender_thread is not None:
|
||||
sender_thread.join(timeout=0.5)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Minimal raw TCP debug server")
|
||||
parser.add_argument(
|
||||
"--host", default="0.0.0.0", help="Listen host, default: 0.0.0.0"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port", type=int, default=8081, help="Listen port, default: 8081"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--echo", action="store_true", help="Echo received payload back to client"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-stdin",
|
||||
action="store_true",
|
||||
help="Disable stdin sender thread (receive-only mode)",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
try:
|
||||
return serve(args.host, args.port, args.echo, args.no_stdin)
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n[{ts()}] Stopped by user")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user