Перейти к содержимому
НовостиGitHubСкачать

Fallbacks для VLESS и Trojan

Входящий VLESS или Trojan на обычном TCP-транспорте может делить порт с сайтом. После TLS слушатель читает первые байты потока. Если это не запрос одного из его пользователей, поток вместе с уже прочитанными байтами уходит на локальный сервер, например nginx. Браузер или сканер, открывший адрес, видит этот сайт, а не закрытое соединение.

Это опция fallbacks из Xray-core с теми же полями: name, alpn, path, dest и xver.

listeners:
- name: vless-in
type: vless
port: 443
listen: 0.0.0.0
users:
- { username: alice, uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68, flow: xtls-rprx-vision }
certificate: /etc/citrus-server/fullchain.pem
private-key: /etc/citrus-server/privkey.pem
fallbacks:
- dest: 8080 # всё остальное: 127.0.0.1:8080
- path: /api # GET /api?x HTTP/1.1
dest: 127.0.0.1:8081
xver: 1 # сначала заголовок PROXY protocol v1
- alpn: h2 # браузеры, договорившиеся о HTTP/2
dest: /run/nginx/h2.sock
- name: blog.example.com # по SNI
dest: 127.0.0.1:8082

У Trojan такой же список.

  • VLESS: первый байт не версия 0 или следующий за ним UUID не принадлежит пользователю.
  • Trojan: первые 56 байт не строчный hex с CRLF после него или ключ не принадлежит пользователю.

Неизвестный UUID или ключ, как и без fallbacks, попадает в события как неудачный вход. Если начало потока ещё может оказаться заголовком протокола, но поток обрывается раньше конца заголовка, он закрывается по сроку рукопожатия через 15 секунд и в fallback не уходит.

Слушатель читает столько, сколько нужно для решения: для большей части веб-трафика один байт, для VLESS 17 байт, для Trojan 58 байт. Если у какого-то fallback задан path, он дочитывает до конца пути, в пределах первых 256 байт. Заголовок может прийти в нескольких записях, если успевает до срока рукопожатия.

Поле Сравнивается с Пусто
name SNI из TLS: само имя, иначе самый длинный name, который содержится в SNI, как в Xray. example.com подходит и для www.example.com (и для notexample.com). Регистр не важен, шаблонов нет. любое имя и отсутствие TLS
alpn согласованный ALPN, например h2 или http/1.1 любой
path путь из строки запроса HTTP/1.x: /api в GET /api?x HTTP/1.1. Начинается с /. У HTTP/2 нет строки запроса, поэтому path с h2 не совпадает любой

Порядок в списке не важен. Fallback выбирается как в Xray: сначала name, внутри него alpn, затем path. Если совпадения нет, на каждом шаге берётся пустое значение. Fallback с name наследует пары alpn и path от fallback без name, если сам не задаёт такую же пару. Например, при {path: /ws, dest: 10001} и {name: blog.example.com, dest: 8082} запрос GET /ws с SNI blog.example.com уходит на 10001, а остальной трафик блога на 8082. Так же fallback с alpn наследует пути fallback без alpn с тем же name. Два fallback с одинаковыми name, alpn и path — ошибка конфига. Поток, который не совпал ни с одним fallback, закрывается. Для значения по умолчанию добавьте fallback только с dest.

Если хотя бы у одного fallback задан alpn, TLS предлагает перечисленные значения и http/1.1. Без alpn ALPN не согласуется, как и раньше. Клиент, который предлагает только ALPN вне этого списка, не пройдёт рукопожатие. Клиент с http/1.1 или вообще без ALPN подключится. Стандартный для Trojan h2, http/1.1 тоже работает.

  • dest: 8080 (порт на 127.0.0.1), host:port или unix-сокет: /run/web.sock, @name (abstract, Linux) или unix:C:\web.sock.
  • xver: 1 или 2: сначала отправить заголовок PROXY protocol этой версии с адресом и портом клиента и адресом слушателя. Если у входящего включён proxy-protocol, в заголовок идёт адрес из заголовка фронтенда. Адрес клиента не IP (например, у слушателя на unix-сокете) уходит как UNKNOWN (v1) или неуказанный адрес (v2). nginx читает заголовок с listen ... proxy_protocol.

Подключение к dest идёт напрямую, мимо правил. Предполагается, что это локальный адрес. Таймаут подключения — 5 секунд.

Как в Xray, у ретрансляций fallback нет лимита на число и таймаута простоя. Через них идут сайт и входящие, подключённые через path, например ws, а такие сессии могут подолгу молчать. Лимиты задаются на сервере в dest, например через limit_conn и keepalive_timeout в nginx. Закрытие слушателя закрывает его ретрансляции.

Если dest недоступен, первая ошибка пишется в лог как предупреждение, дальше не чаще одного предупреждения в минуту. Остальные ошибки уходят в debug-лог: вызвать такую ошибку может любой клиент.

  • Только на обычном TCP-транспорте. fallbacks вместе с ws-path, grpc-service-name или xhttp-config — ошибка конфига. На этих HTTP-транспортах чужие запросы обслуживает fallback-page.
  • С любым слоем безопасности: TLS (certificate), REALITY, ShadowTLS, JLS, ResTLS или без него с allow-insecure. name работает с TLS и REALITY, alpn только с TLS: REALITY не согласует ALPN, поэтому за ним совпадает только пустой alpn. За остальными слоями оба поля пусты и совпадают только с пустыми значениями.
  • Не работает с decryption у VLESS и ss-option у Trojan: там заголовок зашифрован, и fallback получил бы шифротекст. Оба сочетания — ошибка конфига.

fallback-page — статическая HTML-страница для ws, grpc и xhttp. Её отдаёт тот же HTTP-сервер на запросы, которые не относятся к входящему. Она и fallbacks никогда не обслуживают одно соединение. С fallbacks транспорт — обычный TCP, а там fallback-page не действует. Если заданы оба, слушатель пишет предупреждение в лог.