Document REALITY session ownership and test relay failure cleanup

This commit is contained in:
evgeny committed 2026-10-25 23:48:38 +01:00
1 parent 88b05c07d9
commit 7e526521eb
6 files changed
+179 -34

No files matched your search

+33 -24
View File
@@ -45,43 +45,53 @@ extern "C" {
struct reality_client_config;
struct reality_server_config;
/* Контекст одноразового Hello и последующих защищённых записей. */
/* Одноразовая сессия: перед start обнулить, после любой ошибки вызвать cleanup.
* Все функции — в потоке владельца; входы/выходы не должны перекрываться. */
struct reality_session {
uint8_t ephemeral_private[32], auth_key[32], session_id[32];
uint8_t finished_send[32], finished_recv[32];
struct reality_traffic send, recv;
uint8_t ephemeral_private[32], auth_key[32], session_id[32]; // секреты Hello и ожидаемый echo SessionId
uint8_t finished_send[32], finished_recv[32]; // ключи проверки transcript по направлениям
struct reality_traffic send, recv; // независимые ключи и счётчики защищённых записей
void *transcript; // EVP_MD_CTX, точные handshake-байты без заголовков записей
uint32_t auth_timestamp;
uint8_t is_server, keys_ready;
uint32_t auth_timestamp; // timestamp авторизованного ClientHello для replay-кеша I/O
uint8_t is_server, keys_ready; // роль и готовность ключей после обоих Hello
};
// После обоих Finished удаляет transcript и handshake-секреты, сохраняя send/recv; NULL допустим.
void reality_session_handshake_done(struct reality_session *s);
// Освобождает transcript и стирает весь контекст; NULL допустим, повторный вызов безопасен.
void reality_session_cleanup(struct reality_session *s);
// Старт клиента: собирает Hello и сохраняет контекст для client_accept; cap >= REALITY_MAX_CH_SIZE.
// now — Unix-секунды. 0/REALITY_ERR_*; written действителен только при успехе.
int reality_session_client_start(struct reality_session *s, const struct reality_client_config *cfg, uint32_t now,
uint8_t *out, size_t cap, size_t *written);
// Старт сервера: проверяет нормализованный ClientHello и выводит ключи; cap >= REALITY_MAX_SH_SIZE.
// Проверяет время/short_id, но уникальность Hello проверяет replay-кеш reality_io; 0/REALITY_ERR_*.
int reality_session_server_start(struct reality_session *s, const struct reality_server_config *cfg, uint32_t now,
const uint8_t *ch, size_t len, uint8_t *out, size_t cap, size_t *written);
// Клиент принимает один полный ServerHello после client_start и выводит traffic/Finished-ключи; 0/REALITY_ERR_*.
int reality_session_client_accept(struct reality_session *s, const uint8_t *sh, size_t len);
// Добавляет точные handshake-байты (с handshake-заголовком) в SHA256 transcript; 0/-1.
int reality_transcript_add(struct reality_session *s, const uint8_t *data, size_t len);
// Вычисляет 32 байта Finished для текущего transcript; sending выбирает свой/чужой ключ; 0/-1.
int reality_finished(struct reality_session *s, int sending, uint8_t out[32]);
// Конфигурация клиента
struct reality_client_config {
uint8_t server_static_pubkey[REALITY_AUTH_KEY_SIZE]; // X25519 static pubkey сервера
uint8_t short_id[REALITY_SHORT_ID_SIZE];
uint8_t short_id[REALITY_SHORT_ID_SIZE]; // выбранный серверный идентификатор
uint8_t version[REALITY_VERSION_SIZE]; // версия протокола utun
char server_name[REALITY_SERVER_NAME_MAX]; // SNI-таргет (напр. "www.microsoft.com")
char server_name[REALITY_SERVER_NAME_MAX]; // непустое NUL-terminated SNI, максимум 255 байт
int fingerprint; // REALITY_FP_*
};
// Конфигурация сервера
struct reality_server_config {
uint8_t static_privkey[REALITY_AUTH_KEY_SIZE]; // X25519 static privkey сервера
uint8_t short_ids[REALITY_MAX_SHORT_IDS][REALITY_SHORT_ID_SIZE];
int short_id_count;
uint8_t short_ids[REALITY_MAX_SHORT_IDS][REALITY_SHORT_ID_SIZE]; // допустимые идентификаторы клиента
int short_id_count; // 1..REALITY_MAX_SHORT_IDS
uint8_t version[REALITY_VERSION_SIZE]; // принимаемая версия
int64_t time_window_sec; // допуск timestamp; replay-кеш находится в instance
int64_t time_window_sec; // допуск timestamp 0..3600 секунд; replay-кеш в instance
int relay_idle_timeout_sec; // idle-timeout релея неавторизованных (0=выкл)
int fingerprint;
int fingerprint; // REALITY_FP_*; сервер сейчас использует профиль OpenSSL
};
// Общая конфигурация reality (секция [reality] в конфиге utun)
@@ -92,23 +102,21 @@ struct reality_config {
char dest[REALITY_DEST_MAX]; // "host:port" для релея (fallback)
uint8_t short_id[REALITY_SHORT_ID_SIZE]; // клиентский short_id
uint8_t short_ids[REALITY_MAX_SHORT_IDS][REALITY_SHORT_ID_SIZE]; // сервер: список
int short_id_count;
int short_id_count; // число допустимых серверных short_ids
uint8_t public_key[REALITY_AUTH_KEY_SIZE]; // клиент: static pubkey сервера
uint8_t private_key[REALITY_AUTH_KEY_SIZE]; // сервер: static privkey
uint8_t has_public_key;
uint8_t has_private_key;
uint8_t has_public_key; // public_key задан в конфиге
uint8_t has_private_key; // private_key задан в конфиге
uint8_t version[REALITY_VERSION_SIZE]; // версия протокола
int time_window_sec;
int time_window_sec; // допустимое расхождение Unix-времени (0..3600)
int relay_idle_timeout_sec; // idle-timeout релея неавторизованных (0=выкл)
int fingerprint;
int fingerprint; // REALITY_FP_*
};
// Заполняет cfg->version/fingerprint/time_window значениями по умолчанию
// (version=2.0.0, fingerprint=openssl, time_window=30). Вызывается при инициализации.
// Сбрасывает cfg: выключен, ключи/строки пусты; version=2.0.0, OpenSSL, time_window=30, relay_idle=5; NULL допустим.
void reality_config_set_defaults(struct reality_config *cfg);
// Заполняет клиентскую конфигурацию (version=2.0.0, fingerprint=openssl).
// Вызывается при парсинге reality-ключей в секции [client].
// Сбрасывает клиентский cfg: ключ/short_id/SNI пусты, version=2.0.0, OpenSSL; NULL допустим.
void reality_client_config_set_defaults(struct reality_client_config *cfg);
// Клиент: собрать ClientHello (TLS record + handshake) с REALITY-авторизацией.
@@ -122,7 +130,8 @@ int reality_client_hello_build_at(const struct reality_client_config *cfg, uint3
uint8_t *out, size_t out_cap, size_t *out_len);
// Сервер: распарсить ClientHello, проверить авторизацию, собрать ServerHello.
// ch/ch_len — принятые байты (TLS record + handshake, допускается одна запись).
// ch/ch_len — ровно одна полная нормализованная запись TLS с ClientHello; out_cap >= REALITY_MAX_SH_SIZE.
// Обёртка без контекста сессии: не проверяет повторы Hello и не сохраняет ключи защищённых записей.
// REALITY_OK — авторизация валидна, out/out_len = ServerHello.
// REALITY_ERR_AUTH — авторизация не прошла (вызывающий код запускает релей).
// REALITY_ERR_* — формат/аргумент/крипто-ошибка.
@@ -138,11 +147,11 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3
uint8_t *out, size_t out_cap, size_t *out_len);
// Сгенерировать X25519 пару ключей (для конфигурации: сервер хранит privkey,
// клиент — pubkey сервера). Возвращает REALITY_OK / REALITY_ERR_CRYPTO.
// клиент — pubkey сервера). Возвращает REALITY_OK / REALITY_ERR_ARG / REALITY_ERR_CRYPTO.
int reality_generate_keypair(uint8_t priv[REALITY_AUTH_KEY_SIZE],
uint8_t pub[REALITY_AUTH_KEY_SIZE]);
// Вычислить X25519 pubkey из privkey. Возвращает REALITY_OK / REALITY_ERR_CRYPTO.
// Вычислить X25519 pubkey из privkey. Возвращает REALITY_OK / REALITY_ERR_ARG / REALITY_ERR_CRYPTO.
int reality_pubkey_from_priv(const uint8_t priv[REALITY_AUTH_KEY_SIZE],
uint8_t pub[REALITY_AUTH_KEY_SIZE]);
+21 -4
View File
@@ -11,7 +11,8 @@
* "uTun-reality-record-v2"); в transcript входят handshake-заголовки, но не record-заголовки.
* От root раздельно выводятся client-traffic/server-traffic и их key/iv/finished-ключи.
* После собственного SH fallback запрещён. До него relay получает весь исходный буфер без нормализации.
* Таймеры/списки/кеш replay принадлежат instance; owner_cleanup принудительно закрывает I/O без ожидания callbacks. */
* Списки relay и кеш replay принадлежат instance; owner_cleanup закрывает только эти общие ресурсы.
* Состояния активных STCP-соединений освобождаются отдельно через reality_io_cleanup. */
#ifndef REALITY_IO_H
#define REALITY_IO_H
#include "reality.h"
@@ -24,37 +25,53 @@ struct reality_io;
struct reality_replay_cache;
struct reality_relay;
struct reality_owner {
struct reality_replay_cache *replay;
struct reality_relay *relays;
struct reality_replay_cache *replay; // ленивый кеш 1024 Hello до истечения timestamp + window
struct reality_relay *relays; // активные relay, не активные STCP-соединения
struct reality_relay *closed_relays; // закрытые контексты до call_soon или синхронного owner_cleanup
unsigned relay_count;
unsigned relay_count; // число активных relay; предупреждение выше 64, жёсткого лимита нет
};
// ── Запуск защищённого соединения ──
// Создаёт оболочку и ставит ClientHello в очередь STCP; now — Unix-секунды, padding копируется (можно NULL).
// ready вызывается после проверки Finished, не внутри start. 0/-1; ошибка полностью удаляет новое I/O.
// c и ready обязательны, c->reality_io должен быть NULL; соединение остаётся собственностью вызывающего.
int reality_io_client_start(struct stcp_conn *c, const struct reality_client_config *cfg, uint32_t now,
const struct ETCP_PADDING *padding, void (*ready)(struct stcp_conn *));
// Создаёт серверную оболочку ожидания Hello с настройками c; 0/-1, ready обязателен.
int reality_io_server_start(struct stcp_conn *c, void (*ready)(struct stcp_conn *));
// ── Состояние и обмен данными ──
// Число ещё не обработанных wire-байтов; NULL/отсутствие оболочки даёт 0.
size_t reality_io_buffered(const struct stcp_conn *c);
// Статическая диагностическая строка состояния, "none" для NULL/отсутствия оболочки.
const char* reality_io_state_name(const struct stcp_conn *c);
// Неблокирующий recv и обработка записей: 1 — приняты байты, 0 — пока нет, -1 — EOF/ошибка/close_notify.
// Ошибки закрывают STCP; close_notify запускает штатный finish. Обработчики STCP могут закрыть c.
int reality_io_read(struct stcp_conn *c);
// Обрабатывает буфер после снятия rx_paused; учитывает finish/close_notify; NULL допустим.
void reality_io_resume(struct stcp_conn *c);
// Передаёт plaintext после Finished; 0 — отправлено, 1 — есть очередь, -1 — ошибка.
// При успехе забирает и u_free(data); при ошибке буфер остаётся вызывающему, соединение следует закрыть:
// часть записей уже могла попасть в очередь. Для пустой записи допустим NULL/0.
int reality_io_send(struct stcp_conn *c, uint8_t *data, size_t len);
// Один раз ставит close_notify: 0 — готово/не требуется, 1 — очередь, -1 — ошибка (требуется закрытие).
int reality_io_close_notify(struct stcp_conn *c);
// ── Освобождение соединения и общих ресурсов ──
// Стирает ключи и освобождает оболочку, не закрывая fd; повторный вызов и NULL безопасны.
void reality_io_cleanup(struct stcp_conn *c);
// Закрывает relay, отменяет их отложенное освобождение и освобождает replay-кеш синхронно.
// До уничтожения UASYNC, вне callbacks relay; owner изначально обнулён, NULL допустим.
void reality_owner_cleanup(struct reality_owner *owner);
#ifdef __cplusplus
+10 -6
View File
@@ -4,6 +4,8 @@
// не прошла (не utun-клиент — DPI-проба, случайный посетитель), соединение
// прозрачно проксируется на реальный сайт (dest), чтобы проба видела настоящий
// HTTPS-сеанс, а не ошибку/разрыв.
// Все операции — в uasync-потоке owner; owner изначально обнулён и живёт до shutdown.
// DNS выполняется асинхронно (только A); подключение использует первый IPv4-адрес.
#ifndef REALITY_RELAY_H
#define REALITY_RELAY_H
@@ -19,20 +21,22 @@ struct UASYNC;
struct reality_owner;
// Начать релей. Забирает владение client_sock (non-blocking, зарегистрирован
// в uasync) и проксирует его на dest ("host:port").
// в uasync либо ещё не зарегистрирован) и проксирует его на dest (IPv4/hostname[:port], по умолчанию 443).
// IPv6 dest не поддерживается. Явный порт — десятичный 1..65535, вся строка dest короче 256 байт.
// initial_data/initial_len — уже прочитанные байты от клиента (ClientHello),
// отправляются на dest первыми (буфер КОПИРУЕТСЯ — владение остаётся у вызывающего).
// idle_timeout_sec — время простоя (без трафика в обе стороны), после которого
// релей принудительно закрывается; 0 = таймер выключен.
// Возвращает 0 = ok, -1 = ошибка (client_sock уже закрыт внутри).
// релей принудительно закрывается; 0 = таймер выключен, максимум INT_MAX/10000 секунд.
// initial_len <= UINT32_MAX; NULL допустим только при initial_len=0.
// Возвращает 0 = запуск принят, -1 = синхронная ошибка (client_sock уже закрыт внутри).
// Ошибки DNS/connect после принятия закрывают relay и логируются; отдельного callback нет.
int reality_relay_start(struct reality_owner *owner, struct UASYNC *ua, socket_t client_sock,
const char *dest,
const uint8_t *initial_data, size_t initial_len,
int idle_timeout_sec);
// Принудительно завершить все активные релеи (вызывается при shutdown).
// Освобождение отложено через uasync_call_soon — после вызова нужно
// uasync_drain_immediate, чтобы реальный u_free прошёл.
// Синхронно завершает активные и освобождает закрытые relay, отменяя их call_soon.
// Вызывать до уничтожения UASYNC и вне callbacks relay; повторный вызов/NULL безопасны.
void reality_relay_shutdown(struct reality_owner *owner);
#ifdef __cplusplus