Нативный setup для Ubuntu 24.04 без Docker. Он устанавливает HAProxy из репозитория Ubuntu и по умолчанию поднимает два публичных TCP-порта:
443: TLS termination и передача chat-трафика вg.whatsapp.net:5222с корректным PROXY header587: прозрачная передача media-трафика вwhatsapp.net:443
Схема совместима с рекомендацией официального проекта WhatsApp Proxy для неблагоприятных сетевых условий. VoIP официальным proxy не поддерживается.
- Добавлен 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
- Добавлены независимые исходящие маршруты
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 и SNIprobes.media_upstream_host - Отсутствующий
routesили отдельный backend сохраняет прежний byte-for-byte direct render и всю семантику runtime DNS, frontend и PROXY v1
- Для каждого 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-ротации
- DNS-upstream теперь представлен пулом из 16 независимо проверяемых IPv4: недоступный с VM адрес исключается из новых подключений, пока остальные доступные адреса продолжают обслуживать трафик
- HAProxy в течение 15 минут сохраняет ранее полученные одиночные A-records, поэтому последовательная DNS-ротация наполняет пул без restart или reload
- Healthcheck проверяет состояние всего runtime-пула и считает backend рабочим, если назначен и доступен хотя бы один адрес
- Внешняя YAML-схема не изменилась; literal IPv4 остаётся одним статическим upstream без DNS-ротации
- HAProxy переразрешает DNS-имена chat- и media-upstream в runtime и обновляет их IPv4-адреса без restart или reload
- Внешняя YAML-схема не изменилась: конфигурации версии 1.0.3 продолжают работать без изменений
- TLS termination и PROXY v1 для chat
443, raw TCP passthrough для media587, ACL, limits, timeouts и certificate lifecycle не изменились
- 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: 11081routes, 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.yamlDirect backend только отмечается как не требующий отдельного preflight. Для SOCKS4 preflight получает IPv4-кандидаты через системный resolver VM и пробует CONNECT к ним; media считается рабочим только после проверенного TLS handshake через созданный tunnel. Полные три DNS-view и все runtime slots затем проверяет VM-side healthcheck запущенного HAProxy.
Некоторые сети доставляют 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 в приложении.
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не считаются готовностью. Только после этого проверяются локальные frontend443/587 - DNS-пул хранится в памяти процесса. После холодного restart HAProxy или VM он собирается заново из последующих DNS-ответов; отдельного selector-daemon и постоянного кэша нет
Если в upstream указан literal IPv4, HAProxy оставляет его статическим.
Для перехода на 1.2.1 не нужно добавлять ключи в config.yaml: все текущие
поля и их значения сохраняют прежнюю семантику.
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.jsonWindows 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В обычном режиме укажите публичный 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-ufwCleaner останавливает HAProxy и удаляет /etc/haproxy, выделенные логи,
runtime state и backup этого setup. --purge-package удаляет пакет
HAProxy, но намеренно не запускает глобальный apt autoremove. Cloud firewall
и общий systemd journal не меняются. Cleaner не затрагивает 3proxy.
- Официальный проект: https://github.com/WhatsApp/proxy
- Официальная HAProxy-схема: https://github.com/WhatsApp/proxy/blob/main/proxy/src/proxy_config.cfg
- Runtime DNS HAProxy 2.8: https://docs.haproxy.org/2.8/configuration.html#5.3.2
- HAProxy
server-template: https://docs.haproxy.org/2.8/configuration.html#server-template - HAProxy
socks4: https://docs.haproxy.org/2.8/configuration.html#5.2-socks4 - HAProxy
check-via-socks4: https://docs.haproxy.org/2.8/configuration.html#5.2-check-via-socks4 - HAProxy
req.ssl_sni: https://docs.haproxy.org/2.8/configuration.html#7.3.5-req.ssl_sni