From 88b05c07d9b15a65f20cb68b4763af9eec2d7ef2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 25 Oct 2026 23:40:56 +0100 Subject: [PATCH] Harden REALITY record lifetime, relay cleanup and input contracts --- src/transport_layer/reality.c | 20 +++-- src/transport_layer/reality_fingerprint.h | 6 +- src/transport_layer/reality_io.c | 29 ++++--- src/transport_layer/reality_io.h | 1 + src/transport_layer/reality_record.c | 8 +- src/transport_layer/reality_record.h | 18 ++++- src/transport_layer/reality_relay.c | 95 ++++++++++++++++------- 7 files changed, 123 insertions(+), 54 deletions(-) diff --git a/src/transport_layer/reality.c b/src/transport_layer/reality.c index 986e9547..a83ead3c 100644 --- a/src/transport_layer/reality.c +++ b/src/transport_layer/reality.c @@ -55,7 +55,9 @@ struct rbuf { }; static int rbuf_put(struct rbuf *b, const void *data, size_t n) { - if (b->len + n > b->cap) return -1; + if (n > b->cap - b->len) { + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "Hello output buffer full: used=%zu add=%zu cap=%zu", b->len, n, b->cap); return -1; + } memcpy(b->p + b->len, data, n); b->len += n; return 0; @@ -70,7 +72,7 @@ static void rbuf_patch_u16(struct rbuf *b, size_t pos, uint16_t v) { if (pos + 2 <= b->cap) { b->p[pos] = (uint8_t)(v >> 8); b->p[pos + 1] = (uint8_t)(v & 0xff); - } + } else DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "Hello length patch outside buffer: offset=%zu cap=%zu", pos, b->cap); } // ─── Крипто-хелперы (OpenSSL) ─── @@ -94,16 +96,19 @@ out: int reality_generate_keypair(uint8_t priv[32], uint8_t pub[32]) { if (!priv || !pub) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid keypair output"); return REALITY_ERR_ARG; } - return reality_gen_keypair(priv, pub) == 0 ? REALITY_OK : REALITY_ERR_CRYPTO; + int rc = reality_gen_keypair(priv, pub); + if (rc) DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "X25519 keypair generation failed"); + return rc == 0 ? REALITY_OK : REALITY_ERR_CRYPTO; } int reality_pubkey_from_priv(const uint8_t priv[32], uint8_t pub[32]) { - if (!priv || !pub) return REALITY_ERR_ARG; + if (!priv || !pub) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "missing X25519 key argument"); return REALITY_ERR_ARG; } EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, priv, 32); - if (!pkey) return REALITY_ERR_CRYPTO; + if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "X25519 private key initialization failed"); return REALITY_ERR_CRYPTO; } size_t len = 32; int rc = (EVP_PKEY_get_raw_public_key(pkey, pub, &len) <= 0 || len != 32) ? REALITY_ERR_CRYPTO : REALITY_OK; EVP_PKEY_free(pkey); + if (rc) DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "X25519 public key extraction failed"); return rc; } @@ -529,7 +534,7 @@ static int server_hello_build(struct reality_session *session, const struct real // AAD = hs с обнулённым SessionId [39:71] uint8_t aad[REALITY_MAX_CH_SIZE]; - if (hs_len > sizeof(aad)) { rc = REALITY_ERR_FORMAT; goto out; } + if (hs_len > sizeof(aad)) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "ClientHello exceeds AAD capacity: %zu", hs_len); rc = REALITY_ERR_FORMAT; goto out; } memcpy(aad, hs, hs_len); memset(aad + 39, 0, 32); @@ -658,6 +663,7 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3 } void reality_session_handshake_done(struct reality_session *s) { + if (!s) return; EVP_MD_CTX_free(s->transcript); s->transcript = NULL; OPENSSL_cleanse(s->finished_send, 32); @@ -671,6 +677,7 @@ void reality_session_cleanup(struct reality_session *s) { } int reality_transcript_add(struct reality_session *s, const uint8_t *data, size_t len) { + if (!s || (!data && len)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid handshake transcript arguments"); return -1; } if (!s->transcript) { s->transcript = EVP_MD_CTX_new(); if (!s->transcript || EVP_DigestInit_ex(s->transcript, EVP_sha256(), NULL) != 1) goto fail; @@ -718,6 +725,7 @@ out: } int reality_finished(struct reality_session *s, int sending, uint8_t out[32]) { + if (!s || !out) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid Finished arguments"); return -1; } uint8_t hash[32]; unsigned n = 0; if (!s->keys_ready || transcript_digest(s, hash) || diff --git a/src/transport_layer/reality_fingerprint.h b/src/transport_layer/reality_fingerprint.h index 5d616f75..3468fdbe 100644 --- a/src/transport_layer/reality_fingerprint.h +++ b/src/transport_layer/reality_fingerprint.h @@ -14,8 +14,8 @@ extern "C" { #include struct reality_fingerprint { - int id; - const char *name; + int id; // REALITY_FP_OPENSSL (0), единственный текущий профиль + const char *name; // "openssl", заимствованная неизменяемая строка const uint16_t *cipher_suites; // список, терминирован 0x0000 const uint16_t *supported_groups; // список, терминирован 0x0000 const uint16_t *sig_algs; // список, терминирован 0x0000 @@ -23,7 +23,7 @@ struct reality_fingerprint { uint16_t server_cipher; // выбранный cipher suite для ServerHello }; -// Возвращает отпечаток по id (REALITY_FP_*) или NULL, если не найден. +// Возвращает общий неизменяемый static-профиль по id или NULL для неизвестного id; не освобождать. const struct reality_fingerprint *reality_fingerprint_get(int id); #ifdef __cplusplus diff --git a/src/transport_layer/reality_io.c b/src/transport_layer/reality_io.c index 584348c8..96f6c9c6 100644 --- a/src/transport_layer/reality_io.c +++ b/src/transport_layer/reality_io.c @@ -16,7 +16,7 @@ #define REPLAY_CAPACITY 1024 enum reality_phase { WAIT_HELLO, WAIT_SERVER_FLIGHT, WAIT_CLIENT_FINISHED, RECORD_DATA }; struct reality_replay_cache { - struct { uint8_t hash[32]; int64_t expires; } entries[REPLAY_CAPACITY]; + struct { uint8_t hash[32]; int64_t expires; int used; } entries[REPLAY_CAPACITY]; }; struct reality_io { struct reality_session session; @@ -29,7 +29,7 @@ struct reality_io { }; const char* reality_io_state_name(const struct stcp_conn *c) { - if (!c->reality_io) return "none"; + if (!c || !c->reality_io) return "none"; switch (c->reality_io->phase) { case WAIT_HELLO: return c->is_server ? "waiting-ClientHello" : "waiting-ServerHello"; case WAIT_SERVER_FLIGHT: return "waiting-server-authentication"; @@ -47,7 +47,7 @@ static int replay_accept(struct reality_owner *owner, const uint8_t *ch, size_t if (!SHA256(ch, len, hash)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "replay hash failed"); return -1; } int slot = -1; for (unsigned i = 0; i < REPLAY_CAPACITY; i++) { - if (owner->replay->entries[i].expires >= now) { + if (owner->replay->entries[i].used && owner->replay->entries[i].expires >= now) { if (!CRYPTO_memcmp(hash, owner->replay->entries[i].hash, 32)) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "replayed ClientHello rejected"); return -1; @@ -57,18 +57,21 @@ static int replay_accept(struct reality_owner *owner, const uint8_t *ch, size_t if (slot < 0) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "replay cache full: capacity=%d", REPLAY_CAPACITY); return -1; } memcpy(owner->replay->entries[slot].hash, hash, 32); owner->replay->entries[slot].expires = (int64_t)timestamp + window; + owner->replay->entries[slot].used = 1; DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "replay entry stored: slot=%d expires=%lld", slot, (long long)owner->replay->entries[slot].expires); return 0; } void reality_owner_cleanup(struct reality_owner *owner) { + if (!owner) return; reality_relay_shutdown(owner); u_free(owner->replay); owner->replay = NULL; } void reality_io_cleanup(struct stcp_conn *c) { + if (!c) return; struct reality_io *io = c->reality_io; if (!io) return; c->reality_io = NULL; @@ -79,6 +82,9 @@ void reality_io_cleanup(struct stcp_conn *c) { } static struct reality_io *io_create(struct stcp_conn *c, void (*ready)(struct stcp_conn *)) { + if (!c || !ready || c->reality_io) { + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "record I/O start: invalid connection/callback or already started"); return NULL; + } struct reality_io *io = u_calloc(1, sizeof(*io)); if (!io) goto fail; io->wire = u_malloc(STCP_RECV_BUF_MAX); @@ -102,9 +108,10 @@ int reality_io_client_start(struct stcp_conn *c, const struct reality_client_con if (padding) { io->padding.min = padding->min; io->padding.max = padding->max; } uint8_t *ch = u_malloc(REALITY_MAX_CH_SIZE); size_t n = 0; - if (!ch) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "ClientHello buffer allocation failed"); return -1; } + if (!ch) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "ClientHello buffer allocation failed"); reality_io_cleanup(c); return -1; } if (reality_session_client_start(&io->session, cfg, now, ch, REALITY_MAX_CH_SIZE, &n) || stcp_send_raw(c, ch, n) < 0) { u_free(ch); + reality_io_cleanup(c); return -1; } return 0; @@ -136,6 +143,7 @@ static int send_key_update(struct stcp_conn *c) { } int reality_io_send(struct stcp_conn *c, uint8_t *data, size_t len) { + if (!c || !c->reality_io || (!data && len)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid record send arguments"); return -1; } struct reality_io *io = c->reality_io; if (io->phase != RECORD_DATA || io->close_sent) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "application tx before Finished or after close: phase=%d", io->phase); @@ -150,7 +158,7 @@ int reality_io_send(struct stcp_conn *c, uint8_t *data, size_t len) { if (pad < 0) return -1; size_t take = len - offset; if (take > REALITY_INNER_MAX - 1 - (size_t)pad) take = REALITY_INNER_MAX - 1 - (size_t)pad; - if (send_record(c, REALITY_TYPE_APPLICATION, data + offset, take, pad) < 0) return -1; + if (send_record(c, REALITY_TYPE_APPLICATION, data ? data + offset : NULL, take, pad) < 0) return -1; offset += take; } while (offset < len); u_free(data); @@ -158,6 +166,7 @@ int reality_io_send(struct stcp_conn *c, uint8_t *data, size_t len) { } int reality_io_close_notify(struct stcp_conn *c) { + if (!c || !c->reality_io) return 0; struct reality_io *io = c->reality_io; if (io->phase != RECORD_DATA || io->close_sent) return 0; io->close_sent = 1; @@ -215,8 +224,9 @@ static int fallback(struct stcp_conn *c) { c->sock = SOCKET_INVALID; c->socket_id = NULL; struct reality_owner *owner = c->reality_owner; - reality_relay_start(owner, c->ua, sock, c->reality_dest, io->wire, io->wire_len, c->reality_srv.relay_idle_timeout_sec); - DEBUG_INFO(DEBUG_CATEGORY_REALITY, "unauthorized connection transferred to relay: buffered=%zu", io->wire_len); + int rc = reality_relay_start(owner, c->ua, sock, c->reality_dest, io->wire, io->wire_len, c->reality_srv.relay_idle_timeout_sec); + if (rc == 0) DEBUG_INFO(DEBUG_CATEGORY_REALITY, "unauthorized connection transferred to relay: buffered=%zu", io->wire_len); + else DEBUG_WARN(DEBUG_CATEGORY_REALITY, "unauthorized relay startup failed: buffered=%zu", io->wire_len); stcp_conn_do_close(c, ECANCELED); return -1; } @@ -373,7 +383,7 @@ reject: } void reality_io_resume(struct stcp_conn *c) { - if (c->reality_io && records_process(c) < 0) stcp_conn_do_close(c, ECANCELED); + if (c && c->reality_io && records_process(c) == -1) stcp_conn_do_close(c, ECANCELED); } size_t reality_io_buffered(const struct stcp_conn *c) { @@ -381,6 +391,7 @@ size_t reality_io_buffered(const struct stcp_conn *c) { } int reality_io_read(struct stcp_conn *c) { + if (!c || !c->reality_io) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid record read connection"); return -1; } struct reality_io *io = c->reality_io; if (io->wire_len == STCP_RECV_BUF_MAX) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "record receive buffer limit exceeded"); @@ -390,7 +401,7 @@ int reality_io_read(struct stcp_conn *c) { ssize_t n = recv(c->sock, io->wire + io->wire_len, STCP_RECV_BUF_MAX - io->wire_len, 0); if (n < 0) { int err = socket_get_error(); - if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) return 0; + if (err == ERR_AGAIN || err == ERR_WOULDBLOCK || err == ERR_INTR) return 0; DEBUG_WARN(DEBUG_CATEGORY_REALITY, "record socket read failed: err=%d", err); stcp_conn_do_close(c, err); return -1; diff --git a/src/transport_layer/reality_io.h b/src/transport_layer/reality_io.h index 7ae54e09..9dbdff3f 100644 --- a/src/transport_layer/reality_io.h +++ b/src/transport_layer/reality_io.h @@ -26,6 +26,7 @@ struct reality_relay; struct reality_owner { struct reality_replay_cache *replay; struct reality_relay *relays; + struct reality_relay *closed_relays; // закрытые контексты до call_soon или синхронного owner_cleanup unsigned relay_count; }; diff --git a/src/transport_layer/reality_record.c b/src/transport_layer/reality_record.c index fa049cf9..ee0297d0 100644 --- a/src/transport_layer/reality_record.c +++ b/src/transport_layer/reality_record.c @@ -9,7 +9,8 @@ int reality_kdf(const uint8_t *ikm, size_t ikm_len, const uint8_t *salt, size_t salt_len, const char *label, uint8_t *out, size_t out_len) { - if ((!ikm && ikm_len) || (!salt && salt_len) || !label || !out || !out_len || ikm_len > INT_MAX || salt_len > INT_MAX) { + if ((!ikm && ikm_len) || (!salt && salt_len) || !label || !out || !out_len || ikm_len > INT_MAX || salt_len > INT_MAX || + (label && strlen(label) > INT_MAX)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid KDF arguments"); return -1; } @@ -28,7 +29,7 @@ int reality_kdf(const uint8_t *ikm, size_t ikm_len, const uint8_t *salt, size_t int reality_traffic_init(struct reality_traffic *t, const uint8_t secret[32]) { if (!t || !secret) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid traffic key arguments"); return -1; } memcpy(t->secret, secret, 32); - t->sequence = 0; + t->sequence = 0; t->generation = 0; if (reality_kdf(secret, 32, NULL, 0, "uTun-reality-key", t->key, 16) || reality_kdf(secret, 32, NULL, 0, "uTun-reality-iv", t->iv, 12)) return -1; return 0; @@ -36,12 +37,13 @@ int reality_traffic_init(struct reality_traffic *t, const uint8_t secret[32]) { int reality_traffic_update(struct reality_traffic *t) { if (!t) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "missing traffic state on KeyUpdate"); return -1; } + uint32_t generation = t->generation; uint8_t next[32]; int rc = reality_kdf(t->secret, 32, NULL, 0, "uTun-reality-update", next, 32); if (!rc) rc = reality_traffic_init(t, next); OPENSSL_cleanse(next, sizeof(next)); if (rc) return -1; - t->generation++; + t->generation = generation + 1; DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "record KeyUpdate accepted: generation=%u", t->generation); return 0; } diff --git a/src/transport_layer/reality_record.h b/src/transport_layer/reality_record.h index ab0a8bc0..21f86f81 100644 --- a/src/transport_layer/reality_record.h +++ b/src/transport_layer/reality_record.h @@ -22,17 +22,29 @@ extern "C" { #define REALITY_TYPE_ALERT 21 struct reality_traffic { - uint8_t secret[32], key[16], iv[12]; - uint64_t sequence; - uint32_t generation; + uint8_t secret[32], key[16], iv[12]; // traffic secret и производные AES-128-GCM ключ/базовый nonce + uint64_t sequence; // следующий sequence направления; до REALITY_KEY_LIMIT-1 включительно + uint32_t generation; // диагностический счётчик KeyUpdate, init=0, циклический uint32_t }; +/* HKDF-SHA256, label — NUL-строка info. Входы NULL допустимы при длине 0; + * out/out_len ненулевые, длины ikm/salt/label должны помещаться в int OpenSSL. 0/-1. + * Входы заимствованы, выход принадлежит caller; при ошибке выход не использовать. */ int reality_kdf(const uint8_t *ikm, size_t ikm_len, const uint8_t *salt, size_t salt_len, const char *label, uint8_t *out, size_t out_len); +/* Производит key/iv из 32 байт secret, сбрасывает sequence/generation. Все указатели обязательны; 0/-1. */ int reality_traffic_init(struct reality_traffic *t, const uint8_t secret[32]); +/* Выводит следующий secret/key/iv, сбрасывает sequence, увеличивает generation; t обязателен, 0/-1. */ int reality_traffic_update(struct reality_traffic *t); +/* Тип — HANDSHAKE/APPLICATION/ALERT; len+1+padding<=REALITY_INNER_MAX. + * data=NULL допустим при len=0 только для APPLICATION. out вмещает 5+len+1+padding+16. + * Успех — 0 и *written=wire length; ошибка — -1, written/sequence не меняются, out может измениться. */ int reality_record_seal(struct reality_traffic *t, uint8_t type, const uint8_t *data, size_t len, size_t padding, uint8_t *out, size_t cap, size_t *written); +/* Одна полная запись, без хвоста: 22..REALITY_RECORD_MAX байт. cap>=len-21 для всей расшифровки. + * Успех — 0, *written=длина content без inner_type/padding, *type=тип, sequence++. + * Все выходные указатели обязательны. -1 сохраняет written/type/sequence; + * при отказе аутентификации/inner type расшифрованная область out очищается. После ошибки закрыть сеанс. */ int reality_record_open(struct reality_traffic *t, const uint8_t *record, size_t len, uint8_t *out, size_t cap, size_t *written, uint8_t *type); #ifdef __cplusplus diff --git a/src/transport_layer/reality_relay.c b/src/transport_layer/reality_relay.c index 2ae3e1fb..ae9e8b65 100644 --- a/src/transport_layer/reality_relay.c +++ b/src/transport_layer/reality_relay.c @@ -17,6 +17,7 @@ #include #include #include +#include #ifndef _WIN32 #include #endif @@ -26,8 +27,10 @@ #ifdef _WIN32 #define RELAY_SHUT_WR SD_SEND +#define RELAY_IN_PROGRESS WSAEINPROGRESS #else #define RELAY_SHUT_WR SHUT_WR +#define RELAY_IN_PROGRESS EINPROGRESS #endif struct reality_relay { @@ -52,6 +55,7 @@ struct reality_relay { int client_read_eof; // клиент закрыл свою сторону записи int dest_read_eof; // dest закрыл свою сторону записи int closed; + void* free_call; // отложенное освобождение, отменяемое при owner shutdown size_t bytes_c2d, bytes_d2c; @@ -60,7 +64,7 @@ struct reality_relay { static void relay_free(struct reality_relay *r); -static void relay_touch(struct reality_relay *r); +static int relay_touch(struct reality_relay *r); static void relay_idle_timeout_cb(void *arg); static void relay_flush_c2d(struct reality_relay *r); static void relay_flush_d2c(struct reality_relay *r); @@ -70,7 +74,11 @@ static void relay_client_write_cb(socket_t sock, void *arg); static void relay_dest_write_cb(socket_t sock, void *arg); static void relay_free_cb(void *arg) { - u_free(arg); + struct reality_relay* r = arg; + struct reality_relay** pp = &r->owner->closed_relays; + while (*pp && *pp != r) pp = &(*pp)->next; + if (*pp) *pp = r->next; + u_free(r); } static void relay_free(struct reality_relay *r) { @@ -92,14 +100,19 @@ static void relay_free(struct reality_relay *r) { if (r->c2d) { u_free(r->c2d); r->c2d = NULL; } if (r->d2c) { u_free(r->d2c); r->d2c = NULL; } DEBUG_INFO(DEBUG_CATEGORY_REALITY, "reality_relay closed: c2d=%zu d2c=%zu", r->bytes_c2d, r->bytes_d2c); - uasync_call_soon(r->ua, r, relay_free_cb); + r->next = r->owner->closed_relays; r->owner->closed_relays = r; + r->free_call = uasync_call_soon(r->ua, r, relay_free_cb); + if (!r->free_call) DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "relay free scheduling failed; context retained until owner cleanup"); } // Перезапустить таймер простоя: сброс на любую активность (чтение данных). -static void relay_touch(struct reality_relay *r) { - if (r->idle_timeout_tb <= 0) return; +static int relay_touch(struct reality_relay *r) { + if (r->idle_timeout_tb <= 0) return 0; + void* timer = uasync_set_timeout(r->ua, r->idle_timeout_tb, r, relay_idle_timeout_cb, "relay_idle"); + if (!timer) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "relay idle timer allocation failed"); return -1; } if (r->idle_timer) { uasync_cancel_timeout(r->ua, r->idle_timer); r->idle_timer = NULL; } - r->idle_timer = uasync_set_timeout(r->ua, r->idle_timeout_tb, r, relay_idle_timeout_cb, "relay_idle"); + r->idle_timer = timer; + return 0; } static void relay_idle_timeout_cb(void *arg) { @@ -111,17 +124,19 @@ static void relay_idle_timeout_cb(void *arg) { } static void relay_flush_c2d(struct reality_relay *r) { - if (!r->c2d) return; + if (r->closed || !r->c2d) return; while (r->c2d_off < r->c2d_len) { - ssize_t sent = send(r->dest_sock, r->c2d + r->c2d_off, r->c2d_len - r->c2d_off, 0); + ssize_t sent = socket_send(r->dest_sock, r->c2d + r->c2d_off, r->c2d_len - r->c2d_off); if (sent < 0) { int err = socket_get_error(); + if (err == ERR_INTR) continue; if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) { uasync_set_socket_write(r->ua, r->dest_sid, 1); return; } DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay send to dest failed err=%d", err); relay_free(r); return; } - if (sent == 0) { relay_free(r); return; } + if (sent == 0) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "relay send to dest made no progress"); relay_free(r); return; } r->c2d_off += (size_t)sent; + if (relay_touch(r) < 0) { relay_free(r); return; } } r->bytes_c2d += r->c2d_len; u_free(r->c2d); r->c2d = NULL; @@ -130,17 +145,19 @@ static void relay_flush_c2d(struct reality_relay *r) { } static void relay_flush_d2c(struct reality_relay *r) { - if (!r->d2c) return; + if (r->closed || !r->d2c) return; while (r->d2c_off < r->d2c_len) { - ssize_t sent = send(r->client_sock, r->d2c + r->d2c_off, r->d2c_len - r->d2c_off, 0); + ssize_t sent = socket_send(r->client_sock, r->d2c + r->d2c_off, r->d2c_len - r->d2c_off); if (sent < 0) { int err = socket_get_error(); + if (err == ERR_INTR) continue; if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) { uasync_set_socket_write(r->ua, r->client_sid, 1); return; } DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay send to client failed err=%d", err); relay_free(r); return; } - if (sent == 0) { relay_free(r); return; } + if (sent == 0) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "relay send to client made no progress"); relay_free(r); return; } r->d2c_off += (size_t)sent; + if (relay_touch(r) < 0) { relay_free(r); return; } } r->bytes_d2c += r->d2c_len; u_free(r->d2c); r->d2c = NULL; @@ -157,7 +174,7 @@ static void relay_client_read_cb(socket_t sock, void *arg) { ssize_t n = recv(r->client_sock, buf, RELAY_IO_BUF, 0); if (n < 0) { int err = socket_get_error(); - if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) { u_free(buf); return; } + if (err == ERR_AGAIN || err == ERR_WOULDBLOCK || err == ERR_INTR) { u_free(buf); return; } DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay recv from client failed err=%d", err); u_free(buf); relay_free(r); return; } @@ -181,7 +198,7 @@ static void relay_client_read_cb(socket_t sock, void *arg) { #endif r->c2d = buf; r->c2d_len = (size_t)n; r->c2d_off = 0; uasync_set_socket_read(r->ua, r->client_sid, 0); // backpressure - relay_touch(r); + if (relay_touch(r) < 0) { relay_free(r); return; } relay_flush_c2d(r); } @@ -194,7 +211,7 @@ static void relay_dest_read_cb(socket_t sock, void *arg) { ssize_t n = recv(r->dest_sock, buf, RELAY_IO_BUF, 0); if (n < 0) { int err = socket_get_error(); - if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) { u_free(buf); return; } + if (err == ERR_AGAIN || err == ERR_WOULDBLOCK || err == ERR_INTR) { u_free(buf); return; } DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay recv from dest failed err=%d", err); u_free(buf); relay_free(r); return; } @@ -208,7 +225,7 @@ static void relay_dest_read_cb(socket_t sock, void *arg) { } r->d2c = buf; r->d2c_len = (size_t)n; r->d2c_off = 0; uasync_set_socket_read(r->ua, r->dest_sid, 0); // backpressure - relay_touch(r); + if (relay_touch(r) < 0) { relay_free(r); return; } relay_flush_d2c(r); } @@ -239,23 +256,25 @@ static void relay_dest_write_cb(socket_t sock, void *arg) { // Создать неблокирующий dest-сокет и начать connect() к уже разрезолвенному адресу. // Вызывается сразу (literal IP) или из коллбэка async DNS. -static void relay_connect_dest(struct reality_relay *r, const struct sockaddr_in *addr) { +static int relay_connect_dest(struct reality_relay *r, const struct sockaddr_in *addr) { socket_t dest_sock = socket(AF_INET, SOCK_STREAM, 0); if (dest_sock == SOCKET_INVALID) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay: socket failed for %s:%u", r->host, r->port); relay_free(r); - return; + return -1; } - socket_set_nonblocking(dest_sock); r->dest_sock = dest_sock; + if (socket_set_nonblocking(dest_sock) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "relay dest nonblocking setup failed: %d", socket_get_error()); relay_free(r); return -1; + } int cr = connect(dest_sock, (const struct sockaddr *)addr, sizeof(*addr)); if (cr < 0) { int err = socket_get_error(); - if (err != EINPROGRESS && err != ERR_WOULDBLOCK) { + if (err != RELAY_IN_PROGRESS && err != ERR_WOULDBLOCK) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay connect %s:%u failed err=%d", r->host, r->port, err); relay_free(r); - return; + return -1; } } else { r->connecting = 0; @@ -263,12 +282,13 @@ static void relay_connect_dest(struct reality_relay *r, const struct sockaddr_in } r->dest_sid = uasync_add_socket_t(r->ua, dest_sock, relay_dest_read_cb, relay_dest_write_cb, NULL, "reality_dest", r); - if (!r->dest_sid) { relay_free(r); return; } + if (!r->dest_sid) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "relay dest registration failed"); relay_free(r); return -1; } if (!r->connecting) { if (r->c2d) relay_flush_c2d(r); else uasync_set_socket_read(r->ua, r->client_sid, 1); } + return r->closed ? -1 : 0; } static void relay_dns_done_cb(const struct adns_result *res, void *arg) { @@ -292,7 +312,8 @@ int reality_relay_start(struct reality_owner *owner, struct UASYNC *ua, socket_t const uint8_t *initial_data, size_t initial_len, int idle_timeout_sec) { if (ua && client_sock != SOCKET_INVALID) uasync_remove_socket_t(ua, client_sock); - if (!owner || !ua || client_sock == SOCKET_INVALID || !dest || !dest[0] || (!initial_data && initial_len)) { + if (!owner || !ua || client_sock == SOCKET_INVALID || !dest || !dest[0] || (!initial_data && initial_len) || + initial_len > UINT32_MAX || idle_timeout_sec < 0 || idle_timeout_sec > INT_MAX / 10000) { if (client_sock != SOCKET_INVALID) socket_close_wrapper(client_sock); DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: invalid args"); return -1; @@ -310,13 +331,12 @@ int reality_relay_start(struct reality_owner *owner, struct UASYNC *ua, socket_t owner->relay_count++; if (owner->relay_count > RELAY_WARN_CONNS) DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay: %d concurrent relays (over %d) — возможен скан-флуд", owner->relay_count, RELAY_WARN_CONNS); - relay_touch(r); // таймер покрывает DNS + connect (застрявший connect тоже чистится) + if (relay_touch(r) < 0) { relay_free(r); return -1; } // покрывает DNS + connect // берём владение client_sock сразу (снимаем старую регистрацию) — до парсинга, // чтобы любая ошибка ниже не оставила сокет зарегистрированным в uasync. - uasync_remove_socket_t(ua, client_sock); r->client_sid = uasync_add_socket_t(ua, client_sock, relay_client_read_cb, relay_client_write_cb, NULL, "reality_client", r); - if (!r->client_sid) { relay_free(r); return -1; } + if (!r->client_sid) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "relay client registration failed"); relay_free(r); return -1; } // не читаем от клиента, пока dest не подключён (иначе буферили бы без границ во время DNS) uasync_set_socket_read(ua, r->client_sid, 0); @@ -326,9 +346,19 @@ int reality_relay_start(struct reality_owner *owner, struct UASYNC *ua, socket_t if (dl >= sizeof(r->host)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: dest too long"); relay_free(r); return -1; } memcpy(r->host, dest, dl + 1); char *colon = strrchr(r->host, ':'); - if (colon) { *colon = '\0'; r->port = (uint16_t)atoi(colon + 1); } + if (colon) { + *colon = '\0'; char* end; + errno = 0; + unsigned long port = strtoul(colon + 1, &end, 10); + if (colon[1] < '0' || colon[1] > '9' || *end || errno || !port || port > 65535) { + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "relay invalid destination port: %s", dest); relay_free(r); return -1; + } + r->port = (uint16_t)port; + } else r->port = 443; - if (!r->host[0] || r->port == 0) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: bad dest '%s'", dest); relay_free(r); return -1; } + if (!r->host[0] || strchr(r->host, ':') || r->port == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: invalid/unsupported IPv6 dest '%s'", dest); relay_free(r); return -1; + } } if (initial_len && initial_data) { @@ -345,8 +375,7 @@ int reality_relay_start(struct reality_owner *owner, struct UASYNC *ua, socket_t sin.sin_family = AF_INET; sin.sin_port = htons(r->port); if (inet_pton(AF_INET, r->host, &sin.sin_addr) == 1) { - relay_connect_dest(r, &sin); - return 0; + return relay_connect_dest(r, &sin); } // асинхронный A-запрос — не блокирует event loop @@ -370,6 +399,12 @@ void reality_relay_shutdown(struct reality_owner *owner) { relay_free(r); // сам вынимает r из g_relays и откладывает u_free через call_soon count++; } + while (owner->closed_relays) { + struct reality_relay* r = owner->closed_relays; + owner->closed_relays = r->next; + if (r->free_call) uasync_call_soon_cancel(r->ua, r->free_call); + u_free(r); + } if (count > 0) DEBUG_INFO(DEBUG_CATEGORY_REALITY, "reality_relay_shutdown: closed %d active relays", count); }