Document REALITY session ownership and test relay failure cleanup
This commit is contained in:
1 parent
88b05c07d9
commit
7e526521eb
6 files changed
+179
-34
No files matched your search
@@ -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]);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user