--- title: SNI Fallback --- # Маскировка и разделение трафика по доменам с помощью функции SNI Fallback VLESS - это очень легкий протокол, который, как и Trojan, не использует сложного шифрования и обфускации трафика. Вместо этого он, подобно тому, как искусный мастер кунг-фу скрывает свою силу, шифрует трафик с помощью протокола TLS, маскируя его под обычный HTTPS-трафик и позволяя ему беспрепятственно проходить через Великий китайский файрвол. Для лучшей маскировки от активного зондирования вместе с VLESS была представлена функция Fallbacks (резервирование). В этой статье мы рассмотрим, как использовать функцию Fallbacks входящего протокола VLESS в Xray совместно с Nginx или Caddy для реализации разделения трафика по доменам при обеспечении полной маскировки. ## Сценарии использования Из-за XTLS Xray необходимо прослушивать порт 443, что создает проблему, если на сервере уже запущен веб-сайт - сайт либо не сможет работать, либо его придется запускать на другом порту, что нежелательно. Есть три способа решить эту проблему: - Xray прослушивает другие часто используемые порты (например, 22, 3389, 8443). Это самое простое решение, но оно не идеально. - Nginx или HAProxy прослушивает порт 443 и выполняет обратное проксирование на уровне L4 с разделением трафика по SNI, что позволяет использовать один порт для нескольких сервисов. Этот вариант более сложный и требует определенных знаний Nginx или HAProxy, поэтому мы не будем его здесь подробно рассматривать. - Xray прослушивает порт 443 и использует функцию Fallbacks для перенаправления трафика веб-сайта на Nginx или Caddy на основе SNI. Этот вариант имеет среднюю сложность и является тем, который мы рассмотрим в этом руководстве. ## Что такое SNI **SNI** (Server Name Indication) - это расширение протокола TLS. Те, кто знаком с обратным проксированием, знают, что для правильной маршрутизации трафика по доменному имени необходимо следующее правило: ```nginx proxy_set_header Host имя_хоста; ``` Эта строка устанавливает HTTP-заголовок "Host" на определенное имя хоста. Зачем это нужно? Обычно у одного сервера один IP-адрес, но на нем может быть запущено несколько сайтов. Пользователи получают IP-адрес по доменному имени и обращаются к серверу, но как сервер определяет, какой именно сайт запрашивает пользователь? Для этого используются виртуальные хосты, основанные на имени. Когда веб-сервер получает запрос, он проверяет заголовок "Host" и направляет пользователя на нужный сайт. Однако, когда HTTP-трафик шифруется с помощью TLS, этот простой метод перестает работать. TLS-рукопожатие происходит до того, как сервер увидит какие-либо HTTP-заголовки, поэтому сервер не может использовать информацию из заголовка "Host", чтобы решить, какой сертификат предоставить, и тем более не может определить, к какому сайту обращается пользователь. SNI решает эту проблему, позволяя клиенту отправлять имя хоста как часть TLS-рукопожатия. Поэтому при использовании Nginx для обратного проксирования HTTPS-трафика необходимо добавить в конфигурацию `proxy_ssl_server_name on;`. В этом случае Nginx будет отправлять информацию SNI на проксируемый сервер, решая проблему неработающих виртуальных хостов по HTTPS. Кроме того, при использовании SNI можно получить доступ к нужному сайту, даже не указывая заголовок "Host". ## Идея ![Схема работы Xray Fallbacks](./fallbacks-with-sni-resources/xray-fallbacks.svg) После получения трафика на порт 443 Xray расшифровывает TLS и перенаправляет трафик с длиной первого пакета менее 18 байт, неверной версией протокола или ошибкой аутентификации на адрес, указанный в `dest`, на основе совпадения `name`, `path` или `alpn`. ## Добавление DNS-записей ![DNS-записи](./fallbacks-with-sni-resources/xray-dns-records.webp) Измените домен и IP-адрес в соответствии с вашей ситуацией. ## Запрос TLS-сертификата Поскольку нам нужно разделять трафик по доменам с разными поддоменами, а wildcard-сертификат покрывает только домены между двумя точками (например, сертификат для `*.example.com` не будет действовать для `example.com` и `*.*.example.com`), нам нужно запросить wildcard-сертификат с [SAN](https://ru.wikipedia.org/wiki/Subject_Alternative_Name). Согласно информации на сайте Let's Encrypt[^1], для запроса wildcard-сертификата требуется проверка DNS-01. В этом руководстве мы рассмотрим, как запросить бесплатный TLS-сертификат Let's Encrypt с помощью [acme.sh](https://acme.sh) для домена, управляемого Cloudflare. Инструкции для других провайдеров DNS можно найти в [dnsapi · acmesh-official/acme.sh Wiki](https://github.com/acmesh-official/acme.sh/wiki/dnsapi). Сначала нужно создать API-токен на [панели управления Cloudflare](https://dash.cloudflare.com/profile/api-tokens). Параметры следующие: ![Настройка прав доступа API-токена](./fallbacks-with-sni-resources/cf-api-token-permissions-for-acme.webp) Настройка прав доступа очень важна, остальные параметры можно оставить по умолчанию. После создания вы получите строку символов - это и есть ваш API-токен (`CF_Token`). Сохраните его в надежном месте, так как он больше не будет отображаться. ::: tip Внимание Следующие действия необходимо выполнять от имени пользователя root. Использование sudo может привести к ошибкам. ::: ```bash curl https://get.acme.sh | sh # Установка acme.sh export CF_Token="sdfsdfsdfljlbjkljlkjsdfoiwje" # Установка переменной окружения с API-токеном acme.sh --issue -d example.com -d *.example.com --dns dns_cf # Запрос сертификата с проверкой DNS-01 mkdir /etc/ssl/xray # Создание каталога для хранения сертификата acme.sh --install-cert -d example.com --fullchain-file /etc/ssl/xray/cert.pem --key-file /etc/ssl/xray/privkey.key --reloadcmd "chown nobody:nogroup -R /etc/ssl/xray && systemctl restart xray" # Установка сертификата в указанный каталог и настройка команды для автоматического перезапуска Xray после обновления сертификата ``` ## Конфигурация Xray ```json { "log": { "loglevel": "warning" }, "inbounds": [ { "port": 443, "protocol": "vless", "settings": { "clients": [ { "id": "UUID", "flow": "xtls-rprx-vision" } ], "decryption": "none", "fallbacks": [ { "name": "example.com", "path": "/vmessws", "dest": 5000, "xver": 1 }, { "dest": 5001, "xver": 1 }, { "alpn": "h2", "dest": 5002, "xver": 1 }, { "name": "blog.example.com", "dest": 5003, "xver": 1 }, { "name": "blog.example.com", "alpn": "h2", "dest": 5004, "xver": 1 } ] }, "streamSettings": { "network": "tcp", "security": "tls", "tlsSettings": { "alpn": ["h2", "http/1.1"], "certificates": [ { "certificateFile": "/etc/ssl/xray/cert.pem", "keyFile": "/etc/ssl/xray/privkey.key" } ] } } }, { "listen": "127.0.0.1", "port": 5000, "protocol": "vmess", "settings": { "clients": [ { "id": "UUID" } ] }, "streamSettings": { "network": "ws", "wsSettings": { "acceptProxyProtocol": true, "path": "/vmessws" } } } ], "outbounds": [ { "protocol": "freedom" } ] } ``` Эта конфигурация предназначена для Nginx. Обратите внимание на следующие детали: - О Proxy Protocol Proxy Protocol - это протокол, разработанный HaProxy для решения проблемы потери информации о клиенте при проксировании, часто используемый в цепочках прокси-серверов и обратных прокси. Традиционные методы решения этой проблемы, как правило, сложны и имеют много ограничений, в то время как Proxy Protocol очень прост - он просто добавляет пакет данных с информацией об исходном соединении (четверка "источник-назначение:порт") при передаче данных. У всего есть свои плюсы и минусы, и Proxy Protocol не исключение. - Если есть отправка, должен быть и прием, и наоборот. - Один и тот же порт не может одновременно поддерживать соединения с данными Proxy Protocol и без них (например, разные виртуальные хосты (server) Nginx на одном порту, что по сути является следствием предыдущего пункта)[^2][^3]. Если вы столкнулись с ошибками, убедитесь, что ваша конфигурация соответствует этим условиям. Здесь мы используем Proxy Protocol, чтобы целевой сервер, на который перенаправляется трафик, получал реальный IP-адрес клиента. Кроме того, если в конфигурации входящего трафика Xray есть `"acceptProxyProtocol": true`, ReadV будет отключен. - О HTTP/2 Во-первых, порядок элементов в `inbounds.streamSettings.tlsSettings.alpn` важен: `h2` должен быть перед `http/1.1`, чтобы обеспечить совместимость при использовании HTTP/2. Обратный порядок приведет к тому, что HTTP/2 будет понижен до HTTP/1.1 во время согласования, делая конфигурацию недействительной. В приведенной выше конфигурации каждая запись fallback для Nginx разделена на две. Это связано с тем, что h2 - это обязательное зашифрованное соединение HTTP/2, что хорошо для безопасности передачи данных в Интернете, но не нужно внутри сервера. h2c же - это незашифрованное соединение HTTP/2, подходящее для этой среды. Однако Nginx не может одновременно прослушивать HTTP/1.1 и h2c на одном порту. Чтобы решить эту проблему, необходимо указать `alpn` (в разделе `fallbacks`, а не `tlsSettings`), чтобы сопоставить результаты согласования TLS ALPN. Рекомендуется указывать `alpn` только в двух случаях[^4]: - опустить - `"h2"` Если вы используете Caddy, то вам не нужно так усложнять, поскольку он **может** одновременно прослушивать HTTP/1.1 и h2c на одном порту. Изменения в конфигурации следующие: ```json { "fallbacks": [ { "name": "example.com", "path": "/vmessws", "dest": 5000, "xver": 1 }, { "dest": 5001, "xver": 1 }, { "name": "blog.example.com", "dest": 5002, "xver": 1 } ] } ``` ## Конфигурация Nginx Nginx будет установлен из официального репозитория. ```bash sudo apt install curl gnupg2 ca-certificates lsb-release echo "deb [arch=amd64] http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \ | sudo tee /etc/apt/sources.list.d/nginx.list curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add - sudo apt update sudo apt install nginx ``` Удалите `/etc/nginx/conf.d/default.conf` и создайте `/etc/nginx/conf.d/fallbacks.conf` со следующим содержимым: ```nginx set_real_ip_from 127.0.0.1; real_ip_header proxy_protocol; server { listen 127.0.0.1:5001 proxy_protocol default_server; listen 127.0.0.1:5002 proxy_protocol default_server http2; location / { root /srv/http/default; } } server { listen 127.0.0.1:5003 proxy_protocol; listen 127.0.0.1:5004 proxy_protocol http2; server_name blog.example.com; location / { root /srv/http/blog.example.com; } } server { listen 80; return 301 https://$host$request_uri; } ``` ## Конфигурация Caddy Инструкции по установке Caddy можно найти в [Install — Caddy Documentation](https://caddyserver.com/docs/install). Чтобы Caddy мог получать реальный IP-адрес посетителя, необходимо скомпилировать Caddy с модулем Proxy Protocol. Рекомендуется скомпилировать его прямо на сайте Caddy. ```bash sudo curl -o /usr/bin/caddy "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fmastercactapus%2Fcaddy2-proxyprotocol&idempotency=79074247675458" sudo chmod +x /usr/bin/caddy ``` Просто замените существующий бинарный файл. ::: tip Рекомендуется сначала установить Caddy, следуя инструкциям на официальном сайте, а затем заменить бинарный файл. Это избавит от необходимости настраивать запуск сервиса вручную. ::: Отредактируйте `/etc/caddy/Caddyfile`: ```Caddyfile { servers 127.0.0.1:5001 { listener_wrappers { proxy_protocol } protocol { allow_h2c } } servers 127.0.0.1:5002 { listener_wrappers { proxy_protocol } protocol { allow_h2c } } } :5001 { root * /srv/http/default file_server log bind 127.0.0.1 } http://blog.example.com:5002 { root * /srv/http/blog.example.com file_server log bind 127.0.0.1 } :80 { redir https://{host}{uri} permanent } ``` ## Ссылки 1. [Указание имени сервера - Википедия](https://ru.wikipedia.org/wiki/Указание_имени_сервера) 2. [Home · acmesh-official/acme.sh Wiki](https://github.com/acmesh-official/acme.sh/wiki) 3. [HTTP/2 - Википедия](https://ru.wikipedia.org/wiki/HTTP/2) ## Примечания [^1]: [Часто задаваемые вопросы - Let's Encrypt - бесплатные SSL/TLS сертификаты](https://letsencrypt.org/ru/docs/faq/) [^2]: [Proxy Protocol - HAProxy Technologies](https://www.haproxy.com/blog/haproxy/proxy-protocol/) [^3]: [proxy protocol 介绍及 nginx 配置 - 简书](https://www.jianshu.com/p/cc8d592582c9) [^4]: [v2fly-github-io/vless.md at master · rprx/v2fly-github-io](https://github.com/rprx/v2fly-github-io/blob/master/docs/config/protocols/vless.md)