Маршрутизация по протоколу
Несколько служб делят один публичный порт (например, сайт, VPN, SSH и удалённый рабочий стол одновременно используют 443). Gately определяет протокол по первым данным, которые отправляет клиент, затем смотрит домен (SNI) и ALPN в TLS-рукопожатии или Host в HTTP и передаёт всё соединение без изменений подходящему бэкенду. Здесь ничего не расшифровывается и не терминируется, поэтому TLS-сертификаты остаются на бэкендах. Модуль выполняет ту же задачу, что правила req_ssl_sni в haproxy вместе с sslh.
Маршрутизация по домену (маршрутизация по SNI) — один из её случаев: добавьте маршрут TLS и укажите в нём домены.
Правило содержит:
- прослушивание: протокол (только TCP; tcp — двойной стек, tcp4 / tcp6 — одно семейство адресов), адрес и порты прослушивания (
443,443,8443или диапазон); - маршруты: у каждого маршрута есть протокол, имя, адреса целей (несколько адресов пробуются по очереди, с переключением при отказе), необязательный порт цели (пусто означает порт прослушивания) и настройка протокола PROXY (выключен, v1 или v2). Настройка PROXY относится к маршруту, потому что одним бэкендам она необходима, а другие её отвергают;
- цель по умолчанию: собственные адреса целей, порты целей и настройка протокола PROXY самого правила; используется, если ни один маршрут не подошёл или протокол не распознан;
- тайм-аут распознавания: сколько секунд ждать от клиента данных, по которым можно определить протокол; 0 означает значение по умолчанию, 5 секунд.
Распознаваемые протоколы
| Протокол маршрута | Признак | Дополнительное сопоставление |
|---|---|---|
| TLS | Запись TLS-рукопожатия (ClientHello) | Домен (SNI), ALPN |
| HTTP | Метод и версия HTTP/ в строке запроса, включая CONNECT, методы WebDAV и HTTP/2 с предварительным знанием (h2c) | Домен (заголовок Host) |
| SSH | Строка версии клиента SSH- | — |
| RDP (удалённый рабочий стол) | Заголовок TPKT и запрос подключения X.224 | — |
| SOCKS4 / SOCKS5 | Запросы SOCKS4 и SOCKS4a и согласование метода SOCKS5 | — |
| OpenVPN (TCP) | Пакет жёсткого сброса (hard reset) от клиента, включая tls-auth, tls-crypt и tls-crypt-v2 | — |
| XMPP | XML-декларация или <stream:stream | — |
| MQTT | Пакет CONNECT (3.1, 3.1.1, 5.0) | — |
| PostgreSQL | Стартовое сообщение, SSLRequest и GSSENCRequest | — |
| Minecraft (Java Edition) | Пакет рукопожатия; запрос списка серверов в версиях 1.6 и более ранних | Адрес сервера |
| RTSP | Версия RTSP/1.0 или RTSP/2.0 в строке запроса (IP-камеры, видеорегистраторы) | — |
| SIP | Версия SIP/2.0 в строке запроса (TCP) | — |
| Redis | Команды, отправленные массивами RESP | — |
| AMQP | Заголовок протокола AMQP (0-9-1 и 1.0, RabbitMQ и др.) | — |
| SMB | Запросы согласования SMB1, SMB2 и SMB3 в сообщении сеанса NetBIOS | — |
| Git | Первая pkt-line протокола git:// (git daemon) | — |
| IRC | Команда CAP, PASS, NICK или USER, которую клиент отправляет первой | — |
| ADB | Сообщение CNXN протокола Android Debug Bridge (adb connect) | — |
| BitTorrent | Нешифрованное рукопожатие между пирами | — |
| Ожидание | Клиент ничего не отправил за время тайм-аута распознавания | — |
Сопоставление
Маршруты TLS, HTTP и Minecraft сопоставляются по домену (для Minecraft это адрес сервера, который ввёл клиент, из пакета рукопожатия; данные, добавленные Forge, BungeeCord или TCPShield, игнорируются): сначала точные домены (vpn.example.com), затем самый длинный шаблон (*.example.com, любой глубины), затем * (любое соединение, содержащее домен) и в конце маршруты без домена (все соединения этого протокола, включая соединения без SNI или Host). Регистр доменов не учитывается. При сохранении домены приводятся к нижнему регистру, интернационализированные преобразуются в punycode; значения с портом, путём или @ отклоняются.
В маршрутах TLS можно также указать значения ALPN: маршрут подходит, только если клиент предлагает одно из них. Для одного домена приоритет у маршрутов с ALPN, а если ALPN не совпал, поиск продолжается по более общим доменам. Например, acme-tls/1 можно отправить программе, которая отвечает на проверку сертификата TLS-ALPN-01, а postgresql — на прямые TLS-соединения PostgreSQL 17. В пределах протокола домен (с данным ALPN) может принадлежать только одному маршруту; для остальных протоколов допускается по одному маршруту.
«Ожидание» предназначено для протоколов, где сервер отправляет данные первым: VNC, MySQL, FTP, SMTP, POP3, IMAP. Их клиенты подключаются и ждут сервер, поэтому Gately ничего не получает и не может определить, какой это протокол. Значит, на каждом порту маршруту «Ожидание» можно отдать только один из них, и такие соединения перенаправляются после истечения тайм-аута распознавания. Тайм-аут распознавания можно задать с одним знаком после запятой: клиенты TLS, SSH, HTTP и подобные отправляют первый пакет через миллисекунды после рукопожатия, поэтому при наличии маршрута «Ожидание» достаточно 0,3–1 секунды — столько же ждут VNC и подобные соединения.
На цель по умолчанию отправляются соединения, если: ни один маршрут не подошёл; данные не относятся ни к одному протоколу из таблицы (например, протоколы без сигнатуры, такие как Shadowsocks, VMess или BitTorrent с шифрованием, а также протоколы, первый пакет которых состоит только из поля длины и легко путается с другими, такие как MongoDB или DNS over TCP); ClientHello не удаётся разобрать; клиент ничего не отправляет, а маршрута «Ожидание» нет. Без цели по умолчанию такие соединения закрываются и учитываются как «разорвано». Клиент, который отключился до завершения распознавания, отбрасывается.
Примечания
- Только TCP. UDP-трафик, например HTTP/3 (QUIC) или WireGuard, не маршрутизируется, для него создайте отдельное правило;
- зашифрованные варианты SIP, Redis, AMQP, IRC и подобных протоколов работают поверх TLS; направляйте их маршрутами TLS по домену или ALPN;
- ClientHello может быть разбит на несколько TLS-записей и достигать 64 КБ; строка запроса HTTP, RTSP и SIP может занимать до 8 КБ, а для HTTP читается до 32 КБ заголовков запроса, чтобы найти Host;
- каждое соединение распознаётся один раз, поэтому последующие запросы в том же keep-alive-соединении HTTP идут на бэкенд, выбранный в первый раз;
- на карточке правила показано, сколько соединений получили каждый маршрут и цель по умолчанию; отладочные записи журнала работы фиксируют для каждого соединения распознанный протокол, SNI, ALPN или Host;
- при горячем обновлении правила (примечание, маршруты, цель по умолчанию, список IP источников и т. п.) маршруты с прежними протоколом, доменами и ALPN сохраняют счётчики попаданий, в том числе при перемещении, переименовании или смене цели; новые маршруты и маршруты с изменённым протоколом, доменами или ALPN начинают с 0; после отключения и повторного включения или перезапуска правила все счётчики начинаются с 0;
- изменения маршрутов, цели по умолчанию и тайм-аута распознавания применяются сразу, без остановки прослушивания, а открытые соединения сохраняются.
- Для каждого правила можно задать белый или чёрный список IP источника (в том числе по подписанным спискам IP веб-служб); проверка выполняется до определения протокола: не прошедшее её соединение закрывается без чтения данных и не считается попаданием маршрута; блокировки защиты от вторжений имеют приоритет. Правка списка не прерывает прослушивание, карточка правила показывает число отказов. Работает так же, как в пробросе портов.
Пример: замена haproxy
Такая конфигурация haproxy:
frontend https
bind 192.0.2.10:443
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
use_backend ocserv if { req_ssl_sni -i tz.example.com }
default_backend web
backend ocserv
server ocserv 127.0.0.1:443 send-proxy-v2
backend web
server web 127.0.0.1:8443 send-proxy-v2превращается в одно правило Gately:
| Параметр | Значение |
|---|---|
| Протокол | TCP |
| Адрес / порт прослушивания | 192.0.2.10 / 443 |
Маршрут ocserv | протокол TLS, домен tz.example.com, цель 127.0.0.1, порт 443, PROXY v2 |
| Цель по умолчанию | 127.0.0.1, порт цели 8443, PROXY v2 |
Укажите в качестве адреса прослушивания публичный адрес сервера, чтобы не возникало конфликта с бэкендами, которые слушают 127.0.0.1:443. Чтобы раздавать сайты средствами самого Gately, добавьте прослушивание веб-служб на 127.0.0.1:8443 с включённым «Принимать протокол PROXY» и 127.0.0.0/8 в доверенных источниках PROXY. Перед включением правила остановите haproxy: порт может слушать только одна программа.
Пример: замена sslh
Такие аргументы sslh:
sslh --listen 0.0.0.0:443 --timeout 2 \
--ssh 127.0.0.1:22 --openvpn 127.0.0.1:1194 --tls 127.0.0.1:8443соответствуют одному правилу Gately:
| Параметр | Значение |
|---|---|
| Протокол | TCP |
| Адрес / порт прослушивания | пусто / 443 |
| Тайм-аут распознавания | 2 |
Маршрут ssh | протокол SSH, цель 127.0.0.1, порт 22 |
Маршрут vpn | протокол OpenVPN, цель 127.0.0.1, порт 1194 |
Маршрут web | протокол TLS, без доменов, цель 127.0.0.1, порт 8443 |
Маршрут vnc (необязательно) | протокол «Ожидание», цель 127.0.0.1, порт 5900 |
По умолчанию sslh после тайм-аута отправляет соединения на SSH. Gately распознаёт SSH сразу по строке версии SSH-, поэтому тайм-аут можно отдать службе, где сервер отправляет данные первым, например VNC. Можно добавить маршруты для RDP, SOCKS и HTTP или распределить TLS между бэкендами по доменам.
Переход с проброса портов
Раньше маршрутизация по протоколу была режимом «Маршрутизация по SNI» в правилах проброса портов. При первом запуске после обновления правила маршрутизации по SNI из проброса портов автоматически переносятся в модуль «Маршрутизация по протоколу»: каждое правило сохраняет ключ, имя, состояние включения, группу и все маршруты, а старые маршруты становятся маршрутами TLS. Если модуль проброса портов был отключён, маршрутизация по протоколу тоже отключается, а API-токены с доступом к пробросу портов получают такой же уровень доступа к маршрутизации по протоколу.
После переноса управляйте этими правилами через /api/protoroute/rules: поле маршрутов переименовано из sniRoutes в routes, поле mode больше не используется; правила маршрутизации, отправленные в /api/portforward/rules, отклоняются. Метрики теперь называются gately_protoroute_*, см. Мониторинг.
Рекомендации по развёртыванию
Маршрутизация по протоколу слушает порты непосредственно на хосте, поэтому для Docker рекомендуется --network host.
Группы и порядок
Правилам можно задать «Группу». Группы, сортировка и поиск работают так же, как в пробросе портов; поле поиска фильтрует по имени, группе, порту прослушивания, цели, домену или протоколу маршрута.