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:
Jeremy Grossmann 2026-05-25 00:00:09 +08:00 committed by GitHub
commit ec55b9f089
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 827 additions and 0 deletions

View File

@ -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

View 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).

View File

@ -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

View File

@ -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
]

View File

@ -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:

View File

@ -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",

View 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)