A research prototype exploring what happens when you encode arbitrary network traffic inside a WebRTC video stream hosted on a nationally whitelisted conferencing platform.
Several countries maintain a national whitelist — a set of domestic services whose traffic is guaranteed to pass through internet filtering infrastructure untouched. The question this project investigates:
If a video conferencing platform is on a national whitelist and uses DTLS-SRTP over TURN relays, can its media stream carry arbitrary data in a way that is transparent to packet inspection?
telepath is a headless, pure-Go prototype that answers this question in practice. It encodes TCP/UDP payloads inside VP8 video frames sent through a real conference session, then exposes a SOCKS5 proxy on the receiving end.
The entire implementation runs without a browser — no Electron, no WebView, no JS injection. The signaling and media stacks are handled directly in Go via the pion/webrtc library.
A Yandex Telemost conference uses an SFU (Selective Forwarding Unit) architecture with dual PeerConnections: one for publishing media (pub) and one for subscribing (sub). Both go through TURN relays at turn.webrtc.yandex.net. The TURN traffic is DTLS-SRTP encrypted — the relay sees only opaque UDP datagrams.
┌──────────────────────────────────────────────────────────────────┐
│ Side A (creator) Side B (joiner) │
│ │
│ TCP/UDP ← relay bridge SOCKS5 :1080 → apps │
│ │ │ │
│ VP8 encode ──→ pub PC sub PC ──→ VP8 decode │
│ │ │ │
│ DTLS-SRTP / TURN relay │
│ turn.webrtc.yandex.net │
└──────────────────────────────────────────────────────────────────┘
Standard VP8 frame headers have constrained first-byte values (keyframe: bit0=0; interframe: bit0=1, max tag byte 0xB1). The byte 0xFF never appears as a valid VP8 header, making it a safe out-of-band marker:
Data frame: [ 0xFF | 4-byte length (big-endian) | payload ]
Keepalive frame: [ valid VP8 interframe, 17 bytes, 25 fps ]
(keyframe every 60th frame to reset decoder state)
The SFU forwards the sample as-is inside DTLS-SRTP. No modification at the network layer occurs.
Multiple TCP/UDP connections share the single video stream via a framing protocol:
[ 4-byte total length | 4-byte connID | 1-byte msgType | payload ]
Message types: Connect, ConnectOK, ConnectErr, Data, Close, UDP, UDPReply.
telepath/
├── cmd/
│ ├── sniffer/ ← MITM HTTPS/WS proxy — captures platform signaling
│ ├── creator/ ← conference creator + relay (unrestricted side)
│ └── joiner/ ← conference joiner + SOCKS5 :1080 (restricted side)
└── internal/
├── telemost/ ← HTTP conference API + WebSocket signaling client
├── pion/ ← dual PeerConnection, ICE relay-only, VP8+Opus tracks
├── tunnel/ ← VP8 frame encoding/decoding, keepalive scheduler
└── relay/ ← TCP/UDP multiplexer + SOCKS5 server
git clone https://github.com/Konstant1nov1ch/telepath
cd telepath
make all # → bin/sniffer bin/creator bin/joiner
make linux # cross-compile linux/amd64 for remote VPSRequires Go 1.21+.
The platform uses a WebSocket signaling protocol that needs to be observed once. The sniffer is a local MITM proxy that records the exchange:
./bin/sniffer --port 8080 --output telemost-dump.jsonTrust the generated CA (macOS):
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain \
~/Library/Caches/suckrtunnel/ca.crtSet your browser proxy to http://localhost:8080, open the conference page, create and join a conference. The full WebSocket frame dump is written to telemost-dump.json.
./bin/creator
# prints a join link to stdoutFor VPS deployment:
make linux
scp bin/creator-linux-amd64 user@vps:/tmp/telepath-creator
ssh user@vps "/tmp/telepath-creator"./bin/joiner --link https://telemost.yandex.ru/j/XXXX
# SOCKS5 proxy starts at 127.0.0.1:1080Configure any application to use socks5://127.0.0.1:1080.
From preliminary experiments:
| Channel type | Observed throughput |
|---|---|
| WebRTC DataChannel | up to ~40 Mbps |
| VP8-encoded data | limited by video bitrate budget |
GoTurn probe (STUN/TURN reachability from Timeweb SPb VPS):
| Endpoint | Result |
|---|---|
stun.rtc.yandex.net:3478 UDP |
responds, RTT ~20 ms |
stun.rtc.yandex.net:3478 TCP |
responds, RTT ~39 ms |
turn.webrtc.yandex.net:3478 |
timeout without SFU-issued credentials (expected) |
TURN servers require credentials derived during signaling — they do not respond to unauthenticated allocation requests. This is consistent with standard TURN security practice and means the tunnel only works after a real conference session is established.
- Signaling accuracy:
internal/telemost/signaling.gois based on the expected message shape. Running the sniffer and updating the message structs is required before the headless flow works end-to-end. - Bandwidth ceiling: VP8 video bitrate is negotiated at session setup; high-throughput data may trigger SFU rate limiting.
- Session lifetime: It is unclear how long an idle (but keepalive-pinging) session remains open on the SFU side.
- Detectability: While the payload is opaque to DPI, connection metadata (TURN server IP, session duration, absence of audio energy) could in principle be used for statistical classification.
The most useful contribution right now is a real telemost-dump.json from a browser session. It resolves the only unknown: the exact signaling message format. Open a PR updating internal/telemost/signaling.go or attach the dump to an issue.
MIT — see LICENSE.
This is a research prototype. It is published for study and discussion of how video conferencing infrastructure interacts with network filtering. No warranty is provided.