All-in-one VK-call tunnel for MikroTik RouterOS - multi-stream DTLS proxy + WireGuard
142
All-in-one VK-call tunnel for MikroTik RouterOS: multi-stream DTLS proxy + WireGuard in ONE container. Traffic rides VK Calls' TURN relay (disguised as call media), terminates on the built-in WireGuard, which NATs out to the internet. No separate WG host, no external backend, no reverse proxy.
Pairs with a WireGuard-TURN Android client that supports Session-ID stream aggregation: multiple DTLS streams from one client are grouped by its UUID into ONE backend connection — more throughput and stability while the call still shows a single participant.
| Layer | Size |
|---|---|
| Compressed (Docker Hub) | ~20 MB |
| Unpacked (on device) | ~45 MB |
Multi-stage build — proxy server + userspace wireguard-go on Alpine, no Go toolchain in the final image. Multi-arch: amd64 / arm64 / armv7.
| Component | Role |
|---|---|
| DTLS multi-stream proxy | Listens for VK-relayed DTLS, decrypts, forwards to local WireGuard. Session-ID aggregation + Round-Robin. |
| WireGuard | The actual VPN that routes to the internet. Userspace wireguard-go (works in containers without the kernel module). |
| NAT | Masquerades the WG subnet out the container's egress. Set up automatically. |
If 56000 is taken by another container, change the port in ONE place — config.json in the mount:
{
"public_ip": "YOUR.VPS.IP",
"listen_port": 56001,
"wg_port": 51821,
"wg_dns": "1.1.1.1",
"stream_num": 4
}
listen_port — where the VK relay sends DTLS. Change this to avoid clashes; it's auto-written into the client config.wg_port — internal WireGuard port (not exposed; the client reaches WG through the tunnel)./interface/veth add name=veth-wg address=172.16.184.2/24 gateway=172.16.184.1
/ip/address add address=172.16.184.1/24 interface=veth-wg
/ip/firewall/nat/add chain=srcnat src-address=172.16.184.0/24 action=masquerade comment="turnable-wg out"
/ip/firewall/nat/add chain=dstnat protocol=udp dst-port=56001 action=dst-nat to-addresses=172.16.184.2 to-ports=56001 comment="turnable-wg listen"
Only your listen_port (default 56001) is forwarded in. The WG port stays internal — the client reaches WireGuard THROUGH the TURN tunnel, not directly.
/container/mounts/add name=vkwg_conf src=/usb1/turnable-wg-confs dst=/etc/vkturn
Holds config.json, plus auto-created .keys/ (WG server + client keypairs + PSK) and clients/client-wg.conf (the ready client config). Without this mount the keys regenerate on every restart and old client configs break.
/container/envs/add name=vkwg-env key=PUBLIC_IP value=your.vps.ip
/container/envs/add name=vkwg-env key=VKTURN_LISTEN_PORT value=56001
/container/envs/add name=vkwg-env key=WG_PORT value=51821
Config.json takes priority over env if both are set.
/container/add
remote-image=ulfrasark/turnable-wg:v1
interface=veth-wg
mounts=vkwg_conf
envlist=vkwg-env
root-dir=/usb1/turnable-wg
dns=8.8.8.8
start-on-boot=yes
restart-policy=on-failure
restart-interval=60s
logging=yes
RouterOS caches images by name:tag. When updating, use a NEW tag AND a NEW root-dir. WireGuard in a container needs the tun device and NET_ADMIN. If the platform blocks kernel WG, the built-in userspace wireguard-go handles it (slightly higher CPU).
/container/start [find interface=veth-wg]
/log print where topics~"container"
On startup the container prints the WireGuard client config. It's also saved to the mount:
/usb1/turnable-wg-confs/clients/client-wg.conf
The generated client-wg.conf is for a WireGuard-TURN Android client (with Session-ID aggregation), NOT for Xray-based clients. Before importing, fill in:
| Field | What to set |
|---|---|
| VKLink | Your VK call link (https://vk.com/call/join/...) |
| public_ip | Your VPS white IP (via config.json, else placeholder) |
| PeerType | proxy_v2 (Session-ID aggregation — this server supports it) |
| StreamNum | Parallel streams (4 is a good start) |
| UseUDP | Client-side only: TCP (off) is safer, UDP (on) is faster where allowed. Server is unchanged either way. |
phone: WG-TURN client (multi-stream DTLS) -> VK TURN relay -> turnable-wg :56001 -> decrypt -> local WireGuard -> NAT -> internet
The client briefly joins the VK call to get TURN credentials, then rides the allocation. Session-ID aggregation packs several DTLS streams under one call presence — more speed at the antifraud profile of a single participant.
Session-ID aggregation groups N streams under ONE call presence: you get the throughput of N streams while the call still shows a single participant. This beats raw per-allocation scaling, where each extra stream is another "participant" and raises the antifraud signal. Tune StreamNum to taste — more streams, more speed, still one participant.
This image packages and wires existing open-source work for RouterOS — all protocol credit goes to the upstream authors:
Use the Android client above — it's the one that speaks the Session-ID protocol this server implements.
Source and Dockerfile: coming soon
Content type
Image
Digest
sha256:a41375bb1…
Size
11.8 MB
Last updated
13 days ago
docker pull ulfrasark/turnable-wg:v1