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
|
Used as fallback when the REST API does not expose the config endpoint
|
||||||
(e.g. HP 2530 / YA firmware with Mocana SSH).
|
(e.g. HP 2530 / YA firmware with Mocana SSH).
|
||||||
|
|
||||||
Uses paramiko directly (not netmiko) to:
|
Uses a raw socket with settimeout() so the timeout covers ALL phases of
|
||||||
- Explicitly enable diffie-hellman-group1-sha1 (required by Mocana SSH 6.3)
|
the SSH handshake (TCP connect, banner, KEX, auth) — not just TCP connect.
|
||||||
- Keep total blocking time ≤ 10 seconds
|
This is the only reliable way to prevent a hanging KEX from blocking
|
||||||
|
indefinitely inside the poll task.
|
||||||
"""
|
"""
|
||||||
|
import socket as _socket
|
||||||
|
|
||||||
import paramiko
|
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 = [
|
_LEGACY_KEX = [
|
||||||
"diffie-hellman-group1-sha1",
|
"diffie-hellman-group1-sha1",
|
||||||
"diffie-hellman-group14-sha1",
|
"diffie-hellman-group14-sha1",
|
||||||
"diffie-hellman-group14-sha256",
|
"diffie-hellman-group14-sha256",
|
||||||
"diffie-hellman-group-exchange-sha256",
|
|
||||||
]
|
]
|
||||||
_LEGACY_DISABLED = {
|
|
||||||
"pubkeys": ["rsa-sha2-256", "rsa-sha2-512"],
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = paramiko.SSHClient()
|
sock = _socket.create_connection(
|
||||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
(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
|
transport = paramiko.Transport(sock)
|
||||||
# paramiko will negotiate group1-sha1 with the old Mocana SSH server.
|
transport.get_security_options().kex = _LEGACY_KEX
|
||||||
orig_kex = paramiko.Transport._preferred_kex
|
transport.connect(
|
||||||
try:
|
username=self.username,
|
||||||
paramiko.Transport._preferred_kex = tuple(_LEGACY_KEX)
|
password=self.password,
|
||||||
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
|
|
||||||
|
|
||||||
_, stdout, _ = client.exec_command("show running-config", timeout=_FALLBACK_TIMEOUT)
|
channel = transport.open_session()
|
||||||
config = stdout.read().decode("utf-8", errors="replace")
|
channel.settimeout(_FALLBACK_TIMEOUT)
|
||||||
client.close()
|
channel.exec_command("show running-config")
|
||||||
return 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:
|
except Exception as exc:
|
||||||
logger.warning("SSH config fallback failed for %s: %s", self.hostname, exc)
|
logger.warning("SSH config fallback failed for %s: %s", self.hostname, exc)
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
Reference in New Issue
Block a user