Add SSH keepalive and TCP keepalive to prevent silent disconnects
All checks were successful
Build ts3query-proxy / build (push) Successful in 35s

- SSH keepalive every 30s (configurable via SSH_KEEPALIVE_INTERVAL)
- TCP keepalive on client sockets
- Increase pre-auth timeout from 30s to 300s
- Log keepalive settings on startup
This commit is contained in:
joshii 2026-03-16 20:43:44 +01:00
parent 8894916d03
commit 0bf4bf15e9

View file

@ -17,6 +17,10 @@ TS6_SSH_PORT = int(os.environ.get("TS6_SSH_PORT", "10022"))
LISTEN_HOST = "0.0.0.0"
LISTEN_PORT = int(os.environ.get("LISTEN_PORT", "10011"))
# Keepalive settings
SSH_KEEPALIVE_INTERVAL = int(os.environ.get("SSH_KEEPALIVE_INTERVAL", "30"))
SSH_KEEPALIVE_COUNT_MAX = int(os.environ.get("SSH_KEEPALIVE_COUNT_MAX", "3"))
TS3_BANNER = (
b"TS3\n\r"
b"Welcome to the TeamSpeak 3 ServerQuery interface, "
@ -36,6 +40,12 @@ class ClientHandler:
async def handle(self) -> None:
log.info("Client connected from %s", self.addr)
try:
# Enable TCP keepalive on the client socket
sock = self.writer.get_extra_info("socket")
if sock:
import socket
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.writer.write(TS3_BANNER)
await self.writer.drain()
await self._auth_loop()
@ -50,7 +60,7 @@ class ClientHandler:
async def _auth_loop(self) -> None:
while True:
line = await asyncio.wait_for(self.reader.readline(), timeout=30)
line = await asyncio.wait_for(self.reader.readline(), timeout=300)
if not line:
return
cmd = line.decode("utf-8", errors="replace").strip()
@ -98,6 +108,8 @@ class ClientHandler:
username=username,
password=password,
known_hosts=None,
keepalive_interval=SSH_KEEPALIVE_INTERVAL,
keepalive_count_max=SSH_KEEPALIVE_COUNT_MAX,
),
timeout=10,
)
@ -118,7 +130,7 @@ class ClientHandler:
except asyncio.TimeoutError:
break
log.info("SSH session established for %s", self.addr)
log.info("SSH session established for %s (keepalive=%ds)", self.addr, SSH_KEEPALIVE_INTERVAL)
return True
except asyncssh.PermissionDenied:
log.warning("SSH auth failed for %s (bad credentials)", self.addr)
@ -161,7 +173,6 @@ class ClientHandler:
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
for t in pending:
t.cancel()
# Await cancelled tasks to suppress warnings
for t in pending:
try:
await t
@ -201,6 +212,7 @@ async def main() -> None:
server = await asyncio.start_server(handle_client, LISTEN_HOST, LISTEN_PORT)
log.info("TS3 Query Proxy listening on %s:%d", LISTEN_HOST, LISTEN_PORT)
log.info("Forwarding to %s:%d (SSH Query)", TS6_HOST, TS6_SSH_PORT)
log.info("SSH keepalive: interval=%ds, max_count=%d", SSH_KEEPALIVE_INTERVAL, SSH_KEEPALIVE_COUNT_MAX)
async with server:
await server.serve_forever()