mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
marker: validate def BPF once, skip re-validation on inherited fan-out
A marker definition validated its BPF N times — once per link in the fan-out (start_marker runs validate_bpf_syntax on every copy), spawning one tcpdump -d subprocess per link for the same expression. Definitions did not validate BPF at all; only direction was checked. Move the single validation point to the definition layer (create/update), and validate each definition's BPF on project load (dropping any that have gone invalid, like private markers). The inherited fan-out (start_marker) and def sync (update_marker) now skip validate_bpf_syntax for inherited copies, since the BPF comes from an already-validated definition. Private per-link markers still validate inline as before. uBridge still runs pcap_compile at install, so an invalid expression can never slip through. Creating a definition over N links now runs one tcpdump instead of N.
This commit is contained in:
4 files changed
+139
-12
No files matched your search
@@ -28,6 +28,7 @@ import uuid
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from contextlib import ExitStack
|
||||
|
||||
from tests.utils import AsyncioMagicMock
|
||||
|
||||
@@ -38,11 +39,20 @@ from gns3server.controller.controller_error import ControllerError, ControllerNo
|
||||
|
||||
|
||||
def _valid_bpf():
|
||||
"""Bypass tcpdump-based BPF validation so tests don't depend on tcpdump."""
|
||||
return patch(
|
||||
"""Bypass tcpdump-based BPF validation so tests don't depend on tcpdump.
|
||||
|
||||
Patches both namespaces that import ``validate_bpf_syntax`` by name: the
|
||||
per-link ``udp_link`` (private marker create/update) and the project layer
|
||||
(definition create/update/load), which is now the single validation point
|
||||
for inherited copies.
|
||||
"""
|
||||
stack = ExitStack()
|
||||
for target in (
|
||||
"gns3server.controller.udp_link.validate_bpf_syntax",
|
||||
return_value={"valid": True, "error": None},
|
||||
)
|
||||
"gns3server.controller.project.validate_bpf_syntax",
|
||||
):
|
||||
stack.enter_context(patch(target, return_value={"valid": True, "error": None}))
|
||||
return stack
|
||||
|
||||
|
||||
async def _make_link(project):
|
||||
@@ -438,6 +448,73 @@ async def test_apply_defs_to_new_link(project):
|
||||
assert new_link.markers["global-arp"]["inherited_from"] == "arp"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_marker_definition_validates_bpf_once(project):
|
||||
# A definition validates its BPF once (project layer); the inherited fan-out
|
||||
# to every link must NOT re-validate — no tcpdump subprocess per link.
|
||||
with patch("gns3server.controller.project.validate_bpf_syntax",
|
||||
return_value={"valid": True, "error": None}) as proj_val, \
|
||||
patch("gns3server.controller.udp_link.validate_bpf_syntax",
|
||||
return_value={"valid": True, "error": None}) as link_val:
|
||||
await _make_link(project)
|
||||
await _make_link(project)
|
||||
await project.create_marker_definition("arp", "arp")
|
||||
|
||||
assert proj_val.call_count == 1 # validated once at the def layer
|
||||
assert link_val.call_count == 0 # fan-out skipped per-link validation
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_marker_definition_rejects_invalid_bpf(project):
|
||||
|
||||
with patch("gns3server.controller.project.validate_bpf_syntax",
|
||||
return_value={"valid": False, "error": "syntax error"}):
|
||||
with pytest.raises(ControllerError):
|
||||
await project.create_marker_definition("arp", "not a real bpf")
|
||||
assert "arp" not in project.marker_definitions
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_marker_definition_skips_per_link_validation(project):
|
||||
# Updating a def's BPF validates once more (project); the per-link sync
|
||||
# (update_marker with inherited=True) must NOT re-validate.
|
||||
with patch("gns3server.controller.project.validate_bpf_syntax",
|
||||
return_value={"valid": True, "error": None}) as proj_val, \
|
||||
patch("gns3server.controller.udp_link.validate_bpf_syntax",
|
||||
return_value={"valid": True, "error": None}) as link_val:
|
||||
await _make_link(project)
|
||||
await _make_link(project)
|
||||
await project.create_marker_definition("arp", "arp")
|
||||
await project.update_marker_definition("arp", bpf="arp or rarp")
|
||||
|
||||
assert proj_val.call_count == 2 # once on create, once on update
|
||||
assert link_val.call_count == 0 # sync skipped per-link validation
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_marker_skips_validation_for_inherited(project):
|
||||
# An inherited marker rides an already-validated definition BPF, so
|
||||
# start_marker must not call validate_bpf_syntax.
|
||||
with patch("gns3server.controller.udp_link.validate_bpf_syntax",
|
||||
return_value={"valid": True, "error": None}) as link_val:
|
||||
link = await _make_link(project)
|
||||
await link.inherit_marker("arp", {"bpf": "arp"})
|
||||
|
||||
assert link_val.call_count == 0
|
||||
assert link.markers["global-arp"]["bpf"] == "arp"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_marker_validates_for_private(project):
|
||||
# A private (non-inherited) marker still validates inline.
|
||||
with patch("gns3server.controller.udp_link.validate_bpf_syntax",
|
||||
return_value={"valid": True, "error": None}) as link_val:
|
||||
link = await _make_link(project)
|
||||
await link.start_marker("icmp", "icmp")
|
||||
|
||||
assert link_val.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_markers_aggregation(project):
|
||||
|
||||
|
||||
Reference in new issue
Block a user