Files
netrunner-proxy/core/src/nrxp/mod.rs
T
nineap 99a9eee908 Улучшить TLS-маскировку, ввести версионирование протокола, добавить тесты и нагрузочный инструмент
Маскировка:
- ChangeCipherSpec middlebox-compat запись после ClientHello/ServerHello —
  без неё последовательность типов TLS-записей отличала нас от настоящего
  браузера даже при идеальном JA3/JA4-отпечатке.
- SNI-aware stealth fallback: невалидный клиент проксируется на запрошенный
  им хост, а не всегда на один и тот же decoy — иначе активное зондирование
  с разными SNI на одном IP получало одинаковый ответ. Резолв только в
  публичные IP (защита от SSRF на внутреннюю сеть ноды через подставной SNI).
- Профиль браузера привязан к session_id, а не к номеру попытки реконнекта:
  раньше при нескольких быстрых ретраях с одного IP летели ClientHello
  Chrome→Edge→Firefox подряд — сама по себе аномалия для DPI.
- Бакетный паддинг Data/UdpData кадров вместо полного отсутствия паддинга;
  джиттер перед ServerHello вместо мгновенного детерминированного ответа.

Версионирование:
- PROTOCOL_VERSION в session_id ClientHello — сервер узнаёт версию клиента
  до отправки чего-либо и может включать версионно-зависимое поведение
  (сейчас — CCS) только для тех, кто его понимает. Позволяет катить будущие
  несовместимые изменения протокола без синхронного flag-day релиза.

Тесты (core, было 0):
- Round-trip тесты TLS-записей/hello-сообщений/кадров/кодека.
- Полный цикл хендшейка с реальным выводом ключей на обеих сторонах.
- Вся SSRF-защита (hostname/IP валидация, v4/v6).
- Интеграционные тесты по настоящему TCP: легитимный хендшейк и
  stealth-fallback на мусорный ClientHello.

tools/loadtest — нагрузочный тест поверх настоящего скомпилированного
netrunner-server (реальный процесс, метрики RSS/CPU из /proc) и настоящего
публичного клиентского API. Попутно найден и обойдён гоночный баг:
Connect-кадр в ClientHandler::connect() уходит через try_send ещё до того,
как поднимется хоть одна нога тунеля, и молча теряется при синтетической
нагрузке.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 18:05:10 +07:00

60 lines
4.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! # Протокол NRXP (`nrxp`) — Netrunner eXchange Protocol
//!
//! Прикладной протокол, который ездит **внутри** замаскированного TLS-канала.
//! Снаружи трафик выглядит как обычные TLS-записи `ApplicationData` (`0x17`), а
//! внутри каждой записи лежит один зашифрованный кадр NRXP с мультиплексированием
//! логических потоков.
//!
//! Этот блок отвечает за «упаковку/распаковку» и ничего не знает о сети как
//! таковой — он лишь превращает `(stream_id, тип, payload)` в байты и обратно.
//!
//! ## Состав блока
//!
//! | Файл | Ответственность |
//! |-------------|-----------------------------------------------------------------------|
//! | [`frame`] | Структура кадра, его (де)сериализация, паддинг. |
//! | [`codec`] | Шифрующий слой: [`TxCodec`]/[`RxCodec`] (кадр ⇄ зашифрованный TLS). |
//! | [`bridge`] | TLS-обёртка: хендшейк (`ClientHello`/`ServerHello`) и `ApplicationData`.|
//! | [`errors`] | [`TlsError`] и стратегия реакции ([`ErrorAction`]: Wait/Redirect/Drop).|
//!
//! ## Формат кадра (25-байтовый заголовок + payload + padding)
//!
//! ```text
//! ┌──────────────┬───────────┬──────┬────────────┬────────────┬─────────┬─────────┐
//! │ Auth Tag │ Stream ID │ Type │ Payload Len│ Padding Len│ Payload │ Padding │
//! │ 16 байт │ 4 байта │ 1 б. │ 2 байта │ 2 байта │ N байт │ 0..255 │
//! └──────────────┴───────────┴──────┴────────────┴────────────┴─────────┴─────────┘
//! └──────────────────── FRAME_HEADER_SIZE = 25 ────────────────────┘
//! ```
//!
//! - **Auth Tag** — TOTP-подобный HMAC-тег (см. [`SessionAuth`](crate::crypto)),
//! привязан ко времени → защита от replay со стороны DPI.
//! - **Stream ID** — id логического потока внутри туннеля (нечётные у клиента,
//! чётные у сервера — так стороны не конфликтуют за номера).
//! - **Type** — [`FrameType`]: `Connect`/`Data`/`Close`/`Heartbeat`/`UdpConnect`/`UdpData`.
//! - **Padding** — ломает анализ длин пакетов. Управляющие кадры получают
//! 0..255 случайных байт; `Data`/`UdpData` выравниваются до ближайшего
//! "круглого" бакета (256/512/1024/2048/4096/8192, см. `Frame::into_bytes`)
//! — кроме кадров, уже близких к максимальному размеру (крупные закачки),
//! где паддинг только бил бы по throughput без выигрыша в приватности.
//!
//! ## Конвейер кодирования
//!
//! ```text
//! TX: Frame::new → into_bytes(tag) → AEAD encrypt in-place → TlsBridge::pack_app_data
//! RX: TlsBridge::unpack_app_data → AEAD decrypt in-place (staging) → Frame::parse
//! ```
//!
//! **Инвариант кодирования:** одна TLS-запись `ApplicationData` = ровно один
//! зашифрованный кадр NRXP. На нём держится потоковая расшифровка в [`codec`].
mod bridge;
mod codec;
mod errors;
mod frame;
pub(crate) use bridge::TlsBridge;
pub(crate) use codec::{Codec, RxCodec, TxCodec};
pub(crate) use errors::{ErrorAction, ErrorStage, TlsError};
pub(crate) use frame::{Frame, FrameType, MAX_FRAME_PAYLOAD};