mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
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.
This commit is contained in:
4 files changed
+452
-3
No files matched your search
@@ -367,8 +367,12 @@ enable_kernel_datapath = True
|
||||
Intended to grow into the global datapath switch as IOU / Dynamips migrate to
|
||||
kernel bridges. **QEMU has landed** on the same pattern (persistent TAP anchor
|
||||
enslaved into the per-link bridge, zero relay) — see
|
||||
`docs/features/qemu-kernel-datapath.md`; the shared half of this document's
|
||||
compute machinery now lives in `gns3server/compute/kernel_datapath.py`.
|
||||
`docs/features/qemu-kernel-datapath.md` — and **IOU after it**
|
||||
(`docs/features/iou-kernel-datapath.md`; the fabric terminator in uBridge
|
||||
gained a TAP-terminated port, `iol_bridge add_nio_tap`, specified in
|
||||
`docs/design/ubridge-iol-tap-anchor-spec.md`). The shared half of this
|
||||
document's compute machinery now lives in
|
||||
`gns3server/compute/kernel_datapath.py`.
|
||||
|
||||
## Verification
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
See LICENSE file for licensing information.
|
||||
-->
|
||||
|
||||
# IOU kernel datapath
|
||||
|
||||
IOU's only physical layer is the Unix-datagram IOL fabric (`/tmp/netio<uid>/`,
|
||||
8-byte IOL headers, NETMAP), so it has no host netdev to enslave — a kernel
|
||||
link instead gives each **Ethernet bay/unit** a persistent TAP anchor
|
||||
(`gi{node_id[:8]}e{bay}p{unit}`), and uBridge's `iol_bridge` (the fake IOL
|
||||
instance the NETMAP maps to) binds the port to that TAP with
|
||||
`iol_bridge add_nio_tap`. Links then attach to the anchor exactly as they do
|
||||
for Docker (veth) and QEMU (TAP): a per-link Linux bridge enslaves it, tc
|
||||
impairments run on it, capture and markers anchor on it. The uBridge
|
||||
capability is specified in `docs/design/ubridge-iol-tap-anchor-spec.md` and
|
||||
delivered by the `feature/iol-tap-anchor` branch of the ubridge fork.
|
||||
|
||||
Serial bays never anchor (a TAP carries Ethernet frames): serial links stay
|
||||
on the relay datapath whatever the compute's capabilities.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
IOU process root namespace
|
||||
┌──────────┐ unix datagram ┌──────────────────────────────┐
|
||||
│ IOL │◄──── /tmp/netio ──►│ uBridge iol_bridge (listener)│
|
||||
│ fabric │ (8-byte hdr) │ │ holds the TAP fd │
|
||||
└──────────┘ │ ▼ │
|
||||
│ gi{id}e{bay}p{unit} ──────┼──► gns3{link_id}
|
||||
│ (persistent TAP anchor) │ (per-link kernel
|
||||
└──────────────────────────────┘ bridge port)
|
||||
```
|
||||
|
||||
One userspace hop on the IOU leg is **irreducible** — the fabric is Unix
|
||||
sockets, so frames always cross fabric → listener thread → TAP fd. What the
|
||||
kernel datapath buys is the **link segment**: anchor → per-link bridge →
|
||||
peer anchor crosses entirely in the kernel (including IOU ↔ Docker/QEMU
|
||||
links), impairments run as kernel tc instead of the userspace delay line
|
||||
(the known blocking-nanosleep RTT defect), and suspend maps to anchor
|
||||
admin-down (writes to a down TAP fail EIO and are dropped by the delivered
|
||||
§B hardening). Direction semantics on the anchor match QEMU's exactly:
|
||||
uBridge holds the fd like the QEMU process does.
|
||||
|
||||
## Datapath selection
|
||||
|
||||
`UDPLink._kernel_datapath_eligible` extends the Docker/QEMU rules with:
|
||||
|
||||
* the compute must report **`ubridge_iol_tap`** (its own capability: the tap
|
||||
module and `iol_bridge add_nio_tap` land independently, and either can be
|
||||
missing on an old build). Probed by running one add/delete cycle on a
|
||||
scratch bridge + scratch TAP (`probe_iol_tap_support`, cached by binary
|
||||
identity, scratch bridge id 1050 — above the per-node id space 513..1024
|
||||
the fabric locks); any failure means unknown → relay;
|
||||
* **both linked ports must be Ethernet** — an IOU serial port disqualifies
|
||||
the kernel path per-port, not per-node, so serial links between capable
|
||||
IOU nodes silently stay on the relay instead of failing at the compute.
|
||||
|
||||
The NIO schema on the compute NIO routes accepts `nio_bridge` alongside the
|
||||
UDP/TAP/Ethernet NIOs (the same widening QEMU needed — without it the
|
||||
controller's NIO POST is 422'd before the node ever sees it).
|
||||
|
||||
## Anchor lifecycle — and why the stop order is QEMU's reverse
|
||||
|
||||
* **Birth (node start)** — `_prepare_tap_datapath` probes the capability and
|
||||
creates one persistent TAP per Ethernet bay/unit (4 units per bay, swept
|
||||
for stale leftovers first, born DOWN). uBridge holds a device's fd only
|
||||
while a kernel link binds the port; until then the TAP has no holder.
|
||||
* **Life** — the TAP is never recreated. A kernel link adds
|
||||
`iol_bridge add_nio_tap <bridge> <iol_id> <bay> <unit> <tap>` then
|
||||
enslaves the anchor (`_kernel_attach`); a relay link keeps
|
||||
`iol_bridge add_nio_udp` and never touches the anchor. Switching a port
|
||||
between datapaths is just an NIO swap — the IOL port frees its previous
|
||||
NIO (fd or socket) on attach.
|
||||
* **Death (node stop)** — `_stop_ubridge` runs the cleanup **in the reverse
|
||||
order of QEMU's**, because here uBridge itself holds the anchor fds:
|
||||
`iol_bridge delete` (releases every port's TAP fd) → `tap delete` per
|
||||
anchor (a held TAP answers EBADFD/207) → `brctl delete` for per-link
|
||||
bridges still held → stop the hypervisor. Removing a single kernel link
|
||||
does the same in miniature: `_remove_kernel_nio` (markers, tc reset,
|
||||
delif, brctl delete) then `iol_bridge delete_nio_tap` — releasing the fd
|
||||
while the control channel lives.
|
||||
|
||||
## Link operations
|
||||
|
||||
| Operation | Kernel-datapath action |
|
||||
|---|---|
|
||||
| Create / attach | `iol_bridge add_nio_tap` (bind port ↔ anchor, uBridge holds the fd) + `brctl create` (EEXIST tolerated) + `link set <bridge> up` + `brctl addif` + carrier up + markers + `tc netem set` |
|
||||
| Delete / detach | marker teardown → `tc reset <tap>` → carrier off → `brctl delif`/`brctl delete` (last endpoint wins) → `iol_bridge delete_nio_tap` (release the fd; the device survives) |
|
||||
| Update (filters/markers changed) | reconcile on the anchor only — no re-binding, no re-enslaving |
|
||||
| Suspend | anchor admin-down (IOL relay writes fail EIO → dropped; reads block) + resume restores |
|
||||
|
||||
## Capture, markers, filters
|
||||
|
||||
Capture on a kernel link is `capture start_kernel <tap>` (driven by the NIO
|
||||
type); markers ride the base reconcile with the anchor as the location —
|
||||
IOU's marker primitives are polymorphic (`marker add_kernel/enable_kernel/
|
||||
delete_kernel` on an anchor, `iol_bridge … mark` on an IOL location), which
|
||||
let the old IOU-specific reconcile override be deleted outright. Filters are
|
||||
one tc netem qdisc per anchor plus cls_bpf/eBPF drops — the port's
|
||||
userspace `iol_bridge add_packet_filter` filters carry relay links only and
|
||||
are never combined with tc on the same port.
|
||||
|
||||
## Relay fallback
|
||||
|
||||
A uBridge without `iol_bridge add_nio_tap` (old build) keeps the node on the
|
||||
relay datapath unchanged: every link rides `iol_bridge add_nio_udp`, the
|
||||
node starts fine, and the warning says it cannot carry kernel links. The
|
||||
controller never offers such a node a kernel link (capability unreported).
|
||||
|
||||
## Verified
|
||||
|
||||
Unit level: anchor lifecycle and naming, kernel/relay binding on add,
|
||||
update and remove, the stop order (iol_bridge delete before tap delete
|
||||
before brctl delete before hypervisor stop), `_networking` on restart with
|
||||
mixed kernel/relay ports, capture and marker command shapes on both
|
||||
datapaths, the capability probe (cycle, old-build fallback, cleanup,
|
||||
caching), capability plumbing, and controller eligibility (per-compute
|
||||
capability, its independence from `ubridge_tap`, serial-port exclusion).
|
||||
|
||||
End-to-end on a live server (isolated instance, two IOU nodes behind a fake
|
||||
ELF image, the test playing the IOL fabric on `/tmp/netio<uid>/` and pushing
|
||||
frames through it): **64/64 checks** on the kernel datapath and **62/62** on
|
||||
the relay (`enable_kernel_datapath = false`). Kernel highlights:
|
||||
|
||||
* 8 anchors per node (2 bays × 4 units), persistent, DOWN, address-free;
|
||||
`link_list` reports `kernel_datapath: true` and one per-link bridge holds
|
||||
both anchors with `brport/state = 3`;
|
||||
* fabric → anchor → bridge → anchor → fabric in both directions at ~0.2 ms;
|
||||
`delay 100` ⇒ netem on both anchors and a measured **100.5 ms** one-way;
|
||||
* suspend ⇒ anchor DOWN, no traffic, resume ⇒ 100.4 ms with the qdisc intact;
|
||||
* capture ⇒ a 328-byte pcap written by `capture start_kernel`;
|
||||
* link delete ⇒ bridge gone, qdisc clean, anchor alive, re-create at 0.6 ms;
|
||||
* a **serial** link between the same two capable nodes stays on the relay
|
||||
(`kernel_datapath: false`) — per-port exclusion, not per-node;
|
||||
* single-sided restart ⇒ stopped node's anchors removed, peer's kept, link
|
||||
re-attached from the NIO (0.3 ms); stop ⇒ no stray anchors, bridges or
|
||||
fabric sockets.
|
||||
|
||||
The relay run exercises the same lifecycle with the IOL UDP NIOs (no tc
|
||||
qdiscs, userspace `delay` at 100.7 ms, suspend via the synthetic
|
||||
frequency_drop, `iol_bridge start_capture`), which is also the regression
|
||||
net for the port-coordinate capture restore this work touched.
|
||||
|
||||
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
|
||||
design depends on.
|
||||
|
||||
One pre-existing bug surfaced here and is fixed: the IOU NIO **update** route
|
||||
never copied the body's `suspend` onto the NIO (Docker and QEMU routes do).
|
||||
Harmless on the relay, where suspend is emulated by the synthetic
|
||||
frequency_drop filter, but fatal for a kernel link, whose suspend *is* the
|
||||
anchor's admin state — a "suspended" link kept forwarding.
|
||||
|
||||
## Configuration
|
||||
|
||||
```ini
|
||||
[Server]
|
||||
# Wire eligible links (same compute, both endpoints Ethernet and able to
|
||||
# anchor) through kernel interfaces instead of the uBridge UDP relay.
|
||||
enable_kernel_datapath = True
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
Dynamips next (hypervisor-created taps, enslavable as they are), then the
|
||||
Ethernet switch (its anchors are ubridge-owned by design) and the IOL Docker
|
||||
node (same `bridge add_nio_tap` shape as this one). Cross-compute kernel
|
||||
links need VXLAN/GENEVE encapsulation — deferred until every node type is
|
||||
kernelized.
|
||||
@@ -161,8 +161,15 @@ addresses involved, IPv4 has no per-device IGMP switch).
|
||||
|
||||
## Roadmap
|
||||
|
||||
IOU (its fabric would need a kernel-side port in `iol_bridge`), Dynamips
|
||||
IOU (its fabric terminator in uBridge needs a TAP-terminated port —
|
||||
`iol_bridge add_nio_tap`, frozen in
|
||||
`docs/design/ubridge-iol-tap-anchor-spec.md`; unlike QEMU, one userspace hop
|
||||
on the IOU leg is irreducible, the fabric is Unix sockets), Dynamips
|
||||
(hypervisor-created taps, enslavable as they are), and the Ethernet switch /
|
||||
cloud paths (their anchors are ubridge-owned by design) are still on the
|
||||
relay; cross-compute kernel links need VXLAN/GENEVE encapsulation, which is
|
||||
a separate project (it would serve Docker links the same way).
|
||||
|
||||
Update: **IOU has landed** on the same mixin — see
|
||||
`docs/features/iou-kernel-datapath.md` (Ethernet bays anchor on persistent
|
||||
TAPs bound to the IOL fabric; serial links stay relay).
|
||||
Reference in new issue
Block a user