mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
nginx ends a proxied websocket that sends nothing for 60 seconds, and the VNC console sends nothing while the VM's screen stays the same: an idle console, an installer's progress screen, closed after a minute and had to be connected again. novncd now pings the browser every 30 seconds. The SSH tunnel of an SSH compute sends a keepalive as often, for a firewall on the way that ends idle connections.
61 lines
2.3 KiB
Python
61 lines
2.3 KiB
Python
"""The VNC console of an SSH compute, carried by the SSH connection: `ssh -W`
|
|
forwards its standard input and output to the console's address or Unix
|
|
socket on the compute, so the compute needs no nc and runs no shell."""
|
|
|
|
import ipaddress
|
|
import socket
|
|
import subprocess
|
|
import tempfile
|
|
|
|
|
|
def tunnel_target(address, port, unix_socket):
|
|
"""Where on the compute ssh forwards to: the Unix socket, or the address
|
|
and port, a wildcard or missing address meaning the compute's loopback."""
|
|
if unix_socket:
|
|
return unix_socket
|
|
address = {None: "127.0.0.1", "": "127.0.0.1", "0.0.0.0": "127.0.0.1", "::": "::1"}.get(address, address)
|
|
try:
|
|
if ipaddress.ip_address(address).version == 6:
|
|
address = f"[{address}]"
|
|
except ValueError: # a host name
|
|
pass
|
|
return f"{address}:{port}"
|
|
|
|
|
|
class Tunnel:
|
|
"""The ssh process that carries a console, and its error output, kept in
|
|
a file: a pipe nobody reads while the console runs would stop ssh once
|
|
it filled."""
|
|
|
|
def __init__(self, argv, sock):
|
|
self.errors = tempfile.TemporaryFile()
|
|
self.process = subprocess.Popen(argv, stdin=sock, stdout=sock, stderr=self.errors)
|
|
|
|
def close(self):
|
|
"""End ssh, killed if it does not stop within 5 seconds; its error
|
|
output if it failed, else ""."""
|
|
if self.process.poll() is None:
|
|
self.process.terminate()
|
|
try:
|
|
self.process.wait(5)
|
|
except subprocess.TimeoutExpired:
|
|
self.process.kill()
|
|
self.process.wait()
|
|
self.errors.seek(0)
|
|
error = self.errors.read().decode(errors="replace").strip()
|
|
self.errors.close()
|
|
return error if self.process.returncode not in (0, -15) else ""
|
|
|
|
|
|
def open_tunnel(host, user, target):
|
|
"""A socket connected to target on host, and the Tunnel carrying it.
|
|
ssh never asks for anything: a missing key or an unknown host key fails."""
|
|
ours, theirs = socket.socketpair()
|
|
# ServerAliveInterval: a console whose screen stays the same sends nothing,
|
|
# and a firewall on the way may end an idle connection
|
|
argv = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-o", "ServerAliveInterval=30", "-W", target]
|
|
if user:
|
|
argv += ["-l", user]
|
|
tunnel = Tunnel(argv + ["--", host], theirs)
|
|
theirs.close()
|
|
return ours, tunnel
|