Skip to content

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:

text
TCP/UDP socket or TUN
  -> nexus-agent classifier and dataplane
  -> RAW / TLS / REALITY / Noise / QUIC path
  -> upstream socket, peer agent, or peer TUN

DCO 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-dco is obsolete for new OpenVPN releases. Its README says the out-of-tree ovpn-dco module 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 ovpn kernel module. OpenVPN's README.dco.md says ovpn is merged upstream starting with Linux 6.16, and older kernels should use ovpn-backports.
  • The upstream kernel documentation exposes an ovpn generic 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:

text
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 DCO

That control/data split maps well to the NexusNet DCO direction:

text
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 authority

It 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:

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:

text
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 sessions

Feature Status

FeatureStateEvidence
User-space TCP/UDP gatewayCompletenexus-agent/src/gateway/mod.rs and udp.rs.
User-space TLS/REALITY/Noise classificationCompleteGateway TLS, REALITY, and UDP Noise paths.
User-space QUIC REALITY and DATAGRAMCompletenexus-agent/src/gateway/quic.rs.
User-space CONNECT-IP dataplaneCompletenexus-agent/src/l3/*.
QUIC GSO tuningCompleteAgent and L3 config expose GSO toggles.
Kernel/eBPF/helper DCO implementationPlannedNo project DCO code exists in this repository.
Kernel/user ABI for DCOPlannedNot defined.
DCO conformance vectorsPlannedNot defined.
Replacing user-space route classificationRejectedToo 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.

SeamCurrent interfaceCurrent adapterFuture offload adapter ideaNotes
L3 host interfacenexus-agent/src/l3/tun.rs TunDeviceTunRsDeviceDcoTunDevice, AfXdpTunDevice, or helper-owned TUN adapterThe interface already supports single-packet and batched receive/send.
L3 transportnexus-agent/src/l3/transport.rs L3DatagramTransportQuinn REALITY L3 DATAGRAM transport and fake test endpointDcoL3Transport or helper-ring transportBest seam for replacing packet movement while keeping CONNECT-IP tests.
L3 dataplane loopnexus-agent/src/l3/dataplane.rs L3Dataplane<Tun, Transport>Generic over TUN and transportKeep as the conformance harness for DCO adaptersThe same pump and validation behavior can exercise userspace/helper/kernel paths.
L3 runtime reconciliationnexus-agent/src/l3/mod.rs L3TunnelManagerStarts/stops TUN + QUIC runtime by prepared tunnelInstall/remove offload entries by generationAlready has config apply and stale runtime reconciliation.
QUIC socket pathnexus-agent/src/gateway/quic.rs Quinn endpoint setupUDP socket buffers and Quinn GSOSocket offload/tuning only, not QUIC state offloadQuinn continues to own recovery, pacing, loss, and QUIC crypto.
Network confignexus-agent/src/l3/net_config.rs NetworkConfigPlanLinux route add/delete wrapperRoute/mark/table generation for helper/kernelMirrors the OpenVPN DCO lesson that routing should stay visible in host tables.
Metricsnexus-agent/src/l3/metrics.rs and gateway QUIC metricsIn-process atomic countersCounter reconciliation from helper/kernelNeeds 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 / ovpnNexusNet 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.

OptionFitWhy
Use ovpn directly for current QUIC L3PoorCurrent 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 modePossible but separateThis would be an OpenVPN interoperability feature, not an acceleration of the current NexusNet REALITY/QUIC dataplane.
Borrow ovpn architecture for NexusNet helper/kernel DCOStrongThe userspace-control/kernel-fast-path split matches the current roadmap and preserves NexusNet protocol semantics.
Build a custom kernel module immediatelyPrematureThe ABI, counters, generation model, fallback semantics, and benchmark baseline are not proven yet.
Build a privileged helper firstStrongGives 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

AreaCandidateUseful forExpected effect
Socket buffersSO_RCVBUF, SO_SNDBUF, autotuning, busy-pollTCP RAW/TLS/REALITY, UDP RAW/Noise/QUICMore burst tolerance and fewer drops under load.
TCP tuningTCP_NODELAY, keepalive, TCP_INFO, pacing-related sysctlsAll TCP listeners and upstream connectionsLower latency, better observability, better failure cleanup.
UDP GSO/GROUDP segmentation and aggregation offloadQUIC REALITY, UDP DATAGRAM, L3 DATAGRAMFewer syscalls and lower per-packet CPU for large bursts.
NIC checksum/TSO/LROHardware offload via NIC driverTCP and UDP outer packetsLower checksum/segmentation CPU cost.
RSS/RPS/XPSKernel/NIC queue steeringHigh-volume TCP/UDP listenersBetter CPU distribution across queues.
SO_REUSEPORT + BPFKernel listener selection among worker socketsScale-out after a listener family is chosenMore accept/recv parallelism; not route authorization.
XDPDriver-ingress eBPFUDP floods, closed ports, coarse allow/dropDrop or count traffic before SKB allocation.
tc eBPFSKB ingress/egress hookL3 tunnel marks, redirect, rate/drop policyFast packet marking and coarse tunnel policy.
nftables/iptablesNetfilter policyHost firewall, NAT, marks, coarse deniesProtects agent sockets; not protocol classification.
TUN/TAP offloadmultiqueue, checksum/GSO flags, batch IOIP over QUIC REALITY DATAGRAMLower TUN-side per-packet overhead.
Unified TUN I/O engineShared packet pool, VNET header metadata, Wintun ring adapterQUIC L3 and userspace WireGuardSee Unified TUN I/O Engine.
AF_XDPUserspace packet ringsHeavy UDP and L3 dataplane experimentsKernel bypass for selected queues with explicit helper ownership.
io_uringAsync socket/TUN batchingTCP/UDP relay and helper designsLower syscall overhead without kernel protocol logic.
Kernel moduleCustom dataplaneStable L3/UDP fast path after route authorizationHighest 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 pathGood DCO targetShould stay in user-spaceNotes
TCP RAWaccept scaling, socket buffers, TCP_INFO, io_uring relayRule conflict decisionsRAW cannot be same-port multiplexed without a protocol tag.
TCP TLSpost-SNI stable relay, socket/TCP tuningTLS accept, cert selection, SNI route policyKernel can help after TLS is accepted, not before route policy is settled.
TCP REALITYstable accepted stream relayREALITY auth, fallback target probing, short ID policyFallback semantics are part of the product behavior and must remain auditable.
TCP over QUIC REALITYQUIC UDP GSO, buffers, queue steeringQUIC connection state and REALITY crypto bridgeQuinn owns recovery, pacing, loss, and crypto integration today.
UDP RAWrecv batching, socket buffers, session table accelerationRule conflicts and upstream policyRAW route key is client address only.
UDP Noisepost-handshake session fast pathFirst-datagram key trial, key lifecycle, error logsThe first packet can be expensive, but it is security-sensitive.
UDP over QUIC REALITYUDP GSO/GRO, DATAGRAM queue metrics, AF_XDP experimentQUIC connection and REALITY authenticationGood DCO candidate after user-space session setup.
IP over QUIC REALITY DATAGRAMTUN multiqueue, tc marks, AF_XDP/helper ringsTunnel authorization, routes, MTU policyBest long-term DCO candidate because packets are uniform after setup.
Control planenone for dataplane DCOvalidators, credentials, deployment generationKernel must never become source of truth for config validity.

What DCO Must Not Own

BehaviorReason
Listener conflict resolutionSame-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 allocationExplicit listener conflicts must fail or drop non-demultiplexable duplicates visibly.
TLS certificate selection and accept policyRequires control-plane credential state and clear operator errors.
REALITY authentication and fallbackIncludes X25519 auth, SNI, short ID, target probing, and camouflage fallback.
UDP Noise key lifecycleKey lookup, rotation, validation, and logging belong to the agent/backend.
RAW same-port multiplexingRAW has no route discriminator. Kernel cannot infer route intent safely.
QUIC recovery/congestionQuinn currently owns this state; partial offload must not split semantics.
Human-readable diagnosticsOperators 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:

text
agent config generation
  -> helper route table
  -> packet rings / batched sockets
  -> agent-owned session setup and teardown

Expected 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:

text
generation + listener id + protocol family
  -> peer tuple / session id / tunnel id
  -> allowed prefixes or client address
  -> MTU, marks, queues
  -> counters and expiry

Good 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

ObjectPurposeInstalled byRemoved by
GenerationNames one deployed config snapshot.Agent after accepting backend config.Agent when a newer generation is active and old counters are reconciled.
ListenerFastPathCoarse listener/socket namespace entry.Agent after listener priority resolution.Agent on config change or listener shutdown.
TunnelFastPathL3 tunnel entry keyed by network/tunnel/context.Agent after L3 tunnel authorization and runtime start.Agent on config change, tunnel disable, or peer loss.
SessionFastPathUDP 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.
PeerEndpointRemote tuple and optional floating state.Agent after peer is known.Agent or helper/kernel when endpoint expires or changes.
CryptoMaterialOptional future key material.Agent after successful negotiation.Agent on key rotation or generation teardown.
CounterSnapshotPackets, 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_id or listener id where applicable.
  • protocol_family: tcp, udp, ip, or future explicit value.
  • local_tuple and peer_tuple where socket traffic is involved.
  • datagram_context_id for CONNECT-IP entries.
  • mtu and maximum outer payload size.
  • allowed_source_prefixes for L3.
  • mark, queue id, or routing table id where the host routing policy needs it.
  • idle_timeout and hard expires_at or 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:

OperationMeaning
generation.newStart a new config generation and declare feature/capability expectations.
generation.commitMark a generation active after all required entries are installed.
generation.retireStop accepting new traffic for an old generation and prepare counter drain.
listener.new / listener.delInstall or remove a coarse listener fast-path.
tunnel.new / tunnel.set / tunnel.delInstall, update, or remove an L3 tunnel fast-path.
session.new / session.set / session.delInstall, refresh, or remove a stable L4 session fast-path.
key.new / key.swap / key.delOptional future crypto lifecycle if any crypto leaves userspace.
counters.getRead and acknowledge generation/entry counters.
entry.flushRemove all entries matching generation, listener, tunnel, peer, or protocol.
events.subscribeReceive 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.retire must 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:

text
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_packets

Possible helper/kernel split:

text
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 id

For 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 shapeGood forRisk
io_uring helperSocket/TUN batching with ordinary file descriptors.Lower risk, but still copies through kernel/userspace boundaries.
AF_XDP helperHeavy UDP ingress/egress experiments and queue ownership.Requires NIC/driver support and careful queue isolation.
Shared-memory ring helperStable L3 frames between agent and privileged process.Requires custom flow control and crash cleanup.
tc/XDP-only programsEarly drop, mark, coarse counters.Not enough for full tunnel/session offload.
Kernel moduleFull custom dataplane.Highest performance potential and highest compatibility/safety burden.

The preferred proof path is:

text
userspace baseline
  -> io_uring or ring helper
  -> AF_XDP where the environment supports it
  -> kernel module only after ABI and tests stabilize

Port Reuse Boundary

DCO must respect the same priority order as the agent:

text
REALITY > TLS > Noise > RAW

Recommended ownership:

PhaseOwnerNotes
Config validationBackendValidate credentials, route uniqueness, and impossible combinations.
Listener conflict resolutionBackend + agentDecide which routes can coexist on one port; log rejected or non-demultiplexable duplicates.
First connection/datagram classificationAgent user-spaceParse ClientHello, REALITY auth, or Noise first datagram.
Fallback/reject decisionAgent user-spaceRequired for REALITY camouflage and operator diagnostics.
Stable session fast pathKernel/helper optionalOnly after route selection and authorization.
Counters and teardownAgent + helper/kernelCounters 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.

CapabilityDetection sourceStatus field idea
Linux TUN offloadTUN adapter creation and feature flags.tun_offload_available, tun_offload_enabled.
TUN multiqueueTUN adapter creation result.tun_multiqueue_available, queue_count.
QUIC UDP GSOQuinn/socket setup and runtime send behavior.quic_gso_requested, quic_gso_active.
Socket buffer tuningsetsockopt result and effective socket buffer if readable.socket_buffer_requested, socket_buffer_effective.
XDP/tc program supportLoader capability check.prefilter_available, prefilter_program_id.
AF_XDPNIC/driver/queue support check.afxdp_available, afxdp_queue_count.
Helper daemonVersion handshake.helper_version, helper_capabilities.
Kernel modulenetlink 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_id and entry_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 groupWhat it proves
L3 frame conformanceCONNECT-IP varint context, raw IP validation, and MTU.
Source policyAllowed source prefixes, host peer addresses, reject counters.
Multicast/broadcast policyallow_multicast and allow_broadcast behavior stays consistent.
KeepaliveKeepalive frames do not become user packets and wrong context ids are counted.
Batch behaviorBatched send/receive preserves packet boundaries and partial-send accounting.
Generation teardownOld entries stop accepting packets after config update.
Counter reconciliationUserspace and offload counters match under normal drain and forced teardown.
FallbackUnsupported capability, missing helper, and helper crash return to userspace path.
Restart cleanupAgent restart discovers or eliminates stale helper/kernel entries.
Route visibilityHost 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:

DimensionValues
TUN offloadoff, on
TUN multiqueueoff, on, queue count where supported
QUIC GSOoff, on
Batch size1, 32, 128, 512, 1024
MTU1280, 1500, 4096, 9000
Transport pathuserspace Quinn, helper ring, AF_XDP where supported, future kernel module
Directionconnector to listener, listener to connector, bidirectional
Traffic shapesingle flow, parallel flows, small packets, MTU-sized packets, mixed packets
Failure modeclean 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

DecisionState
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

ItemState
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

NexusNet documentation