NexusNet Kernel DCO Roadmap
Status: Roadmap
Last reviewed: 2026-05-15
Purpose
Kernel DCO is the project-level dataplane offload roadmap for NexusNet. The name is intentionally broad: it is not only about QUIC REALITY. It covers every place where Linux kernel facilities, eBPF, socket offloads, TUN/TAP features, or a privileged helper could reduce CPU cost or improve packet handling for the agent.
No custom DCO implementation exists in the current repository. The implemented product path is still user-space:
TCP/UDP socket or TUN
-> nexus-agent classifier and dataplane
-> RAW / TLS / REALITY / Noise / QUIC path
-> upstream socket, peer agent, or peer TUNDCO must not replace the agent's policy brain. The backend and agent still own rule validation, credential lookup, listener conflict resolution, TLS/REALITY /Noise handshakes, fallback decisions, and human-readable logs. DCO should be a fast path after user-space has selected and authorized a route or session.
External Reference: OpenVPN DCO And ovpn
The useful reference is the OpenVPN data-channel offload architecture, not the OpenVPN protocol itself.
As of this review:
https://github.com/OpenVPN/ovpn-dcois obsolete for new OpenVPN releases. Its README says the out-of-treeovpn-dcomodule is for OpenVPN 2.6.x and older, is in maintenance mode, and accepts only important bug fixes.- OpenVPN 2.7.0 and newer use the new upstream Linux
ovpnkernel module. OpenVPN'sREADME.dco.mdsaysovpnis merged upstream starting with Linux 6.16, and older kernels should useovpn-backports. - The upstream kernel documentation exposes an
ovpngeneric netlink family for controlling OpenVPN network devices. The operations include peer create, update, get, delete, key create, key get, key swap, key delete, and notifications for peer deletion, peer floating, and key swap.
What matters for NexusNet:
OpenVPN userspace
-> authenticates peer, negotiates cipher, owns config and logs
-> installs peer/key/socket/VPN-IP state through netlink
-> kernel ovpn module encrypts/decrypts and forwards data packets
-> userspace keeps control-plane authority and can disable/fallback DCOThat control/data split maps well to the NexusNet DCO direction:
NexusNet backend + agent userspace
-> validates config, resolves listener priority, authenticates REALITY/TLS/Noise/QUIC
-> installs a generated, already-authorized fast-path entry
-> helper/kernel handles repetitive packet movement and counters
-> userspace keeps fallback, teardown, logging, and config-generation authorityIt does not mean NexusNet should directly depend on the OpenVPN ovpn module as a generic tunnel engine. ovpn is OpenVPN-specific: it expects OpenVPN peer/key/cipher/session semantics and exposes an OpenVPN control ABI. NexusNet currently uses REALITY, Noise, Quinn QUIC, QUIC DATAGRAM, and the CONNECT-IP over QUIC DATAGRAM framing. Direct integration would either force a parallel OpenVPN-compatible mode or make NexusNet tunnel semantics depend on a foreign protocol. That is not the right first DCO target.
Reference links:
- OpenVPN obsolete out-of-tree DCO module: https://github.com/OpenVPN/ovpn-dco
- OpenVPN DCO user documentation: https://github.com/OpenVPN/openvpn/blob/master/README.dco.md
- Linux
ovpnnetlink specification: https://docs.kernel.org/netlink/specs/ovpn.html - Current
ovpndevelopment tree: https://github.com/OpenVPN/ovpn-net-next
Why This Is Project-Level
One listener can now do many different jobs:
- TCP RAW: plain stream relay with no route discriminator.
- TCP TLS: TLS accept with SNI-based routing and optional upstream TLS.
- TCP REALITY: ClientHello classification by SNI + short ID, X25519 authentication, target probing, and deliberate fallback.
- TCP over QUIC REALITY: TCP payload carried over an authenticated QUIC path.
- UDP RAW: session relay by client address.
- UDP Noise: first datagram selects a local X25519 identity; later datagrams stay pinned to that UDP session.
- UDP over QUIC REALITY: UDP payloads carried as QUIC DATAGRAM frames.
- IP over QUIC REALITY DATAGRAM: L3 tunnel packets framed by the agent.
That makes DCO a layered problem, not a single ip:port lookup:
incoming packet / connection
-> socket namespace: TCP, UDP, TUN, or helper ring
-> port/rule priority: REALITY > TLS > Noise > RAW
-> protocol classifier in user-space
-> route / fallback / reject
-> optional kernel/helper fast path for stable sessionsFeature Status
| Feature | State | Evidence |
|---|---|---|
| User-space TCP/UDP gateway | Complete | nexus-agent/src/gateway/mod.rs and udp.rs. |
| User-space TLS/REALITY/Noise classification | Complete | Gateway TLS, REALITY, and UDP Noise paths. |
| User-space QUIC REALITY and DATAGRAM | Complete | nexus-agent/src/gateway/quic.rs. |
| User-space CONNECT-IP dataplane | Complete | nexus-agent/src/l3/*. |
| QUIC GSO tuning | Complete | Agent and L3 config expose GSO toggles. |
| Kernel/eBPF/helper DCO implementation | Planned | No project DCO code exists in this repository. |
| Kernel/user ABI for DCO | Planned | Not defined. |
| DCO conformance vectors | Planned | Not defined. |
| Replacing user-space route classification | Rejected | Too much policy, fallback, and credential behavior belongs in user-space. |
Current NexusNet Fast-Path Seams
The current code already has several useful seams for future offload adapters. These are better starting points than introducing kernel code directly.
| Seam | Current interface | Current adapter | Future offload adapter idea | Notes |
|---|---|---|---|---|
| L3 host interface | nexus-agent/src/l3/tun.rs TunDevice | TunRsDevice | DcoTunDevice, AfXdpTunDevice, or helper-owned TUN adapter | The interface already supports single-packet and batched receive/send. |
| L3 transport | nexus-agent/src/l3/transport.rs L3DatagramTransport | Quinn REALITY L3 DATAGRAM transport and fake test endpoint | DcoL3Transport or helper-ring transport | Best seam for replacing packet movement while keeping CONNECT-IP tests. |
| L3 dataplane loop | nexus-agent/src/l3/dataplane.rs L3Dataplane<Tun, Transport> | Generic over TUN and transport | Keep as the conformance harness for DCO adapters | The same pump and validation behavior can exercise userspace/helper/kernel paths. |
| L3 runtime reconciliation | nexus-agent/src/l3/mod.rs L3TunnelManager | Starts/stops TUN + QUIC runtime by prepared tunnel | Install/remove offload entries by generation | Already has config apply and stale runtime reconciliation. |
| QUIC socket path | nexus-agent/src/gateway/quic.rs Quinn endpoint setup | UDP socket buffers and Quinn GSO | Socket offload/tuning only, not QUIC state offload | Quinn continues to own recovery, pacing, loss, and QUIC crypto. |
| Network config | nexus-agent/src/l3/net_config.rs NetworkConfigPlan | Linux route add/delete wrapper | Route/mark/table generation for helper/kernel | Mirrors the OpenVPN DCO lesson that routing should stay visible in host tables. |
| Metrics | nexus-agent/src/l3/metrics.rs and gateway QUIC metrics | In-process atomic counters | Counter reconciliation from helper/kernel | Needs generation-aware snapshots before real offload. |
These seams also define the intended test surface. A future offload adapter must pass the same L3 packet, source-prefix, MTU, multicast, broadcast, keepalive, counter, and teardown tests as the userspace adapter.
OpenVPN DCO Lessons For NexusNet
OpenVPN DCO provides a useful reference shape because it made a hard separation between control-plane authority and repetitive packet processing. The following lessons apply directly:
Lesson from OpenVPN DCO / ovpn | NexusNet interpretation |
|---|---|
| Userspace remains the control plane. | Backend and agent continue to validate rules, select credentials, classify first packets, authenticate peers, and write operator logs. |
| Kernel state is explicit and installed after negotiation. | NexusNet should only install fast-path entries after REALITY/TLS/Noise/QUIC/L3 route authorization is complete. |
| Peer and key lifecycle are separate operations. | Future ABI should separate tunnel/session creation from crypto/session material, if crypto is ever offloaded. |
| Key swap is an explicit operation with notifications. | Future key or session rotation must be generation-aware and observable, not hidden behind packet loss. |
| Peer deletion and peer floating are explicit notifications. | Helper/kernel path must report teardown, endpoint change, expiry, and error reasons back to the agent. |
| Main kernel routing tables are used for forwarding decisions. | NexusNet should keep L3 routes visible through normal host route tables or explicit routing tables, not hide all forwarding inside DCO. |
| Unsupported features disable DCO and fall back to userspace. | NexusNet control plane must not require DCO. Every offload path needs a userspace fallback and a clear reason when offload is disabled. |
| DCO intentionally rejects complex legacy features such as compression and packet manipulation. | NexusNet DCO should reject unsupported protocol mixes instead of adding policy, fallback, or transformation logic to kernel/helper code. |
The most important negative lesson is that DCO is not a place to preserve every feature. A deep fast-path module earns its depth by having a small interface and strict invariants.
Direct ovpn Integration Assessment
Directly using Linux ovpn as the NexusNet kernel path is not the recommended default. It is only plausible as a separate compatibility mode.
| Option | Fit | Why |
|---|---|---|
Use ovpn directly for current QUIC L3 | Poor | Current NexusNet packets are CONNECT-IP-framed IP over QUIC DATAGRAM with REALITY security. ovpn expects OpenVPN data-channel semantics, peer IDs, keys, ciphers, and its own netlink ABI. |
| Add a dedicated OpenVPN-compatible product mode | Possible but separate | This would be an OpenVPN interoperability feature, not an acceleration of the current NexusNet REALITY/QUIC dataplane. |
Borrow ovpn architecture for NexusNet helper/kernel DCO | Strong | The userspace-control/kernel-fast-path split matches the current roadmap and preserves NexusNet protocol semantics. |
| Build a custom kernel module immediately | Premature | The ABI, counters, generation model, fallback semantics, and benchmark baseline are not proven yet. |
| Build a privileged helper first | Strong | Gives most of the ABI and lifecycle learning with lower kernel compatibility and crash risk. |
If an OpenVPN-compatible mode is ever added, it should be modeled as a distinct transport profile or compatibility product mode. It should not be mixed into the existing payload=ip, outer=quic, security=reality CONNECT-IP profile.
Where Kernel Facilities Can Help
| Area | Candidate | Useful for | Expected effect |
|---|---|---|---|
| Socket buffers | SO_RCVBUF, SO_SNDBUF, autotuning, busy-poll | TCP RAW/TLS/REALITY, UDP RAW/Noise/QUIC | More burst tolerance and fewer drops under load. |
| TCP tuning | TCP_NODELAY, keepalive, TCP_INFO, pacing-related sysctls | All TCP listeners and upstream connections | Lower latency, better observability, better failure cleanup. |
| UDP GSO/GRO | UDP segmentation and aggregation offload | QUIC REALITY, UDP DATAGRAM, L3 DATAGRAM | Fewer syscalls and lower per-packet CPU for large bursts. |
| NIC checksum/TSO/LRO | Hardware offload via NIC driver | TCP and UDP outer packets | Lower checksum/segmentation CPU cost. |
| RSS/RPS/XPS | Kernel/NIC queue steering | High-volume TCP/UDP listeners | Better CPU distribution across queues. |
SO_REUSEPORT + BPF | Kernel listener selection among worker sockets | Scale-out after a listener family is chosen | More accept/recv parallelism; not route authorization. |
| XDP | Driver-ingress eBPF | UDP floods, closed ports, coarse allow/drop | Drop or count traffic before SKB allocation. |
| tc eBPF | SKB ingress/egress hook | L3 tunnel marks, redirect, rate/drop policy | Fast packet marking and coarse tunnel policy. |
| nftables/iptables | Netfilter policy | Host firewall, NAT, marks, coarse denies | Protects agent sockets; not protocol classification. |
| TUN/TAP offload | multiqueue, checksum/GSO flags, batch IO | IP over QUIC REALITY DATAGRAM | Lower TUN-side per-packet overhead. |
| Unified TUN I/O engine | Shared packet pool, VNET header metadata, Wintun ring adapter | QUIC L3 and userspace WireGuard | See Unified TUN I/O Engine. |
| AF_XDP | Userspace packet rings | Heavy UDP and L3 dataplane experiments | Kernel bypass for selected queues with explicit helper ownership. |
| io_uring | Async socket/TUN batching | TCP/UDP relay and helper designs | Lower syscall overhead without kernel protocol logic. |
| Kernel module | Custom dataplane | Stable L3/UDP fast path after route authorization | Highest throughput potential, highest maintenance risk. |
Concrete Kernel Responsibilities To Prefer
These responsibilities are good candidates because they have small interfaces, clear failure modes, and do not require protocol-specific product decisions:
- Allocate, configure, and expose packet devices or queues.
- Batch packet receive/send across TUN, UDP sockets, AF_XDP rings, or shared memory rings.
- Apply GSO/GRO/checksum metadata where the packet format is already stable.
- Drop traffic to disabled listener ports or obviously invalid transport families before it reaches the agent.
- Count packets, bytes, drops, malformed frames, queue depth, and per-entry expiry.
- Mark packets for routing, QoS, or debugging after userspace has generated a coarse policy.
- Enforce allowed source prefixes for an already-authorized L3 tunnel.
- Expire fast-path entries when a config generation is no longer current.
- Notify userspace when an offloaded peer/session is deleted, expired, moved, or encounters an unsupported packet condition.
Kernel Responsibilities To Avoid
These responsibilities are intentionally outside the fast path:
- Parsing ClientHello to decide product routing.
- Running REALITY X25519 authentication, short ID selection, target probing, or camouflage fallback.
- Selecting TLS certificates or deciding SNI policy.
- Trying multiple Noise identities for the first datagram.
- Owning QUIC loss recovery, congestion control, stream state, or DATAGRAM semantics while Quinn is the runtime.
- Creating hidden ports, hidden listeners, or hidden fallback routes.
- Becoming the source of truth for backend config validity.
- Emitting only opaque kernel errors when the operator needs a product-level explanation.
The line is simple: kernel/helper code may accelerate a decision that has already been made, but it must not become the place where a NexusNet route is chosen.
Protocol-Specific Fit
| Protocol path | Good DCO target | Should stay in user-space | Notes |
|---|---|---|---|
| TCP RAW | accept scaling, socket buffers, TCP_INFO, io_uring relay | Rule conflict decisions | RAW cannot be same-port multiplexed without a protocol tag. |
| TCP TLS | post-SNI stable relay, socket/TCP tuning | TLS accept, cert selection, SNI route policy | Kernel can help after TLS is accepted, not before route policy is settled. |
| TCP REALITY | stable accepted stream relay | REALITY auth, fallback target probing, short ID policy | Fallback semantics are part of the product behavior and must remain auditable. |
| TCP over QUIC REALITY | QUIC UDP GSO, buffers, queue steering | QUIC connection state and REALITY crypto bridge | Quinn owns recovery, pacing, loss, and crypto integration today. |
| UDP RAW | recv batching, socket buffers, session table acceleration | Rule conflicts and upstream policy | RAW route key is client address only. |
| UDP Noise | post-handshake session fast path | First-datagram key trial, key lifecycle, error logs | The first packet can be expensive, but it is security-sensitive. |
| UDP over QUIC REALITY | UDP GSO/GRO, DATAGRAM queue metrics, AF_XDP experiment | QUIC connection and REALITY authentication | Good DCO candidate after user-space session setup. |
| IP over QUIC REALITY DATAGRAM | TUN multiqueue, tc marks, AF_XDP/helper rings | Tunnel authorization, routes, MTU policy | Best long-term DCO candidate because packets are uniform after setup. |
| Control plane | none for dataplane DCO | validators, credentials, deployment generation | Kernel must never become source of truth for config validity. |
What DCO Must Not Own
| Behavior | Reason |
|---|---|
| Listener conflict resolution | Same-port route merging is a product rule based on SNI, REALITY short ID, UDP Noise key, QUIC Initial detection, and RAW fallback limits. |
| Hidden fallback port allocation | Explicit listener conflicts must fail or drop non-demultiplexable duplicates visibly. |
| TLS certificate selection and accept policy | Requires control-plane credential state and clear operator errors. |
| REALITY authentication and fallback | Includes X25519 auth, SNI, short ID, target probing, and camouflage fallback. |
| UDP Noise key lifecycle | Key lookup, rotation, validation, and logging belong to the agent/backend. |
| RAW same-port multiplexing | RAW has no route discriminator. Kernel cannot infer route intent safely. |
| QUIC recovery/congestion | Quinn currently owns this state; partial offload must not split semantics. |
| Human-readable diagnostics | Operators need logs that say which rule was kept, dropped, or rejected and why. |
Practical DCO Shapes
Stage 0: Tune Current User-Space
Use kernel-assisted primitives without introducing a new DCO ABI:
- Keep QUIC UDP GSO enabled where benchmarks confirm it helps.
- Tune TCP/UDP socket buffers per listener class.
- Keep TCP_INFO and route-level metrics visible.
- Batch TUN and UDP reads/writes.
- Test multiqueue only where it improves CPU distribution.
Expected effect: safer throughput and loss improvements without new deployment requirements.
Stage 1: eBPF Prefilter And Counters
Use XDP/tc/eBPF for simple decisions:
- Drop traffic to closed or disabled listener ports.
- Count packets, bytes, and drops by listener address and protocol family.
- Mark packets for routing, QoS, or debugging.
- Enforce coarse deny rules generated by the control plane.
Expected effect: less user-space pressure during scans, floods, or high-volume DATAGRAM traffic. This stage still does not classify TLS/REALITY/Noise routes.
Stage 2: Helper Fast Path
Introduce an optional privileged helper using AF_XDP, io_uring, or shared memory rings:
agent config generation
-> helper route table
-> packet rings / batched sockets
-> agent-owned session setup and teardownExpected effect: most batching benefits with easier rollback than a kernel module. This is the preferred proving ground before custom kernel code.
Stage 3: Stable Session Offload
After user-space accepts a route/session, it may install a generated fast-path entry:
generation + listener id + protocol family
-> peer tuple / session id / tunnel id
-> allowed prefixes or client address
-> MTU, marks, queues
-> counters and expiryGood candidates:
- L3 DATAGRAM packets after tunnel setup.
- UDP over QUIC DATAGRAM sessions after REALITY auth.
- UDP Noise sessions after the first datagram selects a key.
- TCP relay after TLS/REALITY handshake and route selection, if a helper can preserve stream semantics cleanly.
Expected effect: lower per-packet CPU for long-lived high-volume sessions. The tradeoff is config generation, counter reconciliation, and failure cleanup.
Stage 4: Kernel Module
A kernel module should be considered only after helper benchmarks prove the ABI and semantics. It could offer the best throughput, but it also creates the largest compatibility, packaging, safety, and support burden.
Draft Fast-Path ABI Model
This is not an implementation commitment. It is the minimal model future ADRs should start from.
Control Objects
| Object | Purpose | Installed by | Removed by |
|---|---|---|---|
Generation | Names one deployed config snapshot. | Agent after accepting backend config. | Agent when a newer generation is active and old counters are reconciled. |
ListenerFastPath | Coarse listener/socket namespace entry. | Agent after listener priority resolution. | Agent on config change or listener shutdown. |
TunnelFastPath | L3 tunnel entry keyed by network/tunnel/context. | Agent after L3 tunnel authorization and runtime start. | Agent on config change, tunnel disable, or peer loss. |
SessionFastPath | UDP or TCP session entry keyed by session tuple/id. | Agent after first packet/handshake route selection. | Agent on idle timeout, error, FIN/RST, or config change. |
PeerEndpoint | Remote tuple and optional floating state. | Agent after peer is known. | Agent or helper/kernel when endpoint expires or changes. |
CryptoMaterial | Optional future key material. | Agent after successful negotiation. | Agent on key rotation or generation teardown. |
CounterSnapshot | Packets, bytes, drops, errors, queue state. | Helper/kernel produces, agent consumes. | Cleared or rolled over only after agent acknowledgement. |
Common Entry Fields
Every offloaded entry should carry:
generation_id: monotonically increasing config generation.entry_id: stable id for logs and counter reconciliation.network_idor listener id where applicable.protocol_family:tcp,udp,ip, or future explicit value.local_tupleandpeer_tuplewhere socket traffic is involved.datagram_context_idfor CONNECT-IP entries.mtuand maximum outer payload size.allowed_source_prefixesfor L3.mark, queue id, or routing table id where the host routing policy needs it.idle_timeoutand hardexpires_ator generation expiry rule.capability_mask: features the helper/kernel actually accepted.fallback_reason: populated when an entry cannot be offloaded.
Lifecycle Operations
The OpenVPN ovpn netlink operations suggest a useful shape, but NexusNet should name operations around NexusNet concepts:
| Operation | Meaning |
|---|---|
generation.new | Start a new config generation and declare feature/capability expectations. |
generation.commit | Mark a generation active after all required entries are installed. |
generation.retire | Stop accepting new traffic for an old generation and prepare counter drain. |
listener.new / listener.del | Install or remove a coarse listener fast-path. |
tunnel.new / tunnel.set / tunnel.del | Install, update, or remove an L3 tunnel fast-path. |
session.new / session.set / session.del | Install, refresh, or remove a stable L4 session fast-path. |
key.new / key.swap / key.del | Optional future crypto lifecycle if any crypto leaves userspace. |
counters.get | Read and acknowledge generation/entry counters. |
entry.flush | Remove all entries matching generation, listener, tunnel, peer, or protocol. |
events.subscribe | Receive delete, expiry, error, endpoint-change, and capability-change events. |
Required Events
The agent must be able to explain every offload state transition in product terms. A helper/kernel path should report:
- entry installed;
- entry rejected with an explicit unsupported capability;
- entry expired by idle timeout;
- entry deleted by generation retirement;
- peer endpoint changed or became unreachable;
- packet dropped by source-prefix policy;
- packet dropped by MTU/oversize policy;
- malformed frame or unsupported IP version;
- queue overflow or backpressure;
- counter rollover or counter read failure;
- helper/kernel capability changed after startup.
Generation Rules
Stale fast-path state is the largest correctness risk. The generation model should be stricter than the current userspace runtime:
- A new generation may be prepared before activation.
- A generation is active only after the agent commits it.
- Entries from non-active generations must not accept new sessions.
- Existing sessions from an old generation may drain only if the agent has explicitly allowed drain.
- L3 entries should normally switch atomically because route and MTU changes can silently misdeliver packets.
generation.retiremust return enough counters for the agent to report final bytes/drops.- Agent restart must either discover and retire stale entries or create entries with process-owned lifetime so stale entries cannot survive unexpectedly.
Fallback Rules
Fallback must be explicit:
- If helper/kernel support is missing, the agent logs a single clear reason and runs userspace dataplane.
- If an entry is rejected, only that entry falls back unless the rejected capability is generation-wide.
- If counters cannot be reconciled, traffic may continue but status must show degraded observability.
- If helper/kernel errors become repeated, the agent should trip a circuit breaker and stop installing new offload entries for that generation.
- Backend config validity must not depend on DCO availability.
First Candidate: L3 DATAGRAM Offload
The first serious offload target should be IP over QUIC REALITY DATAGRAM or a helper-backed variant of that path, because it has the cleanest packet shape after setup.
Current userspace path:
TUN packet
-> TunDevice::recv_packets
-> L3Dataplane::encode_tun_packet
-> CONNECT-IP payload with Context ID
-> L3DatagramTransport::send_frames
-> Quinn QUIC REALITY DATAGRAM
-> peer L3DatagramTransport::recv_datagrams
-> L3Dataplane::decode_transport_datagram
-> TunDevice::send_packetsPossible helper/kernel split:
Agent userspace
-> creates TUN, applies routes, performs QUIC REALITY auth
-> installs TunnelFastPath(generation, context_id, mtu, allowed_sources)
-> keeps fallback userspace dataplane ready
Helper/kernel
-> batches TUN packets
-> validates IP version, MTU, context id, and source prefix
-> moves frames through rings or sockets
-> reports counters and drops by entry idFor a true kernel module, the hard question is whether QUIC remains outside the module. Keeping QUIC in Quinn and only accelerating TUN/ring movement is much less risky than splitting QUIC recovery or DATAGRAM semantics. A custom module that tries to own QUIC would be a different transport runtime, not a small DCO step.
Possible Helper Shapes
| Helper shape | Good for | Risk |
|---|---|---|
| io_uring helper | Socket/TUN batching with ordinary file descriptors. | Lower risk, but still copies through kernel/userspace boundaries. |
| AF_XDP helper | Heavy UDP ingress/egress experiments and queue ownership. | Requires NIC/driver support and careful queue isolation. |
| Shared-memory ring helper | Stable L3 frames between agent and privileged process. | Requires custom flow control and crash cleanup. |
| tc/XDP-only programs | Early drop, mark, coarse counters. | Not enough for full tunnel/session offload. |
| Kernel module | Full custom dataplane. | Highest performance potential and highest compatibility/safety burden. |
The preferred proof path is:
userspace baseline
-> io_uring or ring helper
-> AF_XDP where the environment supports it
-> kernel module only after ABI and tests stabilizePort Reuse Boundary
DCO must respect the same priority order as the agent:
REALITY > TLS > Noise > RAWRecommended ownership:
| Phase | Owner | Notes |
|---|---|---|
| Config validation | Backend | Validate credentials, route uniqueness, and impossible combinations. |
| Listener conflict resolution | Backend + agent | Decide which routes can coexist on one port; log rejected or non-demultiplexable duplicates. |
| First connection/datagram classification | Agent user-space | Parse ClientHello, REALITY auth, or Noise first datagram. |
| Fallback/reject decision | Agent user-space | Required for REALITY camouflage and operator diagnostics. |
| Stable session fast path | Kernel/helper optional | Only after route selection and authorization. |
| Counters and teardown | Agent + helper/kernel | Counters must be reconciled and entries must expire by config generation. |
Capability Detection
The agent should detect offload capabilities at startup and again when applying config. Detection should produce structured status, not just logs.
| Capability | Detection source | Status field idea |
|---|---|---|
| Linux TUN offload | TUN adapter creation and feature flags. | tun_offload_available, tun_offload_enabled. |
| TUN multiqueue | TUN adapter creation result. | tun_multiqueue_available, queue_count. |
| QUIC UDP GSO | Quinn/socket setup and runtime send behavior. | quic_gso_requested, quic_gso_active. |
| Socket buffer tuning | setsockopt result and effective socket buffer if readable. | socket_buffer_requested, socket_buffer_effective. |
| XDP/tc program support | Loader capability check. | prefilter_available, prefilter_program_id. |
| AF_XDP | NIC/driver/queue support check. | afxdp_available, afxdp_queue_count. |
| Helper daemon | Version handshake. | helper_version, helper_capabilities. |
| Kernel module | netlink family or device capability probe. | kernel_dco_available, kernel_dco_capabilities. |
The frontend should eventually distinguish:
- requested but unavailable;
- available but disabled by policy;
- enabled and active;
- enabled but degraded;
- disabled because the profile uses unsupported features.
Metrics And Counter Reconciliation
Offload is not complete unless operators can still see what happened. Counters must preserve at least the current L3 semantics:
- packets and bytes from TUN;
- packets and bytes to TUN;
- datagram/frame transmit and receive counts;
- malformed packets;
- unsupported IP version;
- source-prefix rejects;
- oversized packets;
- TUN write errors;
- datagram/helper/kernel send errors.
Additional offload counters should include:
- helper/kernel accepted entries;
- helper/kernel rejected entries by reason;
- active entries by generation;
- retired entries awaiting counter drain;
- queue depth and high-water mark;
- ring drops and backpressure events;
- kernel/helper packet drops by policy;
- fallback count and fallback reason;
- counter read failures;
- stale generation flush count.
Counter reconciliation rules:
- Counters are keyed by
generation_idandentry_id. - The helper/kernel may use monotonic counters; the agent computes deltas.
- The agent must tolerate counter rollover.
- Final counters must be read before an entry is forgotten, unless the entry was lost due to crash or forced flush.
- A counter snapshot should include the helper/kernel boot id or instance id so the agent can detect helper restart.
Security Model
DCO changes the blast radius of dataplane bugs. The security model should be written before kernel-facing code is merged.
Required properties:
- The backend remains the source of truth for desired config.
- The agent remains the authority that decides which entries are installed.
- Helper/kernel code must accept only generated entries from the local agent.
- Entry ids and generation ids must be unguessable or scoped to an authenticated local control channel when a helper process is used.
- Offload entries must be least-privilege: a tunnel entry may forward only its allowed prefixes, MTU, context id, and peer tuple.
- All unsupported protocol features must fail closed to userspace fallback.
- Kernel/helper crashes must not silently bypass policy.
- Sensitive key material should stay in userspace until there is a concrete reason to offload crypto.
- If crypto material is ever offloaded, key lifecycle must support explicit install, swap, delete, zeroization, and event reporting.
Test Strategy
DCO tests must prove equivalence with the current userspace dataplane before proving speed.
| Test group | What it proves |
|---|---|
| L3 frame conformance | CONNECT-IP varint context, raw IP validation, and MTU. |
| Source policy | Allowed source prefixes, host peer addresses, reject counters. |
| Multicast/broadcast policy | allow_multicast and allow_broadcast behavior stays consistent. |
| Keepalive | Keepalive frames do not become user packets and wrong context ids are counted. |
| Batch behavior | Batched send/receive preserves packet boundaries and partial-send accounting. |
| Generation teardown | Old entries stop accepting packets after config update. |
| Counter reconciliation | Userspace and offload counters match under normal drain and forced teardown. |
| Fallback | Unsupported capability, missing helper, and helper crash return to userspace path. |
| Restart cleanup | Agent restart discovers or eliminates stale helper/kernel entries. |
| Route visibility | Host routes and marks remain inspectable with standard Linux tools. |
The existing generic L3Dataplane<Tun, Transport> design should be preserved as the main conformance harness. New adapters should be tested against the same fake and real-path cases, then compared with local namespace integration tests.
Benchmark Strategy
Benchmarks must isolate the effect of each offload layer. A single "DCO on/off" number will be misleading.
Minimum matrix:
| Dimension | Values |
|---|---|
| TUN offload | off, on |
| TUN multiqueue | off, on, queue count where supported |
| QUIC GSO | off, on |
| Batch size | 1, 32, 128, 512, 1024 |
| MTU | 1280, 1500, 4096, 9000 |
| Transport path | userspace Quinn, helper ring, AF_XDP where supported, future kernel module |
| Direction | connector to listener, listener to connector, bidirectional |
| Traffic shape | single flow, parallel flows, small packets, MTU-sized packets, mixed packets |
| Failure mode | clean run, peer restart, config update during load, helper restart |
Required outputs:
- sender and receiver throughput;
- packet rate;
- p50/p95/p99 latency where applicable;
- CPU per process and per core;
- context switches;
- syscalls where measurable;
- drops and retransmission symptoms;
- queue high-water marks;
- memory high-water marks;
- final reconciled counters.
The existing nexus-agent/scripts/l3_offload_matrix.sh is the right baseline script family. Future scripts should extend it rather than creating unrelated benchmark formats.
Design Constraints
- Preserve REALITY authentication semantics.
- Preserve TLS and Noise credential boundaries.
- Preserve QUIC DATAGRAM packet boundaries.
- Keep MTU behavior explicit for L3.
- Keep user-space fallback available.
- Do not make the control plane depend on DCO availability.
- Never silently create a different port when an explicit listener is occupied.
- Keep logs human-readable enough to explain kept, dropped, and rejected rules.
Open Decisions
| Decision | State |
|---|---|
| eBPF/XDP versus helper versus kernel module. | Planned |
Whether to ever add an OpenVPN-compatible mode using Linux ovpn. | Planned |
| User/kernel ABI shape for sessions, route state, counters, and config generation. | Planned |
| Whether the first useful DCO target should be L3 DATAGRAM, UDP relay, or TCP relay. | Planned |
| Whether future crypto material is ever offloaded, or all crypto remains in userspace. | Planned |
| Whether L3 offload accelerates TUN/ring movement only, or introduces a non-QUIC transport mode. | Planned |
How to reconcile helper/kernel counters into agent MetricsReport. | Planned |
| How to benchmark offload against current Quinn GSO and user-space batching. | Planned |
| How to make stale fast-path entries impossible after config updates. | Planned |
| How helper/kernel status appears in backend API and frontend tunnel status. | Planned |
Next Work
| Item | State |
|---|---|
Record an ADR that ovpn is a reference architecture, not the default NexusNet DCO dependency. | Planned |
| Stabilize user-space L3/L4 route logs before defining offload counters. | Planned |
| Write an ADR before introducing kernel-facing code. | Planned |
Define a DcoCapabilityReport shape for agent status before implementing a helper. | Planned |
| Build conformance tests reusable by user-space, helper, and future DCO. | Planned |
| Add benchmark scripts for no-offload, GSO, TUN batch, io_uring, AF_XDP/helper, and future custom DCO. | Planned |
| Define a minimal config generation model for fast-path install and teardown. | Planned |
| Prototype a helper-backed L3 adapter only after userspace L3 metrics are reported through the control plane. | Planned |