Симптом
Строите транзит между двумя своими нодами: вход → внутренний bridge → выход. Инъекция служебного пользователя в bridge-инбаунд то не работает вовсе, то Reality на промежуточном узле молча отвергает соединение. Прямые пользовательские подключения при этом в порядке.
Причина: flow берётся из хоста, которого у моста нет
Три варианта моста, разобранные на практике:
- Shadowsocks-bridge — панель не инжектит пользователей в shadowsocks-инбаунд, механизма нет.
- VLESS+Reality-bridge — инъекция работает, но Reality отвергает пустой
flow. А vision-flow (xtls-rprx-vision) панель конструирует из клиентского хоста. У внутреннего моста хоста нет → flow приходит пустым → Reality рвёт соединение. - VLESS/TCP с
security: none— пустой flow валиден, инъекция служебного пользователя проходит, мост работает.
То есть vision-flow, обязательный для Reality, собирается из клиентской записи-хоста. Безхостовый служебный инбаунд получить его не может в принципе.
Решение
- Для внутреннего служебного моста между нодами — plain VLESS/TCP (
security: none): flow пустой и валидный, инъекция работает. - Полезная проверка при отладке транзита: сверить сертификат Reality входного узла снаружи и с localhost. Расхождение означает перехват на сетевом уровне — см. гайд про DNAT-перехват порта.
- Компромисс надо осознавать: голый VLESS/TCP без TLS на внутреннем плече раскрывает метаданные плеча (пользовательские данные всё равно идут под HTTPS). При желании плечо позже харденится отдельным TLS или WireGuard.
Вывод. Некоторые параметры протокола конструируются на определённом уровне абстракции и недоступны на других. Reality vision-flow привязан к клиентскому хосту, поэтому его нельзя «просто задать» на служебном безхостовом инбаунде. Когда автоматизация не может сформировать обязательное поле — выбирайте транспорт, для которого это поле не требуется или валидно пустым, вместо борьбы со слоем, который поле не отдаёт.