Тестируем XHTTP

Спасибо. В таком случае этот gRPC конфиг будет работать без проблем, или можно что-то добавить/отредактировать?

location /justthepath {
    if ($content_type !~ "application/grpc") {
        return 404;
    }
    client_max_body_size 0;
    client_body_buffer_size 512k;
    grpc_set_header X-Real-IP $remote_addr;
    client_body_timeout 52w;
    grpc_read_timeout 52w;
    grpc_pass grpc://127.0.0.1:1111;
}

должно работать без проблем, у меня самого такой же.

а дальше все зависит от CDN через которую вы проксируетесь (например часто работает только один из трех режимов).

Все же решусь задать вопрос, так как многие люди в этой ветке видимо понимают, о чем пишут.

Существует ли какое-то внятное описание работы xhttp в xray, за исключением всем известной публикации XHTTP: Beyond REALITY в гитхабе проекта? Много раз пытался хоть что-то понять из этого текста - к сожалению, так ничего и не понял. Там какой-то странный стиль повествования для своих, в котором очень много слов на тему “как лихо мы придумали”, но совершенно не просматривается объяснение базовой концепции: что это, как это работает, почему это работает… Изначально прочитав этот текст, я вообще думал, что это история исключительно про работу с CDN. Потом я наткнулся на сообщения на ntc, из которых следовало, что режим работает и без CDN, и вроде как даже иногда дает какие-то бонусы.

Если кто-то может внятно объяснить, что же это такое, либо если вы где-то видели более доступное описание технологии - пожалуйста, дайте знать.

У меня тоже была такая же конфигурация Nginx, и сначала всё работало неплохо, но потом сервер стал сильно зависать.

Я посмотрел логи Nginx, Xray-core и статистику в 3X-UI. Из подозрительного обнаружил большую нагрузку на процессор (50 % в среднем) и много TCP-подключений в состоянии TIME-WAIT между Nginx и Xray-core. Подключений было 500-2000 (количество постоянно менялось). Пользователей у моего прокси-сервера должно быть немного, людей 10, так что такое количество подключений мне показалось подозрительным.

Я подумал, что такое количество подключений может быть связано с неправильной настройкой таймаутов в конфигурации Nginx. Как и у вас, у меня client_body_timeout и grpc_read_timeout были настроены на 52 недели (52w). Зачем вообще могут пригодиться такие большие таймауты? Я не знаю. На всякий случай переписал их на 60 секунд (60s) каждый.

Ещё в этот день у меня почему-то Xray-core почти каждую минуту перезагружался сам по себе. В логах ничего подозрительного не увидел, поэтому от безысходности просто обновил его до последней версии. Это помогло: Xray-core перезагружаться перестал.

Ещё добавил фильтрацию в Nginx, пропускающую подключения только с активированным мультиплексированием HTTP/2.0. Это мультиплексирование gRPC, которое можно включить в клиенте и в Xray-core. Решил пропускать подключения только с мультиплексированием, чтобы ещё сильнее уменьшить количество TCP-подключений внутри сервера.

С тех пор сервер работает стабильно, нагрузка на процессор 10-25 % в среднем, количество TCP-подключений между Nginx и Xray-core — 100-300.

Совершенно точно могу сказать, что проблема была не только в таймаутах Nginx, ведь мне также пришлось обновить Xray-core.

Была ли проблема в том числе в таймаутах Nginx? Честно говоря, не знаю.

Хотя количество TCP-соединений внутри сервера уменьшилось, но это могло случиться из-за того, что я добавил фильтрацию, разрешающую подключения только с мультиплексированием. К тому же уменьшилось незначительно.

Не знаю, действительно ли таймауты в 52 недели — это ошибка, и помогло ли решить проблему, что я уменьшил их до 60 секунд, но в любом случае 52 недели — это выглядит странно.

А как другие участники форума думают, — зачем нужны такие таймауты? В 52 недели-то. Почему все их используют? Я вот просто скопировал конфигурацию, но уже не помню откуда. Но зачем изначально в этой конфигурации кто-то такие странные значения прописал? Я понять не могу.

52w было у MiraclePtr с habr.com.

“Ещё добавил фильтрацию в Nginx, пропускающую подключения только с активированным мультиплексированием HTTP/2.0. Это мультиплексирование gRPC, которое можно включить в клиенте и в Xray-core. Решил пропускать подключения только с мультиплексированием, чтобы ещё сильнее уменьшить количество TCP-подключений внутри сервера.”

А как это включить? Я не использую панели, чистый Xray.

В конфигурации Xray-core на сервере в inbound в блоке "grpcSettings" нужно прописать "multiMode": true.

Пример Inbound с включённым мультиплексированием:

{
  "listen": "127.0.0.1",
  "port": 13337,
  "protocol": "vless",
  "settings": {
    "clients": [
      {
        "email": "my-proxy",
        "flow": "",
        "id": "655e89f4-f13e-4-4234sdfsc8331c0a"
      }
    ],
    "decryption": "none",
    "fallbacks": []
  },
  "sniffing": {
    "destOverride": [
      "http",
      "tls",
      "quic",
      "fakedns"
    ],
    "enabled": true,
    "metadataOnly": false,
    "routeOnly": false
  },
  "streamSettings": {
    "grpcSettings": {
      "authority": "my.website.ru",
      "multiMode": true,
      "serviceName": "gRPC-534534-ter-43t4td"
    },
    "network": "grpc",
    "security": "none"
  },
  "tag": "inbound-127.0.0.1:13337"
}

В клиенте аналогично: в inbound в блоке "grpcSettings" нужно добавить "multiMode".

Сделать это можно двумя способами:

  1. Включить через интерфейс. Например, в v2rayN в конфигурации подключения есть поле *grpc mode, там из выпадающего списка можно выбрать режим multi.

  2. Прописать в ссылке для подключения, чтобы при импорте конфигурации мультиплексирование настроилось автоматически.

Пример ссылки:

vless://655e89f4-f13e-4-4234sdfsc8331c0a@my.website.ru:443?type=grpc&serviceName=gRPC-534534-ter-43t4td&authority=my.website.ru&mode=multi&security=tls&alpn=h2&fp=chrome&sni=my.website.ru#MyProxy

Подобную ссылку правильно прочитает v2rayN, v2rayNG и Streisand (я проверил). Вероятно, и другие клиенты с ядром Xray-core будут читать правильно.

Nekoray старой версии правильно прочитать не смог: мультиплексирование не включилось. В новой версии Nekoray вырезали поддержку ядра Xray-core, оставив только SingBox, и теперь у меня там gRPC вообще не работает. Вероятно, и другие клиенты на SingBox нормально работать с gRPC больше не будут.

Настраивать фильтрацию в Nginx на мультиплексирование необязательно. Просто у меня ссылка для подключения общедоступная, в итоге некоторые пользователи зачем-то отключают мультиплексирование и подключаются без него, из-за чего дополнительно нагружают сервер.

Спасибо! В Shadowrocket, как я понял, за мультиплексирование отвечает “Muxing”.
Screenshot 2025-05-18 at 19.03.51

Во-первых, у вас речь про gRPC-транспорт, а это тема про XHTTP. Это, мягко говоря, совсем разные вещи :slight_smile:

А ставить длинные таймауты имеет смысл как минимум потому, что в реальной жизни очень часто TCP-подключения живут сильно дольше 60 секунд. Простой пример - вы смотрите видео на Ютубе, видео может длиться больше часа, и большую часть этого времени для скачивания кусков видеофайла (чанков) будет использоваться одно и то же соединение. Если вы огрраничите его таймаутом, то соединения могут захлопываться в самый неподходящий момент, заставляя клиента их постоянно переоткрывать (что требует времени - падает производительность). Или еще хуже - вы скачиваете из сети большой файл, скачивание занимает более 60 секунду - тогда через 60 секунд ваше скачивание в лучше случае замедляется (клиент вынужден переоткрывать соединение и продолжать процесс), а в худшем случаепросто отваливается с ошибкой типа CONN_RESET и надо начинать заново.

multiMode не связан с мультиплексированием

для мультиплекса нужен только http/2 (в xray уже включен, в nginx нужно включать примерно так listen 443 ssl default_server http2)

gRPC в теме о XHTTP

Я понимаю, что тема о XHTTP, но увидел, что целых два человека используют «конские» таймауты, которые могут привести к проблемам. Поэтому решил предупредить, что это чревато.

К тому же XHTTP всё-таки использует внутри себя gRPC, так что подобный конфиг вполне может использоваться и для XHTTP.

Таймауты

Вы правильно считаете, что лучше не делать их слишком маленькими, но, скорее всего, немного путаете их предназначение.

Это не счётчики, которые определяют время жизни активного соединения. Таймауты отсчитывают время после прекращения передачи данных, «отрезая» зависшие или медленные соединения. Они не действуют на соединения, данные в которых активно передаются.

При нормальной работе прокси-сервера таймаутов в 60 секунд должно вполне хватать, к тому же это значение для большинства таймаутов в Nginx по умолчанию. Если данные не передаются более 60 секунд, скорее всего, с соеднинением что-то случилось, поэтому лучше его закрыть.

Если конкретнее, в контексте создания прокси-серверов вместе с Xray-core в конфигурации Nginx обычно используются следующие таймауты [1, 2]:

1. keepalive_timeout

keepalive_timeout определяет, сколько времени соединение будет поддерживаться открытым после обработки последнего запроса. Значение в 60s будет держать соединение открытым до тех пор, пока с момента обработки последнего запроса не пройдёт 60 секунд. Тогда Nginx закроет это соединение.

Например, в YouTube скачивание фрагментов видео происходит примерно каждые 2 секунды, то есть 60 секунд ожидания должно хватить.

Конечно, если поставить видео на паузу и подождать 60 секунд, соединение закроется. Но за это время успеет накопиться большой буфер (в 20 секунд, например), и продолжив воспроизведение, вряд ли вы заметите, что установление нового соединения задержало загрузку буфера на несколько миллисекунд.

Вы же, скорее всего, имели в виду keepalive_time — это, действительно, абсолютное ограничение времени жизни соединения, — несмотря на то, передаёт оно данные или зависло. Но это не таймаут.

2. client_header_timeout

client_header_timeout определяет, как долго Nginx будет ждать полной передачи заголовков HTTP-запроса от клиента.

Заголовки — это просто метаданные запроса, не сама полезная нагрузка. Они не должны весить много, поэтому передаваться обычно должны меньше секунды. 60 секунд точно хватит, чтобы их передать. В ином случае соединение, скорее всего, зависло, либо слишком медленное, поэтому лучше его закрыть.

3. client_body_timeout

client_body_timeout определяет, как долго Nginx будет ждать после операции чтения тела запроса, если тело так и не было отправлено до конца. Похоже на keepalive_timeout, но разница в том, что это ожидание не после каждого запроса, а после каждой операции чтения тела запроса.

То есть, если клиент начал отправлять запрос, но вдруг остановился, так и не отправив его до конца, Nginx подождёт 60 секунд, прежде чем закрыть соединение. Это снова нужно для того, чтобы закрывать зависшие соединения.

Например, если вы что-то скачиваете, то это может быть только один файл. Может быть, в таком случае скачивание будет считаться за единый запрос. В таком случае соединение закроется только в том случае, если передача данных прекратилась на 60 секунд.

Ладно: возможно, в этом случае можно и побольше сделать таймаут, например в 2-3 минуты, чтобы уменьшить вероятность того, что скачивание большого файла начнётся заново (если это вообще так работает. То есть если переустановление соединения точно приведёт к полной потере прогресса загрузки. Но я сомневаюсь, что это действительно так работает).

4. grpc_read_timeout

grpc_read_timeout определяет, как долго Nginx будет ждать ответа от Xray-core, чтобы вернуть клиенту то, что он запросил. Если Xray-core в течение 60 секунд так и не ответит, Nginx закроет соединение.

Что в итоге?

Честно говоря, я не очень понимаю, как gRPC используется в XHTTP, но в описании XHTTP написано, что для того, чтобы XHTTP проходил через Cloudflare, в настройках Cloudflare нужно включить поддержку gRPC. Таким образом, gRPC явно как-то задействуется в XHTTP.

Таймауты же нужны, чтобы закрывать зависшие соединения. Они не трогают активные соединения. И 60 секунд — обычно достаточный запас ожидания, чтобы соединение «ожило». Если за это время оно не «оживёт», лучше его закрыть, чтобы оно не занимало память. В крайнем случае можно сделать таймауты в 2-3 минуты, но зачем держать мёртвое соединение 52 недели?

Не использует.

И это тоже очень важно. Благодаря длинным таймаутам XRay может переиспользовать соединения с сервером, а не создавать каждый раз новое делая в очередной раз хендшейк (это тупо очень медленно). А для XHTTP это еще важнее, потому что там вся суть именно в переиспользовании долго висящих подключений.

Вот это для XHTTP очень важно в некоторых режимах - там два раздельных подключения на прием и передачу, и прием может идти потоком именно таким образом - сначала прием для одной сессии (соединения внутри подключерия к прокси), потом для другой, потом для третьей. Для стороннего наблюдателя, такого как веб-сервер, это выглядит именно как “какие-то данные пришли, но не до конца”.

Никаким образом не задействует. grpc-тип в заголовке нужен только для того, чтобы обмануть Cloudflare и Nginx и заставить их не анализировать каждый запрос-ответ, а передавать данные “как есть” тупо перекидывая пакеты туда-сюда. В случае ипользования других CDN и веб-серверов он не нужен. И даже когда он есть, по факту gRPC-либа и gRPC-протокол никак не используются. Это именно что просто добавляется grpc content-type к тому, что на самом деле никаким grpc не является.

Это вы у авторов XRay спросите - это их официальная рекомендация настраивать именно так.

Спасибо. Действительно, я ошибся. Что-то я часто ошибаюсь, оказывается.

Я тут посмотрел видос https://youtu.be/GB_SHmqotzQ?si=qTZxCuHJcd3Iwvos
Я правильно понимаю, что для реализации xhttp + cdn, мне домен именно на cloudflare?

Cloudflare это один из cdn. На каком cdn нужна работа xhttp на том же и домен нужен соответственно. Другие варианты это напр. Cloudfront или gcore. (Хотя на всех трех работает вебсокет который проще настраивать)

GCore конечно хорош бесплатным тарифом, но у меня в городе заблочен как минимум на двух проводных провайдерах (Я, конечно, понимаю, что раз заблочен у меня, не значит, что заблочен везде, но раз уж где-то сделали и не откатили, могут потом и на всю страну раскатать). На Cloudfront, если я не ошибаюсь, нужна зарубежная карта, если там конечно вообще есть бесплатные тарифы. Остается только Cloudflare.

Сегодня протестировал Gcore и Fastly. У меня заработал только packet-up. У всех так или я что-то неправильно делаю?

да, в большинстве CDN работает только packet-up.

работающий stream-up - это уже удача, а stream-one - вообще невероятное везение, только если CDN’ку удасться обмануть и пропихнуть XHTTP под видом gRPC.

Развер stream-one не худший вариант? Он ведь не разделяет потоки?

не разделяет. но зачем-то же его оставили :slight_smile:

По вашему мнению, через Cf что лучше всего использовать stream-up или stream-one?