fix(get_config): socket-level timeout for SSH fallback — covers KEX phase
paramiko banner_timeout/auth_timeout do not cover kex negotiation. Using raw socket with settimeout(8) ensures the entire SSH handshake is bounded, preventing the poll from hanging and timing out.
This commit is contained in:
+31
-34
@@ -592,53 +592,50 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
|
||||
Used as fallback when the REST API does not expose the config endpoint
|
||||
(e.g. HP 2530 / YA firmware with Mocana SSH).
|
||||
|
||||
Uses paramiko directly (not netmiko) to:
|
||||
- Explicitly enable diffie-hellman-group1-sha1 (required by Mocana SSH 6.3)
|
||||
- Keep total blocking time ≤ 10 seconds
|
||||
Uses a raw socket with settimeout() so the timeout covers ALL phases of
|
||||
the SSH handshake (TCP connect, banner, KEX, auth) — not just TCP connect.
|
||||
This is the only reliable way to prevent a hanging KEX from blocking
|
||||
indefinitely inside the poll task.
|
||||
"""
|
||||
import socket as _socket
|
||||
|
||||
import paramiko
|
||||
|
||||
_FALLBACK_TIMEOUT = 8
|
||||
_FALLBACK_TIMEOUT = 8 # seconds — covers the entire SSH session
|
||||
|
||||
# KEX list that includes the old group1-sha1 required by Mocana SSH 6.3
|
||||
_LEGACY_KEX = [
|
||||
"diffie-hellman-group1-sha1",
|
||||
"diffie-hellman-group14-sha1",
|
||||
"diffie-hellman-group14-sha256",
|
||||
"diffie-hellman-group-exchange-sha256",
|
||||
]
|
||||
_LEGACY_DISABLED = {
|
||||
"pubkeys": ["rsa-sha2-256", "rsa-sha2-512"],
|
||||
}
|
||||
|
||||
try:
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
sock = _socket.create_connection(
|
||||
(self.hostname, self.port or 22),
|
||||
timeout=_FALLBACK_TIMEOUT,
|
||||
)
|
||||
sock.settimeout(_FALLBACK_TIMEOUT) # applies to all recv/send including KEX
|
||||
|
||||
# Patch the preferred kex list on the class before connecting so
|
||||
# paramiko will negotiate group1-sha1 with the old Mocana SSH server.
|
||||
orig_kex = paramiko.Transport._preferred_kex
|
||||
try:
|
||||
paramiko.Transport._preferred_kex = tuple(_LEGACY_KEX)
|
||||
client.connect(
|
||||
hostname=self.hostname,
|
||||
port=self.port or 22,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
timeout=_FALLBACK_TIMEOUT,
|
||||
banner_timeout=_FALLBACK_TIMEOUT,
|
||||
auth_timeout=_FALLBACK_TIMEOUT,
|
||||
look_for_keys=False,
|
||||
allow_agent=False,
|
||||
disabled_algorithms=_LEGACY_DISABLED,
|
||||
)
|
||||
finally:
|
||||
paramiko.Transport._preferred_kex = orig_kex
|
||||
transport = paramiko.Transport(sock)
|
||||
transport.get_security_options().kex = _LEGACY_KEX
|
||||
transport.connect(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
)
|
||||
|
||||
_, stdout, _ = client.exec_command("show running-config", timeout=_FALLBACK_TIMEOUT)
|
||||
config = stdout.read().decode("utf-8", errors="replace")
|
||||
client.close()
|
||||
return config
|
||||
channel = transport.open_session()
|
||||
channel.settimeout(_FALLBACK_TIMEOUT)
|
||||
channel.exec_command("show running-config")
|
||||
|
||||
chunks: list[bytes] = []
|
||||
while True:
|
||||
data = channel.recv(4096)
|
||||
if not data:
|
||||
break
|
||||
chunks.append(data)
|
||||
|
||||
transport.close()
|
||||
return b"".join(chunks).decode("utf-8", errors="replace")
|
||||
except Exception as exc:
|
||||
logger.warning("SSH config fallback failed for %s: %s", self.hostname, exc)
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user