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
|
||||
|
||||
@@ -268,6 +268,10 @@ endif
|
||||
|
||||
if !OS_WINDOWS
|
||||
check_PROGRAMS += test_socket_monitor
|
||||
check_PROGRAMS += test_reality_relay_faults
|
||||
test_reality_relay_faults_SOURCES = test_reality_relay_faults.c
|
||||
test_reality_relay_faults_LDADD = $(LIBUTUN) $(COMMON_LIBS)
|
||||
test_reality_relay_faults_LDFLAGS = -Wl,--wrap=uasync_set_timeout -Wl,--wrap=uasync_call_soon -Wl,--wrap=socket_set_nonblocking
|
||||
check_PROGRAMS += test_call_ring
|
||||
test_call_ring_SOURCES = test_call_ring.c ../src/transport_layer/call_ring.c
|
||||
test_call_ring_LDADD = $(COMMON_LIBS)
|
||||
|
||||
@@ -62,6 +62,11 @@ static int vectors(void) {
|
||||
CHECK(!reality_traffic_update(&recv));
|
||||
CHECK(!reality_record_seal(&send, 23, data, sizeof(data), 0, wire, sizeof(wire), &n));
|
||||
CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type));
|
||||
CHECK(!reality_traffic_update(&send) && !reality_traffic_update(&recv));
|
||||
CHECK(send.generation == 2 && recv.generation == 2 && !send.sequence && !recv.sequence);
|
||||
CHECK(!reality_record_seal(&send, 23, data, sizeof(data), 0, wire, sizeof(wire), &n));
|
||||
CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type));
|
||||
CHECK(!reality_traffic_init(&send, secret) && !send.generation && !send.sequence);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "HKDF, GCM nonce/AAD, replay, limits, empty DATA and KeyUpdate vectors passed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/* Прямой локальный relay: half-close, строгий dest и владение при отказах таймера/free. */
|
||||
#include "../src/transport_layer/reality_relay.h"
|
||||
#include "../src/transport_layer/reality_io.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "test_net.h"
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
static int fail_idle, fail_free, fail_nonblock;
|
||||
extern void* __real_uasync_set_timeout(struct UASYNC*, int, void*, timeout_callback_t, const char*);
|
||||
void* __wrap_uasync_set_timeout(struct UASYNC* ua, int delay, void* arg, timeout_callback_t cb, const char* name) {
|
||||
if (fail_idle && !strcmp(name, "relay_idle") && !--fail_idle) return NULL;
|
||||
return __real_uasync_set_timeout(ua, delay, arg, cb, name);
|
||||
}
|
||||
extern void* __real_uasync_call_soon(struct UASYNC*, void*, timeout_callback_t);
|
||||
void* __wrap_uasync_call_soon(struct UASYNC* ua, void* arg, timeout_callback_t cb) {
|
||||
if (fail_free) { fail_free = 0; return NULL; }
|
||||
return __real_uasync_call_soon(ua, arg, cb);
|
||||
}
|
||||
extern int __real_socket_set_nonblocking(socket_t);
|
||||
int __wrap_socket_set_nonblocking(socket_t fd) {
|
||||
if (fail_nonblock) { fail_nonblock = 0; errno = EIO; return -1; }
|
||||
return __real_socket_set_nonblocking(fd);
|
||||
}
|
||||
|
||||
static void client_pair(socket_t pair[2]) {
|
||||
assert(!socketpair(AF_UNIX, SOCK_STREAM, 0, pair));
|
||||
assert(!socket_set_nonblocking(pair[0]) && !socket_set_nonblocking(pair[1]));
|
||||
}
|
||||
|
||||
static void test_failures(void) {
|
||||
size_t initial = u_get_allocated_count();
|
||||
struct UASYNC* ua = uasync_create(); assert(ua);
|
||||
struct reality_owner owner = {0};
|
||||
const char* invalid[] = {"127.0.0.1:0", "host:-1", "host:65536", "host:1x", "::1"};
|
||||
for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) {
|
||||
socket_t pair[2]; client_pair(pair);
|
||||
assert(reality_relay_start(&owner, ua, pair[0], invalid[i], NULL, 0, 1) == -1);
|
||||
assert(!owner.relay_count && !owner.relays);
|
||||
uint8_t byte; assert(recv(pair[1], &byte, 1, 0) == 0); socket_close_wrapper(pair[1]);
|
||||
uasync_drain_immediate(ua);
|
||||
assert(!owner.closed_relays);
|
||||
}
|
||||
socket_t pair[2]; client_pair(pair);
|
||||
assert(reality_relay_start(&owner, ua, pair[0], "127.0.0.1:1", NULL, 0, INT_MAX) == -1);
|
||||
socket_close_wrapper(pair[1]);
|
||||
client_pair(pair); fail_idle = fail_free = 1;
|
||||
assert(reality_relay_start(&owner, ua, pair[0], "127.0.0.1:1", NULL, 0, 1) == -1);
|
||||
assert(!owner.relay_count && !owner.relays && owner.closed_relays);
|
||||
reality_owner_cleanup(&owner); assert(!owner.closed_relays);
|
||||
socket_close_wrapper(pair[1]);
|
||||
client_pair(pair); fail_nonblock = 1;
|
||||
assert(reality_relay_start(&owner, ua, pair[0], "127.0.0.1:1", NULL, 0, 1) == -1);
|
||||
socket_close_wrapper(pair[1]); reality_owner_cleanup(&owner);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
assert(u_get_allocated_count() == initial);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "PASS relay invalid ports, timeout range, timer/nonblocking failures and retained free context cleanup");
|
||||
}
|
||||
|
||||
static void test_stream(void) {
|
||||
size_t initial = u_get_allocated_count();
|
||||
struct UASYNC* ua = uasync_create(); assert(ua);
|
||||
struct reality_owner owner = {0};
|
||||
socket_t listener = socket(AF_INET, SOCK_STREAM, 0); assert(listener != SOCKET_INVALID);
|
||||
struct sockaddr_in address = {.sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK)};
|
||||
assert(!bind(listener, (struct sockaddr*)&address, sizeof(address)) && !listen(listener, 1));
|
||||
assert(!socket_set_nonblocking(listener));
|
||||
char destination[64]; snprintf(destination, sizeof(destination), "127.0.0.1:%u", test_bound_port(listener));
|
||||
socket_t pair[2]; client_pair(pair);
|
||||
assert(!reality_relay_start(&owner, ua, pair[0], destination, (const uint8_t*)"hello", 5, 1));
|
||||
socket_t remote = SOCKET_INVALID;
|
||||
for (unsigned i = 0; remote == SOCKET_INVALID && i < 1000; i++) { remote = accept(listener, NULL, NULL); uasync_poll(ua, 10); }
|
||||
assert(remote != SOCKET_INVALID && !socket_set_nonblocking(remote));
|
||||
assert(socket_send(pair[1], "tail", 4) == 4 && !shutdown(pair[1], SHUT_WR));
|
||||
char input[16]; size_t have = 0; int eof = 0;
|
||||
for (unsigned i = 0; !eof && i < 1000; i++) {
|
||||
ssize_t n = recv(remote, input + have, sizeof(input) - have, 0);
|
||||
if (n > 0) have += (size_t)n;
|
||||
else if (!n) eof = 1;
|
||||
uasync_poll(ua, 10);
|
||||
}
|
||||
assert(eof && have == 9 && !memcmp(input, "hellotail", 9));
|
||||
assert(socket_send(remote, "response", 8) == 8 && !shutdown(remote, SHUT_WR));
|
||||
have = 0; eof = 0;
|
||||
for (unsigned i = 0; !eof && i < 1000; i++) {
|
||||
ssize_t n = recv(pair[1], input + have, sizeof(input) - have, 0);
|
||||
if (n > 0) have += (size_t)n;
|
||||
else if (!n) eof = 1;
|
||||
uasync_poll(ua, 10);
|
||||
}
|
||||
assert(eof && have == 8 && !memcmp(input, "response", 8) && !owner.relay_count);
|
||||
reality_owner_cleanup(&owner);
|
||||
socket_close_wrapper(pair[1]); socket_close_wrapper(remote); socket_close_wrapper(listener);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1); assert(u_get_allocated_count() == initial);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "PASS relay original prefix, backpressure state, bidirectional half-close and cleanup");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_WARN);
|
||||
test_failures(); test_stream(); return 0;
|
||||
}
|
||||
Reference in new issue
Block a user