mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-12 21:45:41 +03:00
test(e2e): relay filters shape real traffic on both relay engines
Extend the docker and IOU relay negative controls with the filters the
relay itself supports (delay, frequency_drop, bpf), asserted through real
ICMP — the first live coverage of the relay filter path:
* bridge engine (docker): delay 100 grows the RTT by ~200 ms and comes
back on clear; frequency_drop 2 kills the round trips (the filter counts
every packet crossing the bridge, both directions sharing one counter,
so on an alternating ping stream one whole direction dies; verified
against uBridge directly), frequency_drop -1 blackholes, and bpf drops
by libpcap expression ('icmp', then 'greater 150' discriminating by
frame size).
* iol_bridge engine (IOU): the same delay and frequency_drop effects
through the port's iol_bridge filter list, plus the available_filters
spot check.
Docker 33 s, IOU 168 s, both green.
This commit is contained in:
parent
4fc0398423
commit
9d937802d5
@ -465,9 +465,10 @@ L2-anchor spec §E assertions (no L3 identity, forwarding ports, idle
|
||||
silence with the guests' links down), the filter matrix one type at a time
|
||||
(netem core and extensions, cls_bpf match-drop, the eBPF classifier, each
|
||||
asserted through real traffic), suspend, capture, link delete/re-create,
|
||||
container stop/start, a relay negative control, and the relay→kernel
|
||||
upgrade on project reopen (the datapath choice is flipped across a server
|
||||
restart). The container image is digest-pinned and pulled through the
|
||||
container stop/start, a relay negative control — whose own filters
|
||||
(delay/frequency_drop/bpf) are asserted to really shape the relay wire as
|
||||
uBridge userspace bridge filters — and the relay→kernel upgrade on project
|
||||
reopen (the datapath choice is flipped across a server restart). The container image is digest-pinned and pulled through the
|
||||
server's own pull route on a cache miss, so collaborators test the same
|
||||
bytes.
|
||||
|
||||
|
||||
@ -148,7 +148,8 @@ IOU routers** (`tests/e2e/test_iou_kernel_datapath.py`, pytest marker
|
||||
the server's telnet console, driving anchors-born-with-the-node, real ICMP
|
||||
through the port bridge, netem delay, suspend, capture, link
|
||||
delete/re-create, node stop/start rewiring, the serial link staying on the
|
||||
relay, and a relay negative control.
|
||||
relay, and a relay negative control whose real `delay`/`frequency_drop`
|
||||
filters ride the port's `iol_bridge add_packet_filter` list.
|
||||
|
||||
The uBridge side passed its own 30/30 (`tests/iol/test_tap_anchor.py` in the
|
||||
fork), including the DOWN-anchor resilience and kernel-bridge interop this
|
||||
|
||||
@ -79,7 +79,9 @@ Current scenarios:
|
||||
from the images directory, the real IOS CLI on the server's telnet
|
||||
console): kernel link through the [IOL fabric ↔ TAP] port bridge (same
|
||||
lifecycle as the iol-xe containers), a serial link staying on the relay
|
||||
(serial bays never anchor), and a relay negative control.
|
||||
(serial bays never anchor), and a relay negative control whose filters
|
||||
ride the IOU-specific `iol_bridge add_packet_filter` engine (delay and
|
||||
frequency_drop measured through real traffic).
|
||||
* `test_qemu_kernel_datapath.py` — two real IOSv routers (a real qcow2
|
||||
image from the images directory's `QEMU` subfolder, linked clone, the
|
||||
real IOS CLI on the server's telnet console): the anchor TAP is QEMU's
|
||||
@ -91,9 +93,10 @@ Current scenarios:
|
||||
veth datapath): the per-link kernel bridge enslaves exactly the two veth
|
||||
host ends, real ICMP crosses, the filter matrix runs one type at a time
|
||||
(netem core and extensions, cls_bpf match-drop, the eBPF classifier —
|
||||
each measured through real traffic), plus a relay negative control and a
|
||||
relay→kernel reopen upgrade (the server's datapath choice is flipped
|
||||
across a restart).
|
||||
each measured through real traffic), plus a relay negative control whose
|
||||
own filters (delay, frequency_drop, bpf) really shape the wire as uBridge
|
||||
userspace bridge filters, and a relay→kernel reopen upgrade (the server's
|
||||
datapath choice is flipped across a restart).
|
||||
* `test_marker_kernel_datapath.py` — two real Alpine containers on a
|
||||
kernel-only link with traffic-insight markers attached to a veth anchor
|
||||
(`marker add_kernel`): `marker.match` signals collected off the dedicated
|
||||
|
||||
@ -36,7 +36,10 @@ server itself uses. The scenarios cover:
|
||||
offered by ``available_filters`` and everything restores cleanly.
|
||||
* ``test_docker_relay_control`` — the negative control on an isolated
|
||||
relay-configured instance: no per-link kernel bridge, the wire is the
|
||||
uBridge relay, and the kernel-only filter types are hidden.
|
||||
uBridge relay, the kernel-only filter types are hidden — and the relay's
|
||||
own filter set (delay, frequency_drop, bpf) really shapes the wire, as
|
||||
uBridge userspace filters on the filter node's bridge
|
||||
(``bridge add_packet_filter``).
|
||||
* ``test_docker_relay_to_kernel_reopen_upgrade`` — the server's datapath
|
||||
choice is flipped (relay → kernel) across a restart and the project is
|
||||
reopened: the link is rebuilt on the kernel datapath and forwards.
|
||||
@ -481,6 +484,49 @@ def test_docker_relay_control():
|
||||
assert not (offered & {"rate", "reorder", "gemodel", "duplicate", "seed", "limit", "quota", "window_drop"}), (
|
||||
offered
|
||||
)
|
||||
|
||||
# The relay's filters are real uBridge userspace filters walked by
|
||||
# the bridge's two listener threads: both directions of the wire
|
||||
# cross them. delay 100 creates one delay line per direction, so the
|
||||
# RTT grows by ~200 ms.
|
||||
print(".. relay filters: delay 100 ms (one delay line per direction, RTT +~200 ms)")
|
||||
_put_filters(compute, pid, link["link_id"], {"delay": [100]})
|
||||
harness.docker_wait_ping(daemon, n1["container_id"], R2_IP)
|
||||
delayed = harness.docker_ping(daemon, n1["container_id"], R2_IP, count=5)
|
||||
assert delayed["loss"] == 0, delayed["raw"]
|
||||
assert delayed["avg"] >= 150, (relay_ping, delayed)
|
||||
_put_filters(compute, pid, link["link_id"], {})
|
||||
fast = harness.docker_wait_ping(daemon, n1["container_id"], R2_IP)
|
||||
assert fast["loss"] == 0 and fast["avg"] < 50, (relay_ping, fast)
|
||||
|
||||
# frequency_drop counts every packet crossing the bridge, both
|
||||
# directions sharing one counter, so on an alternating ping stream
|
||||
# the same direction dies on every crossing (probed against uBridge
|
||||
# directly: replies drop, round trips die wholesale). -1 is the
|
||||
# drop-everything encoding the suspend emulation uses.
|
||||
print(".. relay filters: frequency_drop 2 (shared counter, one direction dies)")
|
||||
_put_filters(compute, pid, link["link_id"], {"frequency_drop": [2]})
|
||||
dropped = harness.docker_ping(daemon, n1["container_id"], R2_IP, count=10)
|
||||
print(f".. {dropped['loss']} % round-trip loss")
|
||||
assert dropped["loss"] >= 50, dropped["raw"]
|
||||
_put_filters(compute, pid, link["link_id"], {"frequency_drop": [-1]})
|
||||
blackholed = harness.docker_ping(daemon, n1["container_id"], R2_IP, count=4)
|
||||
assert blackholed["loss"] == 100, blackholed["raw"]
|
||||
|
||||
# bpf on the relay is uBridge's libpcap userspace match-drop — a
|
||||
# different engine from the kernel datapath's cls_bpf
|
||||
print(".. relay filters: bpf 'icmp' drops every ICMP frame")
|
||||
_put_filters(compute, pid, link["link_id"], {"bpf": ["icmp"]})
|
||||
icmp_dropped = harness.docker_ping(daemon, n1["container_id"], R2_IP, count=4)
|
||||
assert icmp_dropped["loss"] == 100, icmp_dropped["raw"]
|
||||
print(".. relay filters: bpf 'greater 150' discriminates by frame size")
|
||||
_put_filters(compute, pid, link["link_id"], {"bpf": ["greater 150"]})
|
||||
small = harness.docker_ping(daemon, n1["container_id"], R2_IP, count=4)
|
||||
assert small["loss"] == 0, small["raw"]
|
||||
big = harness.docker_ping(daemon, n1["container_id"], R2_IP, count=4, size=300)
|
||||
assert big["loss"] == 100, big["raw"]
|
||||
_put_filters(compute, pid, link["link_id"], {})
|
||||
assert harness.docker_ping(daemon, n1["container_id"], R2_IP, count=5)["loss"] == 0
|
||||
except BaseException:
|
||||
harness.release(server, pid, failed=True)
|
||||
raise
|
||||
|
||||
@ -34,7 +34,9 @@ Linux bridge, kernel filters, suspend and capture.
|
||||
|
||||
``test_iou_relay_control`` is the negative control on an isolated
|
||||
relay-configured instance: the same topology rides the fabric socket ↔ UDP
|
||||
relay (no per-link bridge, nothing enslaved) and still pings.
|
||||
relay (no per-link bridge, nothing enslaved) and still pings — with its
|
||||
filters applied by the IOU-specific engine
|
||||
(``iol_bridge add_packet_filter`` on the IOL port) really shaping the wire.
|
||||
"""
|
||||
|
||||
import re
|
||||
@ -238,7 +240,9 @@ def test_iou_relay_control():
|
||||
Negative control: the same topology with ``enable_kernel_datapath =
|
||||
false`` (an isolated instance) wires nothing into the kernel — no
|
||||
per-link bridge, no enslavement — and still pings over the fabric
|
||||
socket ↔ UDP relay.
|
||||
socket ↔ UDP relay. The relay's filters ride the IOU-specific engine:
|
||||
the IOL port's userspace filter list (``iol_bridge add_packet_filter``),
|
||||
walked by both the fabric → wire and wire → fabric paths.
|
||||
|
||||
Anchors DO exist here (an IOU node creates its port TAPs at start
|
||||
whenever the uBridge can terminate an IOL port on one); the server
|
||||
@ -281,6 +285,34 @@ def test_iou_relay_control():
|
||||
relay_ping = harness.wait_ping(c1, "10.1.1.2")
|
||||
assert relay_ping["success"] == 100, relay_ping["raw"]
|
||||
|
||||
# The relay link's filters really shape the wire on the IOU engine
|
||||
# too. delay 100: one delay line per direction (the port's IOL and
|
||||
# NIO sides are separate lines), so the RTT grows by ~200 ms.
|
||||
lid = link["link_id"]
|
||||
offered = {entry["type"] for entry in compute.call("GET", f"/projects/{pid}/links/{lid}/available_filters")}
|
||||
assert "delay" in offered and "frequency_drop" in offered, offered
|
||||
|
||||
print(".. iol_bridge relay filter: delay 100 ms (per-direction delay lines, RTT +~200 ms)")
|
||||
compute.call("PUT", f"/projects/{pid}/links/{lid}", {"filters": {"delay": [100]}})
|
||||
delayed = harness.ping(c1, "10.1.1.2", repeat=5)
|
||||
assert delayed["success"] == 100, delayed["raw"]
|
||||
assert delayed["avg"] >= 150, (relay_ping, delayed)
|
||||
compute.call("PUT", f"/projects/{pid}/links/{lid}", {"filters": {}})
|
||||
fast = harness.ping(c1, "10.1.1.2", repeat=3)
|
||||
assert fast["success"] == 100 and fast["avg"] < 50, (relay_ping, fast)
|
||||
|
||||
# frequency_drop shares one counter between the port's two
|
||||
# directions: on an alternating ping stream one whole direction dies
|
||||
# (same semantics as the plain relay's bridge filters).
|
||||
print(".. iol_bridge relay filter: frequency_drop 2 (shared counter, one direction dies)")
|
||||
compute.call("PUT", f"/projects/{pid}/links/{lid}", {"filters": {"frequency_drop": [2]}})
|
||||
dropped = harness.ping(c1, "10.1.1.2", repeat=6)
|
||||
print(f".. success {dropped['success']} %")
|
||||
assert dropped["success"] <= 50, dropped["raw"]
|
||||
compute.call("PUT", f"/projects/{pid}/links/{lid}", {"filters": {}})
|
||||
recovered = harness.wait_ping(c1, "10.1.1.2")
|
||||
assert recovered["success"] == 100, recovered["raw"]
|
||||
|
||||
c1.close()
|
||||
c2.close()
|
||||
except BaseException:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user