40 KiB
Multiprotocol UDP Transport Layer
Functional & Technical Specification v1.0
1. Purpose
This project is a Linux userspace client/server system for transporting arbitrary UDP datagrams through networks where direct UDP connectivity may be restricted.
Its primary initial use case is carrying WireGuard traffic, but the software MUST remain completely protocol-agnostic.
The system MUST NOT:
- configure WireGuard;
- inspect WireGuard packets;
- depend on WireGuard libraries;
- manipulate WireGuard interfaces;
- assume anything about the encapsulated UDP protocol.
The fundamental abstraction is:
Transport opaque UDP datagrams between two endpoints using the least complex viable carrier.
Typical WireGuard deployment:
WireGuard client
│
│ UDP
▼
127.0.0.1:51821
│
▼
Transport client
│
│ selected carrier
▼
Internet
│
▼
Transport server
│
│ UDP
▼
127.0.0.1:51820
│
▼
WireGuard server
The exact same setup MUST be usable with any other UDP application.
2. Design Principles
The project SHALL follow these principles:
- Protocol independence.
- Minimal packet transformation.
- Datagram semantics preserved wherever possible.
- No custom cryptography.
- Pluggable transports.
- Automatic fallback from simpler transports to more compatible ones.
- Low-overhead direct mode.
- Multi-client server from the first release.
- Unprivileged execution wherever possible.
- Human-readable TOML configuration.
- Sensible defaults with optional advanced tuning.
- Explicit observability and diagnostics.
- Architecture prepared for multiple client tunnels in a future release.
- Security-sensitive parsing kept small and deterministic.
- Transport-specific behavior MUST NOT leak unnecessarily into the core.
3. Initial Platform Scope
Initial supported operating system:
Linux
The project SHALL run entirely in userspace.
Root privileges SHALL NOT be required during normal operation.
Binding privileged ports such as 443 MAY be accomplished using:
CAP_NET_BIND_SERVICE
or an external supervisor/socket mechanism.
The daemon SHALL NOT require:
CAP_NET_ADMIN
CAP_SYS_ADMIN
for normal operation.
4. Operating Roles
The project has two runtime roles:
client
server
They SHOULD initially be exposed through one executable:
project client --config client.toml
project server --config server.toml
The final project name may replace project.
5. Client Data Path
The client listens on a configured local UDP address.
Default example:
127.0.0.1:51821
An arbitrary local application sends UDP datagrams to this socket.
The client:
- receives the datagram;
- associates it with the configured tunnel;
- passes it unchanged to the active transport;
- transports it to the server;
- receives return datagrams;
- sends them to the originating local UDP peer.
The application payload MUST remain opaque.
6. Local Client Peer Handling
Although v1 represents one configured tunnel, the local UDP listener MUST track the local source peer.
For example:
127.0.0.1:49218 -> 127.0.0.1:51821
The most recently active local peer becomes the return destination.
For v1, one active local peer per tunnel is sufficient.
If packets arrive concurrently from a different local peer, the implementation SHOULD:
- update the active peer;
- emit a warning/debug event;
- continue operation.
This avoids coupling the software to a fixed local source port.
Future multi-flow support MAY replace this behavior.
7. Server Data Path
The server MUST support multiple clients concurrently.
Every authenticated client transport session maps to its own backend UDP socket.
Example:
Client A
│
▼
Session A
│
UDP socket :41001
│
├─────────────► backend :51820
Client B
│
▼
Session B
│
UDP socket :41002
│
├─────────────► backend :51820
Client C
│
▼
Session C
│
UDP socket :41003
│
└─────────────► backend :51820
Each backend socket provides natural response demultiplexing.
The server MUST NOT inspect the backend protocol in order to determine response ownership.
8. Backend Target
The server configuration defines one UDP backend destination for v1.
Example:
[backend]
target = "127.0.0.1:51820"
Every authenticated client receives an independent UDP socket connected to this backend.
Future versions MAY support client-specific backend targets.
That feature is explicitly out of scope for v1.
9. Transport Architecture
The core operates on logical datagrams.
A transport session SHALL conceptually expose:
pub trait TransportSession {
async fn send_datagram(&self, data: Bytes) -> Result<()>;
async fn recv_datagram(&self) -> Result<Bytes>;
async fn close(&self) -> Result<()>;
}
Connection establishment SHALL be separated from established-session behavior.
Conceptually:
pub trait TransportConnector {
async fn connect(
&self,
endpoint: &ServerEndpoint,
) -> Result<Box<dyn TransportSession>>;
}
The final implementation MAY use associated types, enums, channels, or trait objects depending on ergonomics and performance.
The architectural boundary is more important than the exact Rust syntax.
10. Supported Transports
The planned v1 transport order is:
1. direct UDP
2. QUIC DATAGRAM
3. HTTP/3 CONNECT-UDP
4. TLS/TCP framed fallback
All four SHALL share the same logical session API.
11. Direct UDP Transport
Direct UDP is the lowest-overhead transport.
It provides:
- native datagram behavior;
- minimal encapsulation;
- easiest debugging;
- lowest latency;
- baseline performance measurements.
It also provides the least firewall traversal capability.
Direct UDP MUST therefore have its own lightweight session and authentication protocol.
12. QUIC Transport
QUIC SHALL be the primary encapsulated datagram transport.
Application packets SHALL use QUIC DATAGRAM support rather than QUIC streams.
Desired semantics:
application datagram
│
▼
QUIC DATAGRAM
│
▼
QUIC
│
▼
UDP
QUIC provides:
- TLS 1.3;
- server authentication;
- encrypted transport metadata;
- connection identity;
- connection state;
- congestion control;
- transport-level integrity;
- migration primitives;
- keepalive support.
The QUIC connection itself constitutes the transport session.
No redundant project session identifier SHALL be placed in every QUIC data packet.
13. HTTP/3 Transport
HTTP/3 SHALL use legitimate HTTP/3 semantics.
Preferred architecture:
UDP payload
│
▼
CONNECT-UDP
│
▼
HTTP Datagram
│
▼
HTTP/3
│
▼
QUIC
The implementation SHOULD follow the standardized MASQUE CONNECT-UDP architecture rather than inventing an HTTP-shaped custom transport.
The purpose of this transport is firewall compatibility, not sophisticated fingerprint impersonation.
14. TLS/TCP Fallback
Some restrictive networks may block UDP completely.
The final fallback SHALL therefore operate over TLS/TCP.
Datagrams MUST be explicitly framed because TCP has no message boundaries.
Frame format:
+----------------+----------------------+
| payload length | UDP payload |
| u16 BE | variable |
+----------------+----------------------+
Maximum payload:
65535 bytes
In practice the project SHALL enforce a much lower configurable transport maximum.
The fallback MUST preserve original datagram boundaries after reassembly.
TCP mode SHALL be considered a compatibility mechanism rather than a performance-optimal transport because head-of-line blocking is unavoidable.
15. Automatic Transport Selection
Default client configuration:
[transport]
mode = "auto"
Automatic mode SHALL attempt transports in the following order:
direct_udp
↓ failure
quic
↓ failure
http3
↓ failure
tls_tcp
The first working transport is selected.
The selected transport remains active until:
- the connection fails;
- the daemon is restarted;
- the user explicitly requests reconnection;
- configuration is reloaded requiring reconnection.
After failure, the client SHALL begin transport probing again.
16. Probe Behavior
Default values:
[transport.auto]
probe_timeout_ms = 1500
retries = 2
A transport is considered unavailable if connection establishment or authentication fails within the configured limit.
Authentication rejection MUST be distinguished from connectivity failure.
A rejected credential MUST NOT cause automatic fallback through every transport indefinitely.
Instead:
authentication rejected
↓
stop probing
↓
report configuration/authentication error
This prevents invalid credentials from producing unnecessary connection traffic.
17. Transport Preference Override
Users MAY explicitly select a transport:
[transport]
mode = "quic"
Valid initial values:
auto
direct_udp
quic
http3
tls_tcp
When a specific transport is configured, automatic fallback SHALL be disabled unless separately enabled in a future version.
18. Direct UDP Protocol
Direct UDP requires a small project-specific wire protocol.
Protocol version:
1
All integer fields SHALL use network byte order.
All packet parsers MUST reject malformed or truncated messages before allocating large buffers.
19. Direct UDP Packet Types
Initial packet types:
0x01 HELLO
0x02 ACCEPT
0x03 REJECT
0x10 DATA
0x11 KEEPALIVE
0x12 CLOSE
Unknown packet types MUST be ignored or rejected according to protocol context.
They MUST NOT cause daemon termination.
20. Direct UDP Common Header
All direct transport messages use:
+---------+---------+---------+---------+
| version | type | flags | hdr_len |
| u8 | u8 | u8 | u8 |
+---------+---------+---------+---------+
| |
| session_id |
| u64 |
| |
+-----------------------------------+
Total common header:
12 bytes
Fields:
version
protocol version
type
packet type
flags
type-specific flags
hdr_len
complete header length in bytes
session_id
zero during initial HELLO
assigned by server after authentication
A 64-bit session identifier is selected for v1.
It is sufficiently large when generated randomly and checked for collision.
21. Session Identifier Generation
Session IDs MUST:
- be generated by a cryptographically secure random number generator;
- never use sequential counters;
- never be predictable;
- never use zero;
- be collision-checked against active sessions.
If a collision occurs, the server generates another identifier.
Session IDs are identifiers, not secrets.
Authentication MUST NOT rely solely on knowledge of the session ID.
22. Client Identity
Each configured client has:
client_id
shared_secret
Example server configuration:
[[auth.clients]]
id = "laptop"
secret = "base64-or-hex-secret"
[[auth.clients]]
id = "server-a"
secret = "base64-or-hex-secret"
Client IDs are not confidential.
Secrets MUST have at least 256 bits of entropy.
A project CLI SHOULD eventually provide:
project generate-secret
23. Direct UDP HELLO
The HELLO packet contains:
common header
client_id_length u8
client_id bytes
timestamp u64
client_nonce 16 bytes
authenticator 32 bytes
session_id SHALL be zero.
The authenticator is:
HMAC-SHA256(
client_secret,
protocol_version
|| client_id
|| timestamp
|| client_nonce
)
The exact serialization used for the HMAC input MUST be canonical and documented in the protocol implementation.
24. Replay Protection
The server MUST protect HELLO authentication against replay.
A HELLO is accepted only if:
abs(server_time - client_timestamp) <= allowed_clock_skew
Default:
60 seconds
The server SHALL also retain recently accepted client nonces for the replay window.
A repeated:
client_id + client_nonce
combination MUST be rejected.
Nonce replay state MAY expire after:
2 × allowed_clock_skew
25. Direct UDP ACCEPT
After successful authentication, the server creates a session and responds:
type ACCEPT
session_id generated u64
server_nonce 16 bytes
idle_timeout u32 seconds
max_datagram u16
authenticator 32 bytes
The server authenticator SHALL be:
HMAC-SHA256(
client_secret,
session_id
|| client_nonce
|| server_nonce
|| idle_timeout
|| max_datagram
)
This proves that the ACCEPT originated from a party possessing the configured secret.
26. Direct UDP REJECT
REJECT SHALL contain a small reason code.
Example codes:
0x01 unsupported_version
0x02 unknown_client
0x03 authentication_failed
0x04 replay_detected
0x05 server_busy
0x06 rate_limited
Detailed authentication information MUST NOT be returned.
For example, an unknown client and an invalid HMAC MAY both be externally represented as:
authentication_failed
if reducing information leakage is preferred.
27. Direct DATA Packets
DATA packets SHALL use:
common header
sequence u32
auth_tag 16 bytes
payload remaining bytes
Sequence numbers are included.
They provide:
- replay protection;
- diagnostics;
- loss measurement;
- duplicate detection;
- future protocol extensibility.
They MUST NOT be used to reorder packets.
The transport preserves the natural UDP arrival order.
28. Direct Session Authentication
Direct UDP DATA packets require integrity/authentication.
A per-session key SHALL be derived after the handshake.
Conceptually:
session_key = HKDF-SHA256(
client_secret,
client_nonce || server_nonce || session_id
)
The implementation SHALL use a well-established Rust cryptographic library.
The project SHALL NOT implement HMAC, HKDF, or MAC primitives manually.
29. DATA Authentication Tag
Each DATA packet SHALL carry a truncated authentication tag.
Recommended:
HMAC-SHA256 truncated to 128 bits
The MAC covers:
version
type
flags
session_id
sequence
payload
A 128-bit tag provides ample integrity protection while keeping packet overhead manageable.
Unauthenticated DATA packets MUST be discarded before forwarding.
30. Direct DATA Overhead
Approximate direct data overhead:
common header 12 bytes
sequence 4 bytes
auth tag 16 bytes
--------------------------
total 32 bytes
The design favors simple, robust parsing over saving a handful of bytes.
Future protocol versions MAY reduce overhead if profiling shows a meaningful benefit.
31. Sequence Numbers
Each direction SHALL maintain an independent 32-bit sequence counter.
Example:
client -> server counter
server -> client counter
Initial value SHALL be random.
Sequence numbers increment modulo 2^32.
Replay protection SHALL use a sliding receive window.
Recommended window:
1024 packets
Packets may arrive out of order within this window.
Already-seen sequence numbers MUST be dropped.
Packets older than the replay window MUST be dropped.
32. NAT Rebinding
Direct UDP sessions MUST tolerate client NAT rebinding.
The server SHALL NOT permanently bind session identity to:
source IP + source port
Instead:
- receive packet;
- parse session ID;
- verify packet authentication;
- verify sequence/replay protection;
- if valid, update the session's current peer address.
Therefore:
192.0.2.20:43871
may become:
192.0.2.20:51203
without renegotiating the session.
An attacker cannot redirect the session merely by spoofing its session ID because a valid packet MAC is also required.
33. KEEPALIVE
Direct transport KEEPALIVE messages SHALL use the same session authentication mechanism as DATA messages.
They carry:
common header
sequence
auth_tag
with no application payload.
Default interval:
15 seconds
Keepalive MAY be disabled.
Keepalives count as session activity.
34. CLOSE
CLOSE SHALL be authenticated.
The receiver SHOULD immediately release the associated session.
Loss of a CLOSE packet is harmless because idle timeout remains authoritative.
35. Session Timeout
Default idle timeout:
120 seconds
A session is active when an authenticated:
DATA
KEEPALIVE
packet is received.
Expired sessions SHALL:
- close their backend socket;
- release memory;
- remove replay state;
- increment expiration metrics.
36. QUIC and HTTP/3 Authentication
TLS authenticates the server but does not necessarily authenticate the client.
QUIC and HTTP/3 transports SHALL therefore use the same logical:
client_id
shared_secret
credentials as direct mode.
Authentication occurs once after transport establishment.
It SHALL use a challenge-response protocol based on HMAC rather than directly sending the shared secret.
Conceptually:
server -> random challenge
client -> client_id
client_nonce
HMAC(secret, challenge || client_nonce || client_id)
server -> authenticated
The exact representation MAY use:
- a QUIC control stream;
- an HTTP request;
- a dedicated initial protocol message.
Application DATA MUST NOT be accepted before authentication completes.
37. TLS/TCP Authentication
TLS/TCP SHALL use the same challenge-response client authentication mechanism.
This gives all transports one conceptual authentication model:
server authenticated by TLS where applicable
client authenticated by project shared secret
38. Future Authentication
The architecture SHOULD permit later introduction of:
mTLS
public-key client identity
short-lived credentials
external authentication
without altering the core datagram API.
These are not required for v1.
39. DoS and Resource Protection
The server MUST assume that its public listeners receive hostile traffic.
It SHALL implement:
maximum active sessions
maximum sessions per source IP
handshake timeout
authentication rate limiting
maximum authentication attempts per source
maximum datagram size
idle session timeout
maximum pending handshakes
Defaults SHOULD be conservative.
Example:
[limits]
max_sessions = 1024
max_sessions_per_ip = 32
max_pending_handshakes = 256
handshake_timeout_ms = 3000
Limits MUST be configurable.
40. Allocation Policy
Expensive resources MUST NOT be allocated before authentication where avoidable.
In particular, the server SHOULD NOT open a backend UDP socket until client authentication succeeds.
Desired order:
receive handshake
↓
cheap validation
↓
authentication
↓
rate-limit check
↓
session allocation
↓
backend socket creation
41. MTU and Datagram Size
The project SHALL treat transport datagram size as a first-class capability.
Every active transport session exposes:
max_datagram_size
The core SHALL reject application datagrams larger than this value.
Initial behavior:
oversized datagram
↓
drop
↓
increment counter
↓
debug/warn log
The project SHALL NOT implement application-layer fragmentation in v1.
42. Recommended Initial Datagram Limit
A conservative default application payload limit SHOULD begin around:
1200 bytes
and later be refined using transport/path information.
Users MAY override this value.
The default MUST favor reliability over maximum packet size.
43. Configuration Format
Configuration SHALL use TOML.
Parsing stack:
serde
toml
Configuration MUST be validated before network listeners start.
Invalid configuration SHALL result in a clear fatal error.
44. Minimal Client Configuration
[client]
server = "vpn.example.com"
id = "laptop"
secret_file = "/etc/project/client.secret"
[local]
listen = "127.0.0.1:51821"
[transport]
mode = "auto"
The server hostname is static in v1.
DNS resolution SHALL use the system resolver.
45. Minimal Server Configuration
[server]
listen = "0.0.0.0"
[backend]
target = "127.0.0.1:51820"
[tls]
certificate = "/etc/letsencrypt/live/vpn.example.com/fullchain.pem"
private_key = "/etc/letsencrypt/live/vpn.example.com/privkey.pem"
[[auth.clients]]
id = "laptop"
secret_file = "/etc/project/clients/laptop.secret"
46. Advanced Client Configuration
[client]
server = "vpn.example.com"
id = "laptop"
secret_file = "/etc/project/client.secret"
[local]
listen = "127.0.0.1:51821"
[transport]
mode = "auto"
[transport.auto]
order = [
"direct_udp",
"quic",
"http3",
"tls_tcp",
]
probe_timeout_ms = 1500
retries = 2
[transport.direct_udp]
port = 51830
keepalive_secs = 15
[transport.quic]
port = 443
keepalive_secs = 15
[transport.http3]
port = 443
path = "/tunnel"
[transport.tls_tcp]
port = 443
[network]
max_datagram_size = 1200
[logging]
level = "info"
47. Advanced Server Configuration
[server]
listen = "0.0.0.0"
[backend]
target = "127.0.0.1:51820"
[transports.direct_udp]
enabled = true
port = 51830
[transports.quic]
enabled = true
port = 443
[transports.http3]
enabled = true
port = 443
path = "/tunnel"
[transports.tls_tcp]
enabled = true
port = 443
[tls]
certificate = "/etc/letsencrypt/live/vpn.example.com/fullchain.pem"
private_key = "/etc/letsencrypt/live/vpn.example.com/privkey.pem"
[session]
idle_timeout_secs = 120
[limits]
max_sessions = 1024
max_sessions_per_ip = 32
max_pending_handshakes = 256
handshake_timeout_ms = 3000
[[auth.clients]]
id = "laptop"
secret_file = "/etc/project/clients/laptop.secret"
[[auth.clients]]
id = "desktop"
secret_file = "/etc/project/clients/desktop.secret"
[logging]
level = "info"
48. Secret Storage
Inline secrets MAY be accepted for testing but SHOULD generate a warning.
Preferred configuration:
secret_file = "/path/to/secret"
Secret files SHOULD require restrictive permissions.
Recommended:
0600
Secrets MUST NOT appear in logs.
49. TLS Certificates
TLS-capable transports SHALL use normal X.509 server certificates.
Expected provisioning includes:
Certbot
acme.sh
Caddy
other ACME tooling
The project does not implement ACME itself in v1.
Clients SHALL perform normal hostname verification against the configured static hostname.
Certificate verification MUST NOT be disabled by default.
50. Certificate Reload
Automatic certificate reload is desirable but optional for the first prototype.
The architecture SHOULD allow certificates to be reloaded without rebuilding transport logic.
51. Session Model
Core session representation:
struct Session {
id: SessionId,
client_id: ClientId,
transport: TransportKind,
backend: UdpSocket,
created_at: Instant,
last_activity: Instant,
stats: SessionStats,
}
Transport-specific information SHOULD remain outside the generic session where possible.
52. Datagram Representation
Initial representation:
struct Datagram {
payload: Bytes,
}
Do not add unnecessary metadata until required.
Potential future form:
struct Datagram {
payload: Bytes,
received_at: Instant,
flow_id: Option<FlowId>,
}
53. Client State Machine
STARTING
│
▼
RESOLVING
│
▼
PROBING
│
├─ direct_udp
├─ quic
├─ http3
└─ tls_tcp
│
▼
AUTHENTICATING
│
▼
CONNECTED
│
├──────── traffic
│
└──────── failure
│
▼
RECONNECTING
│
▼
PROBING
A transport failure MUST NOT terminate the daemon.
54. Backoff
Repeated connection failures SHALL use exponential backoff with jitter.
Example bounds:
minimum 250 ms
maximum 30 s
Successful connection resets the backoff.
This avoids aggressive reconnect loops during outages.
55. Server Session Lifecycle
HANDSHAKE
│
▼
AUTHENTICATING
│
├── reject
│
▼
ACTIVE
│
├── client close
├── transport loss
├── timeout
└── server shutdown
│
▼
CLOSED
Resources SHALL be released deterministically.
56. Keepalive Strategy
Keepalive behavior is transport-specific.
direct_udp
authenticated protocol KEEPALIVE
quic
QUIC keepalive
http3
underlying QUIC keepalive
tls_tcp
transport heartbeat or TCP state
Keepalive defaults SHOULD preserve common NAT mappings without producing unnecessary traffic.
57. Observability
At minimum the client SHALL expose:
state
server hostname
resolved remote address
selected transport
connection duration
RTT where available
bytes sent
bytes received
datagrams sent
datagrams received
datagrams dropped
oversized datagrams
authentication failures
reconnections
max datagram size
The server SHALL additionally expose:
active sessions
sessions by transport
sessions by client ID
handshake attempts
authentication rejections
rate-limited attempts
expired sessions
backend errors
58. Logging
Use:
tracing
tracing-subscriber
Logs SHOULD contain structured fields such as:
client_id
session_id
transport
remote_addr
event
Secrets and application payloads MUST NEVER be logged by default.
Packet payload logging SHOULD not exist in normal release builds.
59. Logging Levels
Suggested semantics:
ERROR
unrecoverable subsystem failure
WARN
transport failure
malformed traffic
rejected config behavior
INFO
startup
successful connection
selected transport
authenticated session
reconnect
shutdown
DEBUG
probing details
MTU decisions
session lifecycle
TRACE
low-level protocol state
60. Local Control Interface
The daemon SHOULD expose a Unix domain control socket.
Example:
$XDG_RUNTIME_DIR/project.sock
The control protocol MAY initially use newline-delimited JSON.
Expected commands:
status
stats
reconnect
reload
shutdown
The control socket MUST use filesystem permissions to prevent unauthorized local control.
61. CLI
Initial commands:
project client --config FILE
project server --config FILE
project check-config --config FILE
project generate-secret
Later:
project status
project stats
project reconnect
project reload
62. Graceful Shutdown
On SIGTERM or SIGINT:
- stop accepting new sessions;
- notify transports where appropriate;
- stop local ingestion;
- close backend sockets;
- flush useful final metrics/logs;
- terminate cleanly.
Shutdown MUST have a bounded timeout.
63. Configuration Reload
Full hot reload is not mandatory for the first milestone.
Architecture SHOULD nonetheless separate:
parsed configuration
runtime state
A future reload MUST be capable of updating:
- clients;
- limits;
- log level;
- certificate paths;
without requiring architectural redesign.
64. Future Multiple Tunnel Support
V1 exposes one client tunnel.
Internally, configuration SHOULD already use:
struct TunnelConfig
rather than embedding tunnel fields directly everywhere.
Initial:
struct ClientConfig {
tunnel: TunnelConfig,
}
Future:
struct ClientConfig {
tunnels: Vec<TunnelConfig>,
}
Future TOML:
[[tunnels]]
name = "home"
[[tunnels]]
name = "work"
No v1 UI or runtime behavior is required for multiple tunnels.
65. Rust Technical Stack
Recommended language:
Rust
Recommended runtime:
Tokio
Recommended major libraries:
tokio
quinn
rustls
h3
h3-quinn
bytes
serde
toml
clap
tracing
tracing-subscriber
thiserror
anyhow
socket2
hmac
sha2
hkdf
rand
subtle
Potential optional libraries:
secrecy
zeroize
Exact versions SHALL be selected at implementation time.
66. Error Handling
Library/core modules SHOULD use typed errors with:
thiserror
Binary/CLI boundaries MAY use:
anyhow
Recoverable network failure MUST NOT be represented as an unrecoverable process error.
67. Constant-Time Authentication
Authentication tag comparison MUST use constant-time equality.
Do not compare authentication data with ordinary:
a == b
unless the cryptographic library explicitly guarantees appropriate behavior.
Use primitives supplied by the selected crypto implementation or a constant-time helper library.
68. Crypto Policy
The project SHALL NOT:
- invent encryption algorithms;
- implement SHA manually;
- implement HMAC manually;
- implement HKDF manually;
- implement TLS manually.
All cryptographic operations SHALL use maintained, reviewed Rust libraries.
The direct transport payload itself does not require encryption because the encapsulated application may already be encrypted.
Direct transport DOES require integrity and authentication.
Users requiring outer confidentiality SHALL use QUIC, HTTP/3, or TLS/TCP.
69. Core Module Structure
Initial source tree SHOULD remain simple:
src/
├── main.rs
├── cli.rs
├── config.rs
├── datagram.rs
├── session.rs
├── stats.rs
├── client/
│ ├── mod.rs
│ ├── listener.rs
│ └── supervisor.rs
├── server/
│ ├── mod.rs
│ ├── session.rs
│ ├── backend.rs
│ └── limits.rs
├── protocol/
│ ├── mod.rs
│ ├── direct.rs
│ ├── auth.rs
│ └── replay.rs
└── transport/
├── mod.rs
├── direct_udp.rs
├── quic.rs
├── http3.rs
└── tls_tcp.rs
A Cargo workspace SHOULD NOT be introduced until module boundaries have stabilized.
70. Core Dependency Direction
Desired dependency structure:
configuration
│
▼
core
┌────────┴────────┐
│ │
client server
│ │
└──── transport ──┘
│
┌─────────┼──────────┐
▼ ▼ ▼
UDP QUIC HTTP/3
│
etc.
The core MUST NOT contain scattered transport type checks.
Avoid:
if transport == Transport::Quic {
...
}
unless behavior genuinely belongs at the orchestration layer.
71. Performance Model
The fast path SHOULD minimize allocations.
Preferred packet representation:
Bytes
BytesMut
Buffers SHOULD be reused where practical.
The implementation SHOULD avoid:
- copying payloads unnecessarily;
- global mutexes on every packet;
- per-packet heap allocation where easily avoidable;
- synchronous filesystem operations in packet paths.
Correctness takes priority over premature micro-optimization.
72. Concurrency Model
Tokio tasks SHALL be used around logical session operations.
A server session will approximately contain:
transport receive ─────► backend UDP send
transport send ◄─────── backend UDP receive
These may be implemented using:
- paired tasks;
tokio::select!;- channels;
- a session supervisor.
The simplest implementation that preserves cancellation and ownership clarity SHOULD be preferred.
73. Cancellation
All long-lived tasks MUST support deterministic cancellation.
Recommended primitives include:
CancellationToken
broadcast/watch channels
task ownership through supervisors
Detached tasks that cannot be shut down SHOULD be avoided.
74. Server Multi-Client Requirements
The server MUST:
- accept concurrent clients;
- isolate session errors;
- prevent one client from crashing another session;
- enforce per-client limits;
- maintain independent backend sockets;
- clean up dead sessions;
- maintain aggregate and per-session stats.
One malformed client packet MUST NOT terminate a listener.
75. Direct UDP Listener Model
The direct listener may use one public UDP socket.
Incoming packets are dispatched by:
packet type
session ID
Initial HELLO packets use:
session_id = 0
Authenticated DATA packets identify their session through the assigned random ID.
76. QUIC Listener Model
One QUIC endpoint accepts many connections.
Each authenticated QUIC connection becomes one server session.
QUIC connection state acts as transport identity.
Application packets arrive through QUIC DATAGRAM frames.
77. HTTP/3 Listener Model
HTTP/3 operates over the QUIC listener architecture.
Each successful authenticated CONNECT-UDP exchange maps to one backend session.
Where feasible, QUIC and HTTP/3 MAY share certificate/configuration infrastructure.
78. TCP Listener Model
Each accepted TLS/TCP connection becomes one potential session.
No backend socket is created until project authentication succeeds.
After authentication, framed datagrams are transported bidirectionally.
79. Protocol Versioning
Direct wire protocol packets contain:
version = 1
Breaking protocol changes SHALL increment the version.
Unknown versions MUST result in clean rejection.
Configuration schema and project binary version are independent from transport wire protocol version.
80. Metrics and Benchmarking
The project SHOULD eventually include benchmarks for:
datagrams/sec
throughput
latency overhead
memory/session
CPU usage
direct UDP overhead
QUIC overhead
HTTP/3 overhead
TCP fallback overhead
Performance tests SHOULD compare the tunnel against direct UDP baseline.
81. Testing Strategy
Unit tests SHALL cover:
packet serialization
packet parsing
malformed packets
HMAC verification
session key derivation
sequence windows
replay detection
configuration validation
timeout logic
Integration tests SHALL cover:
client ↔ server direct UDP
multiple simultaneous clients
NAT-like source port change
authentication rejection
server restart
client reconnect
backend unavailable
oversized datagram
session expiration
Later tests SHALL cover each transport.
82. Fuzzing
The direct UDP protocol parser is a good fuzzing target.
The parser SHOULD be written so arbitrary byte strings can be fed into it independently of network state.
Fuzz targets SHOULD include:
direct packet parser
TCP frame parser
authentication message parser
configuration parser where appropriate
83. Development Milestone 1
Goal:
generic authenticated UDP relay
Implement only:
client local UDP listener
direct UDP transport
server listener
authentication handshake
session creation
backend UDP sockets
DATA packet authentication
replay protection
keepalive
session expiry
multiple simultaneous clients
TOML configuration
structured logs
basic statistics
No QUIC yet.
This validates the entire core architecture.
84. Development Milestone 2
Add QUIC.
Requirements:
TLS certificate loading
hostname validation
QUIC connection establishment
project client authentication
QUIC DATAGRAM send/receive
keepalive
MTU reporting
reconnect
multi-client server handling
No core datagram API redesign should be necessary.
If substantial core redesign is required, the transport abstraction should be reconsidered before continuing.
85. Development Milestone 3
Implement:
transport = "auto"
Automatic order:
direct_udp
quic
Include:
- probing;
- timeouts;
- retries;
- exponential reconnect backoff;
- status reporting.
86. Development Milestone 4
Implement:
HTTP/3 CONNECT-UDP
using the existing QUIC infrastructure where practical.
Add:
HTTP/3 listener
CONNECT request handling
authentication
HTTP datagrams
backend session mapping
87. Development Milestone 5
Implement TLS/TCP fallback.
Add:
TLS listener
framed packet parser
project authentication
bidirectional datagram reconstruction
automatic fallback integration
88. Development Milestone 6
Operational polish:
Unix control socket
status command
stats command
reconnect command
config validation command
systemd units
CAP_NET_BIND_SERVICE documentation
certificate reload
configuration reload
benchmarks
fuzzing
installation documentation
89. Definition of v1 Completion
Version 1 is considered complete when:
- multiple clients can concurrently reach one UDP backend;
- no WireGuard-specific logic exists;
- direct UDP works reliably;
- QUIC DATAGRAM works reliably;
- HTTP/3 CONNECT-UDP works;
- TLS/TCP fallback works;
- automatic transport selection works;
- authentication is enforced on every public transport;
- direct UDP includes replay and integrity protection;
- NAT rebinding works for direct UDP;
- sessions expire and clean up correctly;
- the server survives malformed and unauthenticated traffic;
- the daemon runs unprivileged except for optional privileged-port capability;
- TOML configuration is validated clearly;
- meaningful status and statistics are available;
- reconnects occur automatically;
- automated integration tests cover multi-client operation.
90. Explicit Non-Goals for v1
The following SHALL remain out of scope:
WireGuard configuration
TUN/TAP interfaces
routing management
firewall management
multi-hop routing
browser fingerprint imitation
traffic shaping for censorship evasion
packet-size mimicry
TLS fingerprint spoofing
dynamic server discovery
server clustering
automatic roaming between servers
GUI
Windows
macOS
multiple simultaneous client tunnels
application-layer fragmentation
custom encryption
These MAY be explored after the core transport system is mature.
91. Architectural Summary
The system can be summarized as:
CLIENT
Application
│
│ UDP
▼
Local listener
│
▼
Tunnel/session core
│
▼
Transport abstraction
│
├── Direct UDP
├── QUIC DATAGRAM
├── HTTP/3 CONNECT-UDP
└── TLS/TCP
│
════════════════ Internet ════════════════
│
▼
Transport listener
│
▼
Authenticated server session
│
▼
Dedicated backend UDP socket
│
▼
Configured UDP backend
The core sees only:
send datagram
receive datagram
session opened
session closed
transport capabilities
It does not care whether the external representation is:
custom direct UDP packet
QUIC connection
HTTP/3 CONNECT-UDP flow
TLS/TCP stream
92. Final Design Rule
The project SHALL always preserve this separation:
application protocol
≠
tunnel session
≠
transport
≠
authentication
The encapsulated application remains opaque.
Sessions provide logical client isolation.
Transports determine how datagrams cross the network.
Authentication determines who may create sessions.
Keeping those four responsibilities independent is the central architectural rule of the project.