Skip to content

Latest commit

 

History

History
660 lines (491 loc) · 23.2 KB

File metadata and controls

660 lines (491 loc) · 23.2 KB

Call-VPN Server — Docker Deployment

███-сервер, маскирующий трафик под ██████████ через ████ █████ серверы. Одна команда — и сервер готов к работе.


Оглавление


Быстрый старт

Требования

  • Docker Engine 20.10+
  • Docker Compose v2
  • Открытый UDP-порт (по умолчанию 9000)

Установка за 3 шага

1. Создайте директорию на сервере

mkdir call-vpn && cd call-vpn

2. Создайте 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.0
docker 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   │          │
 └──────────┘            │             │              └──────────────┘        └──────────┘
                         └─────────────┘

Принцип работы:

  1. Клиент получает TURN-credentials от ██ API
  2. Создаёт N параллельных ████ █████-соединений
  3. Поверх каждого устанавливает DTLS 1.2 шифрование
  4. Мультиплексор объединяет все соединения в единый туннель
  5. Сервер принимает DTLS-подключения, группирует по session ID, проксирует трафик в интернет

Для внешнего наблюдателя трафик выглядит как обычный ██████████.

Captcha-сервис

██ может требовать прохождения капчи при анонимном подключении к звонку. 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.

Direct mode (по умолчанию)

Сервер слушает на UDP-порту, клиент подключается через ████ █████.

Требования: открытый UDP-порт на сервере.

# .env
IMAGE_TAG=latest
LISTEN_PORT=9000
VPN_TOKEN=your-secret-token
Клиент → ████ █████ (██) → Сервер:9000/UDP → Интернет

Relay-to-relay mode

Клиент и сервер join'ят один ██████████. Обмен адресами идёт через ██ WebSocket signaling (зашифрован AES-256-GCM при наличии VPN_TOKEN). Трафик проходит через два ██ ████ █████.

Требования: ██ call link. Открытый порт не нужен.

Dual mode (direct + relay одновременно)

Сервер одновременно слушает на 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:

  1. Откройте ██ → Мессенджер → любой диалог → кнопка «Звонок» → «Ссылка на звонок»
  2. Скопируйте ID из ссылки: https://vk.com/call/join/AbCdEf123456AbCdEf123456
  3. Передайте одну и ту же ссылку серверу (VK_CALL_LINK) и клиенту (--link)

Порядок запуска:

  1. Запустите сервер — он подключится к ██-звонку и будет ждать клиента
  2. Запустите клиент с той же ссылкой — он подключится к звонку, обменяется адресами и установит туннель

Важно: VPN_TOKEN должен совпадать на клиенте и сервере. Он используется как для аутентификации, так и для шифрования signaling-обмена.


Hot-update скриптов

Параметры VK (API-версии, User-Agent, captcha-константы, stealth-JS) меняются чаще, чем выходят релизы. Чтобы не пересобирать образы при каждом таком изменении, в compose входит отдельный контейнер scripts-updater, который:

  1. Периодически (по умолчанию раз в час, флаг --scripts-interval) скачивает manifest.json с raw.githubusercontent.com/Fokir/vk-call-proxy/master/hot-scripts/.
  2. Проверяет Ed25519-подпись манифеста встроенным публичным ключом (вшит в бинарь через ldflags в CI).
  3. Сверяет SHA-256 каждого файла, скачивает изменённые.
  4. Атомарно записывает всё в named volume scripts, подмонтированный к server и captcha read-only.
  5. server и captcha по mtime manifest.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 в контейнере

Минимальный .env (direct mode)

IMAGE_TAG=latest
LISTEN_PORT=9000

Минимальный .env (relay-to-relay mode)

IMAGE_TAG=latest
VK_CALL_LINK=AbCdEf123456
VPN_TOKEN=your-secret-token

Полный .env

# Версия образа
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

Примеры docker-compose.yml

Базовый — минимальная конфигурация

Самый простой вариант, готовый к работе без изменений:

services:
  server:
    image: ghcr.io/fokir/vk-call-proxy:latest
    ports:
      - "9000:9000/udp"
    restart: unless-stopped

Стандартный — с .env файлом (direct mode)

Рекомендуемый вариант с вынесением настроек в .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

Relay-to-relay mode

Сервер подключается через ██-звонок. Открытый UDP-порт не нужен:

# .env
IMAGE_TAG=latest
VK_CALL_LINK=AbCdEf123456
VPN_TOKEN=your-secret-token
TURN_CONNS=4
MEMORY_LIMIT=256M
CPU_LIMIT=1.0
docker compose up -d

ports в docker-compose.yml можно убрать — в relay mode сервер не слушает UDP.

Нестандартный порт

Если порт 9000 занят или заблокирован — используйте другой:

LISTEN_PORT=51820

Важно: клиенту при подключении нужно указать тот же порт: --server=your-vps-ip:51820

С мониторингом в Slack

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

Фиксированная версия (production)

Для стабильности лучше зафиксировать версию вместо latest:

IMAGE_TAG=1.0.0

Доступные теги:

  • latest — последняя сборка из ветки main (может быть нестабильной)
  • 1.0.0, 1.0 — конкретная версия (рекомендуется для production)
  • sha-abc1234 — привязка к конкретному коммиту

Подключение клиента

После запуска сервера, клиенты подключаются так:

Desktop — direct mode

./client \
  --link=<██-call-link-id> \
  --server=<your-vps-ip>:9000 \
  --token=your-secret-token

Desktop — relay-to-relay mode

./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

Mobile (Android / iOS)

Мобильные приложения используют 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

Slack-алерты

Для настройки:

  1. Создайте Incoming Webhook в Slack
  2. Укажите URL в .env:
    SIREN_SLACK_WEBHOOK=https://hooks.slack.com/services/...
  3. Перезапустите сервер: 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

Клиент не подключается

  1. Проверьте 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
  2. Проверьте доступность с клиентской машины:

    nc -vzu <your-vps-ip> 9000
  3. Убедитесь что клиент указывает правильный --server=<ip>:<port>

Контейнер перезапускается (OOM)

Увеличьте лимит памяти в .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 каждого файла; неверная подпись или хеш → обновление отклоняется, остаётся работать предыдущая версия