From 2c5cfdbee8300936b67eb4214bfe917b968dcb4f Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Tue, 12 May 2026 10:19:25 +0800 Subject: [PATCH] refactor: remove old PacketCaptureTool, replaced by PacketAnalysisTool - Delete packet_capture_tools.py (analyze_packets with packet_number) - PacketAnalysisTool is more flexible and can do everything the old tool could - Update tool registration to use only PacketAnalysisTool - Cleaner, more unified packet analysis interface --- .../agent/gns3_copilot/agent/gns3_copilot.py | 3 - .../agent/gns3_copilot/tools_v2/__init__.py | 2 - .../tools_v2/packet_capture_tools.py | 306 ------------------ 3 files changed, 311 deletions(-) delete mode 100644 gns3server/agent/gns3_copilot/tools_v2/packet_capture_tools.py diff --git a/gns3server/agent/gns3_copilot/agent/gns3_copilot.py b/gns3server/agent/gns3_copilot/agent/gns3_copilot.py index 341f4281f..1ba5cb0d7 100644 --- a/gns3server/agent/gns3_copilot/agent/gns3_copilot.py +++ b/gns3server/agent/gns3_copilot/agent/gns3_copilot.py @@ -94,7 +94,6 @@ from gns3server.agent.gns3_copilot.tools_v2 import GNS3SuspendNodeTool from gns3server.agent.gns3_copilot.tools_v2 import GNS3TemplateTool from gns3server.agent.gns3_copilot.tools_v2 import GNS3UpdateNodeNameTool from gns3server.agent.gns3_copilot.tools_v2.vpcs_tools_netmiko import VPCSCommands -from gns3server.agent.gns3_copilot.tools_v2 import PacketCaptureTool from gns3server.agent.gns3_copilot.tools_v2 import PacketAnalysisTool from gns3server.agent.gns3_copilot.skills import DeviceSkillsTool from gns3server.agent.gns3_copilot.skills import InjectionSkillsTool @@ -116,7 +115,6 @@ TEACHING_ASSISTANT_MODE_TOOLS = [ GNS3UpdateNodeNameTool(), # Update node name ExecuteMultipleDeviceCommands(), # Execute show/display/debug commands # (READ-ONLY) - PacketCaptureTool(), # Analyze packets from active capture PacketAnalysisTool(), # Protocol-oriented packet analysis with tshark DeviceSkillsTool(), # Get device-specific skills and command knowledge ] @@ -134,7 +132,6 @@ LAB_AUTOMATION_ASSISTANT_MODE_TOOLS = [ # (READ-ONLY) ExecuteMultipleDeviceConfigCommands(), # Execute configuration commands VPCSCommands(), # Execute VPCS commands using Netmiko - PacketCaptureTool(), # Analyze packets from active capture PacketAnalysisTool(), # Protocol-oriented packet analysis with tshark DeviceSkillsTool(), # Get device-specific skills and command knowledge ] diff --git a/gns3server/agent/gns3_copilot/tools_v2/__init__.py b/gns3server/agent/gns3_copilot/tools_v2/__init__.py index a4a0734a1..f99450be4 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/__init__.py +++ b/gns3server/agent/gns3_copilot/tools_v2/__init__.py @@ -59,7 +59,6 @@ from .gns3_start_node import GNS3StartNodeTool from .gns3_stop_node import GNS3StopNodeTool from .gns3_suspend_node import GNS3SuspendNodeTool from .gns3_update_node_name import GNS3UpdateNodeNameTool -from .packet_capture_tools import PacketCaptureTool from .packet_analysis_tool import PacketAnalysisTool # Dynamic version management @@ -86,7 +85,6 @@ __all__ = [ "GNS3SuspendNodeTool", "GNS3UpdateNodeNameTool", "GNS3TemplateTool", - "PacketCaptureTool", "PacketAnalysisTool", ] diff --git a/gns3server/agent/gns3_copilot/tools_v2/packet_capture_tools.py b/gns3server/agent/gns3_copilot/tools_v2/packet_capture_tools.py deleted file mode 100644 index 0c43a189d..000000000 --- a/gns3server/agent/gns3_copilot/tools_v2/packet_capture_tools.py +++ /dev/null @@ -1,306 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -# GNS3-Copilot - AI-powered Network Lab Assistant for GNS3 -# -# This file is part of GNS3-Copilot project. -# -# GNS3-Copilot is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# GNS3-Copilot is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNS3-Copilot. If not, see . -# -# Copyright (C) 2025 Yue Guobin (岳国宾) -# Author: Yue Guobin (岳国宾) -# -# Project Home: https://github.com/yueguobin/gns3-copilot -# - -""" -Packet Capture Analysis Tool - -Analyzes specific packets from an active GNS3 capture using tshark. -Downloads the capture file from GNS3 server and returns detailed packet information. -""" - -import logging -import os -import subprocess -import tempfile - -from langchain.tools import BaseTool -from langchain_core.callbacks import CallbackManagerForToolRun - -from gns3server.agent.gns3_copilot.gns3_client.context_helpers import ( - get_current_jwt_token, -) - -logger = logging.getLogger(__name__) - - -class PacketCaptureTool(BaseTool): - """ - LangChain tool for analyzing a specific packet from an active GNS3 capture. - - This tool: - 1. Downloads the capture file from GNS3 server via /capture/file endpoint - 2. Saves it to a temporary file - 3. Runs tshark with verbose output (-V) for the specified packet - 4. Returns the complete packet structure - - Input: - project_id (str, required): UUID of the GNS3 project - link_id (str, required): UUID of the link to analyze - packet_number (int, required): The packet number to analyze - - Output: - str: Detailed packet information in text format - """ - - name: str = "analyze_packets" - description: str = """ - Analyze a specific packet from an active GNS3 capture using tshark. - - Downloads the capture file from GNS3 server and returns detailed packet information. - - Input (JSON format): - - project_id (str, required): UUID of the GNS3 project - - link_id (str, required): UUID of the link to analyze - - packet_number (int, required): The packet number to analyze - - Output: - Complete packet structure in verbose text format - - Example: - # Analyze packet #42 - tool._run( - project_id="xxx", - link_id="yyy", - packet_number=42 - ) - """ - - def _run( - self, - project_id: str, - link_id: str, - packet_number: int, - run_manager: CallbackManagerForToolRun | None = None, - ) -> str: - """ - Analyze a specific packet from an active GNS3 capture. - - Args: - project_id: UUID of the GNS3 project - link_id: UUID of the link to analyze - packet_number: The packet number to analyze - run_manager: LangChain run manager (unused) - - Returns: - str: Detailed packet information - """ - logger.info( - f"PacketCaptureTool invoked: project_id={project_id}, " - f"link_id={link_id}, packet_number={packet_number}" - ) - - # Validate inputs - if not project_id: - return '{"error": "project_id is required"}' - if not link_id: - return '{"error": "link_id is required"}' - if packet_number is None or packet_number <= 0: - return '{"error": "packet_number must be a positive integer"}' - - temp_file = None - try: - # Download capture file - temp_file = self._download_capture(project_id, link_id) - if not temp_file: - return '{"error": "Failed to download capture file"}' - - # Check if file exists and has content - if not os.path.exists(temp_file): - return '{"error": "Capture file not found"}' - - file_size = os.path.getsize(temp_file) - if file_size == 0: - return '{"error": "Capture file is empty, no packets captured yet"}' - - logger.info(f"Capture file downloaded: {temp_file}, size={file_size} bytes") - - # Run tshark verbose analysis for the specific packet - result = self._run_tshark_verbose(temp_file, packet_number) - return result - - except Exception as e: - logger.error(f"PacketCaptureTool error: {e}", exc_info=True) - return f'{{"error": "Analysis failed: {str(e)}"}}' - - finally: - # Clean up temp file - if temp_file and os.path.exists(temp_file): - try: - os.remove(temp_file) - logger.debug(f"Temporary file removed: {temp_file}") - except Exception as e: - logger.warning(f"Failed to remove temp file: {e}") - - def _download_capture(self, project_id: str, link_id: str) -> str | None: - """ - Download capture file from GNS3 server. - - Args: - project_id: Project UUID - link_id: Link UUID - - Returns: - str: Path to temporary capture file, or None on failure - """ - jwt_token = get_current_jwt_token() - if not jwt_token: - logger.error("JWT token not found in context") - return None - - # Detect GNS3 server URL - url = self._detect_gns3_url() - if not url: - return None - - capture_url = f"{url}/v3/projects/{project_id}/links/{link_id}/capture/file" - logger.info(f"Downloading capture from: {capture_url}") - - # Create temp file - temp_fd, temp_file = tempfile.mkstemp(suffix=".pcap", prefix="gns3_capture_") - os.close(temp_fd) - - try: - import requests - - headers = {"Authorization": f"Bearer {jwt_token}"} - # Use verify=False for HTTPS to skip certificate validation (for self-signed certs) - verify_cert = not capture_url.startswith("https://") - response = requests.get( - capture_url, - headers=headers, - stream=True, - timeout=30, - verify=verify_cert, - ) - - if response.status_code != 200: - logger.error(f"Failed to download capture: HTTP {response.status_code}") - os.remove(temp_file) - return None - - # Write to temp file - with open(temp_file, "wb") as f: - for chunk in response.iter_content(chunk_size=8192): - f.write(chunk) - - logger.info(f"Capture file saved: {temp_file}, size={os.path.getsize(temp_file)} bytes") - return temp_file - - except Exception as e: - logger.error(f"Failed to download capture: {e}", exc_info=True) - if os.path.exists(temp_file): - os.remove(temp_file) - return None - - def _detect_gns3_url(self) -> str | None: - """ - Detect GNS3 server URL from Controller or Config. - - Returns: - str: GNS3 server URL, or None on failure - """ - try: - from gns3server.controller import Controller - - controller = Controller.instance() - local_compute = controller.get_compute("local") - url = f"{local_compute.protocol}://{local_compute.host}:{local_compute.port}" - logger.debug(f"Detected GNS3 URL from Controller: {url}") - return url - except Exception as e: - logger.debug(f"Cannot get URL from Controller: {e}") - - try: - from gns3server.config import Config - - server_config = Config.instance().settings.Server - url = f"{server_config.protocol.value}://{server_config.host}:{server_config.port}" - logger.debug(f"Detected GNS3 URL from Config: {url}") - return url - except Exception as e: - logger.debug(f"Cannot get URL from Config: {e}") - - # Fallback default - default_url = "http://127.0.0.1:3080" - logger.warning(f"Using fallback default URL: {default_url}") - return default_url - - def _run_tshark_verbose(self, pcap_file: str, packet_number: int) -> str: - """ - Run tshark verbose output for a specific packet. - - Args: - pcap_file: Path to the capture file - packet_number: The packet number to analyze - - Returns: - str: tshark verbose output - """ - # Build command: tshark -r -Y "frame.number == N" -V - cmd = [ - "tshark", - "-r", pcap_file, - "-Y", f"frame.number == {packet_number}", - "-V" - ] - - logger.info(f"Running tshark: {' '.join(cmd)}") - - try: - result = subprocess.run( - cmd, - capture_output=True, - text=True, - timeout=30, - ) - - output = result.stdout - if result.stderr: - if "tshark:" in result.stderr.lower(): - logger.warning(f"tshark stderr: {result.stderr}") - - if not output.strip(): - return f'No packet found with frame.number == {packet_number}' - - logger.info(f"tshark output: {len(output)} characters") - return output - - except subprocess.TimeoutExpired: - logger.error("tshark timeout after 30 seconds") - return '{"error": "tshark timeout after 30 seconds"}' - except FileNotFoundError: - logger.error("tshark not found. Please install tshark: apt install tshark") - return '{"error": "tshark not installed. Please install tshark: apt install tshark"}' - except Exception as e: - logger.error(f"tshark execution error: {e}", exc_info=True) - return f'{{"error": "tshark failed: {str(e)}"}}' - - -if __name__ == "__main__": - # Test the tool - tool = PacketCaptureTool() - print("Testing PacketCaptureTool...") - print("Note: Set project_id, link_id and packet_number to test with actual GNS3 capture") - print(tool.description)