9.0 KiB
olcRTC - overview
olcRTC (OpenLibreCommunity RTC) is an encrypted TCP-over-WebRTC tunnel. It disguises traffic as ordinary participation in a WebRTC/SFU service: Jitsi Meet, Yandex Telemost or WbStream.
Project: github.com/openlibrecommunity/olcrtc
License: WTFPL
Status: Beta
Why it is needed
In scenarios where direct access to an arbitrary VPS / IP is blocked, traffic has to be carried through services that are already reachable for the user. To an outside observer the connection looks like an ordinary WebRTC call to an allowed service IP, and the payload inside is additionally encrypted with the shared crypto.key.
Important: always check that the video call service you need is on the allow lists. If it is not there, use another one. A list of all allow-listed services will be published soon.
Basic scheme:
app
-> SOCKS5 127.0.0.1:8808
-> olcrtc cnc
-> WebRTC/SFU service
-> olcrtc srv
-> internet
How it works
Client mode cnc starts a local SOCKS5. A browser, curl, sing-box, olcbox or another app connects to it as to an ordinary proxy.
Server mode srv connects to the same room/session, accepts the encrypted smux stream and opens TCP connections to the target addresses on its own behalf.
Inside the tunnel:
SOCKS CONNECT
-> smux stream
-> XChaCha20-Poly1305
-> transport
-> engine
-> WebRTC/SFU
Modes
| Mode | Purpose |
|---|---|
srv |
server side, accepts tunnel streams and does TCP dial to targets |
cnc |
client side, listens on a local SOCKS5 |
gen |
creates Room IDs for providers that can create rooms |
The CLI takes a single YAML file:
olcrtc server.yaml
olcrtc client.yaml
Auth Providers
auth.provider selects the service and the way credentials are obtained.
| Provider | Engine | Comment |
|---|---|---|
jitsi |
jitsi |
Jitsi room URL (meet.small-dm.ru, meet1.arbitr.ru or meet.handyweb.org), no separate registration |
telemost |
goolom |
credentials via Yandex Telemost API, separate registration |
wbstream |
livekit |
credentials via WbBStream API, separate registration |
none |
set in engine.name |
direct engine mode with engine.url and engine.token, separate registration |
The term carrier still appears in the internal API and logs as a historical name for the chosen auth/provider path. In YAML the current field is auth.provider.
Engines
engine is the low-level protocol of a concrete SFU/signaling:
| Engine | Package | Capabilities |
|---|---|---|
livekit |
internal/engine/livekit |
data packets/video tracks/LiveKit SDK |
goolom |
internal/engine/goolom |
Telemost/Goolom signaling, publisher/subscriber PeerConnection |
jitsi |
internal/engine/jitsi |
Jitsi MUC/Jingle/colibri-ws, datachannel/best-effort video |
internal/engine/builtin binds auth.provider to the proper engine. There is no separate internal/carrier package in the current project.
Transports
net.transport defines how tunnel bytes are placed into a WebRTC primitive.
| Transport | How it carries data | Main scenario |
|---|---|---|
datachannel |
native byte/data path of the engine | simplest and fastest path, stable with Jitsi |
vp8channel |
KCP over VP8-like video frames | main video path for WB Stream and Telemost |
seichannel |
payload in H264 SEI NAL units, ACK/retry | fallback for WB Stream / Jitsi |
videochannel |
QR/tile frames via ffmpeg, ACK/retry | experimental visual transport |
Recommended start: jitsi + datachannel. Alternative: wbstream + vp8channel.
Encryption and handshake
internal/crypto uses XChaCha20-Poly1305. The shared key is set as 64 hex characters:
openssl rand -hex 32
smux runs on top of the encrypted muxconn. The first smux stream is occupied by the handshake and the control protocol:
CLIENT_HELLO -> SERVER_WELCOME
CONTROL_PING <-> CONTROL_PONG
If the control pong does not arrive several times in a row, the runtime rebuilds the smux session or hands control to the failover supervisor.
YAML
Minimal server:
mode: srv
auth:
provider: jitsi
room:
# Use the Jitsi server that works in your network:
# https://meet.small-dm.ru/ROOM or https://meet1.arbitr.ru/ROOM or https://meet.handyweb.org/ROOM
id: "https://meet.small-dm.ru/REPLACE_ME_WITH_ROOM_ID"
crypto:
key: "REPLACE_ME_WITH_64_HEX_CHARS"
net:
transport: datachannel
dns: "8.8.8.8:53"
data: data
Minimal client:
mode: cnc
auth:
provider: jitsi
room:
# Use the Jitsi server that works in your network:
# https://meet.small-dm.ru/ROOM or https://meet1.arbitr.ru/ROOM or https://meet.handyweb.org/ROOM
id: "https://meet.small-dm.ru/REPLACE_ME_WITH_ROOM_ID"
crypto:
key: "REPLACE_ME_WITH_64_HEX_CHARS"
net:
transport: datachannel
dns: "8.8.8.8:53"
socks:
host: "127.0.0.1"
port: 8808
data: data
More: configuration.md, settings.md.
Failover
profiles[] lets you run several configurations in order. For example, first wbstream + vp8channel, then jitsi + datachannel. Top-level fields act as defaults, a profile overrides only the parts it needs.
Active smux streams do not migrate when the profile changes. New connections can come up on the next profile.
Repository structure
| Path | What is inside |
|---|---|
cmd/olcrtc |
CLI entrypoint |
cmd/olcrtc-cgo |
c-shared entrypoint |
pkg/olcrtc |
embeddable client/engine API |
pkg/olcrtc/tunnel |
embeddable server tunnel API |
mobile |
gomobile bindings for Android |
internal/config |
YAML parsing, crypto.key_file |
internal/app/session |
defaults, validation, routing into srv/cnc/gen |
internal/auth |
provider-specific credential flows |
internal/engine |
SFU/signaling implementations |
internal/transport |
datachannel/vp8/sei/video transports |
internal/server |
server-side smux, handshake, TCP dial |
internal/client |
SOCKS5 listener, client-side smux |
internal/control |
liveness ping/pong |
internal/supervisor |
failover profiles |
docs |
documentation and YAML examples |
Build
go install github.com/magefile/mage@latest
mage build
mage cross
mage test
mage lint
mage mobile
Go version: 1.26+. videochannel requires ffmpeg; codec: tile requires a resolution of 1080x1080.
Public API
pkg/olcrtc returns a net.Conn-like object on top of auth/engine:
sess, err := olcrtc.New(ctx, olcrtc.Config{
Auth: "jitsi",
// Use meet.small-dm.ru, meet1.arbitr.ru or meet.handyweb.org - whichever works in your network
RoomID: "https://meet.small-dm.ru/myroom",
})
if err != nil {
return err
}
conn, err := sess.Dial(ctx)
pkg/olcrtc/tunnel embeds the server side and exposes hooks:
srv := tunnel.New(tunnel.Config{
Transport: "datachannel",
Carrier: "jitsi",
// Use meet.small-dm.ru, meet1.arbitr.ru or meet.handyweb.org - whichever works in your network
RoomURL: "https://meet.small-dm.ru/myroom",
KeyHex: "<64-char hex>",
DNSServer: "8.8.8.8:53",
})
err := srv.Run(ctx)
In this API the Carrier field is kept for compatibility with existing integrations; semantically it is the auth.provider name.
Mobile / Android
mobile/mobile.go provides a gomobile API:
SetProtectorfor Android VPNprotect(fd);SetTransport,SetDNS,SetVP8Options,SetLivenessOptions;Start,StartWithTransport,Stop;Check/ping helpers to check reachability.
By default the mobile client uses vp8channel; datachannel is also supported.
Tests
go test -count=1 ./...
mage test
mage e2e
Real-provider E2E is enabled via variables:
E2E_CARRIERS=wbstream E2E_TRANSPORTS= vp8channel mage e2e
Common problems
| Symptom | What to check |
|---|---|
key required or invalid key |
the same 64-character hex key on both sides |
| SOCKS5 not listening | mode: cnc, socks.host, socks.port, client logs |
| Jitsi does not connect without a second participant | server and client must be in the same room |
| WB Stream + datachannel does not work | guest flow has no canPublishData; use vp8channel, seichannel or videochannel |
seichannel ack timeout |
the provider throttles/does not route the video path; change transport/provider |
ffmpeg not found |
install ffmpeg or set ffmpeg: /path/to/ffmpeg |
