feat: concurrent kernel captures, one per interface

uBridge's capture module now serves one AF_PACKET capture per interface,
any number per process, and `capture stop_kernel` takes the interface it
stops — this rides that (breaking, paired-binary) protocol change.

The server-side single-slot tracker (_kernel_capture_ifc with its
reserve/release/owned_by helpers) is gone: same-port duplicates were
already refused by the NIO capturing flag, uBridge answers EALREADY on a
same-interface duplicate, and per-interface stops removed the process-
wide stop the ownership gating guarded against.

The docker e2e drives ten concurrent captures on one node's uBridge and
asserts a per-interface stop freezes exactly the stopped half while the
live half keeps recording.
This commit is contained in:
YueGuobin committed 2026-10-14 21:52:34 +08:00
1 parent 149549e029
commit 9e0d5050db
15 files changed
+155 -171

No files matched your search

+5 -11
View File
@@ -1828,19 +1828,16 @@ class IOUVM(KernelDatapathMixin, BaseNode):
if self.ubridge:
if isinstance(nio, NIOBridge):
# Kernel link: capture on the port's TAP anchor (AF_PACKET,
# one capture per uBridge process — see _reserve_kernel_capture;
# a failed start rolls the port's flag back so the port cannot
# later stop the winner's capture).
# one capture per interface — a failed start rolls the port's
# flag back).
anchor = self._kernel_host_ifc(adapter_number, port_number)
try:
if anchor is None:
raise self._kernel_error(
f"Bay {adapter_number}/{port_number} of IOU '{self._name}' has no TAP anchor to capture on"
)
self._reserve_kernel_capture(anchor)
await self._ubridge_send(f'capture start_kernel {anchor} "{output_file}"')
except Exception:
self._release_kernel_capture(anchor)
nio.stop_packet_capture()
raise
else:
@@ -1869,13 +1866,10 @@ class IOUVM(KernelDatapathMixin, BaseNode):
log.debug(f'IOU "{self._name}" [{self._id}]: stopping packet capture on {adapter_number}/{port_number}')
if self.ubridge:
if isinstance(nio, NIOBridge):
# Process-wide and argument-less: only the port owning the
# slot may stop it (a second port's stop would kill this
# capture while its own flag still says capturing).
# Per-interface stop: only this port's capture is stopped.
anchor = self._kernel_host_ifc(adapter_number, port_number)
if self._kernel_capture_owned_by(anchor):
await self._ubridge_send("capture stop_kernel")
self._release_kernel_capture(anchor)
if anchor is not None:
await self._ubridge_send(f'capture stop_kernel {anchor}')
else:
await self._ubridge_send(
f"iol_bridge stop_capture {self._iol_bridge_name()} {adapter_number} {port_number}"