VPN·HUB·CRACK
SMART-балансировщик 2.0 · VPN HUB CRACK
SMART-балансировщик 2.0
SMART-балансировщик 2.0
У типового сервиса два класса серверов. Обычные: заграничные, трафик дешёвый, работают, пока их не срежут блокировкой. И ноды обхода: вход в России с «белым» IP, дальше каскадом за границу; они держатся там, где обычные уже вырезаны, но трафик на них дорогой.
Хочется, чтобы клиент сам сидел на дешёвых, а на дорогие уходил автоматически и только в аварию. И это ещё не всё: одна и та же нода обхода работает у МТС и молчит у Мегафона, значит выбирать нужно ещё и между самими нодами обхода.
Обычный «Автовыбор» (leastPing) так не умеет: он берёт узел с лучшим пингом и не отличает «дешёвый» от «дорогого». Здесь разберём, как собрать одну плитку, которая решает всё это сама. Всё замерено на боевом сервисе, а не переписано из документации; неудачные ходы показаны наравне с рабочими: их-то в документации и нет.
Это продолжение статей «СУПЕР-АВТОВЫБОР» и «Балансировка и автовыбор». Базовые вещи про балансеры смотрите там, здесь только про приоритет и дорогой резерв.
🛠️ Конструктор: впиши свои теги и копируй
Впиши сверху страницы теги, которыми помечены твои хосты в поле Tag, они сами подставятся в конфиг ниже. Веса, интервал и остальное уже проставлены по замерам из статьи; хочешь тоньше, правь прямо в конфиге, всё разобрано по разделам.
1. Где вообще живёт логика
Первое, что стоит уложить в голове: подписка отдаёт клиенту не список серверов, а конфиг Xray с правилами. Внутри приложения (Happ, v2rayNG, Streisand) крутится то же ядро Xray, что и на сервере, и оно исполняет эти правила само. Решение, на какой узел пойти, принимается на устройстве абонента, поэтому оно учитывает именно его оператора и его сеть. Панель к этому моменту уже ни при чём: она собрала конфиг и отдала.
Практическое следствие честно: схема работает только у клиентов на ядре Xray. Проверено: Happ (iOS/Windows), v2rayNG, Streisand получают полноценный балансировщик; Clash, sing-box, Shadowrocket видят обычный список серверов без автопереключения. Это не «наполовину», это «у части клиентов не заработает вовсе», держите в уме, когда объявляете фичу.
Чтобы клиент получал JSON-конфиг, а не плоский список, в панели включается один тумблер под названием «Использовать JSON в базовой подписке»:

2. Стратегий балансировки ровно четыре
random (он же по умолчанию), roundRobin, leastPing, leastLoad. Всё. Никаких priority, weighted или failover в Xray не существует, говорю прямо, потому что их постоянно ищут и пытаются вписать в конфиг. Приоритет собирается не стратегией, а двумя другими механизмами: весами (costs) и аварийным выходом (fallbackTag).
3. Механизм 1: costs у leastLoad
costs это веса по регулярному выражению на тег узла. Работает только со стратегией leastLoad. Метрика узла умножается на квадратный корень из веса (value * math.Pow(cost, 0.5)), дальше сортировка по возрастанию: кто меньше, тот и выигрывает. То есть cost: 100 даёт множитель ×10, cost: 12 даёт примерно ×3.5.
Разложим на живой ситуации. Пусть реальные пинги такие:
| Узел | Реальный пинг | Вес | Итоговая метрика |
|---|---|---|---|
| обычный TCP | 200 мс | ×1 | 200 ← выигрывает |
| gRPC / xHTTP | 250 мс | ×3.5 | 875 |
| нода обхода | 300 мс | ×10 | 3000 |
Пока обычный TCP жив, он всегда впереди: даже с не самым лучшим пингом его метрика 200 против 875 и 3000. Дорогая нода обхода математически задвинута в самый конец.
А теперь важное свойство: мёртвые узлы вообще выпадают из кандидатов. Балансировщик проверяет каждый по трём условиям: живой (Alive), пинг меньше maxRTT, доля ошибок меньше tolerance. Не прошёл, его нет в выборке, множители к нему уже не применяются. Поэтому дорогая группа поднимается сама, когда дешёвая умерла: не нужно писать никаких «если TCP не отвечает, переключись». Всё, что осталось живого, участвует; costs лишь расставляет приоритет между живыми.
И ещё один параметр, без которого схема течёт: expected: 1. Он говорит «бери строго один лучший узел». Если его не задать, leastLoad наберёт несколько узлов и будет ходить по ним вперемешку, а это затянет в выборку и дорогие ноды, даже когда дешёвые в порядке. Один узел: один выбор.
4. Механизм 2: fallbackTag, и три его подводных камня
fallbackTag срабатывает в один момент: когда стратегия не вернула ни одного живого узла. Пул пуст, трафик уходит на запасной тег. Звучит просто, но каждый из трёх камней ниже стоил реальной отладки.
Камень 1: fallbackTag не может указывать на балансировщик, только на outbound. Балансировщик отдаёт строку-тег, которую диспетчер ищет в реестре outbound-хендлеров. А сами балансировщики лежат в другом реестре. Укажете фолбэк на тег балансера, в логе получите цепочку:
fallback to [POOL] → no qualified outbound → non existing outTag: POOL
...и трафик просто оборвётся. Фолбэк ведёт только на конкретный outbound (в нашем случае: на тег одной из нод обхода).
Камень 2: регистр имеет значение. Инжект хостов в Remnawave по правилу ^BYPASS$ создаёт outbound'ы с префиксом из шаблона: маленькими буквами (ihc...). Если в fallbackTag написать заглавными, узел не найдётся, и вы снова в цепочке non existing outTag. Реальный баг из практики: плитка годами имела нерабочий фолбэк, и никто не замечал, просто потому, что пул ни разу до конца не опустел. Пока есть хоть один живой узел, фолбэк не трогается, и опечатка спит.
Камень 3: фолбэк на узел внутри самого пула бесполезен. Соблазнительно поставить fallbackTag на основную группу: «пусть будет универсальная страховка». Но фолбэк срабатывает именно тогда, когда пул пуст, то есть когда все узлы пула мертвы. Страховка приведёт вас в такой же мёртвый узел. Смысл есть только в теге вне пула, в отдельной ноде обхода, которая в подборе по costs не участвует, но в реестре outbound живёт.
5. Ловушка, стоившая отдельной отладки: два observatory
В Xray два механизма проверки здоровья узлов: burstObservatory и обычный observatory со своим probeInterval. Возникает красивая идея: дешёвые ноды пинговать часто, дорогие редко, разнеся их по двум механизмам. Экономия же.
Идея не работает. Если заданы оба, leastLoad берёт данные только из обычного observatory. Burst-пробы при этом физически идут (в debug-логе честно видно burst: checking main), но балансировщик их не видит. И садится на дорогой резерв даже когда с дешёвыми всё исправно.
Отдельно стоит методическая ловушка, из-за которой в эту яму легко поверить. Если проверять схему на конфиге, где дешёвые ноды заглушены (для «чистоты эксперимента»), выбор дорогого узла выглядит правильным и создаёт ложный вывод, что балансер читает оба источника. Тест подтвердил неверную гипотезу. Проверять надо при всех живых узлах, и тогда видно, что дорогой узел выбирается ошибочно.
Вывод простой: все узлы держим в одном burstObservatory. Разделить частоту проб по группам нельзя: это ограничение, а не недоработка конфига.
6. Цена вопроса: пробы стоят трафика
Мониторинг не бесплатный. Каждая проба burstObservatory это новый Reality/TLS-хендшейк к узлу, соединение не переиспользуется. Замер делался счётчиками iptables на IP конкретных нод (правило без target, вставленное первым в цепочку, чтобы считать байты, но не менять маршрут).
Одна проба ≈ 8.8 КБ. Параметр sampling на объём трафика не влияет: это окно усреднения результатов, а не количество проб. Регулировать можно только интервал:
| Интервал | На клиента в месяц | 34 клиента | 100 клиентов |
|---|---|---|---|
| 10 с | 4.4 ГБ | 150 ГБ | 429 ГБ |
| 30 с | 1.5 ГБ | 50 ГБ | 143 ГБ |
| 60 с | 732 МБ | 24 ГБ | 71 ГБ |
| 120 с | 366 МБ | 12 ГБ | 36 ГБ |
Цифры отрезвляющие: на сотне клиентов при интервале 10 секунд мониторинг сам по себе съедает под полтерабайта в месяц, и это трафик на ваших же дорогих нодах. Для дорогого резерва это прямые деньги. 30 секунд разумный дефолт; ниже спускаться стоит, только если реально нужна быстрая реакция на обрыв (см. следующий раздел).
7. Тайминги: тоже замеренные
Хорошая новость: холодный старт не страдает. Первая проба выполняется сразу при запуске ядра, поэтому интернет появляется через ~4 секунды, и когда обычные ноды живы, и когда они уже мертвы (тогда сразу через резерв). Интервал проб на холодный старт не влияет вообще.
Ждать приходится только в одном сценарии (отказ посреди сессии): человек уже сидит в интернете, и тут его ноды срезают блокировкой. Вот тут интервал и sampling решают, сколько секунд клиент просидит без сети:
| Настройка | Простой клиента |
|---|---|
| interval 60 с, sampling 2 | 85 с |
| interval 60 с, sampling 1 | 72 с |
| interval 30 с, sampling 1 | 43 с |
| interval 15 с, sampling 1 | 30 с |
Обратите внимание на sampling. sampling: 2 требует двух неудачных проб подряд и фактически удваивает простой, при этом трафик он не экономит (см. прошлый раздел). Переход на sampling: 1 это бесплатное ускорение вдвое, берите его не задумываясь.
И неприятная деталь для честности: пока узел не признан мёртвым, балансировщик продолжает слать в него трафик и перебирать таких же мёртвых соседей. Всё это время клиент без интернета. Мгновенного переключения не существует; вопрос только в том, 30 это секунд или 85.
8. Готовый конфиг с разбором
Три «этажа» плюс аварийный выход. Конфиг приводится целиком, это и есть та самая одна плитка:
{
"remnawave": {
"injectHosts": [
{ "selector": { "type": "tagRegex", "pattern": "^TAG_TCP$" }, "tagPrefix": "main", "selectFrom": "ALL" },
{ "selector": { "type": "tagRegex", "pattern": "^TAG_GRPC$" }, "tagPrefix": "grp", "selectFrom": "ALL" },
{ "selector": { "type": "tagRegex", "pattern": "^TAG_XHTTP$" }, "tagPrefix": "xht", "selectFrom": "ALL" },
{ "selector": { "type": "tagRegex", "pattern": "^TAG_BYPASS$" }, "tagPrefix": "ihc", "selectFrom": "ALL" }
]
},
"burstObservatory": {
"subjectSelector": ["main", "grp", "xht", "ihc"],
"pingConfig": {
"destination": "http://www.gstatic.com/generate_204",
"interval": "30s",
"timeout": "3s",
"sampling": 1
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"balancers": [
{
"tag": "SMART",
"selector": ["main", "grp", "xht", "ihc"],
"strategy": {
"type": "leastLoad",
"settings": {
"costs": [
{ "regexp": true, "match": "^(grp|xht)", "value": 12 },
{ "regexp": true, "match": "^ihc", "value": 100 }
],
"expected": 1,
"maxRTT": "6s",
"tolerance": 0.2
}
},
"fallbackTag": "ihc"
}
],
"rules": [
{ "type": "field", "network": "tcp,udp", "balancerTag": "SMART" }
]
}
}
Смысл трёх уровней:
main: обычные ноды по TCP, основной режим. Вес ×1, всегда в приоритете, пока живы.grp/xht: те же ноды, но по gRPC и xHTTP. Другой транспорт часто проходит там, где TCP уже режут. Вес 12 (×3.5): включаются, когда TCP заметно просел или умер, но раньше дорогого обхода.ihc: ноды обхода, вес 100 (×10), да ещё и роль последнегоfallbackTag. Последняя линия и по costs, и по аварийному выходу.
Отдельно поговорим про префиксы: это ловушка на ровном месте. selector матчит по началу тега, поэтому префиксы не должны вкладываться друг в друга. Пара alt и alt2 сломается: alt захватит оба. Нужны непересекающиеся: main, grp, xht, ihc. Ни один не является началом другого.
9. Где это живёт в панели
Инжект собирает узлы из хостов по их тегам-инбаундам. Вот как хосты выглядят в панели, у каждого свой тег (VLESS-REALITY, STEAL-SHOP и т.д.), именно по нему injectHosts их и подхватывает:

Теги задаются в Config Profile, наборе инбаундов, на которые ссылаются хосты. Профиль показывает, сколько в нём тегов и нод:

Сам Xray-конфиг правится в JSON-редакторе прямо в панели, с проверкой валидности:

10. Как всё это проверять без риска для боевого сервиса
Три приёма, которыми схема отлаживалась, ни разу не тронув живых клиентов.
1. Проверка маршрутизации без подключения к нодам. Берём реальный рендер подписки и заменяем все outbound'ы на заглушки (freedom / blackhole), сохранив теги. Поднимаем Xray с socks-инбаундом и loglevel: info, ходим через socks обычным curl и смотрим в access-логе строки вида:
accepted tcp:site:443 [socks-in -> main]
Видно решение роутера (на какой тег он отправил), а боевые узлы не тронуты, чужие учётки не используются. Идеально, чтобы убедиться, что costs расставляет приоритет так, как задумано.
2. Имитация блокировки у оператора. Молчаливый дроп, как у реальных систем фильтрации:
iptables -I OUTPUT 1 -d <IP ноды> -j DROP
Так проверяется, что балансировщик действительно замечает мёртвый узел и уходит на соседний, а не висит на нём.
3. Замер трафика. Правило iptables без target, вставленное первым, считает байты и пропускает трафик дальше:
iptables -I OUTPUT 1 -d <IP ноды>
Именно -I ... 1 (в начало), а не -A (в конец): правило в конце может не сработать; до него не дойдёт очередь, если выше есть ACCEPT.
И практическая деталь: чтобы вообще увидеть настоящий рендер подписки, нужен User-Agent клиента и реальный x-hwid уже привязанного устройства. Без заголовка панель отдаёт заглушку «Приложение не поддерживает HWID», с чужим hwid: «Превышено количество устройств».
11. Грабли Remnawave
Собраны на практике, каждая стоила времени.
POST /api/subscription-templatesигнорируетtemplateJson: создаётся шаблон-заглушка на ~700 байт. Содержимое заливается вторым шагом черезPATCH. Симптом, если забыть: плитка рендерится как обычный сервер, без всякого балансировщика. (Проверено на практике: POST действительно кладёт пустышку, конфиг встал только через PATCH.)- Хост-ресивер (тот, что несёт шаблон) не инжектится в свой же пул: его теги надо оставлять пустыми, а узлы пула заводить отдельными скрытыми хостами.
serverDescription: максимум 30 символов. Больше не примет.- Видимость плитки ограничивается через
excludedInternalSquads: перечисляются все сквады, кроме нужных (логика «от обратного»). - Плитку нельзя давать тарифу, где нет инбаундов обхода:
fallbackTagукажет в пустоту, и трафик оборвётся вместо аварийного переключения. То есть ровно в тот момент, когда защита нужнее всего, её не будет.
12. Итог: что получилось и чего добиться нельзя
Получилось: одна плитка вместо десяти в подписке. Клиент сидит на дешёвых нодах, при их падении сам уходит на другой транспорт, а в крайнем случае, на дорогой обход с белым IP, и всё это выбирается на его устройстве под его оператора. Дорогие ноды поднимаются автоматически и только в аварию: costs держит их в самом конце очереди, пока живо хоть что-то дешевле.
Чего добиться нельзя, и с этим придётся жить:
- Разделить частоту проб по группам нельзя: все узлы в одном
burstObservatory, дорогие пингуются так же часто, как дешёвые. Пробы стоят трафика, и на сотне клиентов это сотни гигабайт. - Мгновенного переключения нет: при обрыве посреди сессии клиент простаивает от 30 до 85 секунд, смотря по интервалу.
- Работает только на ядре Xray: Happ, v2rayNG, Streisand. У клиентов на Clash/sing-box/Shadowrocket это обычный список серверов.
fallbackTagведёт ровно на один узел: не на группу и не на балансер.
Это честный потолок механизма. В его пределах SMART-балансировщик делает именно то, ради чего затевался: экономит трафик на дорогих нодах и включает обход сам, ровно тогда, когда обычные серверы перестали работать.
📖 Незнакомые слова? Все термины простыми словами смотрите в Словаре терминов.