You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The dataplane.DataPlane interface (pkg/dataplane/dataplane.go:104-300) is BPF-shaped: ~80 methods are direct BPF map writers (SetZone, SetPolicyRule, SetSNATRule, SetDNATEntry, SetNAT64Config, SetScreenConfig, SetMirrorConfig, SetPolicerConfig, SetFlowConfig, etc., plus the matching ClearXxx / DeleteStaleXxx / ReadXxxCounter methods). The userspace Manager (pkg/dataplane/userspace/manager.go:57-123) embeds dataplane.DataPlane — its interface satisfaction comes from the embedded eBPF dataplane.Manager. Every config write call from the daemon today still flows through bpf_map_update_elem even when running on userspace-dp; the userspace XDP shim doesn't consult those maps. After Phase 4 (BPF source removal) those maps don't exist, and every embedded method becomes either undefined or a compile failure.
This isn't a forwarding feature gap — it's the Phase-3-blocking plumbing concern that all Phase 0–2 work must surface to.
eBPF implementation (source of truth)
pkg/dataplane/dataplane.go:104-300 — full DataPlane interface, 80+ methods, every one BPF-shaped
pkg/dataplane/dataplane.go:13 — var _ DataPlane = (*Manager)(nil) — eBPF Manager satisfies it directly
pkg/dataplane/userspace/manager.go:57-58:
typeManagerstruct {
dataplane.DataPlane// <- this embedinner*dataplane.Manager
pkg/dataplane/userspace/manager.go:148-156 — New() returns &Manager{ DataPlane: inner, inner: inner, ... } (the embed IS the eBPF manager)
pkg/dataplane/userspace/manager.go adds only ~5 methods of its own (Mode, EventStream, Status, SetForwardingArmed, InjectPacket, DrainSessionDeltas, etc.) — the other ~75 methods come from the embedded eBPF type. There's NO standalone DataPlane implementation for userspace.
Recommended fix
Phase 3 plan needs to address this before Phase 4 can land. Two reasonable approaches:
Split the interface: define ConfigSink (high-level: ApplyConfig(*config.Config) error) and ControlPlane (read-only: Status, Counters, Sessions). Move BPF-specific methods onto *ebpf.Manager directly, not the abstract interface. Userspace Manager implements only the new high-level interface.
Stub out BPF writers in userspace: keep the DataPlane interface name but replace every Set*/Clear*/Delete* method on userspace Manager with a no-op (or no-op-with-snapshot-mutation). Daemon code that builds BPF entries continues to compile but is dead code; remove at Phase 3.
(1) is cleaner; (2) is faster to land. Either way, this issue is the central plumbing problem Phase 3 must resolve before Phase 4 can rm -rf bpf/ without orphaning ~80 interface methods.
The userspace XDP shim itself (userspace-xdp/src/lib.rs) is unaffected — it stays. The XSK redirect map, userspace_bindings/ctrl/heartbeat/trace pins, and conntrack pins it uses are the boundary the user-space helper crosses regardless. Only the legacy xdp_main / xdp_screen / xdp_zone / xdp_policy etc. and their associated BPF maps are retired.
This is the Phase 3 blocker. Without splitting or stubbing this interface, pkg/dataplane/*.go cannot shrink to userspace-only and Phase 4 cannot proceed.
Refined contract (added 2026-05-17 after triple-review of #1383)
See docs/pr/1381-dataplane-interface-split/plan.md for the full implementation contract refined through 4 rounds of Claude+Codex+Gemini Pro 3 review. New since the original issue body:
Import-cycle avoidance contract: SessionDeltaSource interface lives in pkg/dataplane/runtime (NOT pkg/dataplane/userspace). DTOs (SessionDelta, SessionDeltaSnapshot) live in the same neutral runtime package. Public interface must not reference any pkg/dataplane/userspace type. Backed by an import canary test.
ApplyResult widened with FilterIDs map[string]uint32, FilterSpans map[string]FilterCounterSpan (FilterID, RuleStart, RuleCount), NATCounterIDs map[string]uint32 — required by current server_show_firewall.go:41, cli_show_nat.go:352, cli_show_security.go:1398 callers.
Cluster stale-reconcile path at pkg/cluster/sync.go:556,571,599 must use same SessionStore.DeleteWithCompanionsV4/V6 / ReconcileClusterBulk semantics as GC. Phase 1 acceptance gate requires tests that fail if cluster sync keeps a local DeleteDNATEntry* cleanup copy.
Gap
The
dataplane.DataPlaneinterface (pkg/dataplane/dataplane.go:104-300) is BPF-shaped: ~80 methods are direct BPF map writers (SetZone,SetPolicyRule,SetSNATRule,SetDNATEntry,SetNAT64Config,SetScreenConfig,SetMirrorConfig,SetPolicerConfig,SetFlowConfig, etc., plus the matchingClearXxx/DeleteStaleXxx/ReadXxxCountermethods). The userspaceManager(pkg/dataplane/userspace/manager.go:57-123) embedsdataplane.DataPlane— its interface satisfaction comes from the embedded eBPFdataplane.Manager. Every config write call from the daemon today still flows throughbpf_map_update_elemeven when running on userspace-dp; the userspace XDP shim doesn't consult those maps. After Phase 4 (BPF source removal) those maps don't exist, and every embedded method becomes either undefined or a compile failure.This isn't a forwarding feature gap — it's the Phase-3-blocking plumbing concern that all Phase 0–2 work must surface to.
eBPF implementation (source of truth)
pkg/dataplane/dataplane.go:104-300— fullDataPlaneinterface, 80+ methods, every one BPF-shapedpkg/dataplane/dataplane.go:13—var _ DataPlane = (*Manager)(nil)— eBPF Manager satisfies it directlypkg/dataplane/userspace/manager.go:57-58:pkg/dataplane/userspace/manager.go:148-156—New()returns&Manager{ DataPlane: inner, inner: inner, ... }(the embed IS the eBPF manager)pkg/dataplane/userspace/snapshot.go:458-472—userspaceMapPins()references BPF pin paths the userspace helper opens (Ctrl, Bindings, Heartbeat, XSK redirect map, conntrack v4/v6, DNAT tables, Trace)Userspace-dp gap
pkg/dataplane/userspace/manager.goadds only ~5 methods of its own (Mode,EventStream,Status,SetForwardingArmed,InjectPacket,DrainSessionDeltas, etc.) — the other ~75 methods come from the embedded eBPF type. There's NO standaloneDataPlaneimplementation for userspace.Recommended fix
Phase 3 plan needs to address this before Phase 4 can land. Two reasonable approaches:
Split the interface: define
ConfigSink(high-level: ApplyConfig(*config.Config) error) andControlPlane(read-only: Status, Counters, Sessions). Move BPF-specific methods onto*ebpf.Managerdirectly, not the abstract interface. UserspaceManagerimplements only the new high-level interface.Stub out BPF writers in userspace: keep the
DataPlaneinterface name but replace everySet*/Clear*/Delete*method on userspaceManagerwith a no-op (or no-op-with-snapshot-mutation). Daemon code that builds BPF entries continues to compile but is dead code; remove at Phase 3.(1) is cleaner; (2) is faster to land. Either way, this issue is the central plumbing problem Phase 3 must resolve before Phase 4 can
rm -rf bpf/without orphaning ~80 interface methods.The userspace XDP shim itself (
userspace-xdp/src/lib.rs) is unaffected — it stays. The XSK redirect map, userspace_bindings/ctrl/heartbeat/trace pins, and conntrack pins it uses are the boundary the user-space helper crosses regardless. Only the legacyxdp_main/xdp_screen/xdp_zone/xdp_policyetc. and their associated BPF maps are retired.Blocker for #1373
This is the Phase 3 blocker. Without splitting or stubbing this interface,
pkg/dataplane/*.gocannot shrink to userspace-only and Phase 4 cannot proceed.Refined contract (added 2026-05-17 after triple-review of #1383)
See
docs/pr/1381-dataplane-interface-split/plan.mdfor the full implementation contract refined through 4 rounds of Claude+Codex+Gemini Pro 3 review. New since the original issue body:pkg/dataplane/runtime(NOTpkg/dataplane/userspace). DTOs (SessionDelta,SessionDeltaSnapshot) live in the same neutral runtime package. Public interface must not reference anypkg/dataplane/userspacetype. Backed by an import canary test.ApplyResultwidened withFilterIDs map[string]uint32,FilterSpans map[string]FilterCounterSpan(FilterID, RuleStart, RuleCount),NATCounterIDs map[string]uint32— required by currentserver_show_firewall.go:41,cli_show_nat.go:352,cli_show_security.go:1398callers.GlobalCtrSessionsNew/Closed) →Telemetry.GlobalCounter; per-IP session-limit map publish → backend-private; persistent-NAT preservation →SessionStore.Delete*atomic orBeforeDeletehook; DNAT/NAT64 reverse cleanup → backend session/NAT store owns it; v4/v6 stats counting → backend-neutral.pkg/cluster/sync.go:556,571,599must use sameSessionStore.DeleteWithCompanionsV4/V6/ReconcileClusterBulksemantics as GC. Phase 1 acceptance gate requires tests that fail if cluster sync keeps a localDeleteDNATEntry*cleanup copy.