|
|
---
|
|
|
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)
|
|
|
|
|
|
|