mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
feat: implement traffic insight marker backend (ubridge mark filter)
Add compute-side marker subsystem that receives ubridge UDP MARK signals
and forwards them as project-scoped notifications to the web UI for
real-time traffic coloring. Matched packets are always saved to per-link
pcaps for future replay.
Key components:
- gns3server/compute/marker/: MarkerManager (singleton, UDP listener +
O(1) registry keyed by (node_id, filter_name)) + MarkerListener
(DatagramProtocol parsing MARK lines per ubridge integration contract)
- gns3server/compute/base_node.py: marker sink/node config at ubridge
startup; shared _ubridge_add_marker_filter / _ubridge_delete_marker_filter
- Per-node start_marker/stop_marker: VPCS (VPCS-{id}), QEMU
(QEMU-{id}-{adapter}), Docker (bridge{adapter})
- Compute REST /markers/start + /markers/stop (vpcs/qemu/docker route files)
- Controller Link._markers state + UDPLink.start_marker/stop_marker/
update_marker (mirror capture pattern: BPF validation, _choose_capture_side,
node.post forwarding, topology persistence)
- Controller REST GET/POST/DELETE/PUT /v3/projects/{p}/links/{l}/markers
- Config: marker_listen_host / marker_listen_port in ServerSettings
- Signal routing: creation-time registry O(1) lookup, no node-table scan;
project-scoped WS stream (not global); event payload always carries
project_id for frontend scoping
Tests: 14 unit tests (registry, listener parsing, UDP round-trip);
562 existing tests pass with zero regressions.
This commit is contained in:
21 files changed
+1135
-1
No files matched your search
@@ -29,6 +29,7 @@ from typing import Union
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.docker import Docker
|
||||
from gns3server.compute.docker.docker_vm import DockerVM
|
||||
from gns3server.compute.marker.marker_manager import MarkerManager
|
||||
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
@@ -353,6 +354,52 @@ async def stop_docker_node_capture(
|
||||
await node.stop_capture(adapter_number)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/start",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def start_docker_node_marker(
|
||||
*,
|
||||
project_id: UUID,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
marker_data: schemas.MarkerCreate,
|
||||
node: DockerVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Attach a traffic-insight ``mark`` filter to the Docker node's uBridge bridge.
|
||||
"""
|
||||
|
||||
pcap_path = os.path.join(
|
||||
node.project.markers_working_directory(),
|
||||
f"{node.id}_{marker_data.link_id}_{marker_data.name}.pcap"
|
||||
)
|
||||
await node.start_marker(adapter_number, marker_data.name, marker_data.bpf, pcap_path, marker_data.tag)
|
||||
MarkerManager.instance().register(
|
||||
str(project_id), node.id, marker_data.name, marker_data.link_id, marker_data.tag
|
||||
)
|
||||
return {"pcap_file_path": str(pcap_path)}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_docker_node_marker(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
marker_data: schemas.MarkerDelete,
|
||||
node: DockerVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Remove a traffic-insight ``mark`` filter from the Docker node's uBridge bridge.
|
||||
"""
|
||||
|
||||
await node.stop_marker(adapter_number, marker_data.name)
|
||||
MarkerManager.instance().unregister(node.id, marker_data.name)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
|
||||
@@ -30,6 +30,7 @@ from gns3server import schemas
|
||||
from gns3server.compute import qemu
|
||||
from gns3server.compute.qemu import Qemu
|
||||
from gns3server.compute.qemu.qemu_vm import QemuVM
|
||||
from gns3server.compute.marker.marker_manager import MarkerManager
|
||||
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
@@ -382,6 +383,52 @@ async def stop_qemu_node_capture(
|
||||
await node.stop_capture(adapter_number)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/start",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def start_qemu_node_marker(
|
||||
*,
|
||||
project_id: UUID,
|
||||
adapter_number: int,
|
||||
marker_data: schemas.MarkerCreate,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Attach a traffic-insight ``mark`` filter to the QEMU node's uBridge bridge.
|
||||
"""
|
||||
|
||||
pcap_path = os.path.join(
|
||||
node.project.markers_working_directory(),
|
||||
f"{node.id}_{marker_data.link_id}_{marker_data.name}.pcap"
|
||||
)
|
||||
await node.start_marker(adapter_number, marker_data.name, marker_data.bpf, pcap_path, marker_data.tag)
|
||||
MarkerManager.instance().register(
|
||||
str(project_id), node.id, marker_data.name, marker_data.link_id, marker_data.tag
|
||||
)
|
||||
return {"pcap_file_path": str(pcap_path)}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_qemu_node_marker(
|
||||
adapter_number: int,
|
||||
marker_data: schemas.MarkerDelete,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Remove a traffic-insight ``mark`` filter from the QEMU node's uBridge bridge.
|
||||
"""
|
||||
|
||||
await node.stop_marker(adapter_number, marker_data.name)
|
||||
MarkerManager.instance().unregister(node.id, marker_data.name)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
|
||||
@@ -29,6 +29,7 @@ from uuid import UUID
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.vpcs import VPCS
|
||||
from gns3server.compute.vpcs.vpcs_vm import VPCSVM
|
||||
from gns3server.compute.marker.marker_manager import MarkerManager
|
||||
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
@@ -303,6 +304,54 @@ async def stop_vpcs_node_capture(
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/start",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def start_vpcs_node_marker(
|
||||
*,
|
||||
project_id: UUID,
|
||||
port_number: int,
|
||||
marker_data: schemas.MarkerCreate,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Attach a traffic-insight ``mark`` filter to the VPCS node's uBridge bridge.
|
||||
On BPF match uBridge emits a MARK signal and appends the packet to the pcap.
|
||||
"""
|
||||
|
||||
pcap_path = os.path.join(
|
||||
node.project.markers_working_directory(),
|
||||
f"{node.id}_{marker_data.link_id}_{marker_data.name}.pcap"
|
||||
)
|
||||
await node.start_marker(port_number, marker_data.name, marker_data.bpf, pcap_path, marker_data.tag)
|
||||
MarkerManager.instance().register(
|
||||
str(project_id), node.id, marker_data.name, marker_data.link_id, marker_data.tag
|
||||
)
|
||||
return {"pcap_file_path": pcap_path}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_vpcs_node_marker(
|
||||
*,
|
||||
port_number: int,
|
||||
marker_data: schemas.MarkerDelete,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Remove a traffic-insight ``mark`` filter from the VPCS node's uBridge bridge.
|
||||
"""
|
||||
|
||||
await node.stop_marker(port_number, marker_data.name)
|
||||
MarkerManager.instance().unregister(node.id, marker_data.name)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
|
||||
Reference in new issue
Block a user