███-сервер, маскирующий трафик под ██████████ через ████ █████ серверы. Одна команда — и сервер готов к работе.
- Быстрый старт
- Как это работает
- Режимы работы
- Hot-update скриптов
- Конфигурация
- Примеры docker-compose.yml
- Подключение клиента
- Мониторинг
- Управление сервером
- Обновление
- Сборка из исходников
- Устранение проблем
- Docker Engine 20.10+
- Docker Compose v2
- Открытый UDP-порт (по умолчанию
9000)
1. Создайте директорию на сервере
mkdir call-vpn && cd call-vpn2. Создайте docker-compose.yml
services:
scripts-updater:
image: ghcr.io/fokir/vk-call-proxy-scripts-updater:${IMAGE_TAG:-latest}
command:
- --scripts-interval=${SCRIPTS_INTERVAL:-1h}
environment:
- CALLVPN_SCRIPTS_DIR=/var/lib/callvpn/scripts
volumes:
- scripts:/var/lib/callvpn/scripts
restart: unless-stopped
server:
image: ghcr.io/fokir/vk-call-proxy:${IMAGE_TAG:-latest}
ports:
- "${LISTEN_PORT:-9000}:9000/udp"
environment:
- VPN_TOKEN=${VPN_TOKEN:-}
- VK_CALL_LINK=${VK_CALL_LINK:-}
- ALSO_DIRECT=${ALSO_DIRECT:-}
- PROXY_URL=${PROXY_URL:-}
- TURN_CONNS=${TURN_CONNS:-4}
- SIREN_SLACK_WEBHOOK=${SIREN_SLACK_WEBHOOK:-}
- CAPTCHA_ENDPOINT=http://captcha:8090
- CALLVPN_SCRIPTS_DIR=/var/lib/callvpn/scripts
- CALLVPN_SCRIPTS_URL=
volumes:
- scripts:/var/lib/callvpn/scripts:ro
depends_on:
- captcha
- scripts-updater
restart: unless-stopped
deploy:
resources:
limits:
memory: ${MEMORY_LIMIT:-256M}
cpus: "${CPU_LIMIT:-1.0}"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
captcha:
image: ghcr.io/fokir/vk-call-proxy-captcha:${IMAGE_TAG:-latest}
environment:
- CALLVPN_SCRIPTS_DIR=/var/lib/callvpn/scripts
- CALLVPN_SCRIPTS_URL=
volumes:
- scripts:/var/lib/callvpn/scripts:ro
depends_on:
- scripts-updater
restart: unless-stopped
deploy:
resources:
limits:
memory: ${CAPTCHA_MEMORY_LIMIT:-512M}
cpus: "${CAPTCHA_CPU_LIMIT:-1.0}"
volumes:
scripts:3. Создайте .env и запустите
IMAGE_TAG=latest
LISTEN_PORT=9000
VPN_TOKEN=
SIREN_SLACK_WEBHOOK=
MEMORY_LIMIT=256M
CPU_LIMIT=1.0docker compose up -dСервер запустится на порту 9000/udp в direct mode и готов принимать подключения.
Для relay-to-relay mode добавьте
VK_CALL_LINK— см. раздел Режимы работы.
██ ████ █████
┌─────────────┐
Клиент │ │ Сервер (ваш VPS)
┌──────────┐ TURN │ ┌───────┐ │ UDP ┌──────────────┐ ┌──────────┐
│ Браузер ├───────────►│ │ Relay ├──┼────────────►│ :9000/udp ├───────►│ Интернет │
│ / Прокси │ (TCP/UDP) │ └───────┘ │ DTLS │ DTLS + MUX │ TCP │ │
└──────────┘ │ │ └──────────────┘ └──────────┘
└─────────────┘
Принцип работы:
- Клиент получает TURN-credentials от ██ API
- Создаёт N параллельных ████ █████-соединений
- Поверх каждого устанавливает DTLS 1.2 шифрование
- Мультиплексор объединяет все соединения в единый туннель
- Сервер принимает DTLS-подключения, группирует по session ID, проксирует трафик в интернет
Для внешнего наблюдателя трафик выглядит как обычный ██████████.
██ может требовать прохождения капчи при анонимном подключении к звонку. Docker Compose автоматически запускает captcha-сервис — отдельный контейнер с headless Chrome, который решает капчу за ~10-15 секунд без участия человека.
Сервер → error_code 14 (Captcha needed)
↓
HTTP POST /solve → captcha-сервис
↓
Headless Chrome: открыть страницу → кликнуть чекбокс → перехватить success_token
↓
Сервер: повторить запрос с success_token → успех
Captcha-сервис использует ~200-500 MB RAM на время решения (Chrome), после чего процесс завершается. Решение кешируется на время жизни сессии.
Сервер поддерживает три режима. Режим определяется автоматически по наличию переменных VK_CALL_LINK и ALSO_DIRECT.
Сервер слушает на UDP-порту, клиент подключается через ████ █████.
Требования: открытый UDP-порт на сервере.
# .env
IMAGE_TAG=latest
LISTEN_PORT=9000
VPN_TOKEN=your-secret-tokenКлиент → ████ █████ (██) → Сервер:9000/UDP → Интернет
Клиент и сервер join'ят один ██████████. Обмен адресами идёт через ██ WebSocket signaling (зашифрован AES-256-GCM при наличии VPN_TOKEN). Трафик проходит через два ██ ████ █████.
Требования: ██ call link. Открытый порт не нужен.
Сервер одновременно слушает на UDP-порту и подключается к ██-звонку. Direct-клиенты подключаются через TURN → :9000/udp, relay-клиенты — через ██-инфраструктуру.
Требования: открытый UDP-порт + ██ call link.
# .env
IMAGE_TAG=latest
LISTEN_PORT=9000
VK_CALL_LINK=AbCdEf123456
VPN_TOKEN=your-secret-token
ALSO_DIRECT=1# .env
IMAGE_TAG=latest
VK_CALL_LINK=AbCdEf123456
VPN_TOKEN=your-secret-token
TURN_CONNS=4Клиент → TURN(клиент) ↔ TURN(сервер) → Интернет
██ signaling (WebSocket)
Как получить ██ call link:
- Откройте ██ → Мессенджер → любой диалог → кнопка «Звонок» → «Ссылка на звонок»
- Скопируйте ID из ссылки:
https://vk.com/call/join/AbCdEf123456→AbCdEf123456 - Передайте одну и ту же ссылку серверу (
VK_CALL_LINK) и клиенту (--link)
Порядок запуска:
- Запустите сервер — он подключится к ██-звонку и будет ждать клиента
- Запустите клиент с той же ссылкой — он подключится к звонку, обменяется адресами и установит туннель
Важно:
VPN_TOKENдолжен совпадать на клиенте и сервере. Он используется как для аутентификации, так и для шифрования signaling-обмена.
Параметры VK (API-версии, User-Agent, captcha-константы, stealth-JS) меняются чаще, чем выходят релизы. Чтобы не пересобирать образы при каждом таком изменении, в compose входит отдельный контейнер scripts-updater, который:
- Периодически (по умолчанию раз в час, флаг
--scripts-interval) скачиваетmanifest.jsonсraw.githubusercontent.com/Fokir/vk-call-proxy/master/hot-scripts/. - Проверяет Ed25519-подпись манифеста встроенным публичным ключом (вшит в бинарь через ldflags в CI).
- Сверяет SHA-256 каждого файла, скачивает изменённые.
- Атомарно записывает всё в named volume
scripts, подмонтированный кserverиcaptcharead-only. serverиcaptchaпо mtimemanifest.jsonвидят обновление и перечитывают конфиг/скрипты без рестарта.
Каталог volume: /var/lib/callvpn/scripts/ внутри контейнеров.
Env-переопределения (если нужно использовать свой источник):
| Переменная | Назначение |
|---|---|
CALLVPN_SCRIPTS_URL |
Альтернативный базовый URL вместо ldflags-дефолта |
CALLVPN_SCRIPTS_PUBKEY |
Альтернативный Ed25519 public key (base64) |
CALLVPN_SCRIPTS_DIR |
Путь к локальному кэшу |
SCRIPTS_INTERVAL |
Период проверки updater'а (формат Go duration: 30m, 2h) |
В readers (server, captcha) оставь CALLVPN_SCRIPTS_URL= (пустой) — тогда они не делают собственных HTTP-запросов и доверяют updater'у через shared volume.
Надёжность:
- Если удалённый манифест недоступен или подпись битая — updater пропускает обновление, клиенты продолжают работать на bundled-копии из образа.
- Если новая версия даёт 3 ошибки за 5 минут — автоматический откат на предыдущую и quarantine плохого manifest'а до следующего изменения.
min_client_versionв манифесте предотвращает применение скриптов, требующих более новый Go-код.
Все параметры задаются через файл .env рядом с docker-compose.yml.
| Переменная | По умолчанию | Описание |
|---|---|---|
IMAGE_TAG |
latest |
Версия Docker-образа. latest — последняя сборка, 1.0.0 — конкретная версия |
LISTEN_PORT |
9000 |
UDP-порт, открытый на хост-машине (только direct mode) |
VPN_TOKEN |
(пусто) | Токен аутентификации клиентов (рекомендуется) |
VK_CALL_LINK |
(пусто) | ID ссылки ██-звонка. Если задан — включается relay-to-relay mode |
ALSO_DIRECT |
(пусто) | 1 — также слушать на UDP-порту в relay mode (dual mode) |
TURN_CONNS |
4 |
Количество TURN-соединений (только relay mode) |
PROXY_URL |
(пусто) | Upstream-прокси для клиентского трафика (socks5://host:port или http://user:pass@host:port). VK/TURN — напрямую |
SIREN_SLACK_WEBHOOK |
(пусто) | URL Slack webhook для алертов мониторинга |
CAPTCHA_ENDPOINT |
http://captcha:8090 |
URL captcha-сервиса (auto-configured в docker-compose) |
CAPTCHA_MAX_CONCURRENT |
1 |
Макс. одновременных Chrome-инстансов для решения капчи |
MEMORY_LIMIT |
256M |
Лимит оперативной памяти контейнера сервера |
CPU_LIMIT |
1.0 |
Лимит CPU сервера (количество ядер) |
CAPTCHA_MEMORY_LIMIT |
512M |
Лимит RAM для captcha-сервиса (Chrome ~200MB) |
CAPTCHA_CPU_LIMIT |
1.0 |
Лимит CPU captcha-сервиса |
SCRIPTS_INTERVAL |
1h |
Частота проверки hot-scripts manifest (updater) |
CALLVPN_SCRIPTS_URL |
(ldflags) | Альтернативный URL источника hot-scripts |
CALLVPN_SCRIPTS_PUBKEY |
(ldflags) | Альтернативный Ed25519 public key |
CALLVPN_SCRIPTS_DIR |
/var/lib/callvpn/scripts |
Каталог shared volume в контейнере |
IMAGE_TAG=latest
LISTEN_PORT=9000IMAGE_TAG=latest
VK_CALL_LINK=AbCdEf123456
VPN_TOKEN=your-secret-token# Версия образа
IMAGE_TAG=latest
# Порт (UDP) — только для direct mode, должен быть открыт в firewall
LISTEN_PORT=9000
# Токен аутентификации (рекомендуется; в relay mode также шифрует signaling)
VPN_TOKEN=your-secret-token
# ██ call link — если задан, включается relay-to-relay mode
VK_CALL_LINK=
# Количество TURN-соединений (relay mode)
TURN_CONNS=4
# Slack алерты (опционально)
SIREN_SLACK_WEBHOOK=<your-slack-webhook-url>
# Ресурсы
MEMORY_LIMIT=512M
CPU_LIMIT=2.0Самый простой вариант, готовый к работе без изменений:
services:
server:
image: ghcr.io/fokir/vk-call-proxy:latest
ports:
- "9000:9000/udp"
restart: unless-stoppedРекомендуемый вариант с вынесением настроек в .env:
# docker-compose.yml
services:
server:
image: ghcr.io/fokir/vk-call-proxy:${IMAGE_TAG:-latest}
ports:
- "${LISTEN_PORT:-9000}:9000/udp"
environment:
- VPN_TOKEN=${VPN_TOKEN:-}
- VK_CALL_LINK=${VK_CALL_LINK:-}
- TURN_CONNS=${TURN_CONNS:-4}
- SIREN_SLACK_WEBHOOK=${SIREN_SLACK_WEBHOOK:-}
restart: unless-stopped
deploy:
resources:
limits:
memory: ${MEMORY_LIMIT:-256M}
cpus: "${CPU_LIMIT:-1.0}"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"# .env
IMAGE_TAG=latest
LISTEN_PORT=9000
VPN_TOKEN=your-secret-token
SIREN_SLACK_WEBHOOK=
MEMORY_LIMIT=256M
CPU_LIMIT=1.0Сервер подключается через ██-звонок. Открытый UDP-порт не нужен:
# .env
IMAGE_TAG=latest
VK_CALL_LINK=AbCdEf123456
VPN_TOKEN=your-secret-token
TURN_CONNS=4
MEMORY_LIMIT=256M
CPU_LIMIT=1.0docker compose up -d
portsв docker-compose.yml можно убрать — в relay mode сервер не слушает UDP.
Если порт 9000 занят или заблокирован — используйте другой:
LISTEN_PORT=51820Важно: клиенту при подключении нужно указать тот же порт:
--server=your-vps-ip:51820
IMAGE_TAG=latest
LISTEN_PORT=9000
SIREN_SLACK_WEBHOOK=<your-slack-webhook-url>Алерты отправляются при:
- Ошибках аутентификации TURN
- Потере пакетов
- Отключении клиентов
- Деградации туннеля
Для сервера с большим количеством клиентов:
IMAGE_TAG=latest
LISTEN_PORT=9000
MEMORY_LIMIT=1G
CPU_LIMIT=4.0Для стабильности лучше зафиксировать версию вместо latest:
IMAGE_TAG=1.0.0Доступные теги:
latest— последняя сборка из ветки main (может быть нестабильной)1.0.0,1.0— конкретная версия (рекомендуется для production)sha-abc1234— привязка к конкретному коммиту
После запуска сервера, клиенты подключаются так:
./client \
--link=<██-call-link-id> \
--server=<your-vps-ip>:9000 \
--token=your-secret-token./client \
--link=<██-call-link-id> \
--token=your-secret-tokenБез
--serverклиент автоматически входит в relay-to-relay mode. Используйте тот же--link, что и вVK_CALL_LINKна сервере.
| Флаг | Описание |
|---|---|
--link |
ID ссылки ██-звонка (обязательный) |
--server |
Адрес VPS с портом. Пустой = relay-to-relay mode |
--token |
Токен аутентификации (должен совпадать с сервером) |
--n |
Количество параллельных TURN-соединений (по умолчанию 4) |
--tcp |
Использовать TCP для TURN (по умолчанию true) |
--socks5-port |
Локальный порт SOCKS5 прокси (по умолчанию 1080) |
--http-port |
Локальный порт HTTP прокси (по умолчанию 8080) |
После запуска клиента настройте браузер/систему на прокси:
- SOCKS5:
127.0.0.1:1080 - HTTP/HTTPS:
127.0.0.1:8080
Мобильные приложения используют gomobile API с теми же параметрами. Если ServerAddr пустой в TunnelConfig — используется relay-to-relay mode.
# Последние 100 строк
docker compose logs --tail=100
# Следить в реальном времени
docker compose logs -f
# Только ошибки
docker compose logs | grep -i error# Состояние контейнера
docker compose ps
# Потребление ресурсов
docker compose statsДля настройки:
- Создайте Incoming Webhook в Slack
- Укажите URL в
.env:SIREN_SLACK_WEBHOOK=https://hooks.slack.com/services/...
- Перезапустите сервер:
docker compose up -d
# Запуск
docker compose up -d
# Остановка
docker compose down
# Перезапуск
docker compose restart
# Просмотр логов
docker compose logs -f# Скачать новый образ и перезапустить
docker compose pull
docker compose up -dЕсли используете фиксированную версию — обновите IMAGE_TAG в .env:
IMAGE_TAG=1.1.0Затем:
docker compose up -dЕсли нужно собрать образ из исходного кода (разработка/тестирование):
git clone https://github.com/Fokir/vk-call-proxy.git
cd vk-call-proxy/deploy/docker
docker compose -f docker-compose.build.yml up --build# Проверьте логи
docker compose logs
# Убедитесь что порт свободен
ss -ulnp | grep 9000-
Проверьте firewall — порт
9000/udpдолжен быть открыт:# Ubuntu/Debian (ufw) sudo ufw allow 9000/udp # CentOS/RHEL (firewalld) sudo firewall-cmd --permanent --add-port=9000/udp sudo firewall-cmd --reload # Или через iptables sudo iptables -A INPUT -p udp --dport 9000 -j ACCEPT
-
Проверьте доступность с клиентской машины:
nc -vzu <your-vps-ip> 9000
-
Убедитесь что клиент указывает правильный
--server=<ip>:<port>
Увеличьте лимит памяти в .env:
MEMORY_LIMIT=512M| Параметр | Минимум | Рекомендуется |
|---|---|---|
| RAM | 128 MB | 256 MB |
| CPU | 1 vCPU | 1+ vCPU |
| Сеть | 10 Mbit/s | 100 Mbit/s |
| ОС | Любая с Docker | Ubuntu 22.04+ |
| Порт | 1 UDP | 1 UDP |
- Образ работает от непривилегированного пользователя (
nonroot) - Используется distroless runtime (минимальная поверхность атаки)
- Трафик шифруется DTLS 1.2 (AES-128-GCM)
- Самоподписанные сертификаты генерируются автоматически при запуске
- Hot-update скрипты защищены Ed25519 подписью + SHA-256 каждого файла; неверная подпись или хеш → обновление отклоняется, остаётся работать предыдущая версия