Repository navigation
Releases: Medium1992/Xray-core-fork
Release list
v26.10.21-mux-ka-25
Сборка 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 ГиБ. Что действует, если параметр не задан:На стрим На соединение Сервер с h2Flow6 МиБ 6 МиБ Сервер без h2Flow1 МиБ 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/netv0.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 МБ. Медианы пяти раундов в случай...
v26.10.10-mux-ka-17
Сборка 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 ГиБ. Что действует, если параметр не задан:На стрим На соединение Сервер с h2Flow6 МиБ 6 МиБ Сервер без h2Flow1 МиБ 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/netv0.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 ...