host.connections¶
Connection management for remote hosts.
ConnectionManager owns all raw transport connections (SSH, SFTP, FTP, Telnet) for a single remote host. It provides lazy-connect coroutines that create the connection on first call and reuse it thereafter.
When a HopTransport is provided (via the hop parameter), all
connections are routed through the hop’s SSH tunnel:
SSH connections use asyncssh’s native
tunnelparameter.Telnet connections use SSH local port forwarding to reach the target through the tunnel.
SFTP piggybacks on the (already tunneled) SSH connection.
FTP uses
TunneledFtpClient, which forwards the control port and dynamically forwards each PASV data port through the tunnel.Netcat transfers use
forward_portto reach the remotenclistener through the tunnel (both PUT and GET directions).
Inject a subclass via RemoteHost._connection_factory to replace the real
transport with a test double — no monkeypatching of library functions needed.
- class otto.host.connections.TunneledFtpClient(hop, dest_host, **kwargs)¶
Bases:
Clientaioftp Client that routes FTP data connections through an SSH hop.
FTP passive mode announces dynamic data ports via PASV responses. This subclass intercepts each data connection attempt and creates a corresponding SSH port forward so the data flows through the tunnel alongside the control connection.
The control connection (port 21) is already forwarded by
ConnectionManagerbeforeconnect()is called, so the tunnel override is only activated after the control connection is established.
-
class otto.host.connections.ConnectionManager(ip, creds, user, term, name, hop=
None, ssh_options=None, telnet_options=None, sftp_options=None, ftp_options=None)¶ Bases:
objectOwns all raw transport connections for a single remote host.
Connections are created lazily and reused across calls. Call
close()to release all open connections.When a
HopTransportis provided (via the hop parameter), an SSH tunnel to the hop host is established lazily on first use. All protocol connections are then routed through this tunnel rather than connecting directly to the target IP.Subclass and inject via
RemoteHost._connection_factoryto swap in test doubles without monkeypatching library functions:class FakeConnections(ConnectionManager): def __init__(self, ip, creds, user, term, name): self._ssh_conn = AsyncMock(spec=SSHClientConnection) self._sftp_conn = None self._ftp_conn = None self._telnet_conn = None async def ssh(self): return self._ssh_conn host = RemoteHost(..., _connection_factory=FakeConnections)- property telnet_options : TelnetOptions¶
Expose the stored
TelnetOptionsso callers that build their ownTelnetClient(e.g.SessionManager.open_session) can honor the same configuration.
- property credentials : tuple[str, str]¶
Return the active (username, password) pair.
- property ip : str¶
- property term : 'ssh' | 'telnet'¶
- property connected : bool¶
Whether any raw connection is currently open.
- property has_tunnel : bool¶
Whether this connection manager is configured to use a tunnel.
- async ssh()¶
Return the live SSH connection, opening it if needed.
- Return type:¶
SSHClientConnection
- async sftp()¶
Return the live SFTP client, opening it (and SSH if needed) first.
- Return type:¶
SFTPClient
- async telnet()¶
Return the live TelnetClient, opening it if needed.
Telnet has no channel multiplexing — the underlying TCP connection and the TelnetClient are 1:1, so when a TelnetSession built on this client closes its writer (or the peer closes the connection), the cached client becomes stale. Rechecking
alivehere catches that case and reconnects, rather than handing back a dead client.- Return type:¶
- async forward_port(dest_port)¶
Forward a local ephemeral port to
self._ip:dest_portthrough the tunnel.This is the public interface for protocols (like netcat) that need additional port forwards beyond the standard ones managed internally.
Returns the local port number to connect to.
Raises
RuntimeErrorif no tunnel is configured.- Return type:¶
int