Skip to main content

Network Fabric architecture (how it works)

This is the deep technical dive into Fabric's VM-edge dataplane. For the short version, see Architecture: FluxVM + GuestKit; for the operator guide (enablement, troubleshooting, UX checklist), see docs/guides/vm-drivers/fluxvm-dataplane.md.

Fabric exposes FluxVM Network Fabric schema v4 (TC/eBPF VM-edge dataplane) as first-class API, CLI, and UI — while keeping Fabric's own host SDN separate. Kernel program source of truth: FluxVM Network Fabric.

Two policy planes (do not conflate)​

PlaneOwnsAPI / UX
Fabric SDNHost isolation (label → nftables)/api/network-policies · Security → Network Policies
VM edge (Network Fabric schema v4)Per-VM allowlists, Mbps/PPS, stats/flows on the TAP/netns edge/api/vms/{name}/dataplane/* · VM → Dataplane tab · fabricctl dataplane …
Service Fabric v6 (BPF schema 4 / gen 6)Maglev VIP LB + EDT/FluxScope/host-routing + leases + HA deltas + identity/L7 policy/api/dataplane/services… · Edge Dataplane → Services · docs/ebpf-service-fabric.md
flowchart LR
UI[Web / fabricctl / Terraform]
Fabricd[zyvor-fabricd]
Client[fluxvm-client]
FluxVM["fluxvm serve"]
TC[TC eBPF VM edge]
SDN[Fabric network-policies nftables]

UI --> Fabricd
Fabricd -->|"/api/vms/name/dataplane/*"| Client
Client -->|"/v1/vms/id/network/*"| FluxVM
FluxVM --> TC
Fabricd --> SDN

Big picture (Fabric + FluxVM + kernel)​

flowchart TB
subgraph fabricCtrl [Fabric control plane]
WebTab[VM Dataplane tab]
Zctl[fabricctl dataplane]
FabAPI["/api/vms/name/dataplane\nstatus policy stats flows"]
Driver[VmDataplaneDriver]
FClient[fluxvm-client]
WebTab --> FabAPI
Zctl --> FabAPI
FabAPI --> Driver --> FClient
end

subgraph fluxCtrl [FluxVM control plane]
NetAPI["/v1/vms/id/network"]
Sched[fluxvm-scheduler]
DP[fluxvm-network dataplane]
NetAPI --> Sched --> DP
end

subgraph durable [Durable + runtime state]
PolJSON["/var/lib/fluxvm/network-policy/uuid.json"]
Pins["/sys/fs/bpf/fluxvm/vms/uuid/\nprogs + maps"]
Meta["/run/fluxvm/ebpf/vms/uuid/\niface prog_id schema fingerprint"]
end

subgraph guestPath [Guest packet path]
Guest[Guest OS]
TAP[TAP / macvtap / netns]
HostEdge["Host-visible iface\nvh-star or tap"]
TC["TC ingress\nfluxvm_egress"]
HostRt[Host routing / Cilium / Fabric SDN]
Guest --> TAP --> HostEdge --> TC --> HostRt
end

FClient --> NetAPI
DP -->|configure maps before attach| Pins
DP -->|fsync policy + fingerprint| PolJSON
DP -->|ownership sidecars| Meta
DP -->|tc filter add / reconfigure| TC
Sched -->|reconcile heal + orphan GC| DP

Namespaced TAP path (what Fabric bridged VMs use)​

Fabric creates bridged VMs with NetworkSpec::Tap { netns: true }. The classifier attaches on the host veth (vh-…), not inside the guest:

flowchart LR
VM[Guest]
TapNs[TAP in netns]
Br[netns bridge]
VethNs[veth in netns]
VethHost["host veth vh-id"]
TcHook["TC ingress FluxVM eBPF"]
Out[Host stack / Cilium / SDN]

VM --> TapNs --> Br --> VethNs --> VethHost --> TcHook --> Out

Direct TAP/macvtap (non-netns) attaches on the host-visible TAP/macvtap itself.

Packet decision inside the TC program​

flowchart TD
In[Packet on ingress] --> Look{fluxvm_id<br/>ifindex lookup}
Look -->|miss| Pass[TC_ACT_OK / pass]
Look -->|hit| Boot{ARP/DHCP/NDP/DHCPv6?}
Boot -->|yes| Allow[allow + stats/flows]
Boot -->|no| Fam{IPv4 or IPv6?}
Fam -->|other| Def{default_allow?}
Def -->|true| Allow
Def -->|false| Drop[drop + stats/events]
Fam -->|v4/v6| Cidr{enforce_cidr?}
Cidr -->|yes| Lpm["LPM fluxvm_v4 / fluxvm_v6"]
Lpm -->|miss| Drop
Lpm -->|hit| L4
Cidr -->|no| L4{enforce_l4?}
L4 -->|yes| Port["fluxvm_l4 proto+port"]
Port -->|miss| Drop
Port -->|hit| Rate
L4 -->|no| Rate{Mbps/PPS set?}
Rate -->|yes| Win["fluxvm_rate fixed 1s window"]
Win -->|over| Drop
Win -->|ok| Allow
Rate -->|no| Allow

Control-plane lifecycle (through Fabric)​

sequenceDiagram
participant Op as Operator Web or CLI
participant Fab as zyvor-fabricd
participant Fv as FluxVM scheduler
participant Dp as dataplane eBPF
participant Kern as Kernel TC maps

Op->>Fab: create or start bridged VM
Fab->>Fv: POST v1 vms Tap netns true
Fv->>Dp: apply_sandbox_policy
Dp->>Kern: load and pin prog maps
Dp->>Kern: write fluxvm_id CIDR L4 rate maps
Dp->>Kern: tc filter add after maps ready
Dp->>Dp: write run meta and fingerprint

Op->>Fab: POST dataplane policy
Fab->>Fv: POST network policy
Fv->>Dp: reconfigure_sandbox_policy
Dp->>Kern: deny-all on iface
Dp->>Kern: replace CIDR L4 rate maps
Dp->>Kern: publish final iface config
Note over Dp,Kern: Brief over-deny window only never allow-all

Op->>Fab: GET dataplane status stats flows
Fab->>Fv: GET network status stats flows
Fv-->>Fab: JSON
Fab-->>Op: same shape

Fv->>Dp: reconcile tick
alt needsRepair
Dp->>Kern: ensure_sandbox_policy reload
end
Dp->>Dp: reconcile_orphan_pins for dead UUIDs

Where state lives​

LocationContents
Fabric APIName-keyed proxy; resolves VM name → FluxVM UUID via fluxvm-client
/sys/fs/bpf/fluxvm/vms/<uuid>/Pinned TC program + maps (fluxvm_id, v4, v6, l4, rate, stats, flows, events)
/run/fluxvm/ebpf/vms/<uuid>/iface, prog_id, schema_version, policy_fingerprint (not on bpffs)
/run/fluxvm/xdp/Optional XDP iface + prog_id
/var/lib/fluxvm/network-policy/<uuid>.jsonDurable per-VM policy (fsync + rename)

Modes vs ownership​

flowchart TB
Mode{sandbox.dataplane.mode}
Mode -->|legacy| Nft[nftables only]
Mode -->|ebpf| Edge[FluxVM TC on VM edge]
Mode -->|cilium| Check[Require cilium.sock + bpffs]
Check --> Edge
Edge --> Own["Pins only under /sys/fs/bpf/fluxvm\nnever Cilium private maps"]
Xdp[Optional XDP on uplink]
Edge -.->|refused when cilium| Xdp
FabSDN[Fabric /network-policies]
FabSDN -.->|independent host SDN| HostNft[host nftables]

REST surface (Fabric ↔ FluxVM)​

FabricFluxVMRole
GET …/dataplane/statusGET …/network/statusmode, attached, schema_version, policy_synced, iface
GET/POST …/dataplane/policyGET/POST …/network/policyRead / replace durable policy (+ live map update)
GET …/dataplane/statsGET …/network/statsallow/drop packet + byte counters
GET …/dataplane/flows?limit=GET …/network/flows?limit=LRU flows with family 4/6
fabricctl dataplane status <name>
fabricctl dataplane policy get|set <name> [--file policy.json]
fabricctl dataplane stats <name>
fabricctl dataplane flows <name> [--limit 100]

HTTPS labs: export ZYVOR_FABRIC_URL=https://127.0.0.1:9095 and export ZYVOR_FABRIC_TOKEN=<jwt> (from /api/auth/login).

Enable packaging​

Ship configs/fluxvm-dataplane.toml (mode = "ebpf"). Compose/k8s mount it as /etc/fluxvm.toml, mount host /sys/fs/bpf, and raise memlock (SYS_RESOURCE / ulimit memlock=-1). The image must include /usr/lib/fluxvm/bpf/fluxvm_tc.bpf.o and, for a direct uplink, fluxvm_direct.bpf.o beside it. After the first green attach (attached=true, schema version whatever FluxVM reports — 11 on a current attach), set required = true for fail-closed production. Service Fabric is not attached to direct taps.

Why Fabric + Network Fabric is ahead of other VMMs​

Most hypervisor stacks still treat VM egress as host netfilter theater: libvirt/iptables chains, a shared bridge + firewall, or QEMU user-mode NAT. Fabric puts operator UX (API · Web · CLI) on top of FluxVM's TC/eBPF VM-edge dataplane, so policy, rate limits, and telemetry are first-class — not afterthought scripts.

flowchart LR
subgraph traditional [Traditional VMM path]
TGuest[Guest] --> TTap[TAP / bridge]
TTap --> TNft[iptables / nft / virbr0]
TNft --> TOut[Host / WAN]
end

subgraph fabricPath [Fabric + FluxVM Network Fabric schema v4]
FGuest[Guest] --> FTap[TAP / netns veth]
FTap --> FEbpf["TC eBPF on VM edge\nLPM · L4 · Mbps/PPS · flows"]
FEbpf --> FOut[Host / Cilium / Fabric SDN]
UX[Web Dataplane tab · fabricctl · REST] -.->|live map rewrite| FEbpf
end
quadrantChart
title Control-plane maturity vs packet-path speed
x-axis Slow / rebuild-heavy --> Fast / in-kernel maps
y-axis Scripted / host-only --> API + UI + telemetry
quadrant-1 Ahead today
quadrant-2 UX without speed
quadrant-3 Legacy baseline
quadrant-4 Fast but opaque
Libvirt nft: [0.25, 0.30]
Shared bridge FW: [0.35, 0.35]
QEMU usernet: [0.15, 0.20]
Cloud hypervisor raw: [0.55, 0.25]
Firecracker CNI: [0.60, 0.40]
Fabric plus Network Fabric schema v4: [0.88, 0.90]
Capabilitylibvirt / virsh + nftShared bridge + host FWQEMU user NATTypical microVM + CNIFabric + Network Fabric schema v4
Per-VM L3/L4 egress allowlistsManual chainsHost-wide rulesSoft / limitedPod-orientedFirst-class allow_cidrs + tcp|udp/PORT
Live policy without detachFlush/reload gapsBlast radiusRestart usernetCNI reconcileIn-place BPF map update (~100–120 ms p50 in lab)
Mbps / PPS egress capsSeparate tc/htbRareSoftDepends on CNIMaps on the same classifier
Dual-stack L3+L4Easy to driftOften IPv4-onlyLimitedVariesOne TC program
Per-VM stats + LRU flows via APItcpdump / conntrackHost-centricAlmost noneSidecar / Hubble-ish/dataplane/stats + /flows
Operator UXvirsh + shellSameSamekubectl-heavyVM → Dataplane tab · fabricctl dataplane · 4 REST verbs
Host SDN still availableYou build itYou build itN/ANetworkPolicyFabric /network-policies orthogonal
Cilium coexistenceiptables fightsSameN/ANativemode=cilium — FluxVM owns VM edge only

The ~100–120ms p50 map-update figure above is a lab observation from the development environment, not a published benchmark with a documented methodology across hardware/kernel versions — treat it as indicative, not a guaranteed SLA.

Shipped in Fabric UX (all wired; lab UX verified):

SurfaceStatus · Policy · Stats · Flows
WebVM details → Dataplane (presets, JSON, identity column, auto-refresh)
REST/api/vms/{name}/dataplane/{status,policy,stats,flows}
CLIfabricctl dataplane … (ZYVOR_FABRIC_URL + ZYVOR_FABRIC_TOKEN for HTTPS labs)
DashboardVM dataplane capability card (mode / attached / schema)

Operator guide (enablement, create-bridged recipe, troubleshooting, UX checklist): docs/guides/vm-drivers/fluxvm-dataplane.md. User console: docs/user/pages/infrastructure/dataplane.md.

Kernel program SoT: FluxVM — Why Network Fabric is faster.