From d3a761baf568e390598ca1b6fb418a3fbde847e7 Mon Sep 17 00:00:00 2001 From: D Stephenson Date: Tue, 12 May 2026 20:04:27 +0000 Subject: [PATCH] Limit scan concurrency: serialise logins, cap at 5 sessions Only one SSH handshake/auth runs at a time via a module-level semaphore to avoid RADIUS lockout. Up to 5 sessions can remain open concurrently (down from 10) while commands run in parallel after login. Co-Authored-By: Claude Sonnet 4.6 --- ssh_client.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ssh_client.py b/ssh_client.py index 9e34bbb..b453077 100644 --- a/ssh_client.py +++ b/ssh_client.py @@ -1,6 +1,7 @@ # ssh_client.py - SSH connections to FS, HP/Aruba ProCurve, and Dell switches import re import logging +import threading from concurrent.futures import ThreadPoolExecutor, as_completed from netmiko import ConnectHandler, NetmikoTimeoutException, NetmikoAuthenticationException from config import SSH_USERNAME, SSH_PASSWORD, SSH_PORT, SSH_TIMEOUT, DEVICE_TYPE @@ -10,6 +11,9 @@ from parser import (parse_lldp_neighbors, parse_mgmt_ip_from_interfaces, logger = logging.getLogger(__name__) +# Serialise SSH logins — only one handshake/auth at a time to avoid RADIUS lockout +_login_lock = threading.Semaphore(1) + def connect_and_query(ip): device = { @@ -36,9 +40,11 @@ def connect_and_query(ip): "ecdsa-sha2-nistp521", "ssh-ed25519", ) - with ConnectHandler(**device) as conn: + with _login_lock: + conn = ConnectHandler(**device) _pt.Transport._preferred_keys = _orig_preferred_keys + try: hostname = conn.find_prompt().replace('#', '').replace('>', '').strip() version_output = conn.send_command("show version", read_timeout=30) vendor = _detect_vendor(version_output) @@ -47,6 +53,8 @@ def connect_and_query(ip): chassis_id, mgmt_ip, model, firmware, neighbors = _query_aruba(conn, version_output, ip) else: chassis_id, mgmt_ip, model, firmware, neighbors = _query_fs(conn, version_output, ip) + finally: + conn.disconnect() logger.info(f" {hostname} ({ip}) [{vendor}]: {len(neighbors)} neighbors") @@ -167,7 +175,7 @@ def _aruba_firmware(version_output): # ── Scan orchestration ──────────────────────────────────────────────────────── -def scan_all_switches(ip_list, progress_callback=None, max_workers=10): +def scan_all_switches(ip_list, progress_callback=None, max_workers=5): results = [] total = len(ip_list) done = 0