Files
shater/03-features-and-model.md
T

11 KiB
Raw Blame History

Фиче-модель и концепция (обсуждение)

Форкаем ли xray? — НЕТ.

xray-core остаётся неизменным (это data-plane движок, ставим как есть). Наш xrayctl — отдельный control-plane демон рядом, а не форк:

  • генерит xray JSON из UCI-модели,
  • управляет подписками, роутингом, фаерволом, policy-routing, procd,
  • читает API xray (gRPC Stats/Observatory) для live-пинга/трафика в UI,
  • использует xray как библиотеку только для парсинга share-links (libXray) и валидации.

Форк = ад поддержки (xray на 26.6.x, релизы каждые недели — мы это уже видели). Аналогия: xray = двигатель, xrayctl = ЭБУ+панель. passwall/homeproxy делают ровно так — не форкают ядро, а оркестрируют его.

Два уровня «переиспользования кода xray» (оба ≠ форк):

  • (a) импорт xray/libXray как Go-модуля в xrayctl (парсинг/валидация, опц. встроить рантайм);
  • (b) shell к стоковому бинарю xray для run/test. Рекомендация: гибрид — libXray для парсинга (корректность), стоковый xray под procd для data-plane. Если чего-то не хватит в xray — контрибьютим апстрим или через его API, но не форк.

Концепция (data-model) — как описать ВСЁ декларативно, не утонув

Ключевой инсайт из текущего сетапа: это конвейер слоёв + policy-routing. Чистая модель из 7 объектов покрывает всё, что есть сейчас, и больше:

  1. Node — сервер/эндпоинт (vless/vmess/trojan/ss/xhttp/reality; позже wg/awg-peer).

  2. Group — набор нод (обычно = подписка) + стратегия выбора (balancer: leastPing / random / roundrobin / failover-priority + observatory health).

  3. Chain — упорядоченные хопы L1→L2→L3; каждый хоп = Group (случайная нода) или фикс-Node. (Ровно твой xray_chain «inverted chain».)

  4. Outlet / Egress — куда в итоге выходит: интерфейс (eth1/wwan/eth2), туннель (awgOut/awgHome/любой wg), Chain/Group, direct, block. ← «выбор интерфейса выхода».

  5. Inbound / Entry — как трафик входит: tproxy (LAN transparent), socks/http (локально), dokodemo (заворот awg-UDP).

  6. Rule — matcher → target(+egress). Упорядочены (первый match побеждает). Явно:

    • source: CIDR/хост (192.168.11.0/24, 192.168.11.14/32, IPv6), MAC (девайс даже при смене IP по DHCP), interface/зона (br-lan / гостевой SSID / VLAN), (позже) расписание.
    • dest: domain / geosite / geoip / ip-CIDR / port / l4proto.
    • target: Chain | Group | Node | direct | block, с привязкой Egress. Где применяется source-CIDR: два слоя — (a) в nft prerouting грубо решаем, какие источники вообще заходят в прокси (ip saddr 192.168.11.14 … tproxy), (b) в xray routing тонко выбираем target ("source": ["192.168.11.14/32"] → balancerTag/outboundTag). → полный контроль «девайс X → цепочка A через awgOut, девайс Y → direct».

    Визуализация «откуда→куда→как→почему» (killer-фича):

    • xrayctl explain <src> <dst> — прогоняет набор правил и печатает путь решения: matched rule → выбранная Chain/Node → Egress-iface → exit-IP (аналог ip route get, но для прокси-политики). В UI — «trace/explain» кнопка.
    • live-соединения из xray-API: src → dst → какой outbound реально используется.
  7. Profile / WAN-mode — условные оверрайды по активному WAN («если default=eth2(SIM) → заворачивать awg в VLESS»). Обобщение твоего sim-setup.sh.

  8. List / Ruleset — переиспользуемые именованные списки (domain-list / ip-cidr-list), источник inline / файл / URL (авто-обновление + кеш). Ссылаются из Rule (dest in @list) и из DNS-правил. Реализация: domain→dnsmasq nftset + xray domain-rules; ip→nft set + xray ip-rules.

Мульти-LAN: инбаунд-перехват настраивается на одну или несколько LAN-сетей/интерфейсов/ бриджей (br-lan / guest / VLAN'ы), LAN-подсеть указывается явно; правила матчат по inbound/source-зоне.

Генератор (xrayctl build) из этой модели рендерит:

  • xray JSON (inbounds, outbounds по нодам, balancers, observatory, routing inboundTag→balancerTag для слоёв цепочки — твой inverted-chain паттерн),
  • nftables-чанки (tproxy, метки, per-egress),
  • ip rule + таблицы (по каждой привязке egress),
  • dnsmasq nftset + DoH,
  • procd-инстансы. → Твой текущий xray_chain + awg-wrap + failover становится выразим декларативно и воспроизводим на любом роутере.

Выбор egress (твой явный запрос) — first-class

Любое Rule/Chain может «прибить» выход к:

  • физическому WAN (eth1 / wwan / eth2),
  • туннелю (awgOut / awgHome / любой wg/awg),
  • прокси-Chain/Group,
  • direct,
  • и комбо (Chain → затем через awgOut → затем физический WAN). Реализация: policy routing (fwmark → table → dev) + xray outbound sockopt.mark + бинд на iface. Плагин сам раскладывает метки/таблицы/правила из модели — вот где passwall неуклюж, а мы чисто.

Почему passwall2 лагает и как мы не лагаем

passwall гоняет кучу хелпер-процессов (dns2socks, chinadns, ipt2socks, haproxy, процесс на ноду), тяжёлый shell, iptables → лаг = лишние userspace-хопы + нет offload + DNS-оверхед. Мы:

  • один процесс xray мультиплексирует всё внутри (routing rules + balancers), не процесс-на-ноду;
  • nftables tproxy — kernel fast-path; bypass-трафик (RU/CN/private) не заходит в userspace (nftset);
  • flow-offload оставляем для bypass-трафика;
  • live-health из xray API, а не curl-спам;
  • рычаги: XUDP/mux, reality 0-RTT, sniffing routeOnly, без per-conn DNS.

Фичи по тирам

  • Tier 0 (MVP): ноды+подписки, один tproxy, balancer+health, DNS-сплит, базовые правила (domain/geo bypass), UI (ноды с пингом / подписки / статус).
  • Tier 1: выбор egress (per-rule бинд на iface/tunnel), per-client policy (девайс→таргет), multi-hop цепочки (перенос xray_chain).
  • Tier 2: WAN-mode профили/failover, awg-wrap (заворот UDP-туннеля в VLESS), FakeIP/FakeDNS, полный IPv6, дашборд статистики (throughput/health), мультиинстанс.
  • Tier 3: sing-box-адаптер, экспорт/импорт конфигов, правила по расписанию (whitelist-часы), внешний API, темы UI.

Источники нод и их идентичность (выбор группа vs отдельная нода)

Node source:

  • subscription — remote URL, обновляемый. Профили заголовков:
    • HAPP-эмуляция: x-hwid (задать вручную ИЛИ авто-сгенерить и запомнить per-sub), x-device-os / x-ver-os / x-device-model, User-Agent Happ/x.y.z.
    • обычная подписка (base64-список / plain / позже Clash-yaml).
  • manual — статические ноды, НЕ обновляются и НЕ пропадают. Способы добавления: вставка одной share-link-строки, много строк через \n, загрузка текст-файла.

Идентичность ноды (fingerprint): стабильный хеш по connection-defining полям (protocol+address+port+id/password+network+security+sni+path/serviceName). Правила и выбор ссылаются на ноду по fingerprint (или group+fingerprint), НЕ по индексу.

Персистентность при обновлении подписки (явный вопрос пользователя):

  • reconcile по fingerprint: новые — добавить; исчезнувшие — пометить stale (держим N обновлений / до ручного удаления), НЕ удаляем молча;
  • если «прибитая» правилом нода исчезла → fallback по политике: балансер группы / direct / block (выбор). Никогда не тихий обрыв;
  • manual-ноды стабильны по своему id, авто-удалению не подлежат.

Selection: target правила = группа (динамический состав + балансер) ИЛИ конкретная нода (по fingerprint; для подписочных = pin + fallback выше).

Открытые вопросы для следующего шага

  • Насколько глубоко сразу закладывать egress-selection и profiles в UCI-схему (даже если UI позже)?
  • Живой пинг/статы: тянуть из gRPC-API xray (нужен api+policy+stats в конфиге) — ок?
  • Нужен ли TUN-режим (для приложений, которые tproxy не ловит) или tproxy достаточно?