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:
YueGuobin committed 2026-08-04 23:20:58 +08:00
1 parent 60e2bbbbbb
commit 1a0ce51f38
4 files changed
+139 -12

No files matched your search

+36 -2
View File
@@ -965,6 +965,21 @@ class Project:
"""
return self._marker_definitions
def _validate_marker_definition_bpf(self, name, bpf):
"""
Validate a marker definition's BPF once, here, so the fan-out to every
link (``_apply_def_to_all_links`` → ``inherit_marker`` → ``start_marker``)
and the per-link sync (``update_marker_definition`` → ``update_marker``)
can skip re-validation for the inherited copies — otherwise one
``tcpdump -d`` subprocess runs per link for the same expression. A
private per-link marker still validates in ``start_marker``/``update_marker``.
"""
result = validate_bpf_syntax(bpf)
if not result.get("valid"):
raise ControllerError(
f"Marker definition '{name}': invalid BPF — {result.get('error', 'unknown error')}"
)
def _validate_marker_definition_direction(self, name, direction):
"""
Reject tx/rx on a marker definition: a definition fans out to every link
@@ -997,6 +1012,7 @@ class Project:
f"Marker definition '{name}' already exists in this project"
)
self._validate_marker_definition_bpf(name, bpf)
self._validate_marker_definition_direction(name, direction)
self._marker_definitions[name] = {"bpf": bpf, "tag": tag, "direction": direction, "color": color, "highlight_duration": highlight_duration, "paused": False}
await self._apply_def_to_all_links(name)
@@ -1015,6 +1031,7 @@ class Project:
d = self._marker_definitions[name]
if bpf is not None:
self._validate_marker_definition_bpf(name, bpf)
d["bpf"] = bpf
if tag is not None:
d["tag"] = tag
@@ -1489,10 +1506,27 @@ class Project:
setattr(self, key, val)
# marker_definitions is loaded separately (it is not a __init__ kwarg
# nor a simple attribute — it backs a read-only property).
# nor a simple attribute — it backs a read-only property). Each BPF
# is validated once here so the inherited fan-out (start_marker) can
# skip re-validation; an invalid definition is dropped with a warning
# rather than failing the open — it could not fan out anyway.
defs = project_data.get("marker_definitions")
if isinstance(defs, dict):
self._marker_definitions = defs
clean_defs = {}
for def_name, d in defs.items():
bpf = d.get("bpf")
if not bpf:
log.warning("Dropping marker definition '%s' on load: missing bpf", def_name)
continue
result = validate_bpf_syntax(bpf)
if not result.get("valid"):
log.warning(
"Dropping marker definition '%s' on load: invalid BPF (%s)",
def_name, result.get("error")
)
continue
clean_defs[def_name] = d
self._marker_definitions = clean_defs
topology = project_data["topology"]
for compute in topology.get("computes", []):