Harden REALITY record lifetime, relay cleanup and input contracts

This commit is contained in:
evgeny committed 2026-10-25 23:40:56 +01:00
1 parent 47bf724748
commit 88b05c07d9
7 files changed
+123 -54

No files matched your search

+14 -6
View File
@@ -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) ||
+3 -3
View File
@@ -14,8 +14,8 @@ extern "C" {
#include <stdint.h>
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
+20 -9
View File
@@ -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;
+1
View File
@@ -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;
};
+5 -3
View File
@@ -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;
}
+15 -3
View File
@@ -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
+65 -30
View File
@@ -17,6 +17,7 @@
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#ifndef _WIN32
#include <netinet/tcp.h>
#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);
}