Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Standalone HAProxy proxy for WhatsApp

Нативный setup для Ubuntu 24.04 без Docker. Он устанавливает HAProxy из репозитория Ubuntu и по умолчанию поднимает два публичных TCP-порта:

  • 443: TLS termination и передача chat-трафика в g.whatsapp.net:5222 с корректным PROXY header
  • 587: прозрачная передача media-трафика в whatsapp.net:443

Схема совместима с рекомендацией официального проекта WhatsApp Proxy для неблагоприятных сетевых условий. VoIP официальным proxy не поддерживается.

Изменения версии 1.4.0

  • Добавлен opt-in режим shared_443: chat и media клиента могут использовать один публичный порт 443, при этом адрес proxy не меняется
  • Публичный 443 становится raw TCP SNI-mux. Известный media SNI передаётся в прежний raw media backend без PROXY header, а остальные соединения идут в local-only TLS terminator chat и далее с обязательным PROXY v1
  • Публичный raw media listener 587 остаётся рабочим fallback; оба media-входа используют один backend и один выбранный direct/SOCKS4 маршрут
  • VM/e2e healthcheck при включённом режиме дополнительно выполняет проверенный TLS handshake до WhatsApp media через общий 443
  • Без блока shared_443 или при enabled: false генерируется byte-for-byte прежняя конфигурация версии 1.3.0

Изменения версии 1.3.0

  • Добавлены независимые исходящие маршруты routes.chat и routes.media: прежний direct либо локальный SOCKS4 bridge на 127.0.0.1
  • HAProxy передаёт через SOCKS4 и рабочий трафик, и L4 health-check; если bridge или его parent недоступен, backend закрывается без direct fallback
  • Новый healthcheck.py --scope routes проверяет SOCKS4 CONNECT до каждого настроенного upstream. Для media дополнительно выполняется TLS handshake с обычной проверкой CA и SNI probes.media_upstream_host
  • Отсутствующий routes или отдельный backend сохраняет прежний byte-for-byte direct render и всю семантику runtime DNS, frontend и PROXY v1

Изменения версии 1.2.1

  • Для каждого hostname-upstream HAProxy использует три независимых DNS-view: resolver VM, Cloudflare (1.1.1.1/1.0.0.1) и Google (8.8.8.8/8.8.4.4). Это позволяет одновременно проверять разные geo-DNS-ответы и выбирать доступный с VM адрес отдельно для chat и media
  • Cold-start readiness больше не принимает переходное состояние UP 1/2: backend считается готовым только после успешного L4 health-check (L4OK)
  • Внешняя YAML-схема не изменилась; literal IPv4 по-прежнему остаётся одним статическим upstream без DNS-ротации

Изменения версии 1.2.0

  • DNS-upstream теперь представлен пулом из 16 независимо проверяемых IPv4: недоступный с VM адрес исключается из новых подключений, пока остальные доступные адреса продолжают обслуживать трафик
  • HAProxy в течение 15 минут сохраняет ранее полученные одиночные A-records, поэтому последовательная DNS-ротация наполняет пул без restart или reload
  • Healthcheck проверяет состояние всего runtime-пула и считает backend рабочим, если назначен и доступен хотя бы один адрес
  • Внешняя YAML-схема не изменилась; literal IPv4 остаётся одним статическим upstream без DNS-ротации

Изменения версии 1.1.0

  • HAProxy переразрешает DNS-имена chat- и media-upstream в runtime и обновляет их IPv4-адреса без restart или reload
  • Внешняя YAML-схема не изменилась: конфигурации версии 1.0.3 продолжают работать без изменений
  • TLS termination и PROXY v1 для chat 443, raw TCP passthrough для media 587, ACL, limits, timeouts и certificate lifecycle не изменились

Изменения версии 1.0.3

  • Production-профили config.<ssh-alias>.yaml и локальный AGENTS.md исключаются из Git и standalone ZIP
  • Release builder исключает локальные окружения и runtime artifacts

Быстрый запуск

unzip whatsapp-haproxy-setup-standalone-1.4.0.zip
cd whatsapp-haproxy-setup
cp config.example.yaml config.yaml
nano config.yaml
chmod +x setuphaproxy.sh cleanhaproxy.sh steps/*.sh tools/*.py
sudo ./setuphaproxy.sh all

В config.yaml обязательно замените server.public_ip. Пустой access.allowed_cidrs означает публичный беспарольный WhatsApp proxy. Это не HTTP- и не SOCKS-прокси: направления backend зафиксированы в HAProxy.

Скрипт поддерживает отдельные шаги:

sudo ./setuphaproxy.sh 0  # диагностика, backup и остановка
sudo ./setuphaproxy.sh 1  # установка HAProxy и зависимостей
sudo ./setuphaproxy.sh 2  # сертификат и конфигурация
sudo ./setuphaproxy.sh 3  # systemd start и VM-side health-check

Можно хранить YAML вне распакованного каталога:

sudo ./setuphaproxy.sh all --config /secure/path/whatsapp-proxy.yaml

Исходящие маршруты

По умолчанию chat и media подключаются к upstream напрямую. Для отдельного backend можно включить локальный SOCKS4 egress:

routes:
  chat:
    mode: "direct"
  media:
    mode: "socks4"
    socks4:
      host: "127.0.0.1"
      port: 11081

routes, routes.chat и routes.media опциональны; отсутствие означает direct. В режиме socks4 обязательны оба поля, host должен быть ровно 127.0.0.1, а port — целым числом 1..65535. HAProxy не принимает здесь логин или пароль: listener должен быть passwordless и доступен только через loopback. Его установка, upstream-аутентификация и failover принадлежат 3proxy-setup; этот проект их не создаёт и не удаляет.

SOCKS4 и check-via-socks4 применяются вместе через backend-local default-server. Поэтому при отказе bridge обычные соединения и health-check не обходят его напрямую. Chat сохраняет обязательный PROXY v1, media остаётся raw TCP passthrough без PROXY header.

При all setup выполняет route preflight до остановки HAProxy и повторяет его перед записью нового конфига. Отдельный step 2 также проверяет route до мутаций. Его можно запустить отдельно:

sudo python3 tools/healthcheck.py --scope routes --config config.yaml

Direct backend только отмечается как не требующий отдельного preflight. Для SOCKS4 preflight получает IPv4-кандидаты через системный resolver VM и пробует CONNECT к ним; media считается рабочим только после проверенного TLS handshake через созданный tunnel. Полные три DNS-view и все runtime slots затем проверяет VM-side healthcheck запущенного HAProxy.

Общий порт 443 для chat и media

Некоторые сети доставляют ClientHello до VM на 443, но блокируют или фильтруют payload на нестандартном media-порту. Для такого случая можно включить экспериментальный SNI-mux:

shared_443:
  enabled: true
  chat_loopback_port: 18443
  probe_sni: "media-hel3-1.cdn.whatsapp.net"
  media_sni:
    exact:
      - "whatsapp.net"
      - "mmg.whatsapp.net"
    suffixes:
      - ".cdn.whatsapp.net"

При включённом режиме public listener 443 не завершает TLS сразу, а ждёт ClientHello до 5 секунд. SNI из media_sni направляется без расшифровки в whatsapp_media; полный TLS ClientHello с неизвестным SNI или без SNI fail-safe направляется в local-only 127.0.0.1:18443, где HAProxy завершает chat TLS и передаёт трафик в whatsapp_chat. Неполный и не-TLS payload отклоняется после inspect-delay.

chat_loopback_port должен быть свободным локальным портом, отличаться от публичных портов и портов настроенных SOCKS4 routes. Его не нужно открывать в UFW или cloud firewall. В правилах SNI разрешены только точные ASCII DNS-имена и узкие suffixes; широкая маска .whatsapp.net запрещена, потому что она перехватила бы chat host g.whatsapp.net. Exact-список обязан содержать probes.media_upstream_host.

probe_sni — обязательный CDN-like SNI для VM/e2e canary; он должен совпадать с одним из exact/suffix правил. Значение выше соответствует реально наблюдаемому Android media ClientHello. Если клиент начинает использовать другой CDN namespace, сначала обновите media rules и этот canary.

Media TLS остаётся сквозным и проверяется телефоном против сертификата WhatsApp; HAProxy видит только незашифрованный SNI стандартного TLS ClientHello. 587 продолжает работать независимо. routes.media, DNS-пулы и health-check backend одинаковы для media на 443 и 587.

Один chat-клиент на общем 443 занимает внешний raw stream и внутренний TLS stream HAProxy. Генератор резервирует для loopback дополнительные process slots и добавляет к process accept-rate резерв в пределах limits.tls_rate_per_second, но отдельная aggregate stick-table по-прежнему ограничивает сумму внешних 443/587 значениями limits.global_connections и limits.global_connection_rate_per_second. Поэтому внутренний re-entry не может быть вытеснен внешними media-соединениями и одновременно не расширяет заданный публичный лимит. ACL и per-IP rate limit применяются только на публичном frontend, а не повторно на loopback. Неполный или не-TLS payload на 443 отклоняется после inspect-delay и не занимает внутренний TLS slot. Резерв предотвращает steady-state starvation, но не гарантирует fairness между listeners во время непрерывного внешнего flood: для публичного proxy cloud DDoS protection остаётся обязательной.

Режим остаётся opt-in: набор media SNI контролируется конфигурацией, поскольку закрытый клиент WhatsApp может менять используемые CDN-имена. Успешный synthetic TLS healthcheck доказывает маршрут и сертификат, но окончательная проверка — отправка и загрузка media в приложении.

Runtime DNS-ротация upstream

HAProxy переразрешает probes.chat_upstream_host и probes.media_upstream_host независимо через три DNS-view: DNS-серверы VM из /etc/resolv.conf, Cloudflare (1.1.1.1/1.0.0.1) и Google (8.8.8.8/8.8.4.4). Ответы разных resolver не смешиваются в гонке за первый ответ: у каждого view собственная группа server slots, а L4 health-check выбирает реально доступные с VM адреса. Если список DNS-серверов VM изменился, HAProxy нужно reload/restart. /etc/hosts в этом маршруте не используется. При SOCKS4-маршруте DNS по-прежнему выполняет HAProxy на VM, а bridge получает уже выбранный IPv4: это не SOCKS4a и не DNS-view страны upstream proxy.

Для каждого DNS-upstream действует следующая семантика:

  • Базовый интервал переразрешения — 5 секунд, когда нет другого триггера; connection timeout health-check может запустить его раньше. Интервал задаёт timeout resolve, а не authoritative DNS TTL и не hold valid
  • HAProxy создаёт по четыре слота на каждый из трёх DNS-view, запрещает дублирование адресов внутри одной группы и накапливает как одновременные, так и последовательные одиночные A-records. Один IP может ожидаемо повториться в разных DNS-view; это не влияет на проверку его доступности
  • Ранее замеченный адрес остаётся кандидатом 15 минут после последнего появления в корректном DNS-ответе. Повторное появление продлевает это окно
  • Каждый назначенный адрес проверяется TCP health-check с VM. Рабочие адреса участвуют в leastconn, недоступные получают DOWN и исключаются из новых подключений. Если UP-адресов нет, backend закрывается без direct fallback
  • Основная проверка выполняется каждые 10 секунд; переходное состояние перепроверяется через 2 секунды, DOWN-адрес — каждые 30 секунд. Для исключения адреса нужны две неудачи подряд, для возврата — один успех
  • Смена адреса или его переход в DOWN не переносит и принудительно не разрывает уже установленные TCP-сессии. Новые сессии используют доступные слоты
  • Внутри неудачного цикла HAProxy делает до трёх DNS-попыток с интервалом 1 секунда и принимает DNS-payload до 4096 байт
  • При NXDOMAIN, REFUSED, timeout и других DNS-ошибках HAProxy проверяет, был ли valid answer за предыдущие 30 секунд. Если нет, соответствующий server выводится из работы до нового корректного answer; затем его готовность снова определяют L4 health-checks
  • init-addr last,none не делает libc fallback. Если прежнего state нет, backend стартует без адреса и сам восстанавливается после успешного DNS-answer
  • VM-side readiness ждёт хотя бы один назначенный сервер со стабильным UP и успешным L4OK в каждом backend; переходные UP 1/2/INI не считаются готовностью. Только после этого проверяются локальные frontend 443/587
  • DNS-пул хранится в памяти процесса. После холодного restart HAProxy или VM он собирается заново из последующих DNS-ответов; отдельного selector-daemon и постоянного кэша нет

Если в upstream указан literal IPv4, HAProxy оставляет его статическим. Для перехода на 1.2.1 не нужно добавлять ключи в config.yaml: все текущие поля и их значения сохраняют прежнюю семантику.

Cloud firewall и DDoS

Setup не меняет cloud security group и не подключает DDoS-защиту. Для публичного сервиса разрешите ingress TCP только на 443 и 587. SSH должен быть ограничен отдельно. Direct backend требует исходящий TCP к своему WhatsApp upstream; SOCKS4 backend требует работоспособный локальный bridge и его собственный egress к parent proxy. VM также нужен DNS-доступ к resolver VM, 1.1.1.1, 1.0.0.1, 8.8.8.8 и 8.8.4.4 на UDP/TCP 53. Если firewall не поддерживает DNS-правила, используйте подходящие общие egress-правила.

UFW по умолчанию не меняется. server.manage_ufw: true добавляет два allow правила, но не заменяет cloud firewall.

Публичного HAProxy stats-порта нет. Диагностика использует локальный сокет /run/haproxy/admin.sock.

Проверки

На VM:

sudo python3 tools/healthcheck.py --scope vm --config config.yaml

С клиентской машины:

python -m venv venv
venv/bin/pip install PyYAML
venv/bin/python tools/healthcheck.py --scope e2e --config config.yaml \
  --json-out whatsapp-haproxy-e2e.json

Windows PowerShell использует venv\Scripts\python.exe. E2E выполняет TLS handshake с самоподписанным frontend 443 и проверенный сквозной TLS handshake до *.whatsapp.net через 587. При shared_443.enabled: true та же проверенная media TLS-проверка дополнительно проходит через общий 443. Он не эмулирует закрытый протокол WhatsApp и не доказывает доставку сообщения в приложении.

Неблокирующая smoke-проверка per-IP лимита:

python tools/healthcheck.py --scope limits --config config.yaml

Настройка WhatsApp

В обычном режиме укажите публичный IP или DNS-имя VM, порт чата 443 и порт медиа 587. При включённом shared_443 можно указать 443 для обоих портов; 587 остаётся fallback. Сертификат chat на 443 создаётся локально при установке и не требует публичного CA. Media TLS на общем 443 HAProxy не завершает.

Полная очистка

Без --yes cleaner всегда работает в dry-run:

./cleanhaproxy.sh
sudo ./cleanhaproxy.sh --yes
sudo ./cleanhaproxy.sh --yes --purge-package --purge-setup

Дополнительные флаги:

sudo ./cleanhaproxy.sh --yes --keep-backups
sudo ./cleanhaproxy.sh --yes --purge-ufw

Cleaner останавливает HAProxy и удаляет /etc/haproxy, выделенные логи, runtime state и backup этого setup. --purge-package удаляет пакет HAProxy, но намеренно не запускает глобальный apt autoremove. Cloud firewall и общий systemd journal не меняются. Cleaner не затрагивает 3proxy.

Источники

About

HAProxy deployment for WhatsApp Chat and Media proxy

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages