fix session handling
This commit is contained in:
@@ -30,7 +30,7 @@ class ScanStatus:
|
||||
class Scanner:
|
||||
"""
|
||||
Main scanning orchestrator for DNSRecon passive reconnaissance.
|
||||
Now provider-agnostic, consuming standardized ProviderResult objects.
|
||||
FIXED: Now preserves session configuration including API keys when clearing graphs.
|
||||
"""
|
||||
|
||||
def __init__(self, session_config=None):
|
||||
@@ -220,13 +220,18 @@ class Scanner:
|
||||
print("Session configuration updated")
|
||||
|
||||
def start_scan(self, target: str, max_depth: int = 2, clear_graph: bool = True, force_rescan_target: Optional[str] = None) -> bool:
|
||||
"""Start a new reconnaissance scan with proper cleanup of previous scans."""
|
||||
"""
|
||||
FIXED: Start a new reconnaissance scan preserving session configuration.
|
||||
Only clears graph data when requested, never destroys session/API keys.
|
||||
"""
|
||||
print(f"=== STARTING SCAN IN SCANNER {id(self)} ===")
|
||||
print(f"Session ID: {self.session_id}")
|
||||
print(f"Initial scanner status: {self.status}")
|
||||
print(f"Clear graph requested: {clear_graph}")
|
||||
print(f"Current providers: {[p.get_name() for p in self.providers]}")
|
||||
self.total_tasks_ever_enqueued = 0
|
||||
|
||||
# **IMPROVED**: More aggressive cleanup of previous scan
|
||||
# FIXED: Improved cleanup of previous scan without destroying session config
|
||||
if self.scan_thread and self.scan_thread.is_alive():
|
||||
print("A previous scan thread is still alive. Forcing termination...")
|
||||
|
||||
@@ -251,15 +256,14 @@ class Scanner:
|
||||
|
||||
if self.scan_thread.is_alive():
|
||||
print("WARNING: Previous scan thread is still alive after 5 seconds")
|
||||
# Continue anyway, but log the issue
|
||||
self.logger.logger.warning("Previous scan thread failed to terminate cleanly")
|
||||
|
||||
# Reset state for new scan with proper forensic logging
|
||||
print("Resetting scanner state for new scan...")
|
||||
# FIXED: Reset scan state but preserve session configuration (API keys, etc.)
|
||||
print("Resetting scanner state for new scan (preserving session config)...")
|
||||
self.status = ScanStatus.IDLE
|
||||
self.stop_event.clear()
|
||||
|
||||
# **NEW**: Clear Redis stop signal explicitly
|
||||
# Clear Redis stop signal explicitly
|
||||
if self.session_id:
|
||||
from core.session_manager import session_manager
|
||||
session_manager.clear_stop_signal(self.session_id)
|
||||
@@ -267,13 +271,14 @@ class Scanner:
|
||||
with self.processing_lock:
|
||||
self.currently_processing.clear()
|
||||
|
||||
# Reset scan-specific state but keep providers and config intact
|
||||
self.task_queue = PriorityQueue()
|
||||
self.target_retries.clear()
|
||||
self.scan_failed_due_to_retries = False
|
||||
|
||||
# Update session state immediately for GUI feedback
|
||||
self._update_session_state()
|
||||
print("Scanner state reset complete.")
|
||||
print("Scanner state reset complete (providers preserved).")
|
||||
|
||||
try:
|
||||
if not hasattr(self, 'providers') or not self.providers:
|
||||
@@ -282,9 +287,12 @@ class Scanner:
|
||||
|
||||
print(f"Scanner {id(self)} validation passed, providers available: {[p.get_name() for p in self.providers]}")
|
||||
|
||||
# FIXED: Only clear graph if explicitly requested, don't destroy session
|
||||
if clear_graph:
|
||||
print("Clearing graph data (preserving session configuration)")
|
||||
self.graph.clear()
|
||||
|
||||
# Handle force rescan by clearing provider states for that specific node
|
||||
if force_rescan_target and self.graph.graph.has_node(force_rescan_target):
|
||||
print(f"Forcing rescan of {force_rescan_target}, clearing provider states.")
|
||||
node_data = self.graph.graph.nodes[force_rescan_target]
|
||||
@@ -304,7 +312,7 @@ class Scanner:
|
||||
# Update GUI with scan preparation state
|
||||
self._update_session_state()
|
||||
|
||||
# Start new forensic session
|
||||
# Start new forensic session (but don't reinitialize providers)
|
||||
print(f"Starting new forensic session for scanner {id(self)}...")
|
||||
self.logger = new_session()
|
||||
|
||||
@@ -318,6 +326,7 @@ class Scanner:
|
||||
self.scan_thread.start()
|
||||
|
||||
print(f"=== SCAN STARTED SUCCESSFULLY IN SCANNER {id(self)} ===")
|
||||
print(f"Active providers for this scan: {[p.get_name() for p in self.providers]}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -12,7 +12,8 @@ from config import config
|
||||
|
||||
class SessionManager:
|
||||
"""
|
||||
Manages multiple scanner instances for concurrent user sessions using Redis.
|
||||
FIXED: Manages multiple scanner instances for concurrent user sessions using Redis.
|
||||
Now more conservative about session creation to preserve API keys and configuration.
|
||||
"""
|
||||
|
||||
def __init__(self, session_timeout_minutes: int = 0):
|
||||
@@ -24,7 +25,10 @@ class SessionManager:
|
||||
|
||||
self.redis_client = redis.StrictRedis(db=0, decode_responses=False)
|
||||
self.session_timeout = session_timeout_minutes * 60 # Convert to seconds
|
||||
self.lock = threading.Lock() # Lock for local operations, Redis handles atomic ops
|
||||
self.lock = threading.Lock()
|
||||
|
||||
# FIXED: Add a creation lock to prevent race conditions
|
||||
self.creation_lock = threading.Lock()
|
||||
|
||||
# Start cleanup thread
|
||||
self.cleanup_thread = threading.Thread(target=self._cleanup_loop, daemon=True)
|
||||
@@ -36,7 +40,7 @@ class SessionManager:
|
||||
"""Prepare SessionManager for pickling."""
|
||||
state = self.__dict__.copy()
|
||||
# Exclude unpickleable attributes - Redis client and threading objects
|
||||
unpicklable_attrs = ['lock', 'cleanup_thread', 'redis_client']
|
||||
unpicklable_attrs = ['lock', 'cleanup_thread', 'redis_client', 'creation_lock']
|
||||
for attr in unpicklable_attrs:
|
||||
if attr in state:
|
||||
del state[attr]
|
||||
@@ -49,6 +53,7 @@ class SessionManager:
|
||||
import redis
|
||||
self.redis_client = redis.StrictRedis(db=0, decode_responses=False)
|
||||
self.lock = threading.Lock()
|
||||
self.creation_lock = threading.Lock()
|
||||
self.cleanup_thread = threading.Thread(target=self._cleanup_loop, daemon=True)
|
||||
self.cleanup_thread.start()
|
||||
|
||||
@@ -62,44 +67,106 @@ class SessionManager:
|
||||
|
||||
def create_session(self) -> str:
|
||||
"""
|
||||
Create a new user session and store it in Redis.
|
||||
FIXED: Create a new user session with thread-safe creation to prevent duplicates.
|
||||
"""
|
||||
session_id = str(uuid.uuid4())
|
||||
print(f"=== CREATING SESSION {session_id} IN REDIS ===")
|
||||
|
||||
try:
|
||||
from core.session_config import create_session_config
|
||||
session_config = create_session_config()
|
||||
scanner_instance = Scanner(session_config=session_config)
|
||||
# FIXED: Use creation lock to prevent race conditions
|
||||
with self.creation_lock:
|
||||
session_id = str(uuid.uuid4())
|
||||
print(f"=== CREATING SESSION {session_id} IN REDIS ===")
|
||||
|
||||
# Set the session ID on the scanner for cross-process stop signal management
|
||||
scanner_instance.session_id = session_id
|
||||
try:
|
||||
from core.session_config import create_session_config
|
||||
session_config = create_session_config()
|
||||
scanner_instance = Scanner(session_config=session_config)
|
||||
|
||||
# Set the session ID on the scanner for cross-process stop signal management
|
||||
scanner_instance.session_id = session_id
|
||||
|
||||
session_data = {
|
||||
'scanner': scanner_instance,
|
||||
'config': session_config,
|
||||
'created_at': time.time(),
|
||||
'last_activity': time.time(),
|
||||
'status': 'active'
|
||||
}
|
||||
|
||||
# Serialize the entire session data dictionary using pickle
|
||||
serialized_data = pickle.dumps(session_data)
|
||||
|
||||
# Store in Redis
|
||||
session_key = self._get_session_key(session_id)
|
||||
self.redis_client.setex(session_key, self.session_timeout, serialized_data)
|
||||
|
||||
# Initialize stop signal as False
|
||||
stop_key = self._get_stop_signal_key(session_id)
|
||||
self.redis_client.setex(stop_key, self.session_timeout, b'0')
|
||||
|
||||
print(f"Session {session_id} stored in Redis with stop signal initialized")
|
||||
print(f"Session has {len(scanner_instance.providers)} providers: {[p.get_name() for p in scanner_instance.providers]}")
|
||||
return session_id
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to create session {session_id}: {e}")
|
||||
raise
|
||||
|
||||
def clone_session_preserving_config(self, source_session_id: str) -> str:
|
||||
"""
|
||||
FIXED: Create a new session that preserves the configuration (including API keys) from an existing session.
|
||||
This is used when we need a fresh scanner but want to keep user configuration.
|
||||
"""
|
||||
with self.creation_lock:
|
||||
print(f"=== CLONING SESSION {source_session_id} (PRESERVING CONFIG) ===")
|
||||
|
||||
session_data = {
|
||||
'scanner': scanner_instance,
|
||||
'config': session_config,
|
||||
'created_at': time.time(),
|
||||
'last_activity': time.time(),
|
||||
'status': 'active'
|
||||
}
|
||||
|
||||
# Serialize the entire session data dictionary using pickle
|
||||
serialized_data = pickle.dumps(session_data)
|
||||
|
||||
# Store in Redis
|
||||
session_key = self._get_session_key(session_id)
|
||||
self.redis_client.setex(session_key, self.session_timeout, serialized_data)
|
||||
|
||||
# Initialize stop signal as False
|
||||
stop_key = self._get_stop_signal_key(session_id)
|
||||
self.redis_client.setex(stop_key, self.session_timeout, b'0')
|
||||
|
||||
print(f"Session {session_id} stored in Redis with stop signal initialized")
|
||||
return session_id
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to create session {session_id}: {e}")
|
||||
raise
|
||||
try:
|
||||
# Get the source session data
|
||||
source_session_data = self._get_session_data(source_session_id)
|
||||
if not source_session_data:
|
||||
print(f"ERROR: Source session {source_session_id} not found for cloning")
|
||||
return self.create_session() # Fallback to new session
|
||||
|
||||
# Create new session ID
|
||||
new_session_id = str(uuid.uuid4())
|
||||
|
||||
# Get the preserved configuration
|
||||
preserved_config = source_session_data.get('config')
|
||||
if not preserved_config:
|
||||
print(f"WARNING: No config found in source session, creating new")
|
||||
from core.session_config import create_session_config
|
||||
preserved_config = create_session_config()
|
||||
|
||||
print(f"Preserving config with API keys: {list(preserved_config.api_keys.keys())}")
|
||||
|
||||
# Create new scanner with preserved config
|
||||
new_scanner = Scanner(session_config=preserved_config)
|
||||
new_scanner.session_id = new_session_id
|
||||
|
||||
print(f"New scanner has {len(new_scanner.providers)} providers: {[p.get_name() for p in new_scanner.providers]}")
|
||||
|
||||
new_session_data = {
|
||||
'scanner': new_scanner,
|
||||
'config': preserved_config,
|
||||
'created_at': time.time(),
|
||||
'last_activity': time.time(),
|
||||
'status': 'active',
|
||||
'cloned_from': source_session_id
|
||||
}
|
||||
|
||||
# Store in Redis
|
||||
serialized_data = pickle.dumps(new_session_data)
|
||||
session_key = self._get_session_key(new_session_id)
|
||||
self.redis_client.setex(session_key, self.session_timeout, serialized_data)
|
||||
|
||||
# Initialize stop signal
|
||||
stop_key = self._get_stop_signal_key(new_session_id)
|
||||
self.redis_client.setex(stop_key, self.session_timeout, b'0')
|
||||
|
||||
print(f"Cloned session {new_session_id} with preserved configuration")
|
||||
return new_session_id
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to clone session {source_session_id}: {e}")
|
||||
# Fallback to creating a new session
|
||||
return self.create_session()
|
||||
|
||||
def set_stop_signal(self, session_id: str) -> bool:
|
||||
"""
|
||||
@@ -208,7 +275,14 @@ class SessionManager:
|
||||
# Immediately save to Redis for GUI updates
|
||||
success = self._save_session_data(session_id, session_data)
|
||||
if success:
|
||||
print(f"Scanner state updated for session {session_id} (status: {scanner.status})")
|
||||
# Only log occasionally to reduce noise
|
||||
if hasattr(self, '_last_update_log'):
|
||||
if time.time() - self._last_update_log > 5: # Log every 5 seconds max
|
||||
print(f"Scanner state updated for session {session_id} (status: {scanner.status})")
|
||||
self._last_update_log = time.time()
|
||||
else:
|
||||
print(f"Scanner state updated for session {session_id} (status: {scanner.status})")
|
||||
self._last_update_log = time.time()
|
||||
else:
|
||||
print(f"WARNING: Failed to save scanner state for session {session_id}")
|
||||
return success
|
||||
|
||||
Reference in New Issue
Block a user