15 KiB
olcRTC Project Map
This is a developer map for finding the useful parts of the project quickly. It focuses on code ownership, runtime flow, extension points, and areas that are worth deeper work.
One-Sentence Model
olcRTC is an encrypted TCP-over-WebRTC tunnel: the client exposes a local SOCKS5 listener, the server dials requested TCP targets, and both sides carry the smux byte stream through a selected WebRTC carrier and transport.
Runtime Stack
YAML config
-> cmd/olcrtc
-> internal/config
-> internal/app/session
-> internal/server or internal/client
-> internal/link/direct
-> internal/transport/{datachannel,vp8channel,seichannel,videochannel}
-> internal/carrier/builtin
-> internal/auth/<provider> + internal/engine/<engine>
-> external service SFU / signaling
Tunnel data path:
local app
-> client SOCKS5
-> smux stream
-> muxconn AEAD encrypt
-> link.Send
-> transport encoding
-> carrier/engine
-> SFU/service
-> peer engine/carrier
-> transport decoding
-> muxconn AEAD decrypt
-> smux stream
-> server TCP dial
-> target host
Entrypoints
| Path | Purpose |
|---|---|
cmd/olcrtc/main.go |
Main CLI. Accepts one YAML file, applies auth and transport defaults, starts srv, cnc, or gen. |
cmd/olcrtc-cgo/main.go |
Small c-shared entrypoint for desktop/native consumers. |
pkg/olcrtc |
Embeddable lower-level API that returns a net.Conn-like handle over an engine data path. |
pkg/olcrtc/tunnel |
Embeddable server-side tunnel API with auth and traffic hooks. |
mobile/mobile.go |
gomobile API for Android clients, including VPN socket protection. |
script/srv.sh, script/cnc.sh |
Interactive shell launchers that generate YAML and run/build the app. |
Dockerfile, script/docker/* |
Container build and server entrypoint/healthcheck. |
Config And Session Layer
internal/config owns YAML parsing and file-backed secret loading.
Important fields:
| YAML | Runtime field | Notes |
|---|---|---|
mode |
session.Config.Mode |
srv, cnc, or gen. |
auth.provider |
Auth |
jitsi, telemost, jazz, wbstream, or none. |
room.id |
RoomID |
Carrier-specific room reference. |
crypto.key / crypto.key_file |
KeyHex |
Shared 32-byte key encoded as 64 hex chars. |
net.transport |
Transport |
datachannel, vp8channel, seichannel, or videochannel. |
net.dns |
DNSServer |
Resolver used by server-side target dials and provider HTTP where wired. |
socks.* |
SOCKS fields | Client listener and optional server egress proxy. |
engine.* |
direct engine fields | Used only with auth.provider: none. |
liveness.* |
control liveness | Ping/pong interval, timeout, and missed-pong threshold. |
internal/app/session is the main router:
- Registers built-ins via
RegisterDefaults. - Applies auth defaults: auth provider decides engine and default service URL.
- Applies transport defaults: documented defaults for
vp8,sei, andvideo. - Validates mode, auth, link, transport, room, key, DNS, transport options, and SOCKS listener safety.
- Runs
server.Run,client.Run, orGen.
Server Side
internal/server accepts encrypted smux sessions from the peer and proxies
each smux stream to a TCP target.
Core pieces:
| Symbol | Role |
|---|---|
server.Run |
Creates cipher, link, smux server, and serve loop. |
bringUpLink |
Builds link.Link, wires reconnect callbacks, connects carrier. |
installSession / reinstallSession |
Creates or replaces muxconn + smux.Session. |
acceptHandshake |
First smux stream; runs handshake.Server. |
handleStream |
Reads connect JSON and dispatches a tunnel stream. |
dispatch |
Dials target, sends ready byte, copies both directions. |
AuthHook |
Embedders can authorize clients after CLIENT_HELLO. |
OnSessionOpen, OnSessionClose, OnTraffic |
Observability hooks. |
Server risk areas:
- Target dialing is powerful by design. Any real product wrapper should add
an
AuthHookand probably destination policy. defaultAuthHookadmits everyone who knows the room and key.- Reconnect rebuilds smux sessions; active streams are sacrificed.
Client Side
internal/client exposes a local SOCKS5 listener and opens one smux stream
per SOCKS CONNECT request.
Core pieces:
| Symbol | Role |
|---|---|
RunWithReady |
Starts link, opens smux client, listens on local SOCKS. |
openControlStream |
First smux stream; runs handshake.Client. |
handleSocks5 |
SOCKS method negotiation and CONNECT parsing. |
sendConnectRequest |
Sends server-side target JSON and waits for ready byte. |
handleReconnect |
Rebuilds smux and control stream after carrier reconnect. |
resolveDeviceID |
Optional persistent client identity for hooks. |
Client risk areas:
- A non-loopback SOCKS listener must require
socks.userandsocks.pass. - SOCKS credentials are simple static credentials, not a full account system.
- Existing streams do not survive reconnect; new SOCKS connections can recover.
Wire Protocol Above WebRTC
internal/muxconn adapts link.Link to io.ReadWriteCloser.
- Every smux write is encrypted with
internal/crypto. - Every inbound link message is decrypted and appended to an internal byte buffer.
- Bad AEAD frames are dropped.
CanSendprovides backpressure before encrypting and sending.
internal/crypto uses XChaCha20-Poly1305 with a random nonce prepended to
each ciphertext.
internal/handshake runs on the first smux stream:
CLIENT_HELLO { version, device_id, claims }
SERVER_WELCOME { version, session_id }
or
SERVER_REJECT { version, reason }
The handshake has a 64 KiB frame cap and a default 15 second timeout.
After handshake, internal/control keeps that same encrypted smux stream open
and exchanges length-prefixed JSON control messages:
CONTROL_PING { version, seq, sent_unix_nano }
CONTROL_PONG { version, seq, sent_unix_nano }
Defaults are liveness.interval: 10s, liveness.timeout: 5s, and
liveness.failures: 3. Missed pongs mark the smux session unhealthy and
trigger a session rebuild/reconnect path.
Registries And Plugin Shape
The universal-carrier refactor centers on small registries:
| Registry | Package | Registers |
|---|---|---|
| Auth providers | internal/auth |
Service-specific credential and room creation flows. |
| Engines | internal/engine |
Wire-level SFU protocol implementations. |
| Carriers | internal/carrier |
Auth + engine adapters exposed as byte/video capability providers. |
| Transports | internal/transport |
Byte transport strategy over carrier primitives. |
| Links | internal/link |
Higher-level link abstraction; currently only direct. |
internal/carrier/builtin connects the auth and engine worlds:
carrier "wbstream" -> auth/wbstream -> engine/livekit
carrier "jazz" -> auth/salutejazz -> engine/salutejazz
carrier "telemost"-> auth/telemost -> engine/goolom
carrier "jitsi" -> auth/jitsi -> engine/jitsi
carrier "none" -> direct user-supplied engine/url/token
Auth Providers
| Provider | Engine | Room generation | Notes |
|---|---|---|---|
jitsi |
jitsi |
No | Parses host/room from a public or self-hosted Jitsi URL. No HTTP auth. |
telemost |
goolom |
No | Calls Telemost room-info flow and returns Goolom credentials. |
wbstream |
livekit |
Yes | Registers guest, optionally creates room, joins room, fetches LiveKit token. |
jazz / salutejazz |
salutejazz |
Yes | Creates or joins SaluteJazz room and returns room/password tuple. |
none |
chosen by config | No | Direct engine mode for downstream tools or self-hosted SFUs. |
Engines
Engines expose the low-level service/SFU protocol.
| Engine | Package | Byte stream | Video track | Main job |
|---|---|---|---|---|
livekit |
internal/engine/livekit |
Yes | Yes | LiveKit SDK room, data packets, local/remote tracks, reconnect with credential refresh. |
goolom |
internal/engine/goolom |
Yes | Yes | Yandex Telemost/Goolom signaling, split publisher/subscriber peer connections, telemetry/keepalive. |
jitsi |
internal/engine/jitsi |
Yes | Best effort | Jitsi MUC/Jingle/colibri-ws plus optional video track negotiation. |
salutejazz |
internal/engine/salutejazz |
Yes | Yes | SaluteJazz WebSocket signaling and split media peer connections. |
Engine work is where most provider breakage and reconnect complexity lives.
Transports
Transports decide how raw tunnel bytes are carried once the carrier provides either a byte stream or a video track.
| Transport | Primitive | Reliability model | Best fit | Notes |
|---|---|---|---|---|
datachannel |
Carrier byte stream | Native reliable ordered messages | Jitsi, direct engines, some Jazz cases | Simple pass-through with 12 KiB message cap. |
vp8channel |
VP8 video track | KCP over VP8-looking frames | WB Stream and Telemost-style video paths | Highest-performance video-path transport. Uses epochs and binding tokens to survive restarts/loopback. |
seichannel |
H264 SEI video track | Custom fragments + ACK/retry | WB Stream fallback | Carries data in SEI NAL units with fragmentation, CRC, ACK. |
videochannel |
Visual frames via ffmpeg | QR/tile frames + ACK/retry | Experimental/inspection-friendly path | Encodes visual payload frames, requires ffmpeg, supports QR and tile codecs. |
Transport work is where throughput, loss recovery, and adaptive tuning should happen.
Public/Embedding Surfaces
| Package | User |
|---|---|
pkg/olcrtc |
Go programs that want a net.Conn over a selected auth/engine. |
pkg/olcrtc/tunnel |
Go programs that want to embed the server-side tunnel with auth/traffic hooks. |
mobile |
Android app bindings. Wraps client mode, VPN socket protection, logging, simple health checks. |
cmd/olcrtc-cgo |
Native desktop/client integrations using c-shared Go export. |
These surfaces are important if the CLI becomes only one frontend among many.
Tests
The project has broad unit coverage:
- Config/session validation and defaults.
- Auth provider HTTP flows with test servers.
- Engine helper logic and reconnect paths.
- SOCKS parsing, smux handshake, server dispatch.
- Crypto, muxconn, names, protect, logging.
- Transport frame codecs, ACK paths, KCP loopback, ffmpeg helpers.
- Memory-backed E2E tunnel tests and optional real-provider E2E matrix.
Useful commands:
go test -count=1 ./...
go test -race -count=1 ./cmd/olcrtc ./internal/app/session ./internal/config ./internal/engine/livekit
go test -race -count=1 -v ./internal/e2e
E2E_CARRIERS=wbstream E2E_TRANSPORTS=vp8channel mage e2e
go build -trimpath -o build/olcrtc ./cmd/olcrtc
High-Value Coding Areas
1. Supervisor And Multi-Profile Failover
The first supervisor layer exists in internal/supervisor: the CLI can run a
prioritized list of carrier/transport profiles and move to the next profile
when the active one fails or ends.
mode: srv
link: direct
crypto:
key_file: ./olcrtc.key
net:
dns: "1.1.1.1:53"
profiles:
- name: wb-vp8
auth:
provider: wbstream
room:
id: WB_ROOM_ID
net:
transport: vp8channel
- name: jitsi-dc
auth:
provider: jitsi
room:
id: https://meet.example.org/olcrtc-room
net:
transport: datachannel
failover:
retry_delay: 2s
max_cycles: 0
Implemented:
- Config schema for
profiles[]. - Ordered supervisor loop.
failover.retry_delay.failover.max_cycles.- Profile start/end logs.
Still valuable:
- Health scoring per profile.
- Control-stream coordination before switching.
- Stream draining and migration instead of dropping active smux streams.
- Shared status output for the active profile and failover history.
Likely files:
internal/config/config.gointernal/app/session/session.gointernal/supervisorinternal/serverinternal/clientdocs/configuration.mdinternal/e2e/tunnel_test.go
2. Transport Telemetry And Adaptive Tuning
Add metrics from transport to link/session:
- Send queue depth.
- ACK latency.
- Retries.
- Reconnect count.
- Dropped/decrypt-failed frames.
- KCP RTT/loss where available.
Then make vp8.batch_size, sei.fragment_size, ACK timeout, and pacing
adaptive instead of static YAML knobs.
3. Control Stream Protocol
The first smux stream now carries control ping/pong after handshake. It is still the natural place for:
- Server policy updates.
- Graceful reconnect notifications.
- Drain/start markers for failover.
- Per-session stats.
Likely files:
internal/controlinternal/serverinternal/client
4. Destination Policy And Real Auth
The tunnel can dial arbitrary server-side TCP targets. A production wrapper
should use AuthHook and enforce:
- Allowed destination CIDRs/domains/ports.
- Per-device or per-plan policy.
- Session expiration.
- Traffic accounting limits.
- Sanitized rejection reasons.
This mostly belongs in pkg/olcrtc/tunnel and internal/server.
5. Provider Hardening
Provider APIs can drift. Worth adding:
- Better typed errors from auth providers.
- Provider health probes.
- Fixture-based contract tests for API response changes.
- Per-provider rate/backoff policy.
- Safer secret/log redaction.
Likely files:
internal/auth/*internal/engine/*internal/carrier/builtin
6. Codebase Hygiene
Some public-facing text and comments are not suitable for a serious external
project. Cleaning that up would improve maintainability and downstream trust.
The most obvious targets are top-level docs and a large hostile block comment
in internal/transport/vp8channel/transport.go.
Where To Look First
| Goal | Start here |
|---|---|
| Change YAML schema | internal/config/config.go, cmd/olcrtc/main.go, docs examples. |
| Change validation/defaults | internal/app/session/session.go. |
| Add a new auth provider | internal/auth, then register in internal/carrier/builtin/register.go. |
| Add a new SFU protocol | internal/engine, then connect through auth/carrier. |
| Add a new byte transport | internal/transport, then register in session.RegisterDefaults. |
| Add link behavior above transports | internal/link; currently only direct. |
| Improve SOCKS behavior | internal/client. |
| Improve server target dialing or policy | internal/server, pkg/olcrtc/tunnel. |
| Improve reconnect | Engines first, then internal/client and internal/server smux rebuild behavior. |
| Improve Android app integration | mobile, internal/protect, client.RunWithReady. |
Mental Model For Big Changes
Prefer to keep the layer boundaries:
- Auth creates credentials; it should not know transport details.
- Engine speaks service/SFU protocol; it should not know SOCKS or smux.
- Carrier adapts auth+engine into byte/video capabilities.
- Transport turns byte/video capabilities into reliable-ish tunnel bytes.
- Link is policy above transport.
- Client/server own SOCKS, smux, handshake, target dialing, and session hooks.
If a change crosses more than two layers, it probably deserves a new orchestrator package instead of pushing more state into an engine or transport.