Harden REALITY record lifetime, relay cleanup and input contracts
This commit is contained in:
1 parent
47bf724748
commit
88b05c07d9
7 files changed
+123
-54
No files matched your search
@@ -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) ||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user