Skip to content

Releases: Medium1992/Xray-core-fork

v26.10.21-mux-ka-25

Choose a tag to compare

Сборка upstream Xray-core v26.10.21 с тремя добавками: Mux.Cool поверх XHTTP с серверным KeepAlive, регулятор окон HTTP/2 для XHTTP (h2Flow) и пониженные буферы. Клиенты остаются официальными: всё работает с обычным Xray, изменений на клиенте не требуется.

Постоянные ссылки на последнюю версию (всегда ведут на актуальный релиз): страница, linux/amd64, linux/arm64.

Mux.Cool поверх XHTTP

Что делает. В upstream XHTTP-инбаунд принимает от Mux.Cool только XUDP. Здесь он принимает и обычные TCP-сессии Mux.Cool, так что клиент может сложить несколько соединений в одну XHTTP-сессию. Дополнительно сервер умеет «подталкивать» простаивающий downlink служебным кадром Mux.Cool KeepAlive, чтобы промежуточные узлы (CDN, NAT) не рвали молчащее соединение. Клиент такой кадр просто отбрасывает.

Как задать. На клиенте включается обычный mux в outbound. На сервере KeepAlive задаётся в xhttpSettings.extra инбаунда (по умолчанию выключен):

"xhttpSettings": {
  "extra": {
    "muxKeepAliveSecs": "16-20",
    "muxKeepAliveBytes": "64-256"
  }
}
  • muxKeepAliveSecs — через сколько секунд тишины в downlink слать кадр; диапазон, значение выбирается случайно. Не задан — KeepAlive выключен.
  • muxKeepAliveBytes — размер случайного паддинга в кадре; диапазон, до 1024. Не задан — кадр без паддинга (6 байт).
  • Работает в режимах packet-up и stream-up; в stream-one не шлётся. Без mux на клиенте параметры ни на что не влияют.

Регулятор окон HTTP/2 (h2Flow)

Что делает. Go выдаёт фиксированные окна HTTP/2: сервер 1 МиБ на стрим и на соединение, клиент 4 МиБ на стрим. Медленный читатель держит столько непрочитанного в памяти на каждый стрим, а на общем XHTTP-соединении эти данные встают в очередь перед всеми остальными стримами. Регулятор стоит между TLS/REALITY и HTTP/2 и выдаёт кредит по тому, сколько читатель реально забирает и какая очередь на пути: быстрый поток получает окно больше стокового, медленный — меньше, а короткие запросы рядом с большой передачей не ждут её. Клиенту ничего менять не нужно.

Чужая очередь. Сервер урезает окна загрузок, когда видит очередь на пути к клиенту, чтобы его же данные не стояли перед другими стримами. Если очередь держит чужой трафик (перегруженная вечером магистраль, соседняя закачка), урезание её не укорачивает, и в ka-17 загрузка проседала почти до нуля: на стенде 60 Мбит/с и RTT 144 мс четыре чужих потока опускали её с 56 до 0–1 Мбит/с. Теперь каждое соединение само измеряет, действует ли урезание: насколько упала очередь в ответ на то, насколько урезание сократило данные в пути, окна и пропускаемую ими скорость. Урезание масштабируется этим отношением: против своей очереди работает как раньше, против чужой — нет, и тогда окно следует за RTT, сохраняя скорость. Соединения не группируются по IP, в трафик ничего не подмешивается. На том же стенде загрузка держит в среднем 23 Мбит/с вместо 10 и 16 вместо 0,8 во второй половине 20-секундного всплеска; при 90-секундном всплеске вторая половина — 10,6 Мбит/с (ka-17 — 0,4, сервер без регулятора — 14), и после всплеска скорость возвращается за 2 с. Снижением считается только то, что срезал сам регулятор: потеря пакетов, следование окна за RTT и колебание чужой очереди его не обманывают. Рядом со своей нагрузкой задержки те же, что в ka-17.

Как задать. По умолчанию выключен. Включается на каждом инбаунде или outbound отдельно:

"xhttpSettings": {
  "extra": {
    "h2Flow": {
      "enabled": true,
      "maxStreamReceiveWindow": 16777216,
      "maxConnectionReceiveWindow": 33554432,
      "maxConnectionSendWindow": 4194304,
      "queueCuts": true
    }
  }
}
  • enabled — включить регулятор здесь. Глобально то же самое делает переменная окружения XRAY_XHTTP_FLOW=on; enabled в конфиге её перекрывает.

  • maxStreamReceiveWindow, maxConnectionReceiveWindow — потолки окон приёма: сколько эта сторона готова держать непрочитанного на один стрим и на всё соединение. От 65535 до 1 ГиБ. Что действует, если параметр не задан:

    На стрим На соединение
    Сервер с h2Flow 6 МиБ 6 МиБ
    Сервер без h2Flow 1 МиБ 1 МиБ
    Клиент (с h2Flow и без) 4 МиБ 1 ГиБ
  • maxConnectionSendWindow — только на сервере: сколько данных сервера клиент одного соединения может держать у себя по всем стримам (непрочитанное плюс неиспользованное окно). По умолчанию 4 МиБ; -1 — без лимита; иначе от 65535 до 1 ГиБ. На клиенте параметр ничего не делает. Нужен клиентам с лимитом памяти (сетевое расширение iOS, 40 МБ): см. раздел ниже.

    • Мост с собственным h2Flow лимитом сервера не ограничивается: такой клиент объявляет стандартное окно 65535, и сервер отдаёт ему всё, что тот вернул, иначе два регулятора могли бы остановить загрузку. Память моста ограничивает его собственный регулятор. Отдельной настройки для моста на инбаунде не нужно.
    • Лимит берётся как задан, какое бы окно ни объявил клиент, поэтому его можно поставить и ниже 4 МиБ. Чем он меньше, тем меньше держит клиент и тем ниже скорость на длинном пути: см. таблицу ниже.
    • Каждому стриму всегда остаётся 16 КиБ (один кадр), так что ни один не ждёт остальных.
  • queueCuts — только на сервере: урезает ли регулятор загрузки против очереди на пути к клиенту. По умолчанию true: очередь держится короткой, и запрос рядом с большой загрузкой проходит за 52–78 мс; против очереди, которую держит чужой трафик, загрузка уступает часть доли (в среднем 23 Мбит/с из 60 при четырёх чужих потоках). false отдаёт очередь TCP: загрузка держит свою долю, как без регулятора (36 Мбит/с в том же тесте), но запрос рядом с нагрузкой ждёт дольше — 150–265 мс. Окна по читателю и лимиты памяти работают в обоих режимах. false имеет смысл на инбаунде для мостов, через которые идёт трафик многих пользователей: их просадка бьёт по всем сразу.

  • Где включать. На сервере — всегда там, куда приходит трафик. На мосту или роутере (Xray как клиент) в outbound — только если сервер без h2Flow: тогда клиентский регулятор держит очередь короткой сам (запрос рядом с восемью загрузками 93 мс вместо 1,3 с). Перед сервером с h2Flow его не включать: очередь уже держит сервер (65 мс без клиентского регулятора, 91 мс с ним), а при чужом трафике на пути клиентский регулятор отнимает скорость (вторая половина 20-секундного всплеска: 6,8 Мбит/с против 16,7). Совместимы они в любом сочетании.

  • downloadSettings — отдельный конфиг со своим extra: h2Flow из внешнего extra на него не распространяется, его надо задать и внутри downloadSettings.xhttpSettings.extra (или включить глобально переменной окружения).

  • Только HTTP/2 поверх TCP (REALITY, TLS, h2c). HTTP/1.1 и HTTP/3 не затрагиваются.

Клиенты с лимитом памяти

Лимит стрима следует за своим читателем, но много стримов, стартовавших разом на быстром канале, в первый момент все выглядят быстрыми, и клиент держит их сумму. Стенд: канал 1 Гбит/с, RTT 50 мс, клиент — официальный Xray с тремя соединениями, двадцать загрузок, каждую читают по 100 КБ/с. Анонимная память клиента снимается каждые 50 мс; выше 40 МБ клиент убивается, как сетевое расширение iOS.

20 медленных читателей, 1 Гбит/с XHTTP h2Flow без лимита (-1) h2Flow, 4 МиБ
Память клиента: медиана / худший прогон, МБ 44 / 45 43 / 44 24 / 25
Клиент убит по лимиту 40 МБ 5 из 5 5 из 5 0 из 5
Запрос рядом p50 / p95, мс не проходят не проходят 52 / 115

То же самое проверено на семи сборках клиента: релизы v26.10.10, v26.9.30, v26.7.28, v26.5.9 и v26.10.10, собранный с http2legacy, с golang.org/x/net v0.61.0 и с обоими. С лимитом у всех 23–25 МБ и ни одного убитого; без лимита убиты все.

Цена лимита — суммарная скорость многих параллельных загрузок одного соединения на длинном RTT: она не превышает «лимит ÷ RTT». При значении по умолчанию одна загрузка не теряет ничего.

3 соединения, 1 Гбит/с RTT h2Flow без лимита h2Flow, 4 МиБ
8 загрузок, Мбит/с 50 мс 913 911
150 мс 822 570
300 мс 593 247
Одна загрузка, Мбит/с 50 мс 597 606
300 мс 82 79

Память процесса клиента складывается из трёх частей: данные в окнах («лимит × число соединений» плюс 16 КиБ на стрим сверх лимита), собственные расходы клиента на каждую открытую загрузку (около 60 КБ: стеки четырёх горутин и объекты) и запас сборщика мусора Go, который при активной работе доходит до размера живых данных. Сервер управляет только первой частью, поэтому при сотнях одновременных загрузок на трёх соединениях значение по умолчанию уже не спасает, и лимит надо ставить ниже:

1 Гбит/с, 3 соединения 4 МиБ 2 МиБ 1 МиБ
20 медленных читателей: память клиента, МБ 24 17 15
192 медленные загрузки: память клиента, МБ 40, убит 4 из 5 35, не убит 33, не убит
Одна загрузка, RTT 50 мс, Мбит/с 546 314 158
Одна загрузка, RTT 150 мс, Мбит/с 193 98 49
8 загрузок, RTT 150 мс, Мбит/с 576 295 149

Оговорки: клиент на стенде — Xray для linux/amd64, а не iOS; буферы сокетов ядра не учитываются; всплеск короче 50 мс мог быть пропущен.

Сборка

  • Go 1.27.2, golang.org/x/net v0.61.0, тег http2legacy (собственный пул HTTP/2-соединений x/net: один дозвон на слот xmux, а не на каждый ждущий запрос).
  • Пониженные буферы для серверов и транзитов: пайп диспетчера 64 КиБ вместо 512 КиБ, рабочий буфер HTTP/2-аплоада 32 КиБ вместо 512 КиБ.
  • Бинарники linux/amd64 и linux/arm64 без архивов и geo-файлов.

Сравнение транспортов

Сервер — эта сборка, клиент — официальный Xray v26.10.10, везде REALITY. Linux-стенд: два сетевых пространства, канал 100 Мбит/с с RTT 50 мс (если не указано иное), буферы TCP до 32 МБ. Медианы пяти раундов в случай...

Read more

v26.10.10-mux-ka-17

Choose a tag to compare

Сборка upstream Xray-core: ветка main после v26.10.10 (2bea2df6) с тремя добавками: Mux.Cool поверх XHTTP с серверным KeepAlive, регулятор окон HTTP/2 для XHTTP (h2Flow) и пониженные буферы. Клиенты остаются официальными: всё работает с обычным Xray, изменений на клиенте не требуется.

Mux.Cool поверх XHTTP

Что делает. В upstream XHTTP-инбаунд принимает от Mux.Cool только XUDP. Здесь он принимает и обычные TCP-сессии Mux.Cool, так что клиент может сложить несколько соединений в одну XHTTP-сессию. Дополнительно сервер умеет «подталкивать» простаивающий downlink служебным кадром Mux.Cool KeepAlive, чтобы промежуточные узлы (CDN, NAT) не рвали молчащее соединение. Клиент такой кадр просто отбрасывает.

Как задать. На клиенте включается обычный mux в outbound. На сервере KeepAlive задаётся в xhttpSettings.extra инбаунда (по умолчанию выключен):

"xhttpSettings": {
  "extra": {
    "muxKeepAliveSecs": "16-20",
    "muxKeepAliveBytes": "64-256"
  }
}
  • muxKeepAliveSecs — через сколько секунд тишины в downlink слать кадр; диапазон, значение выбирается случайно. Не задан — KeepAlive выключен.
  • muxKeepAliveBytes — размер случайного паддинга в кадре; диапазон, до 1024. Не задан — кадр без паддинга (6 байт).
  • Работает в режимах packet-up и stream-up; в stream-one не шлётся. Без mux на клиенте параметры ни на что не влияют.

Регулятор окон HTTP/2 (h2Flow)

Что делает. Go выдаёт фиксированные окна HTTP/2: сервер 1 МиБ на стрим и на соединение, клиент 4 МиБ на стрим. Медленный читатель держит столько непрочитанного в памяти на каждый стрим, а на общем XHTTP-соединении эти данные встают в очередь перед всеми остальными стримами. Регулятор стоит между TLS/REALITY и HTTP/2 и выдаёт кредит по тому, сколько читатель реально забирает и какая очередь на пути: быстрый поток получает окно больше стокового, медленный — меньше, а короткие запросы рядом с большой передачей не ждут её. Клиенту ничего менять не нужно.

Как задать. По умолчанию выключен. Включается на каждом инбаунде или outbound отдельно:

"xhttpSettings": {
  "extra": {
    "h2Flow": {
      "enabled": true,
      "maxStreamReceiveWindow": 16777216,
      "maxConnectionReceiveWindow": 33554432,
      "maxConnectionSendWindow": 4194304
    }
  }
}
  • enabled — включить регулятор здесь. Глобально то же самое делает переменная окружения XRAY_XHTTP_FLOW=on; enabled в конфиге её перекрывает.

  • maxStreamReceiveWindow, maxConnectionReceiveWindow — потолки окон приёма: сколько эта сторона готова держать непрочитанного на один стрим и на всё соединение. От 65535 до 1 ГиБ. Что действует, если параметр не задан:

    На стрим На соединение
    Сервер с h2Flow 6 МиБ 6 МиБ
    Сервер без h2Flow 1 МиБ 1 МиБ
    Клиент (с h2Flow и без) 4 МиБ 1 ГиБ
  • maxConnectionSendWindow — только на сервере: сколько данных сервера клиент одного соединения может держать у себя по всем стримам (непрочитанное плюс неиспользованное окно). По умолчанию 4 МиБ; -1 — без лимита; иначе от 65535 до 1 ГиБ. На клиенте параметр ничего не делает. Нужен клиентам с лимитом памяти (сетевое расширение iOS, 40 МБ): см. раздел ниже.

    • Мост с собственным h2Flow лимитом сервера не ограничивается: такой клиент объявляет стандартное окно 65535, и сервер отдаёт ему всё, что тот вернул, иначе два регулятора могли бы остановить загрузку. Память моста ограничивает его собственный регулятор. Отдельной настройки для моста на инбаунде не нужно.
    • Лимит берётся как задан, какое бы окно ни объявил клиент, поэтому его можно поставить и ниже 4 МиБ. Чем он меньше, тем меньше держит клиент и тем ниже скорость на длинном пути: см. таблицу ниже.
    • Каждому стриму всегда остаётся 16 КиБ (один кадр), так что ни один не ждёт остальных.
  • Где включать. На сервере — всегда там, куда приходит трафик. На мосту или роутере (Xray как клиент) — в outbound, тогда регулируется его загрузка. Мост с h2Flow перед сервером с h2Flow — рабочая схема.

  • downloadSettings — отдельный конфиг со своим extra: h2Flow из внешнего extra на него не распространяется, его надо задать и внутри downloadSettings.xhttpSettings.extra (или включить глобально переменной окружения).

  • Только HTTP/2 поверх TCP (REALITY, TLS, h2c). HTTP/1.1 и HTTP/3 не затрагиваются.

Клиенты с лимитом памяти

Лимит стрима следует за своим читателем, но много стримов, стартовавших разом на быстром канале, в первый момент все выглядят быстрыми, и клиент держит их сумму. Стенд: канал 1 Гбит/с, RTT 50 мс, клиент — официальный Xray с тремя соединениями, двадцать загрузок, каждую читают по 100 КБ/с. Анонимная память клиента снимается каждые 50 мс; выше 40 МБ клиент убивается, как сетевое расширение iOS.

20 медленных читателей, 1 Гбит/с XHTTP h2Flow без лимита (-1) h2Flow, 4 МиБ
Память клиента: медиана / худший прогон, МБ 44 / 45 43 / 44 24 / 25
Клиент убит по лимиту 40 МБ 5 из 5 5 из 5 0 из 5
Запрос рядом p50 / p95, мс не проходят не проходят 52 / 115

То же самое проверено на семи сборках клиента: релизы v26.10.10, v26.9.30, v26.7.28, v26.5.9 и v26.10.10, собранный с http2legacy, с golang.org/x/net v0.61.0 и с обоими. С лимитом у всех 23–25 МБ и ни одного убитого; без лимита убиты все.

Цена лимита — суммарная скорость многих параллельных загрузок одного соединения на длинном RTT: она не превышает «лимит ÷ RTT». При значении по умолчанию одна загрузка не теряет ничего.

3 соединения, 1 Гбит/с RTT h2Flow без лимита h2Flow, 4 МиБ
8 загрузок, Мбит/с 50 мс 913 911
150 мс 822 570
300 мс 593 247
Одна загрузка, Мбит/с 50 мс 597 606
300 мс 82 79

Память процесса клиента складывается из трёх частей: данные в окнах («лимит × число соединений» плюс 16 КиБ на стрим сверх лимита), собственные расходы клиента на каждую открытую загрузку (около 60 КБ: стеки четырёх горутин и объекты) и запас сборщика мусора Go, который при активной работе доходит до размера живых данных. Сервер управляет только первой частью, поэтому при сотнях одновременных загрузок на трёх соединениях значение по умолчанию уже не спасает, и лимит надо ставить ниже:

1 Гбит/с, 3 соединения 4 МиБ 2 МиБ 1 МиБ
20 медленных читателей: память клиента, МБ 24 17 15
192 медленные загрузки: память клиента, МБ 40, убит 4 из 5 35, не убит 33, не убит
Одна загрузка, RTT 50 мс, Мбит/с 546 314 158
Одна загрузка, RTT 150 мс, Мбит/с 193 98 49
8 загрузок, RTT 150 мс, Мбит/с 576 295 149

Оговорки: клиент на стенде — Xray для linux/amd64, а не iOS; буферы сокетов ядра не учитываются; всплеск короче 50 мс мог быть пропущен.

Сборка

  • Go 1.27.2, golang.org/x/net v0.61.0, тег http2legacy (собственный пул HTTP/2-соединений x/net: один дозвон на слот xmux, а не на каждый ждущий запрос).
  • Пониженные буферы для серверов и транзитов: пайп диспетчера 64 КиБ вместо 512 КиБ, рабочий буфер HTTP/2-аплоада 32 КиБ вместо 512 КиБ.
  • Бинарники linux/amd64 и linux/arm64 без архивов и geo-файлов.

Сравнение транспортов

Сервер — эта сборка, клиент — официальный Xray v26.10.10, везде REALITY. Linux-стенд: два сетевых пространства, канал 100 Мбит/с с RTT 50 мс (если не указано иное), буферы TCP до 32 МБ. Медианы пяти раундов в случайном порядке, 250 запусков. «Запрос рядом» — маленький запрос через тот же прокси во время нагрузки; запрос с ошибкой или дольше 10 с считается как 10 с.

Сценарий raw + Vision raw raw + mux XHTTP XHTTP + h2Flow
8 загрузок, 100 Мбит/с, RTT 50 мс: скорость, Мбит/с 94 94 93 94 92
— запрос рядом p50 / p95, мс 748 / 784 757 / 786 898 / 2364 1015 / 2233 66 / 245
— CPU сервера / клиента, с за 12 с 0,3 / 5,5 0,3 / 5,4 0,6 / 2,9 5,1 / 6,0 7,5 / 6,8
4 аплоада: скорость, Мбит/с 110 111 113 94 94
— запрос рядом p50 / p95, мс 822 / 968 831 / 1017 977 / 2457 181 / 305 79 / 213
— CPU сервера / клиента, с за 12 с 4,6 / 0,4 4,3 / 0,4 2,5 / 0,7 9,1 / 5,6 10,7 / 5,5
Одна загрузка, RTT 300 мс: Мбит/с 71 71 76 71 71
Один аплоад, RTT 300 мс: Мбит/с 92 89 93 23 74
2 загрузки + 3 медленных: запрос рядом p50 / p95, мс 490 / 1482 612 / 1481 1590 / 10000 549 / 1674 65 / 192
20 медленных читателей: память клиента / сервера, МБ 36 / 37 36 / 37 33 / 40 103 / 38 39 / 40
— запрос рядом p50 / p95, мс 155 / 688 155 / 691 2505 / 9196 966 / 1449 53 / 416
20 аплоадов в медленный получатель: запрос рядом p50 / p95, мс 155 / 696 155 / 708 639 / 10000 337 / 7554 52 / 429
— память сервера, МБ 36 36 34 39 40
Первые 3 МБ нового потока, RTT 150 ± 20 мс: с 3,4 3,5 2,3 2,9 2,8
Провалы канала до 3 Мбит/с: восстановление после 1-го / 2-го, с >12 / 6,9 >12 / 6,9 >12 / 7,0 9,4 / >8 0,7 / 0,7
Целостность: передач / объём / ошибок 39 / 66 МБ / 0 44 / 48 МБ / 0 53 / 77 МБ / 0 46 / 60 МБ / 0 52 / 106 МБ / 0

Что из этого следует:

  • Скорость у всех одинаковая, кроме аплоада: XHTTP без регулятора на длинном RTT упирается в окно соединения Go (23 Мбит/с при RTT 300 мс), с h2Flow — 74 Мбит/с; raw быстрее всех (89–113 Мбит/с; аплоад считается по отданному в сокет за 12 с, поэтому выходит чуть выше канала).
  • Отзывчивость под нагрузкой — главное отличие h2Flow: запрос рядом с загрузкой проходит за 65–80 мс вместо 0,5–1 с у остальных, и ни один запрос не потерялся (у raw + mux 36 запросов из серии не уложились в 10 с: всё идёт в одном TCP).
  • Память при медленных читателях: XHTTP без регулятора держит на клиенте 103 МБ, с h2Flow — 39 МБ, как raw.
  • Цена — процессор. XHTTP тратит на сервере в разы больше CPU, чем raw ...
Read more