New outbound: turnrelay (tunnel UDP through WebRTC TURN relays, e.g. VK Calls, for censorship circumvention)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 38.2k
- Forks
- 4.6k
- Avg merge
- 19d 15h
- Merged PRs (30d)
- 1
Description
Summary
I would like to propose a UDP-only outbound, turnrelay, that carries datagrams through WebRTC TURN relays disguised as call media, for the existing wireguard endpoint to use as detour. It exists as a GPL-3.0 Go library and, as of now, as a ready external sing-box module (github.com/romanrublev/turnrelay, the outbound lives in singbox/). The open question for you is whether it is also worth landing in-tree behind a with_turnrelay build tag, and whether you have objections to the outbound shape.
Motivation
Russian networks increasingly enforce whitelists where only a few domestic services are reachable. VK Calls relies on TURN relays that are inside those whitelists. Several open source tools already tunnel traffic through them, but all are standalone VPN apps: no domain or geosite routing, and on iOS they collide with the one-VPN limit when combined with a routing client such as Happ. In anton48/vk-turn-proxy-ios#25 the iOS client maintainer suggested the ideal solution would be adding this functionality as a transport inside clients like V2Box/Happ, some of which can run a sing-box core, while noting it is a larger undertaking. This does that.
Proposed design
turnrelay is a UDP-only outbound with pluggable credential providers: vk (anonymous join of a VK call link) and static (fixed credentials for any TURN relay, e.g. self-hosted coturn). It opens N TURN allocations, obfuscates each as a media stream and presents them as one datagram pipe: Write stripes one datagram over the pool, Read returns one merged datagram. TCP returns "only udp is supported".
L4 comes from the wireguard endpoint through detour: transport/wireguard/client_bind.go already dials UDP on the detour and uses the conn as the WireGuard bind, so nothing changes in the endpoint. Rule-sets decide which connections enter it:
{
"outbounds": [
{ "type": "turnrelay", "tag": "relay",
"provider": "vk", "call_link": "https://vk.ru/call/join/<hash>",
"server": "203.0.113.5", "server_port": 56004,
"connections": 30, "mode": "srtp" },
{ "type": "direct", "tag": "direct" }
],
"endpoints": [
{ "type": "wireguard", "tag": "wg", "detour": "relay",
"address": ["10.8.0.2/32"], "private_key": "<client wg key>",
"peers": [{ "address": "203.0.113.5", "port": 56004,
"public_key": "<server wg key>",
"allowed_ips": ["0.0.0.0/0", "::/0"] }] }
],
"route": { "rules": [
{ "rule_set": "geosite-ru", "outbound": "direct" },
{ "rule_set": "geoip-ru", "outbound": "direct" } ],
"final": "wg" }
}
Config schema
| Option | Type | Meaning |
|---|---|---|
provider |
vk | static |
credential source, default vk |
call_link / call_links |
string(s) | VK call link(s); about 20 connections per link |
turn_server |
host:port | static: the relay; vk: optional override |
turn_username, turn_password |
string | static only |
server, server_port |
address, port | the VPS running the relay-side server |
connections |
int | allocations, default 30, max 60 |
mode |
srtp | wrap | dtls |
obfuscation, default srtp |
password |
string | wrap only: HKDF input for the envelope key |
udp |
bool | transport to the relay, default true (false selects TCP) |
captcha |
auto | fail | wait |
on a VK captcha, default auto |
DialerOptions |
for the sockets towards the VK API and the relay (bind_interface, detour), passed to the library as one DialContext hook |
Transport details
The wire protocol (credential chain, TURN usage, byte layouts, control frames, multiplexing, failure handling) is documented in the repository. Modes: srtp is real DTLS-SRTP (RFC 5764, RTP payload type 100), the default; wrap is an RTP-framed ChaCha20-Poly1305 envelope around plain DTLS; dtls is legacy plain DTLS, deprecated.
Security notes
srtpanddtlsuse ephemeral ECDHE; thewrapenvelope uses a static key shared with the server (no forward secrecy at that layer), but the DTLS session inside it and the WireGuard session inside that both do.- WireGuard remains the layer that authenticates both ends.
- VK sees the anonymous call participants (roughly one per 10 connections) and the bytes relayed per allocation, not destinations.
- Plaintext modes are not offered; the ban footprint grows with the connection count (default 30, hard maximum 60).
Licensing
GPL-3.0, compatible with sing-box. It already depends on github.com/sagernet/sing for the N.Dialer interface and M.Socksaddr.
Ask
The external module (option b) exists today, so this is not blocking anyone's use. The questions for maintainers are:
- Would you accept an in-tree
turnrelayoutbound behind awith_turnrelaybuild tag (likewith_wireguard):protocol/turnrelay,option/turnrelay.go,include/turnrelay.go? - Any objections to the outbound shape itself, i.e. a UDP-only outbound used as the
detourof thewireguardendpoint?
If in-tree is welcome, I will open a PR against that shape.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the external module's singbox/ implementation and transport/wireguard/client_bind.go, then read the referenced transport and configuration details in the turnrelay repository. Done means maintainers have decided whether an in-tree turnrelay outbound and its proposed UDP-only wireguard detour shape are acceptable, after which an implementation PR can be opened if invited.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- networking
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100