8 Commits

Author SHA1 Message Date
YueGuobin
3018d3aecc
docs(bugs): record the NetworkManager TAP-release root cause, repro and fix
A kernel-datapath link could die silently after a delete/recreate: on
NetworkManager hosts, NM's state machine releases an NM-managed TAP from
its bridge 58-87 ms after (re)enslavement (~50% while the tap has not
been assumed yet; the port stays up, no GNS3 process logs anything).
Root-caused with a server-free minimal harness (one uBridge, one tap,
one bridge, an addif/delif loop) and a single-variable NM A/B; the
NetworkManager side is explained by three of its commits (assumption
recheck on external enslavement, master removal during activation,
external changes not touching assumed slaves).

Records: forensic signatures (ifindex unchanged, flags 0x300 cleared,
heal-on-next-update), the reproducer, the verified host fix
(unmanaged-devices conf.d snippet: 19-20/40 -> 0/40, and the e2e
scenario green with test-side workarounds disabled), scope and history
(2016 issue #440 veth incident; the 2026-10-01 ethernet-switch
incident previously misattributed). Cross-linked from the L2-anchor
spec and the switch feature doc.
2026-10-06 00:21:11 +08:00
YueGuobin
1c85befbab
test(e2e): take the switch anchors out of NetworkManager's reach
The esw kernel fast-path scenario deletes and re-creates a link, which
re-enslaves the peer anchor — and on NetworkManager hosts NM's netdev
state machine can release an NM-managed TAP from its bridge some 60-70 ms
after enslavement, ~50% of re-enslavements, while the tap is in NM's
not-yet-assumed state. The freed port stays administratively up and no
GNS3 process logs anything: the membership assert sees the join, then the
ping measures a silent blackhole (this was the line-277 e2e failure).

Root-caused with a server-free harness (one uBridge, one fresh tap, one
bridge, an addif/delif loop): the same tap frees 20/40 while NM-managed,
0/40 after `nmcli device set <tap> managed no`, 20/40 again when
re-managed. Docker's veth host ends are not NM-managed, which is why only
TAP anchors ever showed it — including the "anchor lost its bridge
membership after a ports_mapping update (TAP peers only)" incident from
the switch's development, whose conclusion is corrected in the docs.

Unmanage the anchors once the routers have booted, before the scenario
re-enslaves them; document the failure signature and the host-side remedy
(per-device nmcli call, or NM unmanaged-devices configuration) in the
L2-anchor spec and the switch feature doc.
2026-10-06 00:21:11 +08:00
YueGuobin
f03c94db6d
docs: link-local frame transparency on kernel links — spec and per-datapath sections
A Linux bridge standing in for a cable does not forward the IEEE 802.1D
reserved range (01:80:c2:00:00:00-0f) by default, so LACP/LLDP/802.1X never
crossed a kernel-datapath link while the UDP relay carried them fine. uBridge
now opens every enslave'd port's IFLA_BRPORT_GROUP_FWD_MASK to 0xfffd at
brctl addif time (branch feature/bridge-tap-l2only, commits 0284500/03a18f1/
cf4e494), which the six kernel-datapath feature docs now document as a
section: STP, LACP, 802.1X and LLDP/DCBX cross; 802.3x PAUSE / PFC stays
dropped — br_handle_frame()'s case 0x01 discards it unconditionally, no mask
can enable it, and PFC labs must use a relay link. The full contract, kernel
references and requirements live in the new design spec, marked landed.
2026-10-06 00:21:10 +08:00
YueGuobin
ff41a54364
test(e2e): assert the §E.2 idle-silence window in every kernel scenario
The L2-anchor spec's behavioral guarantee — an idle link stays silent for
5 s once the enslavement burst settles — only ran in the Dynamips and
Docker suites. It now also runs in QEMU, IOU, the IOL container, the
switch fast path and the switch cascade, which covers every host-device
creation path:

* tap create (Dynamips/QEMU/IOU anchors), docker create_veth (Docker host
  ends; the cascade's race-loser end is hardened after create by the
  server), bridge add_nio_tap (the IOL container anchor, the fifth
  creator), and the brctl switch bridges, whose multicast snooping is off
  precisely so the bridge role can reach silence;
* each scenario silences its guests first (IOS interfaces shut through
  the console; Docker guests' links down) and re-verifies the ping after;
* the cascade measures 8 devices at once (four absorbed router anchors,
  both cascade ends, both switch bridges).

IOL 158 s, cascade 293 s, ESW fast path 378 s, QEMU 428 s, IOU 308 s —
all green.
2026-10-06 00:21:10 +08:00
YueGuobin
ad39b22ab4
test(e2e): live coverage for the switch cascade kernel datapath
Two switches cascaded through the link-owned veth pair, two c7200
routers per switch, real ICMP crossing both bridges: the pair is born
UP with exactly one end per switch bridge, a dot1q trunk cascade
carries two VLANs at once, the symmetric access/trunk mode flip on the
cascade ends isolates and revives a VLAN in place (asymmetric flips are
semantically broken — the access side egresses untagged into the far
trunk's native PVID), a delay filter lands as netem on both ends (one
per direction, RTT ~= 2x delay), suspend admin-downs both ends, and a
link delete/re-create retires the whole pair and mints a new one under
the new link id — zero host residue after the project goes.
2026-10-06 00:21:10 +08:00
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
02340f2ed3
docs(iol): record the IOL runner container kernel datapath
The feature doc covers the architecture (one irreducible userspace hop
on the unix-socket leg; the link segment kernelized through the port
bridge's swappable TAP leg), the capability gate, the anchor lifecycle,
the swap flow, the inherited capture/markers/filters, the relay
fallback and the live verification — including the ensure-then-add
lesson (tap create is strictly create-only).

The other datapath docs' roadmaps catch up: the IOL runner container
has landed (and the ethernet-switch doc's stale 'cascade deferred'
exclusion from d2f8aba58 is corrected while there); the docker doc's
adapter table no longer rejects IOLDockerVM.
2026-10-06 00:21:09 +08:00
YueGuobin
c343ce0b8f
feat(esw): wire peer anchors straight into the switch bridge
The Ethernet switch's kernel fast path: instead of a link crossing the
switch's per-port uBridge relay, the switch absorbs the peer's anchor into
its own kernel bridge. The peer's interface (Docker veth host end, QEMU
TAP, IOU port TAP, Dynamips port TAP) becomes the switch port, and frames
cross anchor -> switch bridge -> switch ports entirely in the kernel.

Wire format: the switch end receives nio_anchor ('join this interface to
my own bridge' — the mirror of nio_bridge), the peer end nio_bridge with a
null bridge ('your anchor is bridged elsewhere': carrier, markers and
impairments only — never bridge membership, and never a bridge delete, on
an interface this node does not own). Anchor names come from the shared
naming contract (utils.kernel_anchor), a pure function of node type, id,
adapter and port — the controller computes the peer's anchor while the
node may be stopped, which is what makes links to stopped nodes wireable.

Stopped peers defer the join; after any node of a link starts
(UDPLink.node_started) the controller re-pushes the switch NIO, and the
switch completes — or repairs, when a peer restart replaced its interface
under the same name — the join against the kernel's actual membership
instead of assuming it.

VLAN changes now edit membership in place: only the ports whose mode/VLAN
actually changed are touched, each losing exactly the previous mode's
entries the new mode does not want (the dot1q admits-all entry is one
range operation in both directions), and no port ever leaves the bridge to
change its VLANs. This replaces the old 'reset by re-enslaving' shortcut
(delif+addif on every wired port, added when the ports were the switch's
own TAPs): on absorbed anchors, re-enslaving an interface owned by another
node is where live testing watched links die — anchors occasionally lost
their bridge membership right after a ports_mapping update (later updates
then failed with delif EINVAL), reproducibly with qemu, IOU and Dynamips
peers and never with Docker's veths.

Eligibility: the switch side needs no capability of its own (its bridge
exists by construction); the peer must be able to anchor. Switch-to-switch
cascades stay on the relay — a kernel interface belongs to one bridge, and
joining two bridges needs a veth pair, deferred.

Verified live on a real server: qemu peers through four VLAN cycles with
both anchors enslaved throughout; a mixed switch (2 IOU + 2 Dynamips, four
kernel ports); Docker peers with real ICMP isolating and recovering (with
both anchors still enslaved); VPCS relay ports likewise in place.
2026-10-06 00:21:09 +08:00