12 Commits

Author SHA1 Message Date
YueGuobin
f13da59f23
docs(ubridge): the fifth L2-only creator, and the call site that used it
The spec's B table grows its fifth row: bridge add_nio_tap mints a
transient TAP when the name is free and hardens it at creation, while an
attach to an existing device (possibly a user-owned TAP with addresses)
is left untouched. The rule paragraph spells out attach-is-not-a-creation.
Delivery: uBridge fb72758, closing the gap the new e2e assertions found
(live: the switch's gns3*-0 relay TAP carried fe80::). The server issues
no link l2only of its own -- the switch call site now documents that the
TAP is minted-and-hardened by add_nio_tap, and the ESW feature doc
describes the uBridge-side close instead of a server workaround.
2026-10-06 00:21:10 +08:00
YueGuobin
3b8172cbc8
docs(iou): record the IOU kernel datapath and its spec
* docs/features/iou-kernel-datapath.md: architecture (one irreducible
  userspace hop on the fabric leg, kernel link segment), datapath
  selection, the anchor lifecycle with the reverse-of-QEMU stop order,
  capture/markers/filters, relay fallback, and the verified numbers (an
  isolated server with two IOU nodes behind a fake ELF image, the test
  playing the IOL fabric: 64/64 kernel, 62/62 relay).
* docs/design/ubridge-iol-tap-anchor-spec.md: delivered — implemented on
  the ubridge fork's feature/iol-tap-anchor branch (both commands, the
  listener hardening, plus releasing every port NIO on iol_bridge delete
  even when the bridge was not running), with the implementation notes
  that came back (208 vs the older 214 for a missing bridge; one perror
  line per frame dropped on a down anchor).
* The Docker/QEMU feature docs point at it from their roadmaps.
2026-10-06 00:21:09 +08:00
YueGuobin
81ad9b837f
docs(spec): record the delivered L2-only anchors and their residual
uBridge delivered `link l2only` (e2e3155) and the brctl delete refusal
(83a84fb); verified from the server side -- anchors and per-link bridges
report addrgenmode none, carry no addresses, and an idle anchor is silent
once the one-shot membership burst has settled.

That burst is the one thing the command cannot remove, so it is now recorded
where the acceptance criteria live, with the measurement: enslaving a port
makes the kernel announce the bridge's multicast memberships (90 B MLDv2 at
+0.00 s, 54 B IGMPv3 at +0.09 s, 90 B MLDv2 at +0.73 s, then silence; no
addresses involved, and IPv4 has no per-device IGMP switch). Decision:
accepted and documented, not suppressed -- §E.2 measures idle silence after a
settle window instead.

Pins the other delivered behaviour with a test: a bridge now survives a
single-sided node stop, so brctl create answers EEXIST on the common path
rather than only in a create race -- the attach must not short-circuit there,
because the restarted side has a brand-new anchor whose filters and markers
still have to be re-applied.
2026-10-06 00:21:08 +08:00
YueGuobin
0b507773b9
docs(spec): require L2-only host anchors in uBridge
Every host-side device uBridge creates for the data plane (persistent TAP
anchors, veth host ends, per-link kernel bridges) must be pure L2: no IPv4
address, no IPv6 address and no IPv6 stack activity on that device.

Measured today: an idle TAP emits ~6 frames / 2 s of the host's own MLD/DAD
from its own link-local, the production Docker veth host end carries
fe80::5c30:e8ff:fe00:5d8d/64 and the per-link bridge fe80::f0d9:94ff:feac:e42b/64.
On a kernel link the bridge floods those frames into the emulated segment, on a
relay link uBridge relays them to the peer, and the host answers neighbor
solicitations for its own address (a phantom IPv6 neighbour for the emulated
nodes). IPv4 needs no equivalent knob: nothing self-provisions it, and the
anchors hold no IPv4 address.

The spec adds one command (link l2only <iface> [on|off], netlink addr-gen-mode
none -- never a /proc sysctl write, which fails the DAC check for a non-root
setcap'd uBridge), applied by the four creators themselves, with an
idle-silence acceptance test and a known-noise note in the Docker kernel
datapath document. No change to existing command grammar.
2026-10-06 00:21:08 +08:00
YueGuobin
c1d0e6ee21
docs(spec): define the gemodel acceptance target
The F section asked for "gemodel loss within +/-5% of target" without
saying what the target is, and the A.1 table gave ranges only. Spell out
the semantics the kernel implements (and that the veth calibration
confirmed): p is the good-to-bad transition, r the bad-to-good
transition, 1-h the loss chance inside the bad state (the good state
loses nothing), mean loss p/(p+r)*(1-h) per direction. The functional
bullet now names that target and how to measure it (per-direction
counters, round-trip reads 1-(1-L)^2, pre-warm ARP when 1-h=100).

No wire-format or grammar change.
2026-10-06 00:21:08 +08:00
YueGuobin
b4e4739d52
feat(compute): report uBridge tc capabilities and gate filters on them
Consume uBridge's ebpf_modes contract (feature/tc-window 40c36ae): a
stateful mode is usable iff ebpf=1 and its token is listed; builds
predating the field keep their shipped modes (nth/quota/flow), so the
pre-correction window semantics can never be installed silently.

- gns3server/utils/tc_capabilities.py: shared reply parser, mode
  policy and filter-type mappings (compute and controller gate on the
  same rules)
- gns3server/compute/ubridge/tc_probe.py: standalone probe spawning a
  throwaway uBridge as the server's own user, cached by binary
  identity (path/mtime/size - the version string cannot distinguish
  rebuilds); failures cache as unknown
- GET /v3/compute/capabilities gains ubridge_tc {netem, ebpf,
  ebpf_modes, cbpf}; the controller forwards it (Compute.capabilities)
  and available_filters hides kernel-datapath filter types an endpoint
  compute cannot run (both ends apply their own filters, so one
  incapable compute hides the type); computes without a report keep
  the full list and apply-time 409 stays the guard
- docker: _ubridge_apply_ebpf_drops gates per mode token; the cleanup
  path only sends "off" for modes the build declares
- split DockerVM's kernel-datapath machinery (veth lifecycle, carrier,
  marker primitives, tc filter translation, kernel NIO teardown) into
  compute/docker/docker_kernel_datapath.py (DockerKernelDatapathMixin)
2026-10-06 00:21:08 +08:00
YueGuobin
316e7002c1
feat(docker): expose the eBPF time-window filter on kernel links
uBridge's window mode gained the semantics the B.2 spec asked for —
`tc window_drop <if> <start> <outage> <pct> [<period> [<jitter>]]`: a
single outage that traffic passes before *and* after, recurring flaps
with period >= outage, and a per-cycle jitter — so the kernel-only
`window_drop` filter type is now exposed:

- validation mirrors the tc grammar (3-5 params, ranges, period >=
  outage, jitter cap) and adds window_drop to KERNEL_ONLY_FILTERS;
  filter_inactive_filters keeps start=0 — unlike every other filter a
  zero first parameter means "the outage starts now", not "disabled".
- `_ubridge_apply_ebpf_drops` sends `tc window_drop` (positional
  pass-through) and turns the mode off explicitly when absent, under the
  same ebpf=1 gate.
- FILTERS entry (Start/Outage/Chance/Period/Jitter) + available_filters.

The schedule is relative to the apply: reconcile re-sets the classifier
on every apply, so any filter change, node restart or link reset restarts
the window.

Verified on a two-container kernel link, 15/15: a single outage
[1000, 1500, 100] arrives per-seq as [0..4, 13..19] (passing before the
window, dropped inside, passing after); recurring 1000/2000 ms = 52 %
round trip (duty cycle); period == outage = permanent outage; period <
outage = 409 before uBridge; jittered flap keeps the ~50 % duty cycle;
30 % chance on a permanent window = 53 % round trip against the
per-direction theory 1-0.7^2 = 51 %; coexist with netem; node restart
restores; available_filters kernel vs relay (409 + hidden); teardown
clean. P6b re-run as a regression on the same uBridge build: 13/13.

Worth recording for the next rebuild: the first delivered window program
loaded as root but reported ebpf=0 through gns3-server — the verifier's
non-root path loses the loop counter's bound, treats the catch-up loop as
unbounded and rejects the *whole* program (E2BIG, 8193 pending states),
which also took frequency_drop and quota down, with ebpf=0 as the only
symptom. Smaller trip caps did not help; the loop had to go (uBridge
572ca0c), on top of a Makefile header-dependency fix (53e34bb) that had
been shipping a stale program object.
2026-10-06 00:21:08 +08:00
YueGuobin
0c4ec8c38c
feat(docker): eBPF stateful packet filters on kernel links
Consume the eBPF impairment classifier delivered in uBridge
feature/tc-precision: frequency_drop becomes the exact every-Nth mode
(tc nth_drop; -1 = drop everything maps to every 1st) and the new
kernel-only quota type is the byte-cap mode (tc quota_drop, 100 = hard
cutoff). KERNEL_UNSUPPORTED_FILTERS is gone entirely — kernel/relay
eligibility is now purely topological. Both modes gate on ebpf=1 from
the per-process tc capabilities probe (error names the setcap fix); the
cleanup path (modes off) only talks to the eBPF commands when the probe
reported them. Reconcile is a plain re-set: the netem reset-before-set
already detaches the program and clears uBridge's per-interface
registry, so a mode set after it is a fresh load+attach.

Documented semantic difference: the relay counts both directions
through one bridge counter, the kernel counts per direction — N=3
round-trip loss is 1-(2/3)^2 = 55.6% on kernel vs ~1/N on relay
(measured 57%). window_drop is NOT exposed: the delivered
implementation advances the window with no gap, making 'outside the
window packets pass' unreachable (needs outage/period fields —
recorded in the spec); flow_drop awaits a parameter-shape decision.

Also fixes a pre-existing restart bug the e2e exposed: _bridges (relay
bridge names) was never cleared on stop, so restarting a node with a
relay link skipped 'bridge create' and failed on add_nio_udp.

e2e (isolated instance): 13/13 — pref-1 tc_impair classifier on both
veths, 57% round-trip for N=3, -1 = exact 100%, quota hard cutoff,
delay coexistence, clean teardown, suspend/resume, node restart,
relay-leg frequency_drop regression through an Ethernet switch. Full
suite: 1925 passed.
2026-10-06 00:21:08 +08:00
YueGuobin
d4b57d49fb
feat(docker): expose netem-extension packet filters on kernel links
Surface the netem extensions delivered in uBridge feature/tc-netem-ext
as GNS3 filter types: rate (tc-style bandwidth string), reorder (needs
delay), gemodel (Gilbert-Elliot bursty loss, exclusive with
packet_loss), duplicate, seed and limit, plus the delay jitter
distribution and the loss/dup correlation parameters. All are
kernel-datapath only — the relay's packet-filter registry has no
equivalent, so available_filters hides them on relay links, setting one
on a created relay link returns 409, and the project-load path drops
them with a warning (update_filters runs before the datapath is
decided, so it cannot reject there). Validation mirrors the tc grammar:
reorder requires delay, gemodel and packet_loss are mutually exclusive,
a distribution requires jitter > 0, rate is integer+unit capped at
100gbit.

The translation in DockerVM._netem_command_parts emits the frozen
grammar order and gates extension keywords on the 'tc capabilities'
netem token list (probed once per uBridge process; a plain
delay/loss/corrupt/dup filter never probes, keeping old uBridge builds
untouched; the correl suffix has no token and gates on 'rate' as the
extension-build marker).

Reconcile became reset-before-set: the kernel's netem change merges
optional attributes (rate, correlation, reorder, corrupt, gemodel,
distribution, seed keep their previous value when the new message omits
them), so removing a parameter needs an explicit 'tc reset' — the
bpf_drop filters are re-added right after in the same flow, so the
clsact churn is invisible. The capability check runs before the reset
so a failed update leaves the previous qdisc in place.

e2e (isolated instance, kernel link): 24/24 — rate 512kbit adds exactly
2x the per-packet serialization to the RTT (45.5 ms for 1400-byte
pings), paretonormal jitter + rate-cleared leak check, reorder gap 5,
gemodel 50% bursty loss, duplicate, loss 30% correl 50%, seed 42 +
limit 5000 visible in the qdisc dump, suspend/resume and node restart
keep the filters, relay link rejects rate with 409, teardown leaves no
netem. Full unit suite: 1916 passed.
2026-10-06 00:21:08 +08:00
YueGuobin
2b45f29b2c
feat(docker): bpf packet filter on kernel links via cls_bpf match-drop
The GNS3 bpf filter (one expression per line, any line matching drops the
packet) now runs on the kernel datapath: uBridge's tc bpf_drop compiles
each line with libpcap (same compiler as the relay filter) into classic
BPF, attaches it to the veth host end's clsact egress as a cls_bpf
classifier with a gact TC_ACT_SHOT action, and the kernel migrates the
cBPF to eBPF internally -- no CAP_BPF needed.

- reconcile is flush + one add per line at priorities 10, 11, ... (OR
  semantics, same as the relay's per-line filters); tc reset (link
  delete / clear) is the full restore in uBridge and clears them
- capability-gated: the compute probes "tc capabilities" once per uBridge
  process (cached, invalidated on stop); without cbpf=1 the filter apply
  fails with an actionable upgrade error, and old uBridge builds without
  the tc module are never touched when no bpf filter is present
- a line that compiled for the controller's tcpdump but not for the
  compute's uBridge libpcap is skipped with a warning, mirroring the
  relay path
- KERNEL_UNSUPPORTED_FILTERS shrinks to frequency_drop (its eBPF stateful
  classifier is still pending, spec part B)

e2e (22/22): bpf "icmp" = 100% loss; size-discriminating expression
("greater 150") passes 98-byte and drops 300-byte pings (real byte-level
match); two-line OR; coexistence with netem (delay 50 -> small pings
100.2 ms while big dropped); clear removes clsact; suspend/resume keeps
the drops; link delete + recreate leaves no residual filters.
2026-10-06 00:21:08 +08:00
YueGuobin
bd1c205651
docs: frozen uBridge spec for kernel impairment (netem extensions, eBPF/cBPF classifiers) 2026-10-06 00:21:08 +08:00
YueGuobin
1ccea916c3
docs: roadmap for docker image types (vendor profiles)
Design proposal for an image_type discriminator on Docker templates plus
a compute-side profile registry: vendor parameters graduate from
environment markers into schema-gated fields, generic-feature
applicability becomes declared capability data, existing markers stay as
a compatibility fallback. Explicitly not a new node type.

Four profiles already exist to shape it (iol-runner, XRd, SR Linux
prototype, FRR appliance); implementation follows the current PR series.
2026-09-05 21:54:52 +08:00