V3 is deployed on both DebTik hosts. It replaces the v2 PSK-only handshake with Noise IKpsk2, pinned X25519 identities and ephemeral session keys, while keeping XChaCha20-Poly1305 for IP data. This is a custom protocol integration; the testing below is not an independent cryptographic audit or a security certification.
V3 requires the same random 32-byte PSK on both hosts, plus the other endpoint's
pinned static public key. Keys accept 64-character hex or base64 encoding
of 32 bytes. github.com/flynn/noise v1.1.0 implements the handshake pattern
Noise_IKpsk2_25519_ChaChaPoly_SHA256; the application does not implement its own
Diffie-Hellman or AEAD primitive.
The handshake prologue binds the AeroTUN v3 protocol name and the initiating 16-byte header. Encrypted handshake metadata carries the receiver index, MTU, and compression capability. Application IP traffic is never sent as 0-RTT data. The responder verifies the initiator's static identity against its configured peer before publishing a pending session. It activates that session only after receiving authenticated transport data, proving completion and PSK possession.
A domain-separated HMAC-SHA256 truncated to 16 bytes authenticates handshake packets before expensive DH work. A stateless source cookie binds the sender's IP/port and initiation identifier to a process-random secret and a 30-second time bucket; current and previous buckets are accepted. The cookie is itself carried in a PSK-authenticated request. This prevents moving recorded requests to a new source without knowing the PSK. New DH handshakes are limited to one per second per configured tunnel. State remains bounded; no unbounded source-address map is created for unauthenticated traffic.
After Noise completes, its two directional split keys are exported solely to HKDF-SHA256, with the handshake channel binding as salt and an AeroTUN v3 data label as context. Each direction derives a distinct 32-byte XChaCha key and a 16-byte nonce prefix. Noise CipherStates are not subsequently used for data.
Each data/control datagram has this authenticated 16-byte header:
| Field | Bytes |
|---|---|
| Magic | 2 |
| Protocol version | 1 |
| Message kind | 1 |
| Receiver session index | 4 |
| Counter | 8 |
The full header is AEAD additional data. The 24-byte nonce is the derived 16-byte prefix followed by the counter. An atomic counter covers control and data senders, rejects zero/wrap, and triggers renewal before exhaustion. The receiver checks authentication before committing replay state. A fixed 128-byte bitmap supports a 1024-packet replay/reorder window. Unknown sessions and malformed or oversized packets are rejected without creating a data session.
The client renews the handshake every 90 seconds; active traffic continues on the current keys while renewal runs. Previous receive keys have a 30-second grace period. Sessions expire after 180 seconds, pending sessions after five seconds. At most current, previous and pending session objects are retained. Go does not guarantee physical erasure of discarded heap objects, so process-memory compromise is outside any claim of secure key destruction.
V3 does not negotiate down to v2. V2 remains an explicitly selected legacy mode for coordinated rollback. These are separate wire protocols.
ReadBatch/WriteBatch, up to 16 packets; available packets flush without
waiting to fill a batch.Headers plus authentication tag shrink from 60 to 32 bytes. Including IPv4/UDP, the encapsulation overhead becomes 60 bytes instead of 88. The actual outer path later settled at MTU 1380, allowing an overlay MTU of 1320 and a UDP benchmark datagram of 1292 bytes. During an earlier phase the overlay was 1360; sizes are negotiated from route/link limits and peer advertisements.
Offloads remain disabled for the raw veth backend because it does not implement GSO segmentation or completion of partial checksums. Enabling flags without that implementation would reintroduce the original TCP failure. Native TUN is preferred when available. The local container denies TUN device access; changing that needs Proxmox host configuration outside this container's access. The ARM64 peer uses native TUN successfully.
V3 requires explicit allowed source prefixes for the authenticated peer. IPv4 and IPv6 source addresses are checked before injection into the host interface. The deployed pair currently allows only each other's overlay /32 address. To carry LAN traffic, administrators must add the intended LAN prefixes and corresponding routes/firewall policy; a configured peer key does not authorize arbitrary source addresses.
List/save API responses omit PSKs and private identity keys, including for admin
clients. has_psk/has_private_key indicate presence. An empty secret on edit
retains the stored value. Only full administrators can change/start/stop tunnels
or generate PSKs. Generated-key responses use Cache-Control: no-store.
Configuration and generated identity files are mode 0600. Existing management
credentials and listeners are preserved; strong authentication and HTTPS/trusted
management access are still required for appliance administration.
New v3 tunnels can be saved disabled to generate a local identity. Copy its public key to the other endpoint over a trusted channel, set allowed prefixes, then enable both sides. Static private keys are never returned by the UI/API.
For bootstrap on the owner host:
aerotun-keygen -out /root/new-aerotun-identity.json
The command creates the file once with mode 0600 and prints only the public key. The deployed identities were generated separately on their owner hosts and their public keys cross-pinned over the verified SSH channel. The existing PSK was kept.
Both full repository tests and go vet pass. API regression tests cover redaction,
privilege checks and editing without revealing/erasing a key. Protocol tests cover
pinned identities, incorrect PSKs, tampered counters/headers, reflection, replay,
reordering, restart, rekey under concurrent traffic, cookie source binding/expiry,
and in-place encryption. Fuzzing ran about 270,000 inputs per ten-second run
without a crash. Isolated v2/v3 namespace tests passed with race detection,
including TCP both directions, UDP, compression negotiation and constrained links.
A paired 1200-byte encrypt/decrypt microbenchmark on the local i5-4570T, using Go 1.26.10, measured approximately:
| Crypto path | Time | Allocated bytes | Allocations |
|---|---|---|---|
| v2 | 2.85–2.97 μs | 2608 | 4 |
| v3 | 2.23–2.30 μs | 0 | 0 |
These are crypto-path measurements, not Internet throughput claims. Network batch helpers and interface I/O have their own overhead. An isolated no-compression run delivered about 856/737 Mbit/s TCP for v3 versus 635/642 Mbit/s for v2; short runs vary with host load and are not a capacity guarantee.
Live application benchmark results after deployment:
| Test | Confirmed TX | RX | Reported loss |
|---|---|---|---|
| Remote Both / Auto, 10 s | 7.862 Mbit/s | 10.457 Mbit/s | 0.983% |
| Remote RX-only / Auto, 8 s | 0 | 10.098 Mbit/s | 1.162% |
| Local Both / Auto, 4 streams, 8 s | 7.536 Mbit/s | 10.831 Mbit/s | 0.160% |
| TCP Both, fixed 2 Mbit/s target | 1.835 Mbit/s | 1.966 Mbit/s | no application-reported loss |
Live telemetry showed both peers connected at MTU 1320 with no error. Automatic key renewal completed repeatedly on both hosts, with the handshake count advancing to four during validation. A transient rejected packet during the v2/v3 transition is retained in the remote authentication-failure counter; no replay drops were observed. Counts of policy/oversize drops can include startup link-local traffic and MTU adjustment, and are not equivalent to WAN packet loss.
govulncheck found no reachable vulnerable symbols or imported vulnerable packages.
It flagged the unused/unmaintained x/crypto/openpgp package at module level; this
application does not import that package. Builds use Go 1.26.10.
Builds, test logs, benchmark output, deployment state and redacted runtime state:
/root/.local/share/debtik-builds/aerotun-v3/.
Backups on both hosts:
/root/.local/share/debtik-backups/aerotun-v3-20261030T130621Z/.
They contain the prior v2 executable/configuration and route/link snapshots.
Rollback timers protected activation and were cancelled after validation. The
committed marker guards the backup rollback script against accidental execution.
Deliberate rollback must restore both peers' version and configuration together.
Private identity files in the backup directory remain owner-only.
For verification:
go test ./...
go test -race ./internal/aerotun ./internal/api
go vet ./...
go test -run '^$' -bench 'Benchmark.*ProtocolRoundTrip' -benchmem ./internal/aerotun
AEROTUN_INTEGRATION=1 go test -race -run TestVethBandwidthIntegration -v ./internal/aerotun
The integration harness needs root, namespace privileges, ip, tc, nsenter and iperf3. It creates disposable namespaces rather than changing production links.