mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
Merge pull request #2746 from yueguobin/feature/gns3-copilot-packet-filter
feat: add packet filter management tool for GNS3-Copilot fault injection
This commit is contained in:
commit
ec55b9f089
@ -16,5 +16,8 @@
|
||||
- Key point: UDPLink only passes through jwt_token, ultimately used by curl command inside Web Wireshark container to authenticate with GNS3 capture stream API
|
||||
- **[Xpra HTML5 Client](./xpra-html5-client.md)** - Xpra HTML5 client menu control parameters for customizing the web interface
|
||||
|
||||
### Appliance Management
|
||||
- **[GNS3 Appliance Loading](./gns3-appliance-loading.md)** - How GNS3 loads appliance files from builtin and custom directories with priority rules
|
||||
|
||||
### uBridge Permission
|
||||
- **[uBridge Permission Issue](./gns3-ubridge-permission.md)** - Docker containers fail to start due to missing CAP_NET_ADMIN/CAP_NET_RAW capabilities on uBridge
|
||||
|
||||
20
.claude/memory/gns3-appliance-loading.md
Normal file
20
.claude/memory/gns3-appliance-loading.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: gns3-appliance-loading
|
||||
description: GNS3 appliance file loading mechanism and storage locations
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
GNS3 loads appliance (.gns3a) files from two locations with specific priority order:
|
||||
|
||||
1. **Builtin appliances directory**: `~/.local/share/GNS3/appliances/`
|
||||
- Stores automatically downloaded devices from GNS3 registry
|
||||
- Maintained and updated by the system automatically
|
||||
|
||||
2. **Custom appliances directory**: `~/GNS3/appliances/`
|
||||
- Stores user-customized or modified appliance files
|
||||
- Manually managed by users
|
||||
|
||||
**Loading priority**: System loads builtin appliances first, then custom appliances. If both directories contain devices with the same `device_id`, the custom appliance overwrites the builtin one. This design allows users to customize devices without having their modifications overwritten by automatic registry updates.
|
||||
|
||||
**Implementation**: See `gns3server/controller/appliance_manager.py` in the `load_appliances()` method (lines 314-351).
|
||||
@ -0,0 +1,280 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
See LICENSE file for licensing information.
|
||||
-->
|
||||
|
||||
> This document is a roadmap/planning document. The described features have not been implemented yet.
|
||||
|
||||
# AIOps Fault Injection Testing Pipeline — Roadmap
|
||||
|
||||
## Overview
|
||||
|
||||
Build a realistic testing pipeline that duplicates the company's production network architecture into GNS3, then systematically injects network faults using the AI Copilot's fault injection capabilities to validate and train the AIOps module before production deployment.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ AIOps Testing Pipeline │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ Network │ │ Fault │ │ AIOps │ │
|
||||
│ │ Duplication │───▶│ Injection │───▶│ Validation │ │
|
||||
│ │ (Phase 1) │ │ (Phase 2) │ │ (Phase 3) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ GNS3 Network │ │ Fault │ │ Results & │ │
|
||||
│ │ Replica │ │ Scenarios │ │ Reporting │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Core Concept
|
||||
|
||||
1. **Duplicate** the company's production network architecture into a GNS3 simulation environment
|
||||
2. **Select** a set of fault types to test (OSPF, BGP, VxLAN, STP, packet filter, etc.)
|
||||
3. **Inject** faults automatically using the AI Copilot's fault injection capabilities
|
||||
4. **Validate** whether the AIOps module correctly identifies and reports each fault
|
||||
5. **Loop** through all selected fault scenarios, building a comprehensive test matrix
|
||||
6. **Train** the AIOps module on results to improve accuracy before production deployment
|
||||
|
||||
## Phase 1: Network Architecture Duplication
|
||||
|
||||
### Goal
|
||||
|
||||
Create a high-fidelity replica of the company production network in GNS3.
|
||||
|
||||
### Key Tasks
|
||||
|
||||
- [ ] **Topology Mapping**: Document production network topology (devices, links, protocols)
|
||||
- [ ] **Device Selection**: Map production devices to GNS3-compatible images (Cisco IOSv, XRv, Juniper vSRX, etc.)
|
||||
- [ ] **Configuration Extraction**: Export sanitized production configs (remove passwords, public IPs, sensitive data)
|
||||
- [ ] **GNS3 Deployment**: Build the network in GNS3 with accurate device placement and links
|
||||
- [ ] **Config Replication**: Apply adapted configurations to GNS3 devices
|
||||
- [ ] **Connectivity Validation**: Verify OSPF/BGP adjacencies, VLANs, VRFs, and end-to-end reachability
|
||||
- [ ] **Baseline Capture**: Record normal operation metrics (CPU, memory, interface counters, routing tables)
|
||||
|
||||
### Considerations
|
||||
|
||||
- Sanitize all production configurations before importing into GNS3
|
||||
- Use environment-specific IP addressing where necessary (loopbacks, management)
|
||||
- Document all deviations from production for traceability
|
||||
|
||||
## Phase 2: Fault Injection Pipeline
|
||||
|
||||
### Goal
|
||||
|
||||
Systematically inject network faults and validate AIOps detection using the existing AI Copilot fault injection infrastructure.
|
||||
|
||||
### Components
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ Fault Injection Pipeline │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ Scenario │ │ Inject │ │ AIOps │ │ Record │ │
|
||||
│ │ Selector │──▶│ Fault │──▶│ Validate │──▶│ & Report │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ └──────────────┴──────────────┴───────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ Loop until all scenarios tested │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.1 Scenario Selector
|
||||
|
||||
- Read fault scenarios from the GNS3-Skills repository
|
||||
- Support filtering by:
|
||||
- Protocol (OSPF, BGP, VxLAN, STP, VLAN, etc.)
|
||||
- Severity (critical, high, medium, low)
|
||||
- Difficulty (beginner, intermediate, advanced)
|
||||
- Track which scenarios have been tested
|
||||
- Randomize selection order to avoid bias
|
||||
- Exclude previously tested scenarios
|
||||
|
||||
### 2.2 Fault Injection
|
||||
|
||||
- Use existing `manage_gns3_packet_filter` tool for network-level faults
|
||||
- Use existing `execute_multiple_device_config_commands` for configuration faults
|
||||
- Use existing `InjectionSkillsTool` to query and select appropriate faults
|
||||
- Support combined faults (multiple simultaneous issues)
|
||||
- Auto-recovery between scenarios (restore baseline state)
|
||||
|
||||
### 2.3 AIOps Validation
|
||||
|
||||
- Feed network state (after fault injection) to the AIOps module
|
||||
- Record AIOps diagnosis output
|
||||
- Compare AIOps results against expected fault definition:
|
||||
- **Correct identification**: AIOps names the exact fault
|
||||
- **Partial identification**: AIOps identifies related symptoms but not root cause
|
||||
- **Missed**: AIOps fails to detect any issue
|
||||
- **False positive**: AIOps reports a fault that doesn't exist
|
||||
|
||||
### 2.4 Test Execution Flow
|
||||
|
||||
```
|
||||
1. Reset network to clean baseline state
|
||||
2. Select next untested fault scenario
|
||||
3. Inject the fault into the GNS3 network
|
||||
4. Wait for convergence (configurable delay)
|
||||
5. Query AIOps module for diagnosis
|
||||
6. Compare AIOps output with expected fault definition
|
||||
7. Record result (pass/fail/partial)
|
||||
8. Restore network to baseline
|
||||
9. Repeat from step 2 until all scenarios completed
|
||||
```
|
||||
|
||||
## Phase 3: Traffic Injection (Enhanced Realism)
|
||||
|
||||
### Goal
|
||||
|
||||
Add realistic network traffic to the GNS3 simulation so that AIOps has real telemetry data to analyze, rather than a static network.
|
||||
|
||||
### Approaches
|
||||
|
||||
#### 3.1 Traffic Generators in GNS3
|
||||
|
||||
- Deploy traffic generator appliances in GNS3 (e.g., TRex, Ostinato, Scapy on Linux nodes)
|
||||
- Generate realistic traffic patterns:
|
||||
- VoIP/RTP streams
|
||||
- HTTP/HTTPS web traffic
|
||||
- Database replication
|
||||
- Routing protocol updates (OSPF hellos, BGP keepalives)
|
||||
- ICMP monitoring traffic
|
||||
|
||||
#### 3.2 tcpreplay with Captured Traffic
|
||||
|
||||
- Capture real production traffic (sanitized)
|
||||
- Use `tcpreplay` to replay traffic through the GNS3 network
|
||||
- More realistic than synthetic traffic generators
|
||||
|
||||
#### 3.3 Integration with Network Monitoring
|
||||
|
||||
- Feed simulated device telemetry (SNMP, syslog, NetFlow) to the AIOps module
|
||||
- Enable AIOps to analyze real-time telemetry during fault conditions
|
||||
- Validate that AIOps can distinguish between traffic anomalies and actual faults
|
||||
|
||||
## Success Metrics
|
||||
|
||||
| Metric | Target | Measurement Method |
|
||||
|--------|--------|-------------------|
|
||||
| Fault detection rate | >95% | AIOps correctly identifies injected faults |
|
||||
| False positive rate | <5% | AIOps reports fault when none exists |
|
||||
| Time to detection | <30s | Duration from injection to AIOps alert |
|
||||
| Coverage | >80% of defined scenarios | Percentage of scenarios tested |
|
||||
| Accuracy improvement | Measurable per cycle | Compare pass rates across test cycles |
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────┐
|
||||
│ Test Orchestrator │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Test Runner (Python) │ │
|
||||
│ │ - Scenario selection & scheduling │ │
|
||||
│ │ - Fault injection coordination │ │
|
||||
│ │ - AIOps query & result collection │ │
|
||||
│ │ - Report generation │ │
|
||||
│ └──────────┬────────────────────────────────────────────┬───────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────────────┐ ┌────────────────────┐ │
|
||||
│ │ GNS3 Controller │ │ AIOps Module │ │
|
||||
│ │ (gns3-server) │ │ │ │
|
||||
│ │ - Network mgmt │ │ - Fault diagnosis │ │
|
||||
│ │ - Fault injection │ │ - Alert detection │ │
|
||||
│ │ - State queries │ │ - Root cause │ │
|
||||
│ └────────────────────┘ └────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ GNS3 Network │ │
|
||||
│ │ Replica │ │
|
||||
│ │ - Devices │ │
|
||||
│ │ - Traffic │ │
|
||||
│ │ - Telemetry │ │
|
||||
│ └────────────────────┘ │
|
||||
└────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Reporting
|
||||
|
||||
Each test cycle produces:
|
||||
|
||||
- **Summary report**: Pass/fail rates, coverage, trends
|
||||
- **Detailed per-scenario report**: Injection details, AIOps response, comparison
|
||||
- **Regression tracker**: Which scenarios regressed since last cycle
|
||||
- **Accuracy trend**: Improvement or degradation over time
|
||||
|
||||
### Example Report Entry
|
||||
|
||||
```yaml
|
||||
test_cycle: 7
|
||||
date: "2026-06-01"
|
||||
scenarios_planned: 20
|
||||
scenarios_completed: 18
|
||||
failed_injections: 1
|
||||
skipped: 1
|
||||
results:
|
||||
- scenario: ospf_hello_dead_mismatch
|
||||
protocol: ospf
|
||||
severity: major
|
||||
injection_method: device_config
|
||||
target_device: R1
|
||||
aiops_detection: true
|
||||
aiops_diagnosis: "OSPF Hello/Dead interval mismatch between R1 and R2"
|
||||
detection_latency_ms: 12000
|
||||
match: exact
|
||||
- scenario: packet_loss_heavy
|
||||
protocol: performance
|
||||
severity: high
|
||||
injection_method: packet_filter
|
||||
target_link: "R1 ↔ R2 (ethernet)"
|
||||
aiops_detection: true
|
||||
aiops_diagnosis: "High packet loss detected on link R1-R2"
|
||||
detection_latency_ms: 45000
|
||||
match: partial
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [ ] GNS3 network replica ready and validated
|
||||
- [ ] AI Copilot fault injection tools operational
|
||||
- [ ] AIOps module query interface available
|
||||
- [ ] Traffic generation tools deployed (for Phase 3)
|
||||
- [ ] Test orchestrator framework (to be built)
|
||||
- [ ] Result database and reporting system
|
||||
|
||||
## Timeline
|
||||
|
||||
| Phase | Duration | Deliverable |
|
||||
|-------|----------|-------------|
|
||||
| P1: Network Duplication | 2-4 weeks | GNS3 replica of production network |
|
||||
| P2: Fault Injection Pipeline | 2-3 weeks | Automated test runner + first results |
|
||||
| P3: Traffic Injection | 2-4 weeks | Realistic traffic simulation integrated |
|
||||
|
||||
## Status
|
||||
|
||||
- [ ] P1: Network architecture duplication
|
||||
- [ ] Topology mapping documented
|
||||
- [ ] Device configurations sanitized and adapted
|
||||
- [ ] GNS3 replica deployed
|
||||
- [ ] Baseline connectivity verified
|
||||
- [ ] P2: Fault injection pipeline
|
||||
- [ ] Scenario selection framework
|
||||
- [ ] Automated fault injection
|
||||
- [ ] AIOps validation interface
|
||||
- [ ] Report generation
|
||||
- [ ] Loop/retry mechanism
|
||||
- [ ] P3: Traffic injection
|
||||
- [ ] Traffic generator deployment
|
||||
- [ ] Traffic pattern library
|
||||
- [ ] AIOps telemetry integration
|
||||
|
||||
@ -88,6 +88,7 @@ from gns3server.agent.gns3_copilot.tools_v2 import (
|
||||
)
|
||||
from gns3server.agent.gns3_copilot.tools_v2 import GNS3CreateNodeTool
|
||||
from gns3server.agent.gns3_copilot.tools_v2 import GNS3LinkTool
|
||||
from gns3server.agent.gns3_copilot.tools_v2 import GNS3PacketFilterTool
|
||||
from gns3server.agent.gns3_copilot.tools_v2 import GNS3StartNodeTool
|
||||
from gns3server.agent.gns3_copilot.tools_v2 import GNS3StopNodeTool
|
||||
from gns3server.agent.gns3_copilot.tools_v2 import GNS3SuspendNodeTool
|
||||
@ -144,6 +145,7 @@ LAB_AUTOMATION_ASSISTANT_MODE_TOOLS = [
|
||||
TROUBLESHOOTING_INJECTION_MODE_TOOLS = [
|
||||
ExecuteMultipleDeviceCommands(), # Get device configurations (READ-ONLY)
|
||||
ExecuteMultipleDeviceConfigCommands(), # Inject configuration changes
|
||||
GNS3PacketFilterTool(), # Manage packet filters on links (delay, loss, corrupt, etc.)
|
||||
InjectionSkillsTool(), # Query injection skills and fault types
|
||||
GNS3TopologyTool(), # Get topology information
|
||||
]
|
||||
|
||||
@ -1042,6 +1042,66 @@ class Link:
|
||||
# Update object
|
||||
self._update(_response.json())
|
||||
|
||||
@verify_connector_and_id
|
||||
def available_filters(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Gets the list of available packet filters for this link.
|
||||
|
||||
**NOTE:** This endpoint is only available in GNS3 API v3 or later.
|
||||
Attempting to call this method with a v2 connector will raise an error.
|
||||
|
||||
**Required Attributes:**
|
||||
|
||||
- `project_id`
|
||||
- `connector` (must be API v3 or later)
|
||||
- `link_id`
|
||||
|
||||
**Returns:**
|
||||
|
||||
List of available filter types with their parameters (e.g., frequency_drop,
|
||||
packet_loss, delay, corrupt, bpf).
|
||||
|
||||
**Example:**
|
||||
|
||||
```python
|
||||
>>> link = Link(project_id=<pr_id>, link_id=<link_id>, connector=<connector>)
|
||||
>>> filters = link.available_filters()
|
||||
>>> print(filters)
|
||||
[
|
||||
{
|
||||
"type": "frequency_drop",
|
||||
"name": "Frequency drop",
|
||||
"description": "It will drop everything with a -1 frequency...",
|
||||
"parameters": [...]
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
"""
|
||||
_conn = self.connector
|
||||
_project_id = self.project_id
|
||||
|
||||
if _conn is None:
|
||||
raise ValueError("Gns3Connector not assigned under 'connector'")
|
||||
if _project_id is None:
|
||||
raise ValueError("Need to submit project_id")
|
||||
|
||||
# Check API version - available_filters endpoint is only available in v3+
|
||||
if not hasattr(_conn, "api_version") or _conn.api_version < 3:
|
||||
raise ValueError(
|
||||
"The available_filters() method requires GNS3 API v3 or later. "
|
||||
f"Current connector version: v{getattr(_conn, 'api_version', 2)}. "
|
||||
"Please use api_version=3 when creating the Gns3Connector."
|
||||
)
|
||||
|
||||
_url = (
|
||||
f"{_conn.base_url}/projects/{_project_id}/links/{self.link_id}/"
|
||||
"available_filters"
|
||||
)
|
||||
_response = _conn.http_call("get", _url)
|
||||
|
||||
return cast(list[dict[str, Any]], _response.json())
|
||||
|
||||
|
||||
@dataclass(config=config)
|
||||
class Node:
|
||||
|
||||
@ -32,6 +32,7 @@ This package provides various tools for interacting with GNS3 network simulator:
|
||||
- Multiple device command execution using Nornir
|
||||
- VPCS device configuration using Netmiko
|
||||
- Node and link management
|
||||
- Packet filter management
|
||||
|
||||
Main modules:
|
||||
- config_tools_nornir: Multiple device configuration command execution tool using Nornir
|
||||
@ -42,6 +43,7 @@ Main modules:
|
||||
- gns3_start_node: GNS3 node startup tool
|
||||
- gns3_get_node_temp: GNS3 template retrieval tool
|
||||
- gns3_update_node_name: GNS3 node name update tool
|
||||
- gns3_packet_filter: GNS3 packet filter management tool
|
||||
|
||||
Note: GNS3TopologyTool is now available from gns3_client package
|
||||
|
||||
@ -54,6 +56,7 @@ from .display_tools_nornir import ExecuteMultipleDeviceCommands
|
||||
from .gns3_create_link import GNS3LinkTool
|
||||
from .gns3_create_node import GNS3CreateNodeTool
|
||||
from .gns3_get_node_temp import GNS3TemplateTool
|
||||
from .gns3_packet_filter import GNS3PacketFilterTool
|
||||
from .gns3_start_node import GNS3StartNodeQuickTool
|
||||
from .gns3_start_node import GNS3StartNodeTool
|
||||
from .gns3_stop_node import GNS3StopNodeTool
|
||||
@ -79,6 +82,7 @@ __all__ = [
|
||||
"ExecuteMultipleDeviceCommands",
|
||||
"GNS3CreateNodeTool",
|
||||
"GNS3LinkTool",
|
||||
"GNS3PacketFilterTool",
|
||||
"GNS3StartNodeTool",
|
||||
"GNS3StartNodeQuickTool",
|
||||
"GNS3StopNodeTool",
|
||||
|
||||
458
gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py
Normal file
458
gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py
Normal file
@ -0,0 +1,458 @@
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Copyright (C) 2025 Yue Guobin (岳国宾)
|
||||
# Author: Yue Guobin (岳国宾)
|
||||
#
|
||||
# Project Home: https://github.com/yueguobin/gns3-copilot
|
||||
#
|
||||
|
||||
"""
|
||||
|
||||
GNS3 packet filter management tool for network simulation.
|
||||
|
||||
Provides functionality to manage packet filters on GNS3 links,
|
||||
including latency, packet loss, corruption, and BPF filtering.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
from pprint import pprint
|
||||
from typing import Any
|
||||
|
||||
from langchain.tools import BaseTool
|
||||
from langchain_core.callbacks import CallbackManagerForToolRun
|
||||
|
||||
from gns3server.agent.gns3_copilot.gns3_client import Link
|
||||
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_connector
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GNS3PacketFilterTool(BaseTool):
|
||||
"""
|
||||
A LangChain tool to manage packet filters on GNS3 links.
|
||||
|
||||
Supports getting available filters, setting filters, and clearing filters
|
||||
on network links to simulate various network conditions.
|
||||
|
||||
**Input:**
|
||||
A JSON object with project_id, link_id, action, and optional filter parameters.
|
||||
Note: show_filters_icon is automatically set to false by default to hide the filter
|
||||
icon in the GNS3 Web UI.
|
||||
|
||||
Example input for getting available filters:
|
||||
{
|
||||
"project_id": "uuid-of-project",
|
||||
"link_id": "uuid-of-link",
|
||||
"action": "get_available"
|
||||
}
|
||||
|
||||
Example input for setting filters:
|
||||
{
|
||||
"project_id": "uuid-of-project",
|
||||
"link_id": "uuid-of-link",
|
||||
"action": "set",
|
||||
"filters": {
|
||||
"delay": [100, 10],
|
||||
"packet_loss": [5]
|
||||
},
|
||||
"show_filters_icon": false
|
||||
}
|
||||
|
||||
Example input for getting current filters:
|
||||
{
|
||||
"project_id": "uuid-of-project",
|
||||
"link_id": "uuid-of-link",
|
||||
"action": "get"
|
||||
}
|
||||
|
||||
Example input for clearing filters:
|
||||
{
|
||||
"project_id": "uuid-of-project",
|
||||
"link_id": "uuid-of-link",
|
||||
"action": "clear"
|
||||
}
|
||||
|
||||
**Output:**
|
||||
A dictionary containing the action result.
|
||||
|
||||
For "get_available": returns list of available filter types
|
||||
For "set": returns updated link information with applied filters
|
||||
For "get": returns current filters configured on the link
|
||||
For "clear": returns confirmation that filters were cleared
|
||||
"""
|
||||
|
||||
name: str = "manage_gns3_packet_filter"
|
||||
description: str = """
|
||||
Manages packet filters on GNS3 links to inject network faults and simulate network conditions.
|
||||
|
||||
This tool is primarily used for fault injection scenarios to create realistic network problems
|
||||
for troubleshooting practice, such as latency, packet loss, and corruption.
|
||||
|
||||
By default, the filter icon in the GNS3 Web UI is hidden (show_filters_icon=false) to avoid
|
||||
visual clutter when injecting faults for troubleshooting exercises.
|
||||
|
||||
Supported actions:
|
||||
- "get_available": Get list of available filter types for the link
|
||||
- "set": Set packet filters on the link to inject network faults
|
||||
- "get": Get current filters configured on the link
|
||||
|
||||
Common filter types for fault injection:
|
||||
- "frequency_drop": Drop every Nth packet (parameter: frequency, -1 to 32767)
|
||||
- "packet_loss": Packet loss percentage (parameter: chance, 0-100)
|
||||
- "delay": Delay in ms with optional jitter (parameters: latency 0-32767, jitter 0-32767)
|
||||
- "corrupt": Packet corruption percentage (parameter: chance, 0-100)
|
||||
- "bpf": Berkeley Packet Filter (parameter: filter expression text)
|
||||
|
||||
Input is a JSON object with:
|
||||
- project_id (str): GNS3 project UUID
|
||||
- link_id (str): GNS3 link UUID
|
||||
- action (str): One of "get_available", "set", "get", "clear"
|
||||
- filters (dict, optional): Filter configuration for "set" action
|
||||
|
||||
Example for getting available filters:
|
||||
{
|
||||
"project_id": "uuid-of-project",
|
||||
"link_id": "uuid-of-link",
|
||||
"action": "get_available"
|
||||
}
|
||||
|
||||
Example for setting delay and packet loss:
|
||||
{
|
||||
"project_id": "uuid-of-project",
|
||||
"link_id": "uuid-of-link",
|
||||
"action": "set",
|
||||
"filters": {
|
||||
"delay": [100, 10],
|
||||
"packet_loss": [5]
|
||||
}
|
||||
}
|
||||
|
||||
Returns a dictionary with action result, filter information, or error message.
|
||||
"""
|
||||
|
||||
def _run(
|
||||
self,
|
||||
tool_input: str,
|
||||
run_manager: CallbackManagerForToolRun | None = None,
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Manages packet filters on a GNS3 link.
|
||||
|
||||
Args:
|
||||
tool_input: A JSON string with project_id, link_id, action, and optional filters.
|
||||
run_manager: LangChain run manager (unused).
|
||||
|
||||
Returns:
|
||||
dict: A dictionary with action result or an error message.
|
||||
"""
|
||||
# Log received input
|
||||
logger.info("Received input: %s", tool_input)
|
||||
|
||||
try:
|
||||
# Parse input JSON
|
||||
input_data = json.loads(tool_input)
|
||||
project_id = input_data.get("project_id")
|
||||
link_id = input_data.get("link_id")
|
||||
action = input_data.get("action")
|
||||
show_filters_icon = input_data.get("show_filters_icon", False)
|
||||
|
||||
# Validate required fields
|
||||
if not project_id:
|
||||
logger.error("Invalid input: Missing project_id.")
|
||||
return {"error": "Missing project_id."}
|
||||
|
||||
if not link_id:
|
||||
logger.error("Invalid input: Missing link_id.")
|
||||
return {"error": "Missing link_id."}
|
||||
|
||||
if not action:
|
||||
logger.error("Invalid input: Missing action.")
|
||||
return {"error": "Missing action."}
|
||||
|
||||
# Validate action
|
||||
valid_actions = ["get_available", "set", "get", "clear"]
|
||||
if action not in valid_actions:
|
||||
logger.error("Invalid action: %s. Must be one of %s", action, valid_actions)
|
||||
return {
|
||||
"error": f"Invalid action: {action}. Must be one of {valid_actions}"
|
||||
}
|
||||
|
||||
# Validate filters for "set" action
|
||||
if action == "set":
|
||||
filters = input_data.get("filters")
|
||||
if not filters or not isinstance(filters, dict):
|
||||
logger.error("Invalid input: 'set' action requires 'filters' dict.")
|
||||
return {
|
||||
"error": "'set' action requires 'filters' dict with filter configuration."
|
||||
}
|
||||
|
||||
# Initialize Gns3Connector using factory function
|
||||
logger.info("Connecting to GNS3 server...")
|
||||
gns3_server = get_gns3_connector()
|
||||
|
||||
if gns3_server is None:
|
||||
logger.error("Failed to create GNS3 connector")
|
||||
return {
|
||||
"error": "Failed to connect to GNS3 server. "
|
||||
"Please check your configuration."
|
||||
}
|
||||
|
||||
# Create Link object
|
||||
logger.info(
|
||||
"Processing packet filter action '%s' for link %s...", action, link_id
|
||||
)
|
||||
link = Link(
|
||||
project_id=project_id, link_id=link_id, connector=gns3_server
|
||||
)
|
||||
|
||||
# Execute action
|
||||
if action == "get_available":
|
||||
result = self._get_available_filters(link)
|
||||
elif action == "set":
|
||||
filters = input_data.get("filters", {})
|
||||
result = self._set_filters(link, filters, show_filters_icon)
|
||||
elif action == "get":
|
||||
result = self._get_filters(link)
|
||||
elif action == "clear":
|
||||
result = self._clear_filters(link, show_filters_icon)
|
||||
else:
|
||||
result = {"error": f"Unknown action: {action}"}
|
||||
|
||||
# Log result
|
||||
logger.info("Packet filter action '%s' completed successfully.", action)
|
||||
|
||||
return result
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("Invalid JSON input: %s", e)
|
||||
return {"error": f"Invalid JSON input: {e}"}
|
||||
except Exception as e:
|
||||
logger.error("Failed to process packet filter request: %s", e)
|
||||
return {
|
||||
"error": f"Failed to process packet filter request: {str(e)}"
|
||||
}
|
||||
|
||||
def _get_available_filters(self, link: Link) -> dict[str, Any]:
|
||||
"""Get available filter types for the link."""
|
||||
try:
|
||||
filters = link.available_filters()
|
||||
logger.info("Retrieved %d available filter types.", len(filters))
|
||||
return {
|
||||
"action": "get_available",
|
||||
"link_id": link.link_id,
|
||||
"available_filters": filters,
|
||||
"count": len(filters),
|
||||
"status": "success",
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("Failed to get available filters: %s", e)
|
||||
return {
|
||||
"action": "get_available",
|
||||
"link_id": link.link_id,
|
||||
"error": f"Failed to get available filters: {str(e)}",
|
||||
"status": "failed",
|
||||
}
|
||||
|
||||
def _validate_bpf_syntax(self, bpf_expression: str) -> dict[str, Any]:
|
||||
"""
|
||||
Validate BPF filter expression syntax using tshark.
|
||||
|
||||
Args:
|
||||
bpf_expression: BPF filter expression to validate
|
||||
|
||||
Returns:
|
||||
dict with 'valid' (bool) and 'error' (str or None) keys
|
||||
"""
|
||||
try:
|
||||
# Use tshark to validate BPF syntax with 1 second timeout
|
||||
# Use -i lo (loopback) to avoid "(null)" interface in error messages
|
||||
result = subprocess.run(
|
||||
["tshark", "-f", bpf_expression, "-i", "lo"],
|
||||
timeout=1,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# Check if output contains "Invalid" indicating syntax error
|
||||
if "Invalid" in result.stdout or "Invalid" in result.stderr:
|
||||
error_lines = []
|
||||
if "Invalid" in result.stderr:
|
||||
error_lines.extend(
|
||||
line for line in result.stderr.split("\n") if "Invalid" in line
|
||||
)
|
||||
if "Invalid" in result.stdout:
|
||||
error_lines.extend(
|
||||
line for line in result.stdout.split("\n") if "Invalid" in line
|
||||
)
|
||||
|
||||
# Strip interface suffix (e.g., "for interface 'lo'") for cleaner error
|
||||
error_msg_parts = []
|
||||
for line in error_lines:
|
||||
clean = line.split(" for interface")[0].strip()
|
||||
if clean:
|
||||
error_msg_parts.append(clean)
|
||||
error_msg = " ".join(error_msg_parts) if error_msg_parts else "Invalid BPF syntax"
|
||||
logger.warning("BPF syntax validation failed: %s", error_msg)
|
||||
return {"valid": False, "error": error_msg}
|
||||
|
||||
logger.info("BPF syntax validation passed")
|
||||
return {"valid": True, "error": None}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
# Timeout is expected behavior - tshark waits for traffic
|
||||
# No "Invalid" in output means syntax is correct
|
||||
logger.info("BPF syntax validation passed (timeout expected)")
|
||||
return {"valid": True, "error": None}
|
||||
|
||||
except FileNotFoundError:
|
||||
# tshark not installed - skip validation
|
||||
logger.warning(
|
||||
"tshark not found, skipping BPF syntax validation. "
|
||||
"Install tshark to enable BPF validation."
|
||||
)
|
||||
return {"valid": True, "error": None}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unexpected error during BPF validation: %s", e)
|
||||
return {"valid": False, "error": f"BPF validation error: {str(e)}"}
|
||||
|
||||
def _set_filters(
|
||||
self, link: Link, filters: dict[str, Any], show_filters_icon: bool = False
|
||||
) -> dict[str, Any]:
|
||||
"""Set packet filters on the link."""
|
||||
try:
|
||||
# Validate BPF syntax if BPF filter is present
|
||||
if "bpf" in filters:
|
||||
bpf_filters = filters["bpf"]
|
||||
if isinstance(bpf_filters, list):
|
||||
# Validate each BPF expression
|
||||
for idx, bpf_expr in enumerate(bpf_filters):
|
||||
if isinstance(bpf_expr, str):
|
||||
validation = self._validate_bpf_syntax(bpf_expr)
|
||||
if not validation["valid"]:
|
||||
return {
|
||||
"action": "set",
|
||||
"link_id": link.link_id,
|
||||
"error": f"BPF syntax error at index {idx}: {validation['error']}",
|
||||
"status": "failed",
|
||||
}
|
||||
elif isinstance(bpf_filters, str):
|
||||
# Single BPF expression
|
||||
validation = self._validate_bpf_syntax(bpf_filters)
|
||||
if not validation["valid"]:
|
||||
return {
|
||||
"action": "set",
|
||||
"link_id": link.link_id,
|
||||
"error": f"BPF syntax error: {validation['error']}",
|
||||
"status": "failed",
|
||||
}
|
||||
|
||||
# Update filters
|
||||
link.update(filters=filters, show_filters_icon=show_filters_icon)
|
||||
|
||||
# Get updated link info
|
||||
link.get()
|
||||
|
||||
logger.info("Successfully set filters on link %s", link.link_id)
|
||||
return {
|
||||
"action": "set",
|
||||
"link_id": link.link_id,
|
||||
"filters": link.filters,
|
||||
"status": "success",
|
||||
"message": "Filters applied successfully",
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("Failed to set filters: %s", e)
|
||||
return {
|
||||
"action": "set",
|
||||
"link_id": link.link_id,
|
||||
"error": f"Failed to set filters: {str(e)}",
|
||||
"status": "failed",
|
||||
}
|
||||
|
||||
def _get_filters(self, link: Link) -> dict[str, Any]:
|
||||
"""Get current filters configured on the link."""
|
||||
try:
|
||||
# Get link information
|
||||
link.get()
|
||||
|
||||
logger.info("Retrieved current filters for link %s", link.link_id)
|
||||
return {
|
||||
"action": "get",
|
||||
"link_id": link.link_id,
|
||||
"filters": link.filters,
|
||||
"status": "success",
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("Failed to get filters: %s", e)
|
||||
return {
|
||||
"action": "get",
|
||||
"link_id": link.link_id,
|
||||
"error": f"Failed to get filters: {str(e)}",
|
||||
"status": "failed",
|
||||
}
|
||||
|
||||
def _clear_filters(
|
||||
self, link: Link, show_filters_icon: bool = False
|
||||
) -> dict[str, Any]:
|
||||
"""Clear all filters from the link."""
|
||||
try:
|
||||
# Clear filters by setting empty dict
|
||||
link.update(filters={}, show_filters_icon=show_filters_icon)
|
||||
|
||||
# Get updated link info to confirm
|
||||
link.get()
|
||||
|
||||
logger.info("Successfully cleared filters on link %s", link.link_id)
|
||||
return {
|
||||
"action": "clear",
|
||||
"link_id": link.link_id,
|
||||
"filters": link.filters,
|
||||
"status": "success",
|
||||
"message": "Filters cleared successfully",
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("Failed to clear filters: %s", e)
|
||||
return {
|
||||
"action": "clear",
|
||||
"link_id": link.link_id,
|
||||
"error": f"Failed to clear filters: {str(e)}",
|
||||
"status": "failed",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test the tool locally
|
||||
# TODO: Replace with actual project and link UUIDs
|
||||
test_input = json.dumps(
|
||||
{
|
||||
"project_id": "d7fc094c-685e-4db1-ac11-5e33a1b2e066",
|
||||
"link_id": "link-uuid-here",
|
||||
"action": "get_available",
|
||||
}
|
||||
)
|
||||
|
||||
tool = GNS3PacketFilterTool()
|
||||
result = tool._run(test_input)
|
||||
pprint(result)
|
||||
Loading…
x
Reference in New Issue
Block a user