feat: Enhance SSH tunnel handling with Unix socket support

Support forwarding Unix sockets in SSH tunnels by extending
`SSHLocalTunnel`. Update logic to detect and handle remote Unix socket
configurations.

Signed-off-by: Sven Sager <akira@narux.de>
This commit is contained in:
2026-08-06 10:00:14 +02:00
parent 23c09949bb
commit 0582925de6
2 changed files with 48 additions and 9 deletions
+35 -1
View File
@@ -16,6 +16,7 @@ from re import search
from threading import Lock from threading import Lock
from uuid import uuid4 from uuid import uuid4
from xmlrpc.client import Binary, ServerProxy, Transport from xmlrpc.client import Binary, ServerProxy, Transport
from configparser import ConfigParser
from PyQt5 import QtCore from PyQt5 import QtCore
import asyncssh import asyncssh
@@ -365,10 +366,12 @@ class ConnectionManager(QtCore.QThread):
ssh_tunnel_server = None ssh_tunnel_server = None
ssh_tunnel_port = 0 ssh_tunnel_port = 0
ssh_tunnel_socket = None
socket.setdefaulttimeout(revpi_settings.timeout) socket.setdefaulttimeout(revpi_settings.timeout)
if revpi_settings.ssh_use_tunnel: if revpi_settings.ssh_use_tunnel:
# We first connect to find out which target to tunnel
ssh_tunnel_server = SSHLocalTunnel( ssh_tunnel_server = SSHLocalTunnel(
revpi_settings.port, revpi_settings.port,
revpi_settings.address, revpi_settings.address,
@@ -377,6 +380,37 @@ class ConnectionManager(QtCore.QThread):
try: try:
ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials(revpi_settings.ssh_user, ssh_pass) ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials(revpi_settings.ssh_user, ssh_pass)
# Check for Unix socket on remote system
try:
stdout, stderr = ssh_tunnel_server.send_cmd("cat /etc/revpipyload/revpipyload.conf")
if stdout:
config = ConfigParser()
config.read_string(stdout)
if config.has_section("XMLRPC"):
bindip = config.get("XMLRPC", "bindip", fallback="").strip()
if bindip == "socket":
ssh_tunnel_socket = "/run/revpipyload/xmlrpc.socket"
elif bindip.startswith("/") or bindip.startswith("./"):
ssh_tunnel_socket = bindip
if ssh_tunnel_socket:
log.debug("Using remote unix socket: %s", ssh_tunnel_socket)
# Forward local port 0 (dynamic) to remote unix socket
ssh_tunnel_server.disconnect()
ssh_tunnel_server = SSHLocalTunnel(
ssh_tunnel_socket,
revpi_settings.address,
revpi_settings.ssh_port
)
ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials(
revpi_settings.ssh_user, ssh_pass
)
else:
log.debug("Using remote TCP socket: %s", bindip)
except Exception as e:
log.warning(f"Could not check remote config for unix socket: {e}")
if getattr(revpi_settings, "ssh_enable_revpipyload", False): if getattr(revpi_settings, "ssh_enable_revpipyload", False):
ssh_tunnel_server.send_cmd("sudo systemctl enable --now revpipyload") ssh_tunnel_server.send_cmd("sudo systemctl enable --now revpipyload")
@@ -723,7 +757,7 @@ def create_server_proxy(revpi_settings: RevPiSettings, ssh_tunnel_port: int = No
:param ssh_tunnel_port: Use this port if an SSH tunnel is already established :param ssh_tunnel_port: Use this port if an SSH tunnel is already established
:return: ServerProxy instance :return: ServerProxy instance
""" """
if revpi_settings.is_unix_socket: if not ssh_tunnel_port and revpi_settings.is_unix_socket:
return ServerProxy("http://localhost", transport=UnixStreamTransport(revpi_settings.address)) return ServerProxy("http://localhost", transport=UnixStreamTransport(revpi_settings.address))
if ssh_tunnel_port: if ssh_tunnel_port:
+13 -8
View File
@@ -18,15 +18,15 @@ log = getLogger("ssh_tunneling")
class SSHLocalTunnel: class SSHLocalTunnel:
def __init__(self, remote_tunnel_port: int, ssh_host: str, ssh_port: int = 22): def __init__(self, remote_target: Union[int, str], ssh_host: str, ssh_port: int = 22):
""" """
Connect to a ssh remote host and tunnel a port to your host. Connect to a ssh remote host and tunnel a port or unix socket to your host.
:param remote_tunnel_port: Port on the remote host to tunnel through ssh :param remote_target: Port or unix socket path on the remote host to tunnel through ssh
:param ssh_host: ssh remote host address :param ssh_host: ssh remote host address
:param ssh_port: ssh remote host port :param ssh_port: ssh remote host port
""" """
self._remote_tunnel_port = remote_tunnel_port self._remote_target = remote_target
self._ssh_host = ssh_host self._ssh_host = ssh_host
self._ssh_port = ssh_port self._ssh_port = ssh_port
@@ -72,10 +72,15 @@ class SSHLocalTunnel:
config=None, # Do not parse local config config=None, # Do not parse local config
) as conn: ) as conn:
self._conn = conn self._conn = conn
# Forward local port 0 (dynamic) to remote 127.0.0.1:remote_tunnel_port # Forward local port 0 (dynamic) to remote target (port or unix socket)
self._server = await conn.forward_local_port( if isinstance(self._remote_target, int):
'127.0.0.1', 0, '127.0.0.1', self._remote_tunnel_port self._server = await conn.forward_local_port(
) '127.0.0.1', 0, '127.0.0.1', self._remote_target
)
else:
self._server = await conn.forward_local_port_to_path(
'127.0.0.1', 0, self._remote_target
)
self._local_tunnel_port = self._server.get_port() self._local_tunnel_port = self._server.get_port()
self._started.set() self._started.set()