Protect REALITY STCP traffic with authenticated TLS-like records

This commit is contained in:
evgeny committed 2026-10-07 12:30:45 +02:00
1 parent a49cf5434e
commit a069d11ef9
33 files changed
+1786 -421

No files matched your search

+8
View File
@@ -44,6 +44,10 @@ utun_CORE_SOURCES = \
transport_layer/etcp_dump.c \
transport_layer/secure_channel.c \
transport_layer/reality.c \
transport_layer/reality_record.c \
transport_layer/reality_record.h \
transport_layer/reality_io.c \
transport_layer/reality_io.h \
transport_layer/reality_fingerprint.c \
transport_layer/reality_relay.c \
transport_layer/crc32.c \
@@ -184,6 +188,10 @@ libutun_a_SOURCES = \
transport_layer/etcp_dump.c \
transport_layer/secure_channel.c \
transport_layer/reality.c \
transport_layer/reality_record.c \
transport_layer/reality_record.h \
transport_layer/reality_io.c \
transport_layer/reality_io.h \
transport_layer/reality_fingerprint.c \
transport_layer/reality_relay.c \
transport_layer/crc32.c \
+4 -4
View File
@@ -778,8 +778,8 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
return 0;
}
if (strcmp(key, "fingerprint") == 0) {
if (strcmp(value, "chrome") == 0) { cli->reality.fingerprint = REALITY_FP_CHROME; return 0; }
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: unknown client reality fingerprint '%s' (valid: chrome)", filename, line_num, value);
if (strcmp(value, "openssl") == 0) { cli->reality.fingerprint = REALITY_FP_OPENSSL; return 0; }
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: unknown client reality fingerprint '%s' (valid: openssl)", filename, line_num, value);
return -1;
}
@@ -920,8 +920,8 @@ static int parse_reality(const char *key, const char *value, struct reality_conf
if (strcmp(key, "time_window") == 0) { rc->time_window_sec = atoi(value); return 0; }
if (strcmp(key, "relay_idle_timeout") == 0) { rc->relay_idle_timeout_sec = atoi(value); return 0; }
if (strcmp(key, "fingerprint") == 0) {
if (strcmp(value, "chrome") == 0) { rc->fingerprint = REALITY_FP_CHROME; return 0; }
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: unknown reality fingerprint '%s' (valid: chrome)", filename, line_num, value);
if (strcmp(value, "openssl") == 0) { rc->fingerprint = REALITY_FP_OPENSSL; return 0; }
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: unknown reality fingerprint '%s' (valid: openssl)", filename, line_num, value);
return -1;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown reality option '%s'. Valid: enabled, server_name, dest, short_id, short_ids, public_key, private_key, version, time_window, relay_idle_timeout, fingerprint", filename, line_num, key);
+1 -1
View File
@@ -197,7 +197,7 @@ void cm_candidate_ping_timer_cb(void* arg) {
struct stcp_client** slot = NULL;
for (uint8_t i = 0; i < CONN_MGR_MAX_CANDIDATES; i++)
if (!mgr->cand_tcp_pings[i]) { slot = &mgr->cand_tcp_pings[i]; break; }
if (slot) etcp_send_tcp_ping(mgr->instance, ni->public_key, &link->remote_addr, CONN_PROBE_TIMEOUT_MS, cm_cand_tcp_ping_cb, slot, slot);
if (slot) etcp_send_tcp_ping(mgr->instance, ni->public_key, &link->remote_addr, CONN_PROBE_TIMEOUT_MS, cm_cand_tcp_ping_cb, slot, slot, link->reality_set ? &link->reality : NULL);
} else
etcp_send_ping(mgr->instance, ni->public_key, &link->remote_addr, CONN_PROBE_TIMEOUT_MS, cm_bg_ping_noop_cb, NULL, NULL, 0);
}
+19 -1
View File
@@ -52,6 +52,8 @@ struct conn_probe_ctx {
uint16_t min_rtt; // min RTT в текущей серии
uint16_t timeout_ms;
void* ping_timer;
uint8_t reality_set;
struct reality_client_config reality;
struct stcp_client* tcp_cli; // хендл влётного TCP-пинга (для отмены), NULL для UDP
};
@@ -147,7 +149,7 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
static int conn_probe_send(struct conn_probe_ctx* ctx) {
if (ctx->is_tcp) {
return etcp_send_tcp_ping(ctx->instance, ctx->peer_pubkey, &ctx->target_addr,
ctx->timeout_ms, conn_probe_single_cb, ctx, &ctx->tcp_cli);
ctx->timeout_ms, conn_probe_single_cb, ctx, &ctx->tcp_cli, ctx->reality_set ? &ctx->reality : NULL);
}
struct ETCP_SOCKET* sock = ctx->candidate_sockets[ctx->candidate_index];
return etcp_send_ping_to_socket(ctx->instance, sock, ctx->peer_pubkey,
@@ -333,6 +335,14 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_G
ctx->timeout_ms = g_probe_timeout_ms; ctx->best_across_sockets = 65535;
if (a->protocol & TOPO_PROTO_TCP) {
const struct TOPO_REALITY_SOCK *rs = topo_node_find_reality_sock(ni, a->socket_id);
if (a->protocol & TOPO_PROTO_REALITY) {
if (!rs) {
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "probe rejected: REALITY parameters missing for socket=%u", a->socket_id);
u_free(ctx); continue;
}
}
if (rs) { topo_node_reality_config(&ctx->reality, rs); ctx->reality_set = 1; }
ctx->is_tcp = 1;
ctx->candidate_count = 1; ctx->candidate_index = 0; ctx->count_total = 1;
} else {
@@ -371,6 +381,14 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_G
ctx->timeout_ms = g_probe_timeout_ms; ctx->best_across_sockets = 65535;
if (a6->protocol & TOPO_PROTO_TCP) {
const struct TOPO_REALITY_SOCK *rs = topo_node_find_reality_sock(ni, a6->socket_id);
if (a6->protocol & TOPO_PROTO_REALITY) {
if (!rs) {
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "probe rejected: REALITY parameters missing for socket=%u", a6->socket_id);
u_free(ctx); continue;
}
}
if (rs) { topo_node_reality_config(&ctx->reality, rs); ctx->reality_set = 1; }
ctx->is_tcp = 1;
ctx->candidate_count = 1; ctx->candidate_index = 0; ctx->count_total = 1;
} else {
+9 -5
View File
@@ -73,13 +73,17 @@ const struct TOPO_REALITY_SOCK* topo_node_find_reality_sock(const struct TOPO_NO
}
/* Копирует reality-параметры узла в link->reality и поднимает флаг reality_set. */
void topo_node_reality_config(struct reality_client_config *cfg, const struct TOPO_REALITY_SOCK *rs) {
memcpy(cfg->server_static_pubkey, rs->server_pubkey, REALITY_AUTH_KEY_SIZE);
memcpy(cfg->short_id, rs->short_id, REALITY_SHORT_ID_SIZE);
memcpy(cfg->version, rs->version, REALITY_VERSION_SIZE);
snprintf(cfg->server_name, sizeof(cfg->server_name), "%s", rs->server_name);
cfg->fingerprint = REALITY_FP_OPENSSL;
}
void topo_node_apply_reality(struct ETCP_LINK* link, const struct TOPO_REALITY_SOCK* rs) {
if (!link || !rs) return;
memcpy(link->reality.server_static_pubkey, rs->server_pubkey, REALITY_AUTH_KEY_SIZE);
memcpy(link->reality.short_id, rs->short_id, REALITY_SHORT_ID_SIZE);
memcpy(link->reality.version, rs->version, REALITY_VERSION_SIZE);
snprintf(link->reality.server_name, sizeof(link->reality.server_name), "%s", rs->server_name);
link->reality.fingerprint = REALITY_FP_CHROME;
topo_node_reality_config(&link->reality, rs);
link->reality_set = 1;
}
+1
View File
@@ -289,6 +289,7 @@ static inline const struct TOPO_SUBNET6* topo_v6_subnets(const struct TOPO_NOD
const struct TOPO_REALITY_SOCK* topo_node_find_reality_sock(const struct TOPO_NODE* ni, uint8_t socket_id);
/* Заполнить link->reality из reality-инфо пира (rs из BGP). link->reality_set = 1. */
void topo_node_reality_config(struct reality_client_config *cfg, const struct TOPO_REALITY_SOCK *rs);
void topo_node_apply_reality(struct ETCP_LINK* link, const struct TOPO_REALITY_SOCK* rs);
#ifdef __cplusplus
+2 -2
View File
@@ -1679,7 +1679,7 @@ static void tcp_ping_cb_adapter(int success, uint16_t rtt, void* arg) {
int etcp_send_tcp_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin,
const struct sockaddr_storage* addr, int timeout_ms,
etcp_ping_callback_t cb, void* user_arg,
struct stcp_client** out_cli) {
struct stcp_client** out_cli, const struct reality_client_config *reality) {
if (!instance || !peer_pubkey_bin || !addr || timeout_ms <= 0 || !cb) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "bad args: inst=%p pubkey=%p addr=%p timeout=%d cb=%p",
(void*)instance, (void*)peer_pubkey_bin, (void*)addr, timeout_ms, (void*)(uintptr_t)cb);
@@ -1718,7 +1718,7 @@ int etcp_send_tcp_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubke
struct stcp_client* cli = stcp_ping_send(instance->ua, addr_str, port, &instance->my_keys, peer_pubkey_bin,
instance->my_ed25519_pubkey, instance->client_type,
instance->keepalive_interval, timeout_ms, tcp_ping_cb_adapter, a, socks, instance);
instance->keepalive_interval, timeout_ms, tcp_ping_cb_adapter, a, socks, instance, reality);
if (!cli) { u_free(a); return -4; }
if (out_cli) *out_cli = cli;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "tcp ping to %s:%u timeout=%d", addr_str, (unsigned)port, timeout_ms);
+1 -1
View File
@@ -428,7 +428,7 @@ int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET*
int etcp_send_tcp_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin,
const struct sockaddr_storage* addr, int timeout_ms,
etcp_ping_callback_t cb, void* user_arg,
struct stcp_client** out_cli);
struct stcp_client** out_cli, const struct reality_client_config *reality);
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
+282 -160
View File
@@ -25,6 +25,8 @@
#include <time.h>
#include <openssl/evp.h>
#include <openssl/kdf.h>
#include <openssl/hmac.h>
#include <openssl/crypto.h>
// TLS-константы
#define TLS_RECORD_HANDSHAKE 0x16
@@ -43,6 +45,8 @@
#define REALITY_HKDF_INFO "REALITY"
static int session_derive(struct reality_session *s, const uint8_t peer[32]);
// ─── Буфер записи с контролем границ ───
struct rbuf {
uint8_t *p;
@@ -89,6 +93,7 @@ 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;
}
@@ -114,19 +119,19 @@ void reality_config_set_defaults(struct reality_config *cfg) {
memset(cfg->private_key, 0, sizeof(cfg->private_key));
cfg->has_public_key = 0;
cfg->has_private_key = 0;
cfg->version[0] = 1; cfg->version[1] = 0; cfg->version[2] = 0;
cfg->version[0] = 2; cfg->version[1] = 0; cfg->version[2] = 0;
cfg->time_window_sec = 30;
cfg->relay_idle_timeout_sec = 5;
cfg->fingerprint = REALITY_FP_CHROME;
cfg->fingerprint = REALITY_FP_OPENSSL;
}
void reality_client_config_set_defaults(struct reality_client_config *cfg) {
if (!cfg) return;
memset(cfg->server_static_pubkey, 0, sizeof(cfg->server_static_pubkey));
memset(cfg->short_id, 0, sizeof(cfg->short_id));
cfg->version[0] = 1; cfg->version[1] = 0; cfg->version[2] = 0;
cfg->version[0] = 2; cfg->version[1] = 0; cfg->version[2] = 0;
cfg->server_name[0] = '\0';
cfg->fingerprint = REALITY_FP_CHROME;
cfg->fingerprint = REALITY_FP_OPENSSL;
}
static int reality_x25519(const uint8_t priv[32], const uint8_t pub[32], uint8_t shared[32]) {
@@ -149,26 +154,6 @@ out:
return rc;
}
static int reality_hkdf_sha256(const uint8_t *ikm, size_t ikm_len,
const uint8_t *salt, size_t salt_len,
const uint8_t *info, size_t info_len,
uint8_t *out, size_t out_len) {
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if (!ctx) return -1;
int rc = -1;
if (EVP_PKEY_derive_init(ctx) <= 0) goto out;
if (EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256()) <= 0) goto out;
if (EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt, (int)salt_len) <= 0) goto out;
if (EVP_PKEY_CTX_set1_hkdf_key(ctx, ikm, (int)ikm_len) <= 0) goto out;
if (EVP_PKEY_CTX_add1_hkdf_info(ctx, info, (int)info_len) <= 0) goto out;
size_t len = out_len;
if (EVP_PKEY_derive(ctx, out, &len) <= 0 || len != out_len) goto out;
rc = 0;
out:
EVP_PKEY_CTX_free(ctx);
return rc;
}
static int reality_aes_gcm_seal(const uint8_t key[32], const uint8_t nonce[12],
const uint8_t *aad, size_t aad_len,
const uint8_t *plain, size_t plain_len,
@@ -250,21 +235,45 @@ static int reality_build_client_hello_body(const struct reality_client_config *c
if (rbuf_put(&b, cfg->server_name, name_len) < 0) return REALITY_ERR_ARG;
}
const uint8_t point_formats[] = {0, 11, 0, 4, 3, 0, 1, 2};
if (rbuf_put(&b, point_formats, sizeof(point_formats))) return REALITY_ERR_ARG;
// supported_groups
{
int count = 0;
for (const uint16_t *g = fp->supported_groups; *g != 0; g++) count++;
if (rbuf_put_u16(&b, TLS_EXT_SUPPORTED_GROUPS) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, (uint16_t)(2 + count * 2)) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, (uint16_t)(count * 2)) < 0) return REALITY_ERR_ARG;
for (const uint16_t *g = fp->supported_groups; *g != 0; g++)
if (rbuf_put_u16(&b, *g) < 0) return REALITY_ERR_ARG;
}
const uint8_t ticket[] = {0, 35, 0, 0};
if (rbuf_put(&b, ticket, sizeof(ticket))) return REALITY_ERR_ARG;
// ALPN
{
size_t alpn_list_len = 0;
for (const char *const *a = fp->alpn; *a; a++) alpn_list_len += 1 + strlen(*a);
if (rbuf_put_u16(&b, TLS_EXT_ALPN) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, (uint16_t)(alpn_list_len + 2)) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, (uint16_t)alpn_list_len) < 0) return REALITY_ERR_ARG;
for (const char *const *a = fp->alpn; *a; a++) {
size_t l = strlen(*a);
if (rbuf_put_u8(&b, (uint8_t)l) < 0) return REALITY_ERR_ARG;
if (rbuf_put(&b, *a, l) < 0) return REALITY_ERR_ARG;
}
}
const uint8_t legacy_extensions[] = {0, 22, 0, 0, 0, 23, 0, 0};
if (rbuf_put(&b, legacy_extensions, sizeof(legacy_extensions))) return REALITY_ERR_ARG;
// signature_algorithms
{
int count = 0;
for (const uint16_t *s = fp->sig_algs; *s != 0; s++) count++;
if (rbuf_put_u16(&b, TLS_EXT_SIG_ALGS) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, (uint16_t)(2 + count * 2)) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, (uint16_t)(count * 2)) < 0) return REALITY_ERR_ARG;
for (const uint16_t *s = fp->sig_algs; *s != 0; s++)
if (rbuf_put_u16(&b, *s) < 0) return REALITY_ERR_ARG;
@@ -290,20 +299,8 @@ static int reality_build_client_hello_body(const struct reality_client_config *c
if (rbuf_put_u16(&b, 32) < 0) return REALITY_ERR_ARG;
if (rbuf_put(&b, eph_pub, 32) < 0) return REALITY_ERR_ARG;
// ALPN
{
size_t alpn_list_len = 0;
for (const char *const *a = fp->alpn; *a; a++) alpn_list_len += 1 + strlen(*a);
if (rbuf_put_u16(&b, TLS_EXT_ALPN) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, (uint16_t)(alpn_list_len + 2)) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, (uint16_t)alpn_list_len) < 0) return REALITY_ERR_ARG;
for (const char *const *a = fp->alpn; *a; a++) {
size_t l = strlen(*a);
if (rbuf_put_u8(&b, (uint8_t)l) < 0) return REALITY_ERR_ARG;
if (rbuf_put(&b, *a, l) < 0) return REALITY_ERR_ARG;
}
}
const uint8_t certificate_compression[] = {0, 27, 0, 5, 4, 0, 1, 0, 3};
if (rbuf_put(&b, certificate_compression, sizeof(certificate_compression))) return REALITY_ERR_ARG;
rbuf_patch_u16(&b, ext_len_pos, (uint16_t)(b.len - ext_len_pos - 2));
*body_len = b.len;
return REALITY_OK;
@@ -314,7 +311,7 @@ int reality_client_hello_build(const struct reality_client_config *cfg,
return reality_client_hello_build_at(cfg, (uint32_t)time(NULL), out, out_cap, out_len);
}
int reality_client_hello_build_at(const struct reality_client_config *cfg, uint32_t now,
static int client_hello_build(struct reality_session *session, const struct reality_client_config *cfg, uint32_t now,
uint8_t *out, size_t out_cap, size_t *out_len) {
if (!cfg || !out || !out_len || out_cap < REALITY_MAX_CH_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: invalid args");
@@ -325,37 +322,34 @@ int reality_client_hello_build_at(const struct reality_client_config *cfg, uint3
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: unknown fingerprint %d", cfg->fingerprint);
return REALITY_ERR_ARG;
}
if (cfg->server_name[0] == '\0') {
if (cfg->server_name[0] == '\0' || !memchr(cfg->server_name, 0, sizeof(cfg->server_name))) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: empty server_name");
return REALITY_ERR_ARG;
}
// 1. эфемерный ключ + random
uint8_t eph_priv[32], eph_pub[32], random32[32];
uint8_t eph_priv[32] = {0}, eph_pub[32], random32[32], shared[32] = {0}, auth_key[32] = {0}, plaintext[16] = {0};
int rc = REALITY_ERR_CRYPTO;
if (reality_gen_keypair(eph_priv, eph_pub) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: X25519 keygen failed");
return REALITY_ERR_CRYPTO;
rc = REALITY_ERR_CRYPTO; goto out;
}
if (random_bytes(random32, sizeof(random32)) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: random_bytes failed");
return REALITY_ERR_CRYPTO;
rc = REALITY_ERR_CRYPTO; goto out;
}
// 2. shared + AuthKey
uint8_t shared[32], auth_key[32];
if (reality_x25519(eph_priv, cfg->server_static_pubkey, shared) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: X25519 derive failed");
return REALITY_ERR_CRYPTO;
rc = REALITY_ERR_CRYPTO; goto out;
}
if (reality_hkdf_sha256(shared, sizeof(shared), random32, 20,
(const uint8_t *)REALITY_HKDF_INFO, sizeof(REALITY_HKDF_INFO) - 1,
auth_key, sizeof(auth_key)) != 0) {
if (reality_kdf(shared, sizeof(shared), random32, 20, REALITY_HKDF_INFO, auth_key, sizeof(auth_key)) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: HKDF failed");
return REALITY_ERR_CRYPTO;
rc = REALITY_ERR_CRYPTO; goto out;
}
// 3. plaintext[0:16] = version + reserved + timestamp + short_id
uint8_t plaintext[16];
memcpy(plaintext, cfg->version, 3);
plaintext[3] = 0;
plaintext[4] = (uint8_t)(now >> 24);
@@ -365,16 +359,16 @@ int reality_client_hello_build_at(const struct reality_client_config *cfg, uint3
memcpy(plaintext + 8, cfg->short_id, REALITY_SHORT_ID_SIZE);
// 4. собрать body (SessionId = нули)
uint8_t body[REALITY_MAX_CH_SIZE];
uint8_t body[2048];
size_t body_len = 0;
int rc = reality_build_client_hello_body(cfg, fp, random32, eph_pub, body, sizeof(body), &body_len);
rc = reality_build_client_hello_body(cfg, fp, random32, eph_pub, body, sizeof(body), &body_len);
if (rc != REALITY_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: body build failed rc=%d", rc);
return rc;
goto out;
}
// 5. handshake-сообщение = 0x01 + length + body (SessionId на позиции 39..71)
uint8_t hs[REALITY_MAX_CH_SIZE];
uint8_t hs[2048];
size_t hs_len = 4 + body_len;
hs[0] = TLS_HANDSHAKE_CLIENT_HELLO;
hs[1] = (uint8_t)(body_len >> 16);
@@ -387,7 +381,7 @@ int reality_client_hello_build_at(const struct reality_client_config *cfg, uint3
if (reality_aes_gcm_seal(auth_key, random32 + 20, hs, hs_len,
plaintext, 16, seal_out, seal_out + 16) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: AES-GCM seal failed");
return REALITY_ERR_CRYPTO;
rc = REALITY_ERR_CRYPTO; goto out;
}
memcpy(hs + 39, seal_out, 32); // SessionId = ciphertext || tag
@@ -401,15 +395,21 @@ int reality_client_hello_build_at(const struct reality_client_config *cfg, uint3
memcpy(out + 5, hs, hs_len);
*out_len = total;
DEBUG_INFO(DEBUG_CATEGORY_REALITY,
"client hello built: total=%zu sn=%s ver=%d.%d.%d short_id=%02x%02x%02x%02x%02x%02x%02x%02x",
total, cfg->server_name, cfg->version[0], cfg->version[1], cfg->version[2],
plaintext[8], plaintext[9], plaintext[10], plaintext[11],
plaintext[12], plaintext[13], plaintext[14], plaintext[15]);
DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "client auth_key=%02x%02x%02x%02x... shared=%02x%02x%02x%02x...",
auth_key[0], auth_key[1], auth_key[2], auth_key[3],
shared[0], shared[1], shared[2], shared[3]);
return REALITY_OK;
if (session) {
memcpy(session->ephemeral_private, eph_priv, 32);
memcpy(session->auth_key, auth_key, 32);
memcpy(session->session_id, seal_out, 32);
if (reality_transcript_add(session, hs, hs_len)) { rc = REALITY_ERR_CRYPTO; goto out; }
}
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "client hello built: total=%zu sni=%s version=%u.%u.%u", total,
cfg->server_name, cfg->version[0], cfg->version[1], cfg->version[2]);
rc = REALITY_OK;
out:
OPENSSL_cleanse(eph_priv, sizeof(eph_priv));
OPENSSL_cleanse(shared, sizeof(shared));
OPENSSL_cleanse(auth_key, sizeof(auth_key));
OPENSSL_cleanse(plaintext, sizeof(plaintext));
return rc;
}
// ─── Сервер: разбор ClientHello ───
@@ -421,79 +421,84 @@ struct reality_ch_parsed {
};
// Возвращает REALITY_OK / REALITY_ERR_FORMAT.
static uint16_t read_u16(const uint8_t *p) { return ((uint16_t)p[0] << 8) | p[1]; }
static int reality_parse_client_hello(const uint8_t *ch, size_t ch_len,
const uint8_t **hs_out, size_t *hs_len_out,
struct reality_ch_parsed *p) {
if (!ch || ch_len < 5 || ch[0] != TLS_RECORD_HANDSHAKE) return REALITY_ERR_FORMAT;
size_t rec_len = ((size_t)ch[3] << 8) | ch[4];
if (ch_len < 5 + rec_len) return REALITY_ERR_FORMAT;
if (!ch || ch_len < 9 || ch[0] != 22 || ch[1] != 3 || (ch[2] != 1 && ch[2] != 3) ||
read_u16(ch + 3) != ch_len - 5 || ch[5] != 1) return REALITY_ERR_FORMAT;
const uint8_t *hs = ch + 5;
size_t hs_len = rec_len;
if (hs_len < 4 || hs[0] != TLS_HANDSHAKE_CLIENT_HELLO) return REALITY_ERR_FORMAT;
size_t body_len = ((size_t)hs[1] << 16) | ((size_t)hs[2] << 8) | hs[3];
if (hs_len < 4 + body_len) return REALITY_ERR_FORMAT;
const uint8_t *b = hs + 4;
size_t bl = body_len;
if (bl < 2 + 32 + 1) return REALITY_ERR_FORMAT;
b += 2; // legacy_version
p->random = b; b += 32;
uint8_t sid_len = *b; b += 1;
if (sid_len != 32 || bl < 2 + 32 + 1 + 32) return REALITY_ERR_FORMAT;
p->session_id = b; b += 32;
// cipher suites
if (bl < (size_t)(b - (hs + 4)) + 2) return REALITY_ERR_FORMAT;
uint16_t cs_len = (uint16_t)((b[0] << 8) | b[1]); b += 2;
if (bl < (size_t)(b - (hs + 4)) + cs_len + 1) return REALITY_ERR_FORMAT;
b += cs_len; // cipher suites
uint8_t comp_len = *b; b += 1;
if (bl < (size_t)(b - (hs + 4)) + comp_len + 2) return REALITY_ERR_FORMAT;
b += comp_len; // compression
// extensions
uint16_t ext_len = (uint16_t)((b[0] << 8) | b[1]); b += 2;
const uint8_t *ext = b;
size_t remaining = ext_len;
const uint8_t *end = ext + remaining;
if (end > hs + 4 + bl) return REALITY_ERR_FORMAT;
p->key_share = NULL;
while (remaining >= 4) {
uint16_t type = (uint16_t)((ext[0] << 8) | ext[1]);
uint16_t len = (uint16_t)((ext[2] << 8) | ext[3]);
if (remaining < 4 + len) return REALITY_ERR_FORMAT;
if (type == TLS_EXT_KEY_SHARE && len >= 4) {
uint16_t shares_len = (uint16_t)((ext[4] << 8) | ext[5]);
if ((size_t)shares_len > (size_t)(len - 2)) return REALITY_ERR_FORMAT;
const uint8_t *s = ext + 6;
size_t sl = shares_len;
while (sl >= 4) {
uint16_t group = (uint16_t)((s[0] << 8) | s[1]);
uint16_t klen = (uint16_t)((s[2] << 8) | s[3]);
if (sl < 4 + klen) break;
if (group == TLS_GROUP_X25519 && klen == 32) {
p->key_share = s + 4;
break;
size_t n = ch_len - 5;
if (4 + ((size_t)hs[1] << 16) + ((size_t)hs[2] << 8) + hs[3] != n || n < 73 ||
hs[4] != 3 || hs[5] != 3 || hs[38] != 32) return REALITY_ERR_FORMAT;
p->random = hs + 6; p->session_id = hs + 39; p->key_share = NULL;
size_t pos = 71, suites = read_u16(hs + pos);
pos += 2;
if (!suites || suites % 2 || suites > n - pos) return REALITY_ERR_FORMAT;
int cipher_ok = 0, version_ok = 0;
for (size_t i = 0; i < suites; i += 2) if (read_u16(hs + pos + i) == 0x1301) cipher_ok = 1;
pos += suites;
if (n - pos < 4 || hs[pos] != 1 || hs[pos + 1] != 0) return REALITY_ERR_FORMAT;
pos += 2;
size_t extensions = read_u16(hs + pos);
pos += 2;
if (extensions != n - pos) return REALITY_ERR_FORMAT;
uint8_t seen[8192] = {0};
while (pos < n) {
if (n - pos < 4) return REALITY_ERR_FORMAT;
unsigned type = read_u16(hs + pos);
size_t len = read_u16(hs + pos + 2);
pos += 4;
if (len > n - pos || (seen[type / 8] & (1 << (type % 8)))) return REALITY_ERR_FORMAT;
seen[type / 8] |= (1 << (type % 8));
const uint8_t *e = hs + pos;
if (type == TLS_EXT_SUPPORTED_GROUPS || type == TLS_EXT_SIG_ALGS) {
if (len < 4 || read_u16(e) != len - 2 || len % 2) return REALITY_ERR_FORMAT;
} else if (type == TLS_EXT_SUPPORTED_VERSIONS) {
if (len < 3 || e[0] != len - 1 || !(len % 2)) return REALITY_ERR_FORMAT;
for (size_t i = 1; i < len; i += 2) if (read_u16(e + i) == 0x0304) version_ok = 1;
} else if (type == TLS_EXT_KEY_SHARE) {
if (len < 2 || read_u16(e) != len - 2) return REALITY_ERR_FORMAT;
size_t i = 2;
while (i < len) {
if (len - i < 4) return REALITY_ERR_FORMAT;
unsigned group = read_u16(e + i);
size_t size = read_u16(e + i + 2);
i += 4;
if (!size || size > len - i) return REALITY_ERR_FORMAT;
if (group == TLS_GROUP_X25519) {
if (size != 32 || p->key_share) return REALITY_ERR_FORMAT;
p->key_share = e + i;
}
s += 4 + klen;
sl -= 4 + klen;
i += size;
}
} else if (type == TLS_EXT_ALPN) {
if (len < 4 || read_u16(e) != len - 2) return REALITY_ERR_FORMAT;
size_t i = 2;
while (i < len) { size_t size = e[i++]; if (!size || size > len - i) return REALITY_ERR_FORMAT; i += size; }
} else if (type == TLS_EXT_SERVER_NAME) {
if (len < 6 || read_u16(e) != len - 2) return REALITY_ERR_FORMAT;
size_t i = 2;
while (i < len) {
if (len - i < 3) return REALITY_ERR_FORMAT;
size_t size = read_u16(e + i + 1); i += 3;
if (!size || size > len - i) return REALITY_ERR_FORMAT;
i += size;
}
}
ext += 4 + len;
remaining -= 4 + len;
pos += len;
}
*hs_out = hs;
*hs_len_out = hs_len;
if (!cipher_ok || !version_ok) return REALITY_ERR_AUTH;
*hs_out = hs; *hs_len_out = n;
return REALITY_OK;
}
int reality_server_hello_build_at(const struct reality_server_config *cfg, uint32_t now_sec,
static int server_hello_build(struct reality_session *session, const struct reality_server_config *cfg, uint32_t now_sec,
const uint8_t *ch, size_t ch_len,
uint8_t *out, size_t out_cap, size_t *out_len) {
if (!cfg || !ch || !out || !out_len || out_cap < REALITY_MAX_SH_SIZE) {
if (!cfg || !ch || !out || !out_len || out_cap < REALITY_MAX_SH_SIZE || cfg->short_id_count < 1 ||
cfg->short_id_count > REALITY_MAX_SHORT_IDS || cfg->time_window_sec < 0 || cfg->time_window_sec > 3600) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: invalid args");
return REALITY_ERR_ARG;
}
@@ -512,30 +517,27 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3
}
// shared + AuthKey
uint8_t shared[32], auth_key[32];
uint8_t shared[32] = {0}, auth_key[32] = {0}, plain[16] = {0}, eph_priv[32] = {0}, eph_pub[32], srv_random[32];
if (reality_x25519(cfg->static_privkey, p.key_share, shared) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: X25519 derive failed");
return REALITY_ERR_CRYPTO;
rc = REALITY_ERR_CRYPTO; goto out;
}
if (reality_hkdf_sha256(shared, sizeof(shared), p.random, 20,
(const uint8_t *)REALITY_HKDF_INFO, sizeof(REALITY_HKDF_INFO) - 1,
auth_key, sizeof(auth_key)) != 0) {
if (reality_kdf(shared, sizeof(shared), p.random, 20, REALITY_HKDF_INFO, auth_key, sizeof(auth_key)) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: HKDF failed");
return REALITY_ERR_CRYPTO;
rc = REALITY_ERR_CRYPTO; goto out;
}
// AAD = hs с обнулённым SessionId [39:71]
uint8_t aad[REALITY_MAX_CH_SIZE];
if (hs_len > sizeof(aad)) return REALITY_ERR_FORMAT;
if (hs_len > sizeof(aad)) { rc = REALITY_ERR_FORMAT; goto out; }
memcpy(aad, hs, hs_len);
memset(aad + 39, 0, 32);
// расшифровать SessionId[0:16]=ciphertext, [16:32]=tag
uint8_t plain[16];
if (reality_aes_gcm_open(auth_key, p.random + 20, aad, hs_len,
p.session_id, 16, p.session_id + 16, plain) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: AES-GCM auth failed");
return REALITY_ERR_AUTH;
rc = REALITY_ERR_AUTH; goto out;
}
// сверка версии
@@ -543,7 +545,7 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3
DEBUG_WARN(DEBUG_CATEGORY_REALITY,
"reality_server_hello_build: version mismatch got=%d.%d.%d res=%d want=%d.%d.%d",
plain[0], plain[1], plain[2], plain[3], cfg->version[0], cfg->version[1], cfg->version[2]);
return REALITY_ERR_AUTH;
rc = REALITY_ERR_AUTH; goto out;
}
// сверка timestamp
uint32_t ts = ((uint32_t)plain[4] << 24) | ((uint32_t)plain[5] << 16) |
@@ -553,58 +555,61 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3
if (diff < -(int64_t)cfg->time_window_sec || diff > (int64_t)cfg->time_window_sec) {
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: timestamp out of window ts=%u now=%lld diff=%lld",
ts, (long long)now, (long long)diff);
return REALITY_ERR_AUTH;
rc = REALITY_ERR_AUTH; goto out;
}
// сверка short_id
int sid_ok = 0;
for (int i = 0; i < cfg->short_id_count; i++) {
if (memcmp(plain + 8, cfg->short_ids[i], REALITY_SHORT_ID_SIZE) == 0) { sid_ok = 1; break; }
if (CRYPTO_memcmp(plain + 8, cfg->short_ids[i], REALITY_SHORT_ID_SIZE) == 0) { sid_ok = 1; break; }
}
if (!sid_ok) {
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: short_id not in list");
return REALITY_ERR_AUTH;
rc = REALITY_ERR_AUTH; goto out;
}
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: auth OK short_id=%02x%02x%02x%02x%02x%02x%02x%02x ts=%u",
plain[8], plain[9], plain[10], plain[11], plain[12], plain[13], plain[14], plain[15], ts);
if (session) {
session->auth_timestamp = ts;
memcpy(session->auth_key, auth_key, 32);
if (reality_transcript_add(session, hs, hs_len)) { rc = REALITY_ERR_CRYPTO; goto out; }
}
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "client hello authenticated: timestamp=%u", ts);
// ─── собрать ServerHello ───
const struct reality_fingerprint *fp = reality_fingerprint_get(cfg->fingerprint);
if (!fp) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: unknown fingerprint"); return REALITY_ERR_ARG; }
if (!fp) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "unknown server fingerprint"); rc = REALITY_ERR_ARG; goto out; }
uint8_t eph_priv[32], eph_pub[32], srv_random[32];
if (reality_gen_keypair(eph_priv, eph_pub) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: keygen failed");
return REALITY_ERR_CRYPTO;
rc = REALITY_ERR_CRYPTO; goto out;
}
(void)eph_priv;
if (random_bytes(srv_random, sizeof(srv_random)) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: random_bytes failed");
return REALITY_ERR_CRYPTO;
rc = REALITY_ERR_CRYPTO; goto out;
}
// ServerHello body
uint8_t sh_body[256];
struct rbuf b = { sh_body, sizeof(sh_body), 0 };
if (rbuf_put_u16(&b, 0x0303) < 0) return REALITY_ERR_ARG; // legacy_version
if (rbuf_put(&b, srv_random, 32) < 0) return REALITY_ERR_ARG; // random
if (rbuf_put_u8(&b, 32) < 0) return REALITY_ERR_ARG; // session_id_echo len
if (rbuf_put(&b, p.session_id, 32) < 0) return REALITY_ERR_ARG; // echo SessionId клиента
if (rbuf_put_u16(&b, fp->server_cipher) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u8(&b, 0) < 0) return REALITY_ERR_ARG; // compression = null
if (rbuf_put_u16(&b, 0x0303) < 0) { rc = REALITY_ERR_ARG; goto out; } // legacy_version
if (rbuf_put(&b, srv_random, 32) < 0) { rc = REALITY_ERR_ARG; goto out; } // random
if (rbuf_put_u8(&b, 32) < 0) { rc = REALITY_ERR_ARG; goto out; } // session_id_echo len
if (rbuf_put(&b, p.session_id, 32) < 0) { rc = REALITY_ERR_ARG; goto out; } // echo SessionId клиента
if (rbuf_put_u16(&b, fp->server_cipher) < 0) { rc = REALITY_ERR_ARG; goto out; }
if (rbuf_put_u8(&b, 0) < 0) { rc = REALITY_ERR_ARG; goto out; } // compression = null
size_t ext_len_pos = b.len;
if (rbuf_put_u16(&b, 0) < 0) return REALITY_ERR_ARG; // placeholder
if (rbuf_put_u16(&b, 0) < 0) { rc = REALITY_ERR_ARG; goto out; } // placeholder
// supported_versions
if (rbuf_put_u16(&b, TLS_EXT_SUPPORTED_VERSIONS) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, 2) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, TLS_VERSION_1_3) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, TLS_EXT_SUPPORTED_VERSIONS) < 0) { rc = REALITY_ERR_ARG; goto out; }
if (rbuf_put_u16(&b, 2) < 0) { rc = REALITY_ERR_ARG; goto out; }
if (rbuf_put_u16(&b, TLS_VERSION_1_3) < 0) { rc = REALITY_ERR_ARG; goto out; }
// key_share (server)
if (rbuf_put_u16(&b, TLS_EXT_KEY_SHARE) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, 36) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, TLS_GROUP_X25519) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, 32) < 0) return REALITY_ERR_ARG;
if (rbuf_put(&b, eph_pub, 32) < 0) return REALITY_ERR_ARG;
if (rbuf_put_u16(&b, TLS_EXT_KEY_SHARE) < 0) { rc = REALITY_ERR_ARG; goto out; }
if (rbuf_put_u16(&b, 36) < 0) { rc = REALITY_ERR_ARG; goto out; }
if (rbuf_put_u16(&b, TLS_GROUP_X25519) < 0) { rc = REALITY_ERR_ARG; goto out; }
if (rbuf_put_u16(&b, 32) < 0) { rc = REALITY_ERR_ARG; goto out; }
if (rbuf_put(&b, eph_pub, 32) < 0) { rc = REALITY_ERR_ARG; goto out; }
rbuf_patch_u16(&b, ext_len_pos, (uint16_t)(b.len - ext_len_pos - 2));
size_t sh_body_len = b.len;
@@ -622,8 +627,18 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3
memcpy(out + 9, sh_body, sh_body_len);
*out_len = 5 + sh_hs_len;
if (session) {
memcpy(session->ephemeral_private, eph_priv, 32);
if (reality_transcript_add(session, out + 5, sh_hs_len) || session_derive(session, p.key_share)) { rc = REALITY_ERR_CRYPTO; goto out; }
}
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "server hello built: total=%zu", *out_len);
return REALITY_OK;
rc = REALITY_OK;
out:
OPENSSL_cleanse(shared, sizeof(shared));
OPENSSL_cleanse(auth_key, sizeof(auth_key));
OPENSSL_cleanse(plain, sizeof(plain));
OPENSSL_cleanse(eph_priv, sizeof(eph_priv));
return rc;
}
int reality_server_hello_build(const struct reality_server_config *cfg,
@@ -631,3 +646,110 @@ int reality_server_hello_build(const struct reality_server_config *cfg,
uint8_t *out, size_t out_cap, size_t *out_len) {
return reality_server_hello_build_at(cfg, (uint32_t)time(NULL), ch, ch_len, out, out_cap, out_len);
}
int reality_client_hello_build_at(const struct reality_client_config *cfg, uint32_t now,
uint8_t *out, size_t cap, size_t *written) {
return client_hello_build(NULL, cfg, now, out, cap, written);
}
int reality_server_hello_build_at(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) {
return server_hello_build(NULL, cfg, now, ch, len, out, cap, written);
}
void reality_session_handshake_done(struct reality_session *s) {
EVP_MD_CTX_free(s->transcript);
s->transcript = NULL;
OPENSSL_cleanse(s->finished_send, 32);
OPENSSL_cleanse(s->finished_recv, 32);
}
void reality_session_cleanup(struct reality_session *s) {
if (!s) return;
EVP_MD_CTX_free(s->transcript);
OPENSSL_cleanse(s, sizeof(*s));
}
int reality_transcript_add(struct reality_session *s, const uint8_t *data, size_t len) {
if (!s->transcript) {
s->transcript = EVP_MD_CTX_new();
if (!s->transcript || EVP_DigestInit_ex(s->transcript, EVP_sha256(), NULL) != 1) goto fail;
}
if (EVP_DigestUpdate(s->transcript, data, len) == 1) return 0;
fail:
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "handshake transcript update failed: size=%zu", len);
return -1;
}
static int transcript_digest(struct reality_session *s, uint8_t out[32]) {
EVP_MD_CTX *copy = EVP_MD_CTX_new();
unsigned n = 0;
int ok = copy && s->transcript && EVP_MD_CTX_copy_ex(copy, s->transcript) == 1 &&
EVP_DigestFinal_ex(copy, out, &n) == 1 && n == 32;
EVP_MD_CTX_free(copy);
if (!ok) DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "handshake transcript digest failed");
return ok ? 0 : -1;
}
static int session_derive(struct reality_session *s, const uint8_t peer[32]) {
uint8_t ikm[64], hash[32], master[32], c2s[32], s2c[32];
memcpy(ikm, s->auth_key, 32);
int rc = -1;
if (reality_x25519(s->ephemeral_private, peer, ikm + 32) || transcript_digest(s, hash) ||
reality_kdf(ikm, 64, hash, 32, "uTun-reality-record-v2", master, 32) ||
reality_kdf(master, 32, NULL, 0, "client-traffic", c2s, 32) ||
reality_kdf(master, 32, NULL, 0, "server-traffic", s2c, 32) ||
reality_traffic_init(&s->send, s->is_server ? s2c : c2s) ||
reality_traffic_init(&s->recv, s->is_server ? c2s : s2c) ||
reality_kdf(s->send.secret, 32, NULL, 0, "finished", s->finished_send, 32) ||
reality_kdf(s->recv.secret, 32, NULL, 0, "finished", s->finished_recv, 32)) goto out;
s->keys_ready = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "record keys ready: role=%s", s->is_server ? "server" : "client");
rc = 0;
out:
OPENSSL_cleanse(ikm, sizeof(ikm));
OPENSSL_cleanse(master, sizeof(master));
OPENSSL_cleanse(c2s, sizeof(c2s));
OPENSSL_cleanse(s2c, sizeof(s2c));
OPENSSL_cleanse(s->ephemeral_private, 32);
OPENSSL_cleanse(s->auth_key, 32);
if (rc) DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "record key derivation failed");
return rc;
}
int reality_finished(struct reality_session *s, int sending, uint8_t out[32]) {
uint8_t hash[32];
unsigned n = 0;
if (!s->keys_ready || transcript_digest(s, hash) ||
!HMAC(EVP_sha256(), sending ? s->finished_send : s->finished_recv, 32, hash, 32, out, &n) || n != 32) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "Finished calculation failed: sending=%d", sending);
return -1;
}
return 0;
}
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) {
if (!s || s->transcript || s->keys_ready) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "client Hello context already used or missing"); return REALITY_ERR_ARG; }
return client_hello_build(s, cfg, now, out, cap, written);
}
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) {
if (!s || s->transcript || s->keys_ready) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "server Hello context already used or missing"); return REALITY_ERR_ARG; }
s->is_server = 1;
return server_hello_build(s, cfg, now, ch, len, out, cap, written);
}
int reality_session_client_accept(struct reality_session *s, const uint8_t *sh, size_t len) {
/* Единственный допустимый ServerHello: предложенные AES-128-GCM, X25519 и TLS 1.3. */
if (!s || !sh || !s->transcript || s->keys_ready || len != 127 || sh[0] != 22 || sh[1] != 3 || sh[2] != 3 || sh[3] != 0 || sh[4] != 122 ||
sh[5] != 2 || sh[6] || sh[7] || sh[8] != 118 || sh[9] != 3 || sh[10] != 3 || sh[43] != 32 ||
CRYPTO_memcmp(sh + 44, s->session_id, 32) || sh[76] != 0x13 || sh[77] != 1 || sh[78] ||
sh[79] || sh[80] != 46 || memcmp(sh + 81, "\x00\x2b\x00\x02\x03\x04\x00\x33\x00\x24\x00\x1d\x00\x20", 14)) {
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "ServerHello validation failed: size=%zu", len);
return REALITY_ERR_FORMAT;
}
if (reality_transcript_add(s, sh + 5, len - 5) || session_derive(s, sh + 95)) return REALITY_ERR_CRYPTO;
return REALITY_OK;
}
+30 -7
View File
@@ -6,8 +6,8 @@
// (16 байт) несёт version(3) + reserved(1) + timestamp(4) + ShortId(8).
// Ключ AuthKey = HKDF-SHA256(X25519(eph, static), Random[0:20], "REALITY").
//
// Область применения — «hello-only»: обмениваемся только заголовками, реальную
// взаимную аутентификацию делает штатный STCP-хендшейк поверх.
// После Hello весь STCP передаётся в защищённых TLS-подобных записях.
// Это собственный протокол с TLS-фреймированием; ключи не совместимы с TLS.
#ifndef REALITY_H
#define REALITY_H
@@ -17,6 +17,7 @@ extern "C" {
#include <stdint.h>
#include <stddef.h>
#include "reality_record.h"
// Размеры
#define REALITY_SHORT_ID_SIZE 8
@@ -25,7 +26,7 @@ extern "C" {
#define REALITY_RANDOM_SIZE 32
#define REALITY_SESSION_ID_SIZE 32
#define REALITY_TAG_SIZE 16
#define REALITY_MAX_CH_SIZE 2048 // максимальный размер ClientHello (TLS record + handshake)
#define REALITY_MAX_CH_SIZE 65535 // предел нормализованного ClientHello после сборки записей
#define REALITY_MAX_SH_SIZE 512 // максимальный размер ServerHello
#define REALITY_MAX_SHORT_IDS 64
#define REALITY_SERVER_NAME_MAX 256
@@ -38,9 +39,31 @@ extern "C" {
#define REALITY_ERR_FORMAT -4
// Отпечатки (reality_fingerprint.c)
#define REALITY_FP_CHROME 0
#define REALITY_FP_OPENSSL 0
#define REALITY_FP_COUNT 1
struct reality_client_config;
struct reality_server_config;
/* Контекст одноразового Hello и последующих защищённых записей. */
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;
void *transcript; // EVP_MD_CTX, точные handshake-байты без заголовков записей
uint32_t auth_timestamp;
uint8_t is_server, keys_ready;
};
void reality_session_handshake_done(struct reality_session *s);
void reality_session_cleanup(struct reality_session *s);
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);
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);
int reality_session_client_accept(struct reality_session *s, const uint8_t *sh, size_t len);
int reality_transcript_add(struct reality_session *s, const uint8_t *data, size_t len);
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 сервера
@@ -56,7 +79,7 @@ struct reality_server_config {
uint8_t short_ids[REALITY_MAX_SHORT_IDS][REALITY_SHORT_ID_SIZE];
int short_id_count;
uint8_t version[REALITY_VERSION_SIZE]; // принимаемая версия
int64_t time_window_sec; // допуск timestamp (антиреплей)
int64_t time_window_sec; // допуск timestamp; replay-кеш находится в instance
int relay_idle_timeout_sec; // idle-timeout релея неавторизованных (0=выкл)
int fingerprint;
};
@@ -81,10 +104,10 @@ struct reality_config {
};
// Заполняет cfg->version/fingerprint/time_window значениями по умолчанию
// (version=1.0.0, fingerprint=chrome, time_window=30). Вызывается при инициализации.
// (version=2.0.0, fingerprint=openssl, time_window=30). Вызывается при инициализации.
void reality_config_set_defaults(struct reality_config *cfg);
// Заполняет клиентскую конфигурацию (version=1.0.0, fingerprint=chrome).
// Заполняет клиентскую конфигурацию (version=2.0.0, fingerprint=openssl).
// Вызывается при парсинге reality-ключей в секции [client].
void reality_client_config_set_defaults(struct reality_client_config *cfg);
+10 -41
View File
@@ -1,47 +1,16 @@
// reality_fingerprint.c — статические отпечатки для REALITY-камуфляжа
/* Профиль OpenSSL 3.5.5: TLS 1.3, X25519, AES-128-GCM, ALPN http/1.1.
* Эталон и команда воспроизведения: tests/fixtures/reality_openssl_hello.hex. */
#include "reality_fingerprint.h"
#include "reality.h"
#include <stddef.h>
// Chrome-like TLS 1.3 отпечаток (упрощённый, но валидный набор).
// Cipher suites: 3 × TLS 1.3 + несколько legacy ECDHE-суитов для правдоподобия.
static const uint16_t fp_chrome_cipher_suites[] = {
0x1301, 0x1302, 0x1303, // TLS_AES_128/256_GCM, CHACHA20
0xc02c, 0xc02b, 0xc030, 0xc02f, // ECDHE-ECDSA/RSA AES-256/128-GCM
0xcca9, 0xcca8, // ECDHE-ECDSA/RSA CHACHA20-POLY1305
0x0000
static const uint16_t suites[] = {0x1301, 0};
static const uint16_t groups[] = {0x001d, 0};
static const uint16_t signatures[] = {
0x0905, 0x0906, 0x0904, 0x0403, 0x0503, 0x0603, 0x0807, 0x0808, 0x081a, 0x081b,
0x081c, 0x0809, 0x080a, 0x080b, 0x0804, 0x0805, 0x0806, 0x0401, 0x0501, 0x0601, 0
};
static const uint16_t fp_chrome_supported_groups[] = {
0x001d, // X25519
0x0017, 0x0018, 0x0019, // secp256r1/384r1/521r1
0x0000
};
static const uint16_t fp_chrome_sig_algs[] = {
0x0804, 0x0805, 0x0806, // rsa_pss_rsae_sha256/384/512
0x0401, 0x0501, 0x0601, // rsa_pkcs1_sha256/384/512
0x0403, 0x0503, 0x0603, // ecdsa_secp256r1/384r1/521r1
0x0807, 0x0808, // ed25519, ed448
0x0000
};
static const char *const fp_chrome_alpn[] = { "h2", "http/1.1", NULL };
static const struct reality_fingerprint g_fingerprints[REALITY_FP_COUNT] = {
{
.id = REALITY_FP_CHROME,
.name = "chrome",
.cipher_suites = fp_chrome_cipher_suites,
.supported_groups = fp_chrome_supported_groups,
.sig_algs = fp_chrome_sig_algs,
.alpn = fp_chrome_alpn,
.server_cipher = 0x1301, // TLS_AES_128_GCM_SHA256
},
};
static const char *const alpn[] = {"http/1.1", NULL};
static const struct reality_fingerprint profile = {REALITY_FP_OPENSSL, "openssl", suites, groups, signatures, alpn, 0x1301};
const struct reality_fingerprint *reality_fingerprint_get(int id) {
for (int i = 0; i < REALITY_FP_COUNT; i++)
if (g_fingerprints[i].id == id) return &g_fingerprints[i];
return NULL;
return id == REALITY_FP_OPENSSL ? &profile : NULL;
}
+394
View File
@@ -0,0 +1,394 @@
#define OPENSSL_API_COMPAT 0x10100000L
#include "reality_io.h"
#include "stcp.h"
#include "reality_relay.h"
#include "etcp.h"
#include "../utun_instance.h"
#include "../ntp_time.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include <string.h>
#include <errno.h>
#include <time.h>
#include <openssl/crypto.h>
#include <openssl/sha.h>
#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 reality_io {
struct reality_session session;
enum reality_phase phase;
struct ETCP_PADDING padding; // снимок для автономного клиента без instance
uint8_t *wire, *handshake;
size_t wire_len, hello_offset, handshake_len;
uint8_t flight_step, ccs_seen, close_sent;
void (*ready)(struct stcp_conn *);
};
static int replay_accept(struct reality_owner *owner, const uint8_t *ch, size_t len, uint32_t now,
uint32_t timestamp, int64_t window) {
if (!owner->replay) owner->replay = u_calloc(1, sizeof(*owner->replay));
if (!owner->replay) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "replay cache allocation failed"); return -1; }
uint8_t hash[32];
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 (!CRYPTO_memcmp(hash, owner->replay->entries[i].hash, 32)) {
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "replayed ClientHello rejected");
return -1;
}
} else if (slot < 0) slot = (int)i;
}
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;
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) {
reality_relay_shutdown(owner);
u_free(owner->replay);
owner->replay = NULL;
}
void reality_io_cleanup(struct stcp_conn *c) {
struct reality_io *io = c->reality_io;
if (!io) return;
c->reality_io = NULL;
reality_session_cleanup(&io->session);
u_free(io->wire);
u_free(io->handshake);
u_free(io);
}
static struct reality_io *io_create(struct stcp_conn *c, void (*ready)(struct stcp_conn *)) {
struct reality_io *io = u_calloc(1, sizeof(*io));
if (!io) goto fail;
io->wire = u_malloc(STCP_RECV_BUF_MAX);
io->handshake = u_malloc(REALITY_MAX_CH_SIZE);
if (!io->wire || !io->handshake) {
u_free(io->wire); u_free(io->handshake); u_free(io);
goto fail;
}
io->ready = ready;
c->reality_io = io;
return io;
fail:
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "record I/O allocation failed");
return 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 *)) {
struct reality_io *io = io_create(c, ready);
if (!io) return -1;
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 (reality_session_client_start(&io->session, cfg, now, ch, REALITY_MAX_CH_SIZE, &n) || stcp_send_raw(c, ch, n) < 0) {
u_free(ch);
return -1;
}
return 0;
}
int reality_io_server_start(struct stcp_conn *c, void (*ready)(struct stcp_conn *)) {
struct reality_io *io = io_create(c, ready);
if (!io) return -1;
io->session.is_server = 1;
return 0;
}
static int send_record(struct stcp_conn *c, uint8_t type, const uint8_t *data, size_t len, size_t padding) {
uint8_t *wire = u_malloc(5 + len + 1 + padding + 16);
size_t n = 0;
if (!wire) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "record tx allocation failed: content=%zu", len); return -1; }
if (reality_record_seal(&c->reality_io->session.send, type, data, len, padding, wire,
5 + len + 1 + padding + 16, &n) || stcp_send_raw(c, wire, n) < 0) {
u_free(wire);
return -1;
}
return c->send_buf ? 1 : 0;
}
static int send_key_update(struct stcp_conn *c) {
const uint8_t update[] = {24, 0, 0, 1, 0};
if (send_record(c, REALITY_TYPE_HANDSHAKE, update, sizeof(update), 0) < 0) return -1;
return reality_traffic_update(&c->reality_io->session.send);
}
int reality_io_send(struct stcp_conn *c, uint8_t *data, size_t len) {
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);
return -1;
}
const struct ETCP_PADDING *profile = c->etcp_conn ? &c->etcp_conn->padding :
(c->inst ? etcp_padding_for_peer(c->inst, c->peer_pubkey_set ? c->peer_pubkey : NULL) : &io->padding);
size_t offset = 0;
do {
if (io->session.send.sequence >= REALITY_KEY_LIMIT - 1 && send_key_update(c)) return -1;
int pad = etcp_padding_size(profile, ETCP_PADDING_MAX);
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;
offset += take;
} while (offset < len);
u_free(data);
return c->send_buf ? 1 : 0;
}
int reality_io_close_notify(struct stcp_conn *c) {
struct reality_io *io = c->reality_io;
if (io->phase != RECORD_DATA || io->close_sent) return 0;
io->close_sent = 1;
const uint8_t alert[] = {1, 0};
if (io->session.send.sequence >= REALITY_KEY_LIMIT - 1 && send_key_update(c)) return -1;
int rc = send_record(c, REALITY_TYPE_ALERT, alert, sizeof(alert), 0);
DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "close_notify queued: pending=%d", rc);
return rc;
}
static int send_ccs(struct stcp_conn *c) {
const uint8_t ccs[] = {20, 3, 3, 0, 1, 1};
uint8_t *wire = u_malloc(sizeof(ccs));
if (!wire) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "CCS allocation failed"); return -1; }
memcpy(wire, ccs, sizeof(ccs));
if (stcp_send_raw(c, wire, sizeof(ccs)) < 0) { u_free(wire); return -1; }
return 0;
}
static int send_finished(struct stcp_conn *c) {
uint8_t finished[36] = {20, 0, 0, 32};
struct reality_session *s = &c->reality_io->session;
if (!s->is_server && send_ccs(c)) return -1;
if (reality_finished(s, 1, finished + 4) || send_record(c, REALITY_TYPE_HANDSHAKE, finished, 36, 0) < 0 ||
reality_transcript_add(s, finished, sizeof(finished))) return -1;
return 0;
}
static int send_server_flight(struct stcp_conn *c) {
/* Размеры записей эталонного single-certificate RSA flight OpenSSL: 43/832/286/58.
* Cover-данные не используются как PKI; их точные байты включаются в Finished. */
const uint8_t ee[] = {8, 0, 0, 17, 0, 15, 0, 16, 0, 11, 0, 9, 8, 'h', 't', 't', 'p', '/', '1', '.', '1'};
uint8_t certificate[810], verify[264];
if (random_bytes(certificate, sizeof(certificate)) || random_bytes(verify, sizeof(verify))) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "cover flight random failed");
return -1;
}
certificate[0] = 11; certificate[1] = 0; certificate[2] = 3; certificate[3] = 38;
verify[0] = 15; verify[1] = 0; verify[2] = 1; verify[3] = 4;
verify[4] = 8; verify[5] = 4; verify[6] = 1; verify[7] = 0;
struct reality_session *session = &c->reality_io->session;
if (send_ccs(c)) return -1;
if (reality_transcript_add(session, ee, sizeof(ee)) || send_record(c, REALITY_TYPE_HANDSHAKE, ee, sizeof(ee), 0) < 0 ||
reality_transcript_add(session, certificate, sizeof(certificate)) ||
send_record(c, REALITY_TYPE_HANDSHAKE, certificate, sizeof(certificate), 0) < 0 ||
reality_transcript_add(session, verify, sizeof(verify)) || send_record(c, REALITY_TYPE_HANDSHAKE, verify, sizeof(verify), 0) < 0 ||
send_finished(c)) return -1;
return 0;
}
static int fallback(struct stcp_conn *c) {
struct reality_io *io = c->reality_io;
if (!io->session.is_server || io->phase != WAIT_HELLO) return -1;
socket_t sock = c->sock;
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);
stcp_conn_do_close(c, ECANCELED);
return -1;
}
static int hello_process(struct stcp_conn *c) {
struct reality_io *io = c->reality_io;
while (io->wire_len - io->hello_offset >= 5) {
const uint8_t *record = io->wire + io->hello_offset;
size_t n = ((size_t)record[3] << 8) | record[4];
if (record[0] != 22 || record[1] != 3 || (record[2] != 3 && (!io->session.is_server || record[2] != 1)) || !n || n > 16384 ||
io->handshake_len + n > REALITY_MAX_CH_SIZE - 5) goto reject;
if (io->wire_len - io->hello_offset < n + 5) return 0;
memcpy(io->handshake + io->handshake_len, record + 5, n);
io->handshake_len += n;
io->hello_offset += n + 5;
if (io->handshake_len < 4) continue;
size_t want = 4 + ((size_t)io->handshake[1] << 16) + ((size_t)io->handshake[2] << 8) + io->handshake[3];
if (want > REALITY_MAX_CH_SIZE - 5 || io->handshake_len > want) goto reject;
if (io->handshake_len < want) continue;
uint8_t *full = u_malloc(want + 5);
if (!full) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "Hello normalization allocation failed"); return -1; }
full[0] = 22; full[1] = 3; full[2] = io->session.is_server ? 1 : 3;
full[3] = (uint8_t)(want >> 8); full[4] = (uint8_t)want;
memcpy(full + 5, io->handshake, want);
int rc;
if (io->session.is_server) {
uint8_t *sh = u_malloc(REALITY_MAX_SH_SIZE);
size_t written = 0;
uint32_t now = c->inst ? (uint32_t)ntp_time_get_seconds(c->inst) : (uint32_t)time(NULL);
if (!sh) DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "ServerHello output allocation failed");
rc = sh ? reality_session_server_start(&io->session, &c->reality_srv, now, full, want + 5,
sh, REALITY_MAX_SH_SIZE, &written) : REALITY_ERR_CRYPTO;
if (!rc && (!c->reality_owner || replay_accept(c->reality_owner, io->handshake, want, now,
io->session.auth_timestamp, c->reality_srv.time_window_sec))) rc = REALITY_ERR_AUTH;
if (rc) { u_free(sh); u_free(full); goto reject; }
io->phase = WAIT_CLIENT_FINISHED; // fallback запрещён с момента постановки собственного ответа
if (stcp_send_raw(c, sh, written) < 0) { u_free(sh); u_free(full); return -1; }
if (send_server_flight(c)) { u_free(full); return -1; }
} else {
rc = reality_session_client_accept(&io->session, full, want + 5);
if (rc) { u_free(full); return -1; }
io->phase = WAIT_SERVER_FLIGHT;
}
u_free(full);
memmove(io->wire, io->wire + io->hello_offset, io->wire_len - io->hello_offset);
io->wire_len -= io->hello_offset;
io->hello_offset = 0;
io->handshake_len = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "Hello complete: role=%s phase=%d", io->session.is_server ? "server" : "client", io->phase);
return 1;
}
return 0;
reject:
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "Hello rejected: buffered=%zu handshake=%zu", io->wire_len, io->handshake_len);
return fallback(c);
}
static int handshake_process(struct stcp_conn *c, const uint8_t *data, size_t len) {
struct reality_io *io = c->reality_io;
if (len > REALITY_MAX_CH_SIZE - io->handshake_len) {
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "protected handshake too large");
return -1;
}
memcpy(io->handshake + io->handshake_len, data, len);
io->handshake_len += len;
while (io->handshake_len >= 4) {
uint8_t type = io->handshake[0];
size_t n = 4 + ((size_t)io->handshake[1] << 16) + ((size_t)io->handshake[2] << 8) + io->handshake[3];
if (n > REALITY_MAX_CH_SIZE) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "protected handshake length rejected: %zu", n); return -1; }
if (n > io->handshake_len) return 0;
if (io->phase == RECORD_DATA) {
if (n != 5 || io->handshake_len != 5 || type != 24 || io->handshake[4] != 0 || reality_traffic_update(&io->session.recv)) goto reject;
} else if (type == 20) {
uint8_t expected[32];
if (n != 36 || (io->phase == WAIT_SERVER_FLIGHT && io->flight_step != 3) ||
reality_finished(&io->session, 0, expected) || CRYPTO_memcmp(expected, io->handshake + 4, 32) ||
reality_transcript_add(&io->session, io->handshake, n)) goto reject;
if (io->phase == WAIT_SERVER_FLIGHT && send_finished(c)) return -1;
io->phase = RECORD_DATA;
reality_session_handshake_done(&io->session);
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "Finished verified: role=%s; protected STCP enabled", io->session.is_server ? "server" : "client");
io->ready(c);
if (!c->reality_io || c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return -1;
} else {
const uint8_t steps[] = {8, 11, 15};
if (io->phase != WAIT_SERVER_FLIGHT || io->flight_step >= 3 || type != steps[io->flight_step] ||
reality_transcript_add(&io->session, io->handshake, n)) goto reject;
io->flight_step++;
}
memmove(io->handshake, io->handshake + n, io->handshake_len - n);
io->handshake_len -= n;
}
return 0;
reject:
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "protected handshake rejected: phase=%d step=%u type=%u size=%zu", io->phase,
io->flight_step, io->handshake[0], io->handshake_len);
return -1;
}
static int append_application(struct stcp_conn *c, const uint8_t *data, size_t len) {
if (len > STCP_RECV_BUF_MAX - c->recv_buf_len) { DEBUG_WARN(DEBUG_CATEGORY_REALITY, "STCP plaintext buffer full"); return -1; }
if (c->recv_buf_len + len > c->recv_buf_cap) {
size_t cap = c->recv_buf_len + len;
if (cap < STCP_RECV_BUF_INIT) cap = STCP_RECV_BUF_INIT;
uint8_t *p = u_realloc(c->recv_buf, cap);
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "STCP plaintext buffer allocation failed: %zu", cap); return -1; }
c->recv_buf = p; c->recv_buf_cap = cap;
}
if (len) memcpy(c->recv_buf + c->recv_buf_len, data, len);
c->recv_buf_len += len;
stcp_recv_try(c);
return c->reality_io ? 0 : -1;
}
static int records_process(struct stcp_conn *c) {
struct reality_io *io = c->reality_io;
if (io->phase == WAIT_HELLO) {
int rc = hello_process(c);
if (rc <= 0) return rc;
}
while (io->wire_len >= 5 && !c->rx_paused && !c->finish_requested) {
size_t n = 5 + ((size_t)io->wire[3] << 8) + io->wire[4];
if (n > REALITY_RECORD_MAX || n < 6) goto reject;
if (io->wire_len < n) return 0;
if (io->wire[0] == 20) {
const uint8_t ccs[] = {20, 3, 3, 0, 1, 1};
if (io->phase == RECORD_DATA || io->ccs_seen || n != sizeof(ccs) || memcmp(io->wire, ccs, sizeof(ccs))) goto reject;
io->ccs_seen = 1;
} else {
uint8_t plain[REALITY_INNER_MAX], type;
size_t len = 0;
if (reality_record_open(&io->session.recv, io->wire, n, plain, sizeof(plain), &len, &type)) goto reject;
if (type == REALITY_TYPE_HANDSHAKE) {
if (handshake_process(c, plain, len)) return -1;
} else if (type == REALITY_TYPE_APPLICATION) {
if (io->phase != RECORD_DATA || io->handshake_len) goto reject;
if (append_application(c, plain, len)) return -1;
} else {
if (io->phase != RECORD_DATA || len != 2 || plain[0] != 1 || plain[1] != 0) goto reject;
stcp_conn_finish(c);
return -2;
}
if (!c->reality_io) return -1;
}
memmove(io->wire, io->wire + n, io->wire_len - n);
io->wire_len -= n;
}
return 0;
reject:
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "record stream rejected: phase=%d buffered=%zu", io->phase, io->wire_len);
return -1;
}
void reality_io_resume(struct stcp_conn *c) {
if (c->reality_io && records_process(c) < 0) stcp_conn_do_close(c, ECANCELED);
}
size_t reality_io_buffered(const struct stcp_conn *c) {
return c && c->reality_io ? c->reality_io->wire_len : 0;
}
int reality_io_read(struct stcp_conn *c) {
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");
stcp_conn_do_close(c, ENOBUFS);
return -1;
}
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;
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "record socket read failed: err=%d", err);
stcp_conn_do_close(c, err);
return -1;
}
if (!n) {
DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "record socket EOF: phase=%d incomplete=%zu", io->phase, io->wire_len);
stcp_conn_do_close(c, io->wire_len || io->phase != RECORD_DATA ? ECANCELED : 0);
return -1;
}
io->wire_len += (size_t)n;
int rc = records_process(c);
if (rc < 0) { if (rc == -1) stcp_conn_do_close(c, ECANCELED); return -1; }
return 1;
}
+39
View File
@@ -0,0 +1,39 @@
/* Поток записей отделён от внутреннего буфера STCP; все вызовы — в uasync-потоке владельца.
* CH -> SH + CCS + encrypted(EE/cover Certificate/cover CertificateVerify/Server Finished)
* -> CCS + encrypted(Client Finished) -> encrypted(STCP handshake и последующий поток).
* Проверка стороны использует Finished, cover Certificate/CertificateVerify не являются PKI.
* Root = HKDF-SHA256(AuthKey || X25519(client_ephemeral, server_ephemeral), SHA256(CH || SH),
* "uTun-reality-record-v2"); в transcript входят handshake-заголовки, но не record-заголовки.
* От root раздельно выводятся client-traffic/server-traffic и их key/iv/finished-ключи.
* После собственного SH fallback запрещён. До него relay получает весь исходный буфер без нормализации.
* Таймеры/списки/кеш replay принадлежат instance; destroy принудительно закрывает I/O без ожидания callbacks. */
#ifndef REALITY_IO_H
#define REALITY_IO_H
#include "reality.h"
#include "etcp_padding.h"
#ifdef __cplusplus
extern "C" {
#endif
struct stcp_conn;
struct reality_io;
struct reality_replay_cache;
struct reality_relay;
struct reality_owner {
struct reality_replay_cache *replay;
struct reality_relay *relays;
unsigned relay_count;
};
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 *));
int reality_io_server_start(struct stcp_conn *c, void (*ready)(struct stcp_conn *));
size_t reality_io_buffered(const struct stcp_conn *c);
int reality_io_read(struct stcp_conn *c);
void reality_io_resume(struct stcp_conn *c);
int reality_io_send(struct stcp_conn *c, uint8_t *data, size_t len);
int reality_io_close_notify(struct stcp_conn *c);
void reality_io_cleanup(struct stcp_conn *c);
void reality_owner_cleanup(struct reality_owner *owner);
#ifdef __cplusplus
}
#endif
#endif
+128
View File
@@ -0,0 +1,128 @@
#define OPENSSL_API_COMPAT 0x10100000L
#include "reality_record.h"
#include "../lib/debug_config.h"
#include <string.h>
#include <limits.h>
#include <openssl/evp.h>
#include <openssl/kdf.h>
#include <openssl/crypto.h>
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) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid KDF arguments");
return -1;
}
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
size_t n = out_len;
int ok = ctx && EVP_PKEY_derive_init(ctx) > 0 && EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256()) > 0 &&
(!salt_len || EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt, (int)salt_len) > 0) &&
EVP_PKEY_CTX_set1_hkdf_key(ctx, ikm, (int)ikm_len) > 0 &&
EVP_PKEY_CTX_add1_hkdf_info(ctx, (const uint8_t *)label, (int)strlen(label)) > 0 &&
EVP_PKEY_derive(ctx, out, &n) > 0 && n == out_len;
EVP_PKEY_CTX_free(ctx);
if (!ok) DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "record KDF failed: label=%s", label);
return ok ? 0 : -1;
}
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;
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;
}
int reality_traffic_update(struct reality_traffic *t) {
if (!t) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "missing traffic state on KeyUpdate"); return -1; }
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++;
DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "record KeyUpdate accepted: generation=%u", t->generation);
return 0;
}
static void record_nonce(const struct reality_traffic *t, uint8_t nonce[12]) {
memcpy(nonce, t->iv, 12);
for (unsigned i = 0; i < 8; i++) nonce[11 - i] ^= (uint8_t)(t->sequence >> (i * 8));
}
static int valid_type(uint8_t type) {
return type == REALITY_TYPE_HANDSHAKE || type == REALITY_TYPE_APPLICATION || type == REALITY_TYPE_ALERT;
}
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) {
if (!t || !out || !written || !valid_type(type) || (!data && len) ||
len > REALITY_INNER_MAX - 1 || padding > REALITY_INNER_MAX - 1 - len ||
cap < 5 + len + 1 + padding + 16 || t->sequence >= REALITY_KEY_LIMIT ||
(!len && type != REALITY_TYPE_APPLICATION)) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "record seal rejected: type=%u content=%zu padding=%zu", type, len, padding);
return -1;
}
uint8_t plain[REALITY_INNER_MAX], nonce[12];
size_t n = len + 1 + padding, cipher_len = n + 16;
if (len) memcpy(plain, data, len);
plain[len] = type;
memset(plain + len + 1, 0, padding);
out[0] = 23; out[1] = 3; out[2] = 3; out[3] = (uint8_t)(cipher_len >> 8); out[4] = (uint8_t)cipher_len;
record_nonce(t, nonce);
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int size = 0, final = 0;
int ok = ctx && EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, t->key, nonce) == 1 &&
EVP_EncryptUpdate(ctx, NULL, &size, out, 5) == 1 &&
EVP_EncryptUpdate(ctx, out + 5, &size, plain, (int)n) == 1 && (size_t)size == n &&
EVP_EncryptFinal_ex(ctx, out + 5 + size, &final) == 1 && final == 0 &&
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, out + 5 + n) == 1;
EVP_CIPHER_CTX_free(ctx);
OPENSSL_cleanse(plain, n);
if (!ok) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "record encryption failed: seq=%llu", (unsigned long long)t->sequence); return -1; }
DEBUG_TRACE(DEBUG_CATEGORY_REALITY, "record tx: generation=%u seq=%llu type=%u content=%zu padding=%zu",
t->generation, (unsigned long long)t->sequence, type, len, padding);
t->sequence++;
*written = 5 + cipher_len;
return 0;
}
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) {
if (!t || !record || !out || !written || !type || len < 22 || len > REALITY_RECORD_MAX ||
record[0] != 23 || record[1] != 3 || record[2] != 3 ||
(((size_t)record[3] << 8) | record[4]) != len - 5 || cap < len - 21 || t->sequence >= REALITY_KEY_LIMIT) {
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "record header rejected: wire=%zu", len);
return -1;
}
uint8_t nonce[12];
record_nonce(t, nonce);
size_t n = len - 21;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int size = 0, final = 0;
int ok = ctx && EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, t->key, nonce) == 1 &&
EVP_DecryptUpdate(ctx, NULL, &size, record, 5) == 1 &&
EVP_DecryptUpdate(ctx, out, &size, record + 5, (int)n) == 1 && (size_t)size == n &&
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, (void *)(record + 5 + n)) == 1 &&
EVP_DecryptFinal_ex(ctx, out + size, &final) == 1;
EVP_CIPHER_CTX_free(ctx);
if (!ok) {
OPENSSL_cleanse(out, n);
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "record authentication failed: generation=%u seq=%llu", t->generation,
(unsigned long long)t->sequence);
return -1;
}
while (n && !out[n - 1]) n--;
if (!n || !valid_type(out[n - 1]) || (n == 1 && out[0] != REALITY_TYPE_APPLICATION)) {
OPENSSL_cleanse(out, len - 21);
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "record inner type rejected");
return -1;
}
*type = out[--n];
*written = n;
DEBUG_TRACE(DEBUG_CATEGORY_REALITY, "record rx: generation=%u seq=%llu type=%u content=%zu", t->generation,
(unsigned long long)t->sequence, *type, n);
t->sequence++;
return 0;
}
+41
View File
@@ -0,0 +1,41 @@
/* AEAD-записи внешнего REALITY; состояние обнуляется при создании и принадлежит одному TCP-сеансу.
* Wire: 17 03 03 | uint16_be(ciphertext_len) | AES-128-GCM(content || inner_type || zero_padding) | tag[16].
* AAD — пять байт заголовка; nonce[12] = base_iv XOR (zero[4] || uint64_be(sequence)).
* Каждый успешный seal/open увеличивает свой sequence; повторно передаются только готовые wire-байты.
* Ошибка требует закрытия сеанса. KeyUpdate последним сообщением под старым ключом меняет traffic secret
* через HKDF("uTun-reality-update"), затем key/iv и sequence=0. Направления обновляются независимо. */
#ifndef REALITY_RECORD_H
#define REALITY_RECORD_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#define REALITY_INNER_MAX 16385
#define REALITY_RECORD_MAX (5 + REALITY_INNER_MAX + 16)
#ifndef REALITY_KEY_LIMIT
#define REALITY_KEY_LIMIT (UINT64_C(1) << 20)
#endif
#define REALITY_TYPE_HANDSHAKE 22
#define REALITY_TYPE_APPLICATION 23
#define REALITY_TYPE_ALERT 21
struct reality_traffic {
uint8_t secret[32], key[16], iv[12];
uint64_t sequence;
uint32_t generation;
};
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);
int reality_traffic_init(struct reality_traffic *t, const uint8_t secret[32]);
int reality_traffic_update(struct reality_traffic *t);
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);
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
}
#endif
#endif
+18 -14
View File
@@ -5,6 +5,7 @@
// первым. Поддержан half-close: FIN с одной стороны закрывает только свою
// сторону записи на другой, чтобы ответ сайта дошёл до клиента полностью.
#include "reality_relay.h"
#include "reality_io.h"
#include "../lib/u_async.h"
#include "../lib/mem.h"
#include "../lib/debug_config.h"
@@ -30,6 +31,7 @@
#endif
struct reality_relay {
struct reality_owner *owner;
struct UASYNC *ua;
socket_t client_sock;
void *client_sid;
@@ -56,8 +58,6 @@ struct reality_relay {
struct reality_relay *next; // цепочка активных релеев (для shutdown)
};
static struct reality_relay *g_relays = NULL; // активные релеи
static int g_relay_count = 0; // число активных релеев (для warn)
static void relay_free(struct reality_relay *r);
static void relay_touch(struct reality_relay *r);
@@ -76,9 +76,9 @@ static void relay_free_cb(void *arg) {
static void relay_free(struct reality_relay *r) {
if (!r || r->closed) return;
r->closed = 1;
g_relay_count--;
r->owner->relay_count--;
{ // вынуть из реестра активных релеев
struct reality_relay **pp = &g_relays;
struct reality_relay **pp = &r->owner->relays;
while (*pp && *pp != r) pp = &(*pp)->next;
if (*pp) *pp = r->next;
r->next = NULL;
@@ -287,26 +287,29 @@ static void relay_dns_done_cb(const struct adns_result *res, void *arg) {
}
}
int reality_relay_start(struct UASYNC *ua, socket_t client_sock,
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) {
if (!ua || client_sock == SOCKET_INVALID || !dest || !dest[0]) {
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 (client_sock != SOCKET_INVALID) socket_close_wrapper(client_sock);
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: invalid args");
return -1;
}
struct reality_relay *r = u_calloc(1, sizeof(struct reality_relay));
if (!r) { socket_close_wrapper(client_sock); DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: calloc failed"); return -1; }
r->owner = owner;
r->ua = ua;
r->client_sock = client_sock;
r->dest_sock = SOCKET_INVALID;
r->connecting = 1;
r->idle_timeout_tb = idle_timeout_sec > 0 ? idle_timeout_sec * 10000 : 0;
r->next = g_relays; g_relays = r;
g_relay_count++;
if (g_relay_count > RELAY_WARN_CONNS)
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay: %d concurrent relays (over %d) — возможен скан-флуд", g_relay_count, RELAY_WARN_CONNS);
r->next = owner->relays; owner->relays = r;
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 тоже чистится)
// берём владение client_sock сразу (снимаем старую регистрацию) — до парсинга,
@@ -330,7 +333,7 @@ int reality_relay_start(struct UASYNC *ua, socket_t client_sock,
if (initial_len && initial_data) {
r->c2d = u_malloc(initial_len);
if (!r->c2d) { relay_free(r); return -1; }
if (!r->c2d) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "relay initial buffer allocation failed: %zu", initial_len); relay_free(r); return -1; }
memcpy(r->c2d, initial_data, initial_len);
r->c2d_len = initial_len;
r->c2d_off = 0;
@@ -359,10 +362,11 @@ int reality_relay_start(struct UASYNC *ua, socket_t client_sock,
return 0;
}
void reality_relay_shutdown(void) {
void reality_relay_shutdown(struct reality_owner *owner) {
if (!owner) return;
int count = 0;
while (g_relays) {
struct reality_relay *r = g_relays;
while (owner->relays) {
struct reality_relay *r = owner->relays;
relay_free(r); // сам вынимает r из g_relays и откладывает u_free через call_soon
count++;
}
+3 -2
View File
@@ -16,6 +16,7 @@ extern "C" {
#include "../lib/socket_compat.h"
struct UASYNC;
struct reality_owner;
// Начать релей. Забирает владение client_sock (non-blocking, зарегистрирован
// в uasync) и проксирует его на dest ("host:port").
@@ -24,7 +25,7 @@ struct UASYNC;
// idle_timeout_sec — время простоя (без трафика в обе стороны), после которого
// релей принудительно закрывается; 0 = таймер выключен.
// Возвращает 0 = ok, -1 = ошибка (client_sock уже закрыт внутри).
int reality_relay_start(struct UASYNC *ua, socket_t client_sock,
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);
@@ -32,7 +33,7 @@ int reality_relay_start(struct UASYNC *ua, socket_t client_sock,
// Принудительно завершить все активные релеи (вызывается при shutdown).
// Освобождение отложено через uasync_call_soon — после вызова нужно
// uasync_drain_immediate, чтобы реальный u_free прошёл.
void reality_relay_shutdown(void);
void reality_relay_shutdown(struct reality_owner *owner);
#ifdef __cplusplus
}
+105 -23
View File
@@ -30,6 +30,7 @@ void stcp_conn_set_on_close(struct stcp_conn *c, void (*cb)(struct stcp_conn *co
c->close_arg = arg;
}
static void stcp_wire_clear(struct stcp_conn *c);
static struct sockaddr_storage g_peer_storage;
static ip_str_t g_peer_ip;
const char* stcp_conn_peer_str(struct stcp_conn *c) {
@@ -45,6 +46,14 @@ const char* stcp_conn_peer_str(struct stcp_conn *c) {
void stcp_conn_free(struct stcp_conn *c) {
if (!c) return;
if (c->recv_active || c->close_callback_active) {
if (!c->free_requested) {
c->free_requested = 1;
stcp_conn_do_close(c, ECANCELED);
}
return;
}
reality_io_cleanup(c);
callring_put(CR_CONN_FREE, (uintptr_t)c, 0);
stcp_server_remove_conn(c);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_conn_free: c=%p sock=%d sock_id=%p recv_buf=%p state=%d allocated=%d",
@@ -54,6 +63,7 @@ void stcp_conn_free(struct stcp_conn *c) {
if (c->recv_buf) { u_free(c->recv_buf); c->recv_buf = NULL; }
if (c->send_buf) { u_free(c->send_buf); c->send_buf = NULL; }
stcp_pending_clear(c);
stcp_wire_clear(c);
sc_stream_cleanup(&c->stream_send);
sc_stream_cleanup(&c->stream_recv);
if (c->allocated) u_free(c);
@@ -103,6 +113,7 @@ int stcp_frame_decrypt(uint8_t *data, size_t len, struct sc_stream_state *stream
void stcp_pending_queue(struct stcp_conn *c, const uint8_t *data, size_t len) {
if (!c) return;
if (c->finish_requested) { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "DATA rejected during graceful close"); return; }
struct pending_entry *pe = u_malloc(sizeof(*pe));
if (!pe) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_pending_queue malloc entry failed"); return; }
if (len && data) { pe->data = u_malloc(len); if (!pe->data) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_pending_queue malloc data(%zu) failed", len); u_free(pe); return; } memcpy(pe->data, data, len); }
@@ -125,27 +136,50 @@ void stcp_pending_clear(struct stcp_conn *c) {
c->pending_tail = NULL;
}
int stcp_try_send(struct stcp_conn *c, uint8_t *data, size_t len) {
if (c->sock == SOCKET_INVALID) return -1;
if (c->send_buf) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_try_send: send_buf already busy"); return -1; }
static void stcp_wire_clear(struct stcp_conn *c) {
while (c->wire_head) {
struct pending_entry *e = c->wire_head;
c->wire_head = e->next;
u_free(e->data);
u_free(e);
}
c->wire_tail = NULL;
c->wire_bytes = 0;
}
int stcp_send_raw(struct stcp_conn *c, uint8_t *data, size_t len) {
if (c->sock == SOCKET_INVALID || c->wire_bytes + len > STCP_RECV_BUF_MAX * 2) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "wire queue rejected: socket=%d queued=%zu new=%zu", (int)c->sock, c->wire_bytes, len);
return -1;
}
if (c->send_buf) {
struct pending_entry *e = u_malloc(sizeof(*e));
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "wire queue allocation failed"); return -1; }
e->data = data; e->len = len; e->next = NULL;
if (c->wire_tail) c->wire_tail->next = e;
else c->wire_head = e;
c->wire_tail = e;
c->wire_bytes += len;
return 1;
}
ssize_t sent = send(c->sock, data, len, 0);
if (sent < 0) {
int err = socket_get_error();
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) {
c->send_buf = data; c->send_len = len; c->send_offset = 0;
uasync_set_socket_write(c->ua, c->socket_id, 1);
return 1;
if (err != ERR_AGAIN && err != ERR_WOULDBLOCK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp send failed: err=%d", err);
return -1;
}
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_try_send send failed err=%d", err);
return -1;
sent = 0;
}
if ((size_t)sent < len) {
c->send_buf = data; c->send_len = len; c->send_offset = (size_t)sent;
uasync_set_socket_write(c->ua, c->socket_id, 1);
return 1;
}
u_free(data);
return 0;
if ((size_t)sent == len) { u_free(data); return 0; }
c->send_buf = data; c->send_len = len; c->send_offset = (size_t)sent;
uasync_set_socket_write(c->ua, c->socket_id, 1);
return 1;
}
int stcp_try_send(struct stcp_conn *c, uint8_t *data, size_t len) {
if (c->close_err || c->close_after_send) return -1;
return c->reality_io ? reality_io_send(c, data, len) : stcp_send_raw(c, data, len);
}
// Write-error больше не закрывает conn синхронно: do_close → on_close →
@@ -166,10 +200,15 @@ static void stcp_schedule_close(struct stcp_conn *c, int err) {
if (c->close_call_soon) return; // уже запланирован — первый err остаётся
c->close_err = err;
c->close_call_soon = uasync_call_soon(c->ua, c, stcp_close_deferred);
if (!c->close_call_soon) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "write-error close scheduling failed; closing on socket write callback");
uasync_set_socket_write(c->ua, c->socket_id, 1);
}
}
void stcp_write_cb(socket_t sock, void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
if (c->close_err) { if (!c->close_call_soon) stcp_conn_do_close(c, c->close_err); return; }
if (!c->send_buf) { uasync_set_socket_write(c->ua, c->socket_id, 0); return; }
ssize_t sent = send(sock, c->send_buf + c->send_offset, c->send_len - c->send_offset, 0);
if (sent < 0) {
@@ -182,14 +221,24 @@ void stcp_write_cb(socket_t sock, void *arg) {
c->send_offset += (size_t)sent;
if (c->send_offset >= c->send_len) {
u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; c->send_offset = 0;
if (c->wire_head) {
struct pending_entry *e = c->wire_head;
c->wire_head = e->next;
if (!c->wire_head) c->wire_tail = NULL;
c->wire_bytes -= e->len;
c->send_buf = e->data; c->send_len = e->len;
u_free(e);
return;
}
uasync_set_socket_write(c->ua, c->socket_id, 0);
if (c->close_after_send) { stcp_conn_do_close(c, 0); return; }
if (c->finish_requested) { stcp_conn_finish(c); return; }
stcp_flush_pending(c);
}
}
void stcp_flush_pending(struct stcp_conn *c) {
if (!c || c->state != STCP_STATE_DATA) return; // conn закрыт/не DATA — слать нечего
if (!c || c->state != STCP_STATE_DATA || c->close_after_send || c->close_err) return; // conn закрыт/не DATA — слать нечего
while (!c->send_buf && c->pending_head) {
struct pending_entry *pe = c->pending_head;
c->pending_head = pe->next;
@@ -215,6 +264,7 @@ void stcp_flush_pending(struct stcp_conn *c) {
// ====== unified recv ======
int stcp_conn_read(struct stcp_conn *c) {
if (c->reality_io) return reality_io_read(c);
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return -1;
if (!c->recv_buf) {
c->recv_buf_cap = STCP_RECV_BUF_INIT;
@@ -250,10 +300,10 @@ void stcp_recv_set(struct stcp_conn *c, size_t need, int streaming,
if (streaming) { c->recv_in_meta = 1; c->recv_need = 2; }
}
void stcp_recv_try(struct stcp_conn *c) {
static void stcp_recv_process(struct stcp_conn *c) {
if (!c || c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
while (c->recv_buf_len > 0) {
while (c->recv_buf_len > 0 && !c->rx_paused && !c->finish_requested) {
if (!c->recv_streaming) {
if (c->recv_buf_len < c->recv_need) return;
void (*cb)(struct stcp_conn*, uint8_t*, size_t) = c->recv_on_chunk;
@@ -313,6 +363,13 @@ void stcp_recv_try(struct stcp_conn *c) {
}
}
void stcp_recv_try(struct stcp_conn *c) {
if (!c || c->recv_active) return;
c->recv_active = 1;
stcp_recv_process(c);
c->recv_active = 0;
}
// ====== unified close ======
static void stcp_conn_deferred_free(void *arg) {
@@ -327,14 +384,33 @@ void hs_timeout_cb(void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
c->hs_timer = NULL;
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "stcp handshake timeout is_server=%d prev_state=%d", c->is_server, c->state);
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "stcp timeout is_server=%d state=%d closing=%u", c->is_server, c->state, c->close_after_send);
stcp_conn_do_close(c, ETIMEDOUT);
}
void stcp_conn_finish(struct stcp_conn *c) {
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR || c->close_after_send) return;
if (!c->finish_requested) {
c->finish_requested = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "graceful close: role=%u queued_wire=%zu pending_data=%u", c->is_server, c->wire_bytes, c->pending_head != NULL);
if (c->socket_id) uasync_set_socket_read(c->ua, c->socket_id, 0);
if (c->hs_timer) uasync_cancel_timeout(c->ua, c->hs_timer);
c->hs_timer = uasync_set_timeout(c->ua, 10000, c, hs_timeout_cb, "stcp_close");
if (!c->hs_timer) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "close timeout allocation failed"); stcp_conn_do_close(c, ENOMEM); return; }
}
stcp_flush_pending(c);
if (c->close_err) { stcp_conn_do_close(c, c->close_err); return; }
if (c->send_buf || c->pending_head) return;
if (c->reality_io && reality_io_close_notify(c) < 0) { stcp_conn_do_close(c, ECANCELED); return; }
if (!c->send_buf) { stcp_conn_do_close(c, 0); return; }
c->close_after_send = 1;
}
void stcp_conn_do_close(struct stcp_conn *c, int err) {
if (!c) return;
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
callring_put(CR_CONN_DOCLOSE_IN, (uintptr_t)c, 0);
reality_io_cleanup(c);
int prev = c->state;
c->state = STCP_STATE_CLOSED;
@@ -350,6 +426,7 @@ void stcp_conn_do_close(struct stcp_conn *c, int err) {
if (c->recv_buf) { u_free(c->recv_buf); c->recv_buf = NULL; c->recv_buf_len = 0; c->recv_buf_cap = 0; }
if (c->send_buf) { u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; }
stcp_pending_clear(c);
stcp_wire_clear(c);
sc_stream_cleanup(&c->stream_send);
sc_stream_cleanup(&c->stream_recv);
@@ -357,9 +434,12 @@ void stcp_conn_do_close(struct stcp_conn *c, int err) {
void (*cb)(struct stcp_conn*, int, void*) = c->on_close;
c->on_close = NULL;
callring_put(CR_CONN_DOCLOSE_CB, (uintptr_t)c, (uintptr_t)c->close_arg);
c->close_callback_active = 1;
cb(c, err, c->close_arg);
c->close_callback_active = 0;
}
if (c->free_on_close) {
c->free_scheduled = 1;
uasync_call_soon(c->ua, c->free_on_close, stcp_free_on_close_cb);
c->free_on_close = NULL;
}
@@ -374,7 +454,7 @@ void stcp_tx_queue_cb(struct ll_queue *q, void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
struct ll_entry *e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
if (c->state == STCP_STATE_DATA && e->dgram)
if (c->state == STCP_STATE_DATA && !c->finish_requested && !c->close_after_send && !c->close_err && e->dgram)
stcp_pending_queue(c, e->dgram, e->len);
queue_dgram_free(e);
queue_entry_free(e);
@@ -399,16 +479,18 @@ void stcp_rx_push(struct stcp_conn *c, uint8_t *data, size_t len) {
(c->rx_queue->count > STCP_RX_QUEUE_MAX_PACKETS || c->rx_queue->total_bytes > STCP_RX_QUEUE_MAX_BYTES)) {
c->rx_paused = 1;
uasync_set_socket_read(c->ua, c->socket_id, 0);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp rx paused count=%d bytes=%zu", c->rx_queue->count, c->rx_queue->total_bytes);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp rx paused count=%d bytes=%zu tls_buffered=%zu", c->rx_queue->count, c->rx_queue->total_bytes, reality_io_buffered(c));
}
}
void stcp_rx_resume_if_needed(struct stcp_conn *c) {
if (!c || !c->rx_paused || !c->rx_queue) return;
if (c->state != STCP_STATE_DATA) return;
if (c->state != STCP_STATE_DATA || c->finish_requested) return;
if (c->rx_queue->count < STCP_RX_QUEUE_MAX_PACKETS && c->rx_queue->total_bytes < STCP_RX_QUEUE_MAX_BYTES) {
c->rx_paused = 0;
if (c->socket_id) uasync_set_socket_read(c->ua, c->socket_id, 1);
stcp_recv_try(c);
if (c->reality_io && !c->rx_paused) reality_io_resume(c);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp rx resumed count=%d bytes=%zu", c->rx_queue->count, c->rx_queue->total_bytes);
}
}
+11 -3
View File
@@ -14,6 +14,7 @@ extern "C" {
#include "secure_channel.h"
#include "etcp_padding.h"
#include "reality.h"
#include "reality_io.h"
#include "crc32.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
@@ -107,11 +108,12 @@ struct stcp_conn {
uint8_t hs_flags; // client: handshake flags to send (STCP_HANDSHAKE_FLAG_*)
uint8_t peer_flags; // flags received from peer handshake
uint64_t hs_send_time; // client: handshake send time (0.1ms tb), for ping RTT
uint8_t finish_requested;
uint8_t close_after_send; // server: graceful close once pending send is flushed
// reality-камуфляж (только для серверной стороны accept-пути)
uint8_t reality_enabled; // 1 = перед STCP-хендшейком ждём/отвечаем reality ClientHello
uint8_t reality_hdr[5]; // сохранённый TLS record header при двухфазном чтении ClientHello
// Внешний поток REALITY и копия серверной конфигурации для accept/fallback.
struct reality_io *reality_io;
struct reality_owner *reality_owner;
struct reality_server_config reality_srv; // конфиг сервера (копия)
char reality_dest[REALITY_DEST_MAX]; // "host:port" для релея неавторизованных
@@ -127,6 +129,10 @@ struct stcp_conn {
size_t send_len;
size_t send_offset;
struct pending_entry *wire_head, *wire_tail;
size_t wire_bytes;
uint8_t recv_active, close_callback_active, free_requested, free_scheduled;
struct pending_entry *pending_head;
struct pending_entry *pending_tail;
@@ -169,6 +175,7 @@ void stcp_pending_queue(struct stcp_conn *c, const uint8_t *data, size_t len);
void stcp_pending_clear(struct stcp_conn *c);
// data из u_malloc: 0 — отправлен и освобождён, 1 — принят для досылки; -1 оставляет data вызывающему.
int stcp_send_raw(struct stcp_conn *c, uint8_t *data, size_t len);
int stcp_try_send(struct stcp_conn *c, uint8_t *data, size_t len);
void stcp_write_cb(socket_t sock, void *arg);
void stcp_flush_pending(struct stcp_conn *c);
@@ -183,6 +190,7 @@ void stcp_recv_set(struct stcp_conn *c, size_t need, int streaming,
void stcp_recv_try(struct stcp_conn *c);
// unified close: closes socket, frees buffers, calls on_close. does NOT clean streams (stcp_conn_free does)
void stcp_conn_finish(struct stcp_conn *c);
void stcp_conn_do_close(struct stcp_conn *c, int err);
// снимает принятый коннект из списка владеющего сервера (реализация в stcp_server.c)
+33 -47
View File
@@ -38,6 +38,9 @@ struct stcp_client {
struct reality_client_config reality_cfg;
// SOCKS5-прокси: подключение к прокси + CONNECT выполняется до STCP-хендшейка
void *socks_handle; // handle socks_dial (NULL = не через прокси)
uint32_t reality_time_sec;
uint64_t reality_time_tb;
struct ETCP_PADDING outer_padding;
int hs_timeout_tb; // таймаут хендшейка (0.1ms), для установки после dial
};
@@ -46,8 +49,7 @@ static void client_conn_read_cb(socket_t sock, void *arg);
static void client_hs_cb(struct stcp_conn *c, uint8_t *data, size_t len);
static void client_hs_padding_cb(struct stcp_conn *c, uint8_t *data, size_t len);
static void client_data_cb(struct stcp_conn *c, uint8_t *plain_data, size_t data_len);
static void reality_client_sh_hdr_cb(struct stcp_conn *c, uint8_t *data, size_t len);
static void reality_client_sh_body_cb(struct stcp_conn *c, uint8_t *data, size_t len);
static void client_start_stcp(struct stcp_conn *c);
static int client_derive_session(struct stcp_conn *c, const uint8_t *peer_pubkey) {
struct secure_channel sc;
@@ -96,8 +98,7 @@ static void client_send_handshake(struct stcp_conn *c, const uint8_t *server_pub
c->hs_send_time = get_time_tb();
c->state = STCP_STATE_HS_CLIENT_SENT;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: handshake sent (%zu bytes) gop=%d rid=%016llx flags=%02x, entering HS_CLIENT_SENT", total, gop, (unsigned long long)rid, c->hs_flags);
c->send_buf = hs; c->send_len = total; c->send_offset = 0;
uasync_set_socket_write(c->ua, c->socket_id, 1);
if (stcp_try_send(c, hs, total) < 0) { u_free(hs); stcp_conn_do_close(c, ECANCELED); }
}
static void client_hs_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
@@ -154,7 +155,8 @@ static void client_hs_padding_cb(struct stcp_conn *c, uint8_t *data, size_t len)
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: ping OK rtt=%u sock=%d", (unsigned)rtt, (int)c->sock);
cli->ping_done = 1;
if (cli->ping_cb) cli->ping_cb(1, rtt, cli->ping_arg);
stcp_client_destroy(cli);
if (c->reality_io) stcp_conn_finish(c);
else stcp_client_destroy(cli);
return;
}
c->state = STCP_STATE_DATA;
@@ -167,55 +169,23 @@ static void client_data_cb(struct stcp_conn *c, uint8_t *plain_data, size_t data
stcp_rx_push(c, plain_data, data_len);
}
// ─── reality-камуфляж (клиентская сторона) ───
static void reality_client_sh_hdr_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
(void)len;
if (data[0] != 0x16) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "stcp_client: first byte 0x%02x — not TLS record from server", data[0]);
stcp_conn_do_close(c, 1); return;
}
uint16_t rec_len = (uint16_t)((data[3] << 8) | data[4]);
if (rec_len < 4 || rec_len > REALITY_MAX_SH_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "stcp_client: bad ServerHello record len %u", rec_len);
stcp_conn_do_close(c, 1); return;
}
stcp_recv_set(c, rec_len, 0, reality_client_sh_body_cb);
}
static void reality_client_sh_body_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
(void)data; (void)len;
static void client_start_stcp(struct stcp_conn *c) {
struct stcp_client *cli = (struct stcp_client *)c;
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "stcp_client: ServerHello received, continue STCP handshake");
if (client_derive_session(c, cli->peer_pubkey)) { stcp_conn_do_close(c, 1); return; }
if (client_derive_session(c, cli->peer_pubkey)) { stcp_conn_do_close(c, ECANCELED); return; }
client_send_handshake(c, cli->peer_pubkey, cli->my_ed25519_pubkey);
stcp_recv_set(c, SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER, 0, client_hs_cb);
if (c->state != STCP_STATE_CLOSED) stcp_recv_set(c, STCP_HS_SERVER_MIN, 0, client_hs_cb);
}
// После завершения TCP-connect: либо reality-фаза (ClientHello→ServerHello), либо обычный STCP-хендшейк
static void client_after_connect(struct stcp_conn *c) {
struct stcp_client *cli = (struct stcp_client *)c;
int opt = 1; setsockopt(c->sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt));
if (cli->reality_enabled) {
uint8_t ch[REALITY_MAX_CH_SIZE]; size_t ch_len = 0;
uint32_t now_sec = (c->etcp_conn && c->etcp_conn->instance)
? (uint32_t)ntp_time_get_seconds(c->etcp_conn->instance) : (uint32_t)time(NULL);
if (reality_client_hello_build_at(&cli->reality_cfg, now_sec, ch, sizeof(ch), &ch_len) != REALITY_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "stcp_client: ClientHello build failed");
stcp_conn_do_close(c, 1); return;
}
uint8_t *chbuf = u_malloc(ch_len);
if (!chbuf) { stcp_conn_do_close(c, ENOMEM); return; }
memcpy(chbuf, ch, ch_len);
c->send_buf = chbuf; c->send_len = ch_len; c->send_offset = 0;
uasync_set_socket_write(c->ua, c->socket_id, 1);
stcp_recv_set(c, 5, 0, reality_client_sh_hdr_cb);
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "stcp_client: ClientHello sent (%zu bytes), waiting ServerHello", ch_len);
uint32_t now = cli->reality_time_sec + (uint32_t)((get_time_tb() - cli->reality_time_tb) / 10000);
if (reality_io_client_start(c, &cli->reality_cfg, now, &cli->outer_padding, client_start_stcp))
stcp_conn_do_close(c, ECANCELED);
return;
}
if (client_derive_session(c, cli->peer_pubkey)) { stcp_conn_do_close(c, 1); return; }
client_send_handshake(c, cli->peer_pubkey, cli->my_ed25519_pubkey);
stcp_recv_set(c, SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER, 0, client_hs_cb);
client_start_stcp(c);
}
static void client_conn_read_cb(socket_t sock, void *arg) {
@@ -282,13 +252,20 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
cli->ping_cb = ping_cb; cli->ping_arg = ping_arg;
memcpy(cli->peer_pubkey, peer_pubkey, SC_PUBKEY_SIZE);
if (my_ed25519_pubkey) memcpy(cli->my_ed25519_pubkey, my_ed25519_pubkey, SC_PUBKEY_SIZE);
if (reality) { cli->reality_enabled = 1; cli->reality_cfg = *reality; }
if (reality) {
cli->reality_enabled = 1; cli->reality_cfg = *reality;
cli->reality_time_sec = inst ? (uint32_t)ntp_time_get_seconds(inst) : (uint32_t)time(NULL);
cli->reality_time_tb = get_time_tb();
const struct ETCP_PADDING *profile = etcp_padding_for_peer(inst, peer_pubkey);
if (profile) { cli->outer_padding.min = profile->min; cli->outer_padding.max = profile->max; }
}
if (!etcp_conn) {
int padding = etcp_padding_size(etcp_padding_for_peer(inst, peer_pubkey), STCP_HS_PADDING_MAX);
if (padding < 0) { u_free(cli); return NULL; }
cli->handshake_padding = padding;
}
struct stcp_conn *c = &cli->conn;
c->sock = SOCKET_INVALID;
c->ua = ua; c->state = STCP_STATE_INIT; c->is_server = 0; c->my_keys = *keys;
c->on_ready = ready_cb; c->ready_arg = arg;
c->on_close = close_cb; c->close_arg = close_arg;
@@ -346,6 +323,8 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
return cli;
}
static void client_free_cb(void *arg) { u_free(arg); }
void stcp_client_destroy(struct stcp_client *cli) {
if (!cli) return;
if (cli->socks_handle) {
@@ -354,6 +333,13 @@ void stcp_client_destroy(struct stcp_client *cli) {
u_free(cli);
return;
}
if (cli->conn.free_scheduled) return;
if (cli->conn.state == STCP_STATE_CLOSED || cli->conn.state == STCP_STATE_ERROR) {
if (cli->conn.free_on_close) return;
cli->conn.free_scheduled = 1;
if (!uasync_call_soon(cli->ua, cli, client_free_cb)) DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client free scheduling failed");
return;
}
cli->conn.free_on_close = cli;
stcp_conn_do_close(&cli->conn, 0);
}
@@ -378,11 +364,11 @@ struct stcp_client *stcp_ping_send(struct UASYNC *ua, const char *addr, uint16_t
uint8_t device_type, uint16_t keepalive_interval,
int timeout_ms, stcp_ping_cb cb, void *arg,
const struct socks_cfg *socks,
struct UTUN_INSTANCE *inst) {
struct UTUN_INSTANCE *inst, const struct reality_client_config *reality) {
struct stcp_client *cli = stcp_client_connect(ua, addr, port, keys, peer_pubkey, my_ed25519_pubkey,
0, NULL, device_type, keepalive_interval,
STCP_HANDSHAKE_FLAG_PING,
NULL, NULL, cb, arg, ping_close_cb, NULL, NULL, timeout_ms, NULL, socks, inst);
NULL, NULL, cb, arg, ping_close_cb, NULL, NULL, timeout_ms, reality, socks, inst);
if (cli) cli->conn.free_on_close = cli;
return cli;
}
+1 -1
View File
@@ -41,7 +41,7 @@ struct stcp_client *stcp_ping_send(struct UASYNC *ua, const char *addr, uint16_t
uint8_t device_type, uint16_t keepalive_interval,
int timeout_ms, stcp_ping_cb cb, void *arg,
const struct socks_cfg *socks,
struct UTUN_INSTANCE *inst);
struct UTUN_INSTANCE *inst, const struct reality_client_config *reality);
void stcp_client_destroy(struct stcp_client *cli);
struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli);
+8 -73
View File
@@ -41,6 +41,7 @@ struct stcp_server {
struct reality_server_config reality_srv;
char reality_dest[REALITY_DEST_MAX];
struct reality_owner local_reality_owner;
struct stcp_conn *conns; // список принятых (accept) коннектов — закрываются при destroy
};
@@ -57,8 +58,7 @@ static void server_conn_read_cb(socket_t sock, void *arg);
static void server_hs_phase1_cb(struct stcp_conn *c, uint8_t *data, size_t len);
static void server_hs_phase2_cb(struct stcp_conn *c, uint8_t *data, size_t len);
static void server_data_cb(struct stcp_conn *c, uint8_t *plain_data, size_t data_len);
static void reality_ch_hdr_cb(struct stcp_conn *c, uint8_t *data, size_t len);
static void reality_ch_body_cb(struct stcp_conn *c, uint8_t *data, size_t len);
static void server_start_stcp(struct stcp_conn *c);
static int server_derive_session(struct stcp_conn *c, const uint8_t *peer_pubkey, const uint8_t *salt) {
struct secure_channel sc;
@@ -179,8 +179,7 @@ static void server_hs_phase2_cb(struct stcp_conn *c, uint8_t *data, size_t len)
standby_log_rx("tcp ping", stcp_conn_peer_str(c));
#endif
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_server: ping answered, graceful close sock=%d", (int)c->sock);
if (r == 0) { stcp_conn_do_close(c, 0); return; }
c->close_after_send = 1;
stcp_conn_finish(c);
return;
}
c->state = STCP_STATE_DATA;
@@ -210,73 +209,8 @@ void stcp_server_set_reality(struct stcp_server *srv, const struct reality_confi
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "stcp_server_set_reality: enabled, short_ids=%d dest=%s", rc->short_id_count, rc->dest);
}
// Передать неавторизованное соединение в релей: initial = head[head_len] + tail[tail_len]
static void reality_server_start_relay(struct stcp_conn *c,
const uint8_t *head, size_t head_len,
const uint8_t *tail, size_t tail_len) {
size_t init_len = head_len + tail_len;
uint8_t *init = u_malloc(init_len ? init_len : 1);
if (head_len) memcpy(init, head, head_len);
if (tail_len) memcpy(init + head_len, tail, tail_len);
socket_t cli_sock = c->sock;
// отсоединяем сокет от stcp_conn (не закрывая): reality_relay_start сам
// снимет регистрацию из uasync и возьмёт владение сокетом.
c->socket_id = NULL;
c->sock = SOCKET_INVALID;
reality_relay_start(c->ua, cli_sock, c->reality_dest, init, init_len, c->reality_srv.relay_idle_timeout_sec);
u_free(init); /* reality_relay_start копирует данные в свой буфер — владение остаётся у нас */
stcp_conn_do_close(c, 0);
}
static void reality_ch_hdr_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
(void)len;
memcpy(c->reality_hdr, data, 5);
if (data[0] != 0x16) {
#ifdef UTUN_HAVE_STANDBY
standby_log_rx("reality undecryptable", stcp_conn_peer_str(c));
#endif
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "stcp_server: first byte 0x%02x — not TLS, relay", data[0]);
reality_server_start_relay(c, data, 5, c->recv_buf + 5, c->recv_buf_len - 5);
return;
}
uint16_t rec_len = (uint16_t)((data[3] << 8) | data[4]);
if (rec_len < 4 || rec_len > REALITY_MAX_CH_SIZE) {
#ifdef UTUN_HAVE_STANDBY
standby_log_rx("reality undecryptable", stcp_conn_peer_str(c));
#endif
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "stcp_server: bad TLS record len %u, relay", rec_len);
reality_server_start_relay(c, data, 5, c->recv_buf + 5, c->recv_buf_len - 5);
return;
}
stcp_recv_set(c, rec_len, 0, reality_ch_body_cb);
}
static void reality_ch_body_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
// data = тело TLS-записи (rec_len байт); заголовок в c->reality_hdr
uint8_t full[5 + REALITY_MAX_CH_SIZE];
memcpy(full, c->reality_hdr, 5);
memcpy(full + 5, data, len);
uint8_t sh[REALITY_MAX_SH_SIZE];
size_t sh_len = 0;
uint32_t now_sec = c->inst ? (uint32_t)ntp_time_get_seconds(c->inst) : (uint32_t)time(NULL);
int rc = reality_server_hello_build_at(&c->reality_srv, now_sec, full, 5 + len, sh, sizeof(sh), &sh_len);
if (rc == REALITY_OK) {
uint8_t *shbuf = u_malloc(sh_len);
if (!shbuf) { stcp_conn_do_close(c, ENOMEM); return; }
memcpy(shbuf, sh, sh_len);
if (stcp_try_send(c, shbuf, sh_len) < 0) { u_free(shbuf); stcp_conn_do_close(c, 1); return; }
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "stcp_server: reality auth OK, continue STCP handshake");
stcp_recv_set(c, SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT, 0, server_hs_phase1_cb);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "stcp_server: reality auth failed rc=%d, relay", rc);
#ifdef UTUN_HAVE_STANDBY
standby_log_rx("reality undecryptable (auth)", stcp_conn_peer_str(c));
#endif
reality_server_start_relay(c, full, 5 + len, c->recv_buf + len, c->recv_buf_len - len);
static void server_start_stcp(struct stcp_conn *c) {
stcp_recv_set(c, STCP_HS_CLIENT_MIN, 0, server_hs_phase1_cb);
}
static void server_conn_read_cb(socket_t sock, void *arg) {
@@ -322,13 +256,13 @@ static void server_accept_cb(socket_t listen_sock, void *arg) {
return;
}
c->inst = srv->inst;
c->reality_owner = srv->inst ? &srv->inst->reality_owner : &srv->local_reality_owner;
c->device_type = srv->inst ? srv->inst->client_type : 0;
c->keepalive_interval = srv->inst ? srv->inst->keepalive_interval : 200;
if (srv->reality_enabled) {
c->reality_enabled = 1;
c->reality_srv = srv->reality_srv;
snprintf(c->reality_dest, sizeof(c->reality_dest), "%s", srv->reality_dest);
stcp_recv_set(c, 5, 0, reality_ch_hdr_cb);
if (reality_io_server_start(c, server_start_stcp)) { stcp_conn_do_close(c, ENOMEM); return; }
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "stcp_server: reality enabled on accepted conn, waiting ClientHello");
} else {
stcp_recv_set(c, SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT, 0, server_hs_phase1_cb);
@@ -408,6 +342,7 @@ void stcp_server_destroy(struct stcp_server *srv) {
struct stcp_conn *c = srv->conns;
stcp_conn_do_close(c, 0); // снимает c из списка (c->srv) и каскадно закрывает серверный ETCP_LINK
}
reality_owner_cleanup(&srv->local_reality_owner);
u_free(srv);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_server destroyed");
}
+1 -1
View File
@@ -468,7 +468,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
/* Phase J: TCP connections + sockets — MUST be before ETCP connections */
stcp_server_list_destroy_all(instance);
reality_relay_shutdown(); /* активные reality-релеи откладывают u_free через call_soon */
reality_owner_cleanup(&instance->reality_owner); /* активные reality-релеи откладывают u_free через call_soon */
if (instance->tcp_connections) {
struct ll_entry* entry = instance->tcp_connections->head;
int tc = 0;
+2
View File
@@ -18,6 +18,7 @@ extern "C" {
#include "../lib/sqlite3.h"
#include "secure_channel.h"
#include "etcp_padding.h"
#include "reality_io.h"
#include "etcp_api.h"
#include "config_parser.h"
#include "stcp_link.h"
@@ -119,6 +120,7 @@ struct peer_sleep_cbk_entry {
// Состояние одного узла. Вложенные модули освобождаются через их lifecycle API.
struct UTUN_INSTANCE {
struct reality_owner reality_owner;
uint8_t core_started, utun_started, chat_started; // успешный запуск ядра / UTUN / чата
// Identification