Protect REALITY STCP traffic with authenticated TLS-like records
This commit is contained in:
1 parent
a49cf5434e
commit
a069d11ef9
33 files changed
+1786
-421
No files matched your search
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user