diff --git a/build_direct.sh b/build_direct.sh index 37b7237c..7d258182 100644 --- a/build_direct.sh +++ b/build_direct.sh @@ -70,7 +70,7 @@ echo "Building utun.exe..." cd src EXE_NEEDS_REBUILD=0 -CORE_SOURCES="utun.c utun_instance.c config_parser.c config_updater.c routing_layer/route_lib.c topo_bgp.c routing_layer/routing.c tun_if.c tun_route.c transport_layer/etcp.c transport_layer/etcp_padding.c transport_layer/etcp_session.c transport_layer/etcp_connections.c transport_layer/etcp_loadbalancer.c transport_layer/secure_channel.c transport_layer/crc32.c transport_layer/pkt_normalizer.c transport_layer/etcp_api.c tun_windows.c" +CORE_SOURCES="utun.c utun_instance.c config_parser.c config_updater.c routing_layer/route_lib.c topo_bgp.c routing_layer/routing.c tun_if.c tun_route.c transport_layer/etcp.c transport_layer/etcp_padding.c transport_layer/reality_record.c transport_layer/reality_io.c transport_layer/etcp_session.c transport_layer/etcp_connections.c transport_layer/etcp_loadbalancer.c transport_layer/secure_channel.c transport_layer/crc32.c transport_layer/pkt_normalizer.c transport_layer/etcp_api.c tun_windows.c" for src in $CORE_SOURCES; do if [ -f "$src" ]; then diff --git a/src/Makefile.am b/src/Makefile.am index 71eec505..474772b1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/config_parser.c b/src/config_parser.c index aaa632e1..5444e7a1 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -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); diff --git a/src/routing_layer/conn_mgr_monitor.c b/src/routing_layer/conn_mgr_monitor.c index 53bedcbf..363cb50f 100644 --- a/src/routing_layer/conn_mgr_monitor.c +++ b/src/routing_layer/conn_mgr_monitor.c @@ -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); } diff --git a/src/routing_layer/route_connectivity.c b/src/routing_layer/route_connectivity.c index 4ecc566a..11c46b88 100644 --- a/src/routing_layer/route_connectivity.c +++ b/src/routing_layer/route_connectivity.c @@ -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 { diff --git a/src/routing_layer/topo_node.c b/src/routing_layer/topo_node.c index 8f73478c..0dd6d169 100644 --- a/src/routing_layer/topo_node.c +++ b/src/routing_layer/topo_node.c @@ -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; } diff --git a/src/routing_layer/topo_node.h b/src/routing_layer/topo_node.h index 776cf010..f0f1804c 100644 --- a/src/routing_layer/topo_node.h +++ b/src/routing_layer/topo_node.h @@ -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 diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index ab43e7c2..967f7ba2 100644 --- a/src/transport_layer/etcp_connections.c +++ b/src/transport_layer/etcp_connections.c @@ -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); diff --git a/src/transport_layer/etcp_connections.h b/src/transport_layer/etcp_connections.h index f5472d47..2f816e64 100644 --- a/src/transport_layer/etcp_connections.h +++ b/src/transport_layer/etcp_connections.h @@ -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); diff --git a/src/transport_layer/reality.c b/src/transport_layer/reality.c index 03897cb9..33386228 100644 --- a/src/transport_layer/reality.c +++ b/src/transport_layer/reality.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include // 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; +} diff --git a/src/transport_layer/reality.h b/src/transport_layer/reality.h index 8cef9ea5..2c8c0b61 100644 --- a/src/transport_layer/reality.h +++ b/src/transport_layer/reality.h @@ -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 #include +#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); diff --git a/src/transport_layer/reality_fingerprint.c b/src/transport_layer/reality_fingerprint.c index e164bead..0495686a 100644 --- a/src/transport_layer/reality_fingerprint.c +++ b/src/transport_layer/reality_fingerprint.c @@ -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 - -// 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; } diff --git a/src/transport_layer/reality_io.c b/src/transport_layer/reality_io.c new file mode 100644 index 00000000..2eaf15d2 --- /dev/null +++ b/src/transport_layer/reality_io.c @@ -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 +#include +#include +#include +#include + +#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; +} diff --git a/src/transport_layer/reality_io.h b/src/transport_layer/reality_io.h new file mode 100644 index 00000000..1567c8ba --- /dev/null +++ b/src/transport_layer/reality_io.h @@ -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 diff --git a/src/transport_layer/reality_record.c b/src/transport_layer/reality_record.c new file mode 100644 index 00000000..fa049cf9 --- /dev/null +++ b/src/transport_layer/reality_record.c @@ -0,0 +1,128 @@ +#define OPENSSL_API_COMPAT 0x10100000L +#include "reality_record.h" +#include "../lib/debug_config.h" +#include +#include +#include +#include +#include + +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; +} diff --git a/src/transport_layer/reality_record.h b/src/transport_layer/reality_record.h new file mode 100644 index 00000000..ab0a8bc0 --- /dev/null +++ b/src/transport_layer/reality_record.h @@ -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 +#include +#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 diff --git a/src/transport_layer/reality_relay.c b/src/transport_layer/reality_relay.c index 3e92af5f..2ae3e1fb 100644 --- a/src/transport_layer/reality_relay.c +++ b/src/transport_layer/reality_relay.c @@ -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++; } diff --git a/src/transport_layer/reality_relay.h b/src/transport_layer/reality_relay.h index 724a6c03..a3b2ecc4 100644 --- a/src/transport_layer/reality_relay.h +++ b/src/transport_layer/reality_relay.h @@ -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 } diff --git a/src/transport_layer/stcp.c b/src/transport_layer/stcp.c index 70c1a279..c8c6d7aa 100644 --- a/src/transport_layer/stcp.c +++ b/src/transport_layer/stcp.c @@ -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); } } diff --git a/src/transport_layer/stcp.h b/src/transport_layer/stcp.h index 410cd00b..2bc93bdf 100644 --- a/src/transport_layer/stcp.h +++ b/src/transport_layer/stcp.h @@ -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) diff --git a/src/transport_layer/stcp_client.c b/src/transport_layer/stcp_client.c index 797e9929..3a1e3f4a 100644 --- a/src/transport_layer/stcp_client.c +++ b/src/transport_layer/stcp_client.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; } diff --git a/src/transport_layer/stcp_client.h b/src/transport_layer/stcp_client.h index 4557a7ea..1b955e20 100644 --- a/src/transport_layer/stcp_client.h +++ b/src/transport_layer/stcp_client.h @@ -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); diff --git a/src/transport_layer/stcp_server.c b/src/transport_layer/stcp_server.c index 9e8dc9af..e015a37b 100644 --- a/src/transport_layer/stcp_server.c +++ b/src/transport_layer/stcp_server.c @@ -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"); } diff --git a/src/utun_instance.c b/src/utun_instance.c index 8d813301..1af30989 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -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; diff --git a/src/utun_instance.h b/src/utun_instance.h index ec868226..bb78e987 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -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 diff --git a/tests/Makefile.am b/tests/Makefile.am index 8dd6a8a4..b90ef171 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,6 @@ # Tests Makefile.am for utun - all tests with new ll_queue library -EXTRA_DIST = data/silero_speech.pcm data/silero_speech.txt +EXTRA_DIST = fixtures/reality_openssl_hello.hex data/silero_speech.pcm data/silero_speech.txt # All available tests (check_PROGRAMS runs via automake check-TESTS) check_PROGRAMS = \ @@ -120,6 +120,8 @@ check_PROGRAMS = \ test_media_delivery_full \ test_media_delivery_chat \ test_etcp_link_stress \ + test_reality_tls \ + test_reality_record \ test_reality_hello \ test_reality_bgp \ test_reality_config \ @@ -130,7 +132,7 @@ check_PROGRAMS = \ # Linux-only: test_auto_socket_dynamic требует root + dummy-интерфейсы + iproute2/netlink. if OS_LINUX check_PROGRAMS += test_auto_socket_dynamic test_standby test_standby_transport test_group_connect_standby test_android_udp_log test_pool_corruption_log -check_PROGRAMS += test_uasync_regressions test_ll_queue_regressions test_etcp_padding +check_PROGRAMS += test_reality_fallback test_stcp_reality_rotation test_uasync_regressions test_ll_queue_regressions test_etcp_padding endif # Silero VAD: только при включённом ONNX Runtime (--with-silero-vad) @@ -164,7 +166,7 @@ COMMON_LIBS = $(top_builddir)/lib/libuasync.a $(CRYPTO_LIBS) -lpthread $(WIN_LIB # Вариант общего ядра с Android duty-cycle, без JNI и устройства. if OS_LINUX -test_stcp_LDFLAGS = -Wl,--wrap=send -Wl,--wrap=random_bytes +test_stcp_LDFLAGS = -Wl,--wrap=send -Wl,--wrap=recv -Wl,--wrap=random_bytes -Wl,--wrap=u_malloc_impl -Wl,--wrap=u_calloc_impl standby_test_sources = ../tools/chatgui-android/libutun_lite/standby.c ../src/utun_instance.c ../src/transport_layer/etcp_keepalive.c standby_test_cppflags = $(AM_CPPFLAGS) -I$(top_srcdir)/src/chat -I$(top_srcdir)/tools/chatgui-android/libutun_lite -DUTUN_HAVE_STANDBY test_standby_SOURCES = ../tools/chatgui-android/libutun_lite/tests/test_standby.c $(standby_test_sources) @@ -776,3 +778,23 @@ check_PROGRAMS += test_chat_poll_network test_chat_poll_network_SOURCES = test_chat_poll_network.c test_chat_poll_network_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/chat -I$(top_srcdir)/lib test_chat_poll_network_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) + +test_reality_record_SOURCES = test_reality_record.c +test_reality_record_LDADD = $(LIBUTUN) $(COMMON_LIBS) + +if OS_LINUX +# Низкий лимит проверяет реальную смену ключей в обе стороны без миллиона пакетов. +test_stcp_reality_rotation_SOURCES = test_stcp.c ../src/transport_layer/reality_io.c ../src/transport_layer/reality_record.c +test_stcp_reality_rotation_CFLAGS = $(AM_CFLAGS) -DREALITY_KEY_LIMIT=8 +test_stcp_reality_rotation_LDFLAGS = -Wl,--wrap=send -Wl,--wrap=recv -Wl,--wrap=random_bytes -Wl,--wrap=u_malloc_impl -Wl,--wrap=u_calloc_impl +test_stcp_reality_rotation_LDADD = $(LIBUTUN) $(COMMON_LIBS) +endif + +if OS_LINUX +test_reality_fallback_SOURCES = test_reality_fallback.c +test_reality_fallback_LDFLAGS = -Wl,--wrap=ntp_time_get_seconds +test_reality_fallback_LDADD = $(LIBUTUN) $(COMMON_LIBS) +endif + +test_reality_tls_SOURCES = test_reality_tls.c +test_reality_tls_LDADD = $(LIBUTUN) -lssl $(COMMON_LIBS) diff --git a/tests/fixtures/reality_openssl_hello.hex b/tests/fixtures/reality_openssl_hello.hex new file mode 100644 index 00000000..a37e0ded --- /dev/null +++ b/tests/fixtures/reality_openssl_hello.hex @@ -0,0 +1,3 @@ +# OpenSSL 3.5.5, 27 Jan 2026; local capture 2026-10-07. +# openssl s_client -tls1_3 -groups X25519 -ciphersuites TLS_AES_128_GCM_SHA256 -alpn http/1.1 -servername www.microsoft.com -connect 127.0.0.1:PORT +1603010102010000fe0303495c3aad4d7fbc7f0ea12245795f052c33c7700a56c9f15bd58374992a75f617204f5583134eaae74da0bd4cf1ed233297a41f0ff7bd0118d3b0c418098ca5542e00021301010000b30000001600140000117777772e6d6963726f736f66742e636f6d000b000403000102000a00040002001d002300000010000b000908687474702f312e310016000000170000000d002a002809050906090404030503060308070808081a081b081c0809080a080b080408050806040105010601002b0003020304002d00020101003300260024001d002016a64663eb5b32977d0fbbe63eea3498ee62dd67428c7c25d26ac567b4d06904001b00050400010003 diff --git a/tests/test_reality_fallback.c b/tests/test_reality_fallback.c new file mode 100644 index 00000000..cc3ad23b --- /dev/null +++ b/tests/test_reality_fallback.c @@ -0,0 +1,112 @@ +/* Реальный relay: нормализация replay, сохранение хвоста, независимое владение instance. */ +#include "stcp_server.h" +#include "utun_instance.h" +#include "../lib/mem.h" +#include "../lib/debug_config.h" +#include +#include +#include + +#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "FAIL line=%d: %s", __LINE__, #x); return 1; } } while (0) +static uint32_t fake_now; +time_t __wrap_ntp_time_get_seconds(struct UTUN_INSTANCE *inst) { (void)inst; return fake_now; } +struct destination_peer { socket_t sock; struct UASYNC *ua; uint8_t data[4096]; size_t len; }; +struct destination { socket_t sock; struct UASYNC *ua; unsigned count; struct destination_peer peers[4]; }; + +static void destination_read(socket_t sock, void *arg) { + struct destination_peer *p = arg; + ssize_t n = recv(sock, p->data + p->len, sizeof(p->data) - p->len, 0); + if (n > 0) p->len += (size_t)n; + else if (!n) { uasync_remove_socket_t(p->ua, sock); socket_close_wrapper(sock); p->sock = SOCKET_INVALID; } +} +static void destination_accept(socket_t sock, void *arg) { + struct destination *d = arg; + socket_t peer = accept(sock, NULL, NULL); + if (peer == SOCKET_INVALID || d->count >= 4) return; + struct destination_peer *p = &d->peers[d->count++]; + p->sock = peer; p->ua = d->ua; + socket_set_nonblocking(peer); + uasync_add_socket_t(d->ua, peer, destination_read, NULL, NULL, "reality_test_dest", p); +} +static void unexpected_stcp(struct stcp_conn *c, void *arg) { + (void)c; (void)arg; + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "unexpected STCP before Client Finished"); +} +static socket_t connect_to(uint16_t port) { + socket_t s = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in address = {0}; + address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (connect(s, (struct sockaddr *)&address, sizeof(address))) { socket_close_wrapper(s); return SOCKET_INVALID; } + socket_set_nonblocking(s); + return s; +} +static void poll_some(struct UASYNC *ua) { for (unsigned i = 0; i < 100; i++) uasync_poll(ua, 0); } + +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG); debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_WARN); + size_t allocated = u_get_allocated_count(); + struct UASYNC *ua = uasync_create(); CHECK(ua); + struct UTUN_INSTANCE a = {0}, b = {0}; a.ua = b.ua = ua; + struct SC_MYKEYS keys; CHECK(sc_generate_keypair(&keys) == SC_OK); + struct reality_config cfg; reality_config_set_defaults(&cfg); + struct reality_client_config cc; reality_client_config_set_defaults(&cc); + cfg.enabled = 1; cfg.short_id_count = 1; + CHECK(!reality_generate_keypair(cfg.private_key, cc.server_static_pubkey)); + strcpy(cc.server_name, "www.microsoft.com"); + struct destination dest = {0}; dest.ua = ua; + dest.sock = socket(AF_INET, SOCK_STREAM, 0); CHECK(dest.sock != SOCKET_INVALID); + struct sockaddr_in address = {0}; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + CHECK(!bind(dest.sock, (struct sockaddr *)&address, sizeof(address)) && !listen(dest.sock, 4)); + socklen_t size = sizeof(address); CHECK(!getsockname(dest.sock, (struct sockaddr *)&address, &size)); + socket_set_nonblocking(dest.sock); + CHECK(uasync_add_socket_t(ua, dest.sock, destination_accept, NULL, NULL, "reality_test_listener", &dest)); + snprintf(cfg.dest, sizeof(cfg.dest), "127.0.0.1:%u", ntohs(address.sin_port)); + uint16_t port = 32000 + (uint16_t)(time(NULL) % 1000); + struct stcp_server *sa = stcp_server_create(ua, port, &keys, NULL, &a, unexpected_stcp, NULL, NULL, NULL, AF_INET); + struct stcp_server *sb = stcp_server_create(ua, port + 1, &keys, NULL, &b, unexpected_stcp, NULL, NULL, NULL, AF_INET); + CHECK(sa && sb); stcp_server_set_reality(sa, &cfg); stcp_server_set_reality(sb, &cfg); + uint8_t ch[REALITY_MAX_CH_SIZE], answer[4096]; size_t cn; + fake_now = (uint32_t)time(NULL); + CHECK(!reality_client_hello_build_at(&cc, fake_now + 30, ch, sizeof(ch), &cn)); + socket_t first = connect_to(port); CHECK(first != SOCKET_INVALID && send(first, ch, cn, 0) == (ssize_t)cn); + poll_some(ua); + CHECK(recv(first, answer, sizeof(answer), 0) >= 127 && answer[0] == 22); + /* Через 31 секунду тот же future timestamp ещё допустим: replay обязан оставаться в кеше. */ + fake_now += 31; + uint8_t split[4096]; size_t first_part = 11, rest = cn - 5 - first_part; + memcpy(split, ch, 5); split[3] = 0; split[4] = first_part; + memcpy(split + 5, ch + 5, first_part); + size_t pos = 5 + first_part; + memcpy(split + pos, ch, 5); split[pos + 2] = 3; split[pos + 3] = (uint8_t)(rest >> 8); split[pos + 4] = (uint8_t)rest; + memcpy(split + pos + 5, ch + 5 + first_part, rest); + pos += 5 + rest; + const uint8_t tail[] = {20, 3, 3, 0, 1, 1, 23, 3, 3, 0, 4, 1, 2, 3, 4}; + memcpy(split + pos, tail, sizeof(tail)); pos += sizeof(tail); + socket_t replay = connect_to(port); CHECK(replay != SOCKET_INVALID); + CHECK(send(replay, split, 7, 0) == 7); poll_some(ua); + CHECK(send(replay, split + 7, pos - 7, 0) == (ssize_t)(pos - 7)); poll_some(ua); + CHECK(dest.count == 1 && dest.peers[0].len == pos && !memcmp(dest.peers[0].data, split, pos)); + CHECK(a.reality_owner.relay_count == 1); + socket_t other = connect_to(port + 1); CHECK(other != SOCKET_INVALID && send(other, ch, cn, 0) == (ssize_t)cn); + poll_some(ua); + CHECK(recv(other, answer, sizeof(answer), 0) >= 127 && answer[0] == 22 && dest.count == 1); + + cc.short_id[0] = 1; + CHECK(!reality_client_hello_build_at(&cc, fake_now, ch, sizeof(ch), &cn)); + socket_t unauthorized = connect_to(port + 1); + CHECK(unauthorized != SOCKET_INVALID && send(unauthorized, ch, cn, 0) == (ssize_t)cn); + poll_some(ua); CHECK(dest.count == 2 && b.reality_owner.relay_count == 1); + stcp_server_destroy(sb); reality_owner_cleanup(&b.reality_owner); poll_some(ua); + CHECK(a.reality_owner.relay_count == 1 && dest.peers[1].sock == SOCKET_INVALID); + CHECK(send(replay, tail, sizeof(tail), 0) == sizeof(tail)); poll_some(ua); + CHECK(dest.peers[0].len == pos + sizeof(tail) && !memcmp(dest.peers[0].data + pos, tail, sizeof(tail))); + stcp_server_destroy(sa); reality_owner_cleanup(&a.reality_owner); poll_some(ua); + socket_close_wrapper(first); socket_close_wrapper(replay); socket_close_wrapper(other); socket_close_wrapper(unauthorized); + for (unsigned i = 0; i < dest.count; i++) if (dest.peers[i].sock != SOCKET_INVALID) { + uasync_remove_socket_t(ua, dest.peers[i].sock); socket_close_wrapper(dest.peers[i].sock); + } + uasync_remove_socket_t(ua, dest.sock); socket_close_wrapper(dest.sock); uasync_destroy(ua, 1); + CHECK(u_get_allocated_count() == allocated); + DEBUG_INFO(DEBUG_CATEGORY_REALITY, "fragmented replay, future expiry, exact fallback bytes, instance isolation and teardown passed"); + return 0; +} diff --git a/tests/test_reality_hello.c b/tests/test_reality_hello.c index 690f0c21..7d375b26 100644 --- a/tests/test_reality_hello.c +++ b/tests/test_reality_hello.c @@ -43,7 +43,7 @@ static void init_client_cfg(struct reality_client_config *cc, memcpy(cc->short_id, short_id, REALITY_SHORT_ID_SIZE); memcpy(cc->version, version, REALITY_VERSION_SIZE); snprintf(cc->server_name, sizeof(cc->server_name), "www.microsoft.com"); - cc->fingerprint = REALITY_FP_CHROME; + cc->fingerprint = REALITY_FP_OPENSSL; } static void init_server_cfg(struct reality_server_config *sc, @@ -57,7 +57,7 @@ static void init_server_cfg(struct reality_server_config *sc, sc->short_id_count = 1; memcpy(sc->version, version, REALITY_VERSION_SIZE); sc->time_window_sec = window; - sc->fingerprint = REALITY_FP_CHROME; + sc->fingerprint = REALITY_FP_OPENSSL; } // Собирает структурно-валидный ClientHello (TLS record + handshake), тело которого @@ -196,7 +196,7 @@ int main(void) { } // ── Сценарий 8: fingerprint-геттер ── - CHECK(reality_fingerprint_get(REALITY_FP_CHROME) != NULL, "fingerprint chrome found"); + CHECK(reality_fingerprint_get(REALITY_FP_OPENSSL) != NULL, "fingerprint openssl found"); CHECK(reality_fingerprint_get(999) == NULL, "unknown fingerprint = NULL"); // ── Сценарий 9: невалидные аргументы ── @@ -230,8 +230,8 @@ int main(void) { ks[6] = 0x00; ks[7] = 0x1d; // group = X25519 ks[8] = 0x00; ks[9] = 0x20; // klen = 32 (тела нет) ch2_len = build_ch_with_ext(ch2, sizeof(ch2), ks, sizeof(ks)); - CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_AUTH, - "key_share truncated X25519 entry → AUTH"); + CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_FORMAT, + "key_share truncated X25519 entry → FORMAT"); // цепочка записей не-X25519 с klen=0 (в границах) → AUTH, без OOB memset(ks, 0, sizeof(ks)); @@ -241,8 +241,8 @@ int main(void) { ks[6] = 0x00; ks[7] = 0x17; // group = secp256r1 ks[8] = 0x00; ks[9] = 0x00; // klen = 0 ch2_len = build_ch_with_ext(ch2, sizeof(ch2), ks, sizeof(ks)); - CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_AUTH, - "key_share without X25519 → AUTH"); + CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_FORMAT, + "key_share zero-length entry → FORMAT"); } // ── Сценарий 10: парсинг секции [reality] из конфига ── @@ -259,7 +259,7 @@ int main(void) { "private_key=2222222222222222222222222222222222222222222222222222222222222222\n" "version=2.3.4\n" "time_window=45\n" - "fingerprint=chrome\n"; + "fingerprint=openssl\n"; struct utun_config *uc = parse_config_from_buf(cfg_text, strlen(cfg_text), "mem"); CHECK(uc != NULL, "parse config with [reality]"); if (uc) { @@ -270,7 +270,7 @@ int main(void) { CHECK(uc->global.reality.short_id_count == 2, "short_ids parsed"); CHECK(uc->global.reality.version[0] == 2 && uc->global.reality.version[1] == 3 && uc->global.reality.version[2] == 4, "version parsed"); CHECK(uc->global.reality.time_window_sec == 45, "time_window parsed"); - CHECK(uc->global.reality.fingerprint == REALITY_FP_CHROME, "fingerprint parsed"); + CHECK(uc->global.reality.fingerprint == REALITY_FP_OPENSSL, "fingerprint parsed"); free_config(uc); } } @@ -288,7 +288,7 @@ int main(void) { "short_id=0102030405060708\n" "public_key=2222222222222222222222222222222222222222222222222222222222222222\n" "version=2.3.4\n" - "fingerprint=chrome\n" + "fingerprint=openssl\n" "[client: c2]\n" "peer_public_key=1111111111111111111111111111111111111111111111111111111111111111\n" "link=bind_srv:5.6.7.8:1444\n" @@ -306,7 +306,7 @@ int main(void) { if (c1) { CHECK(c1->reality_enabled == 1, "c1 reality enabled"); CHECK(strcmp(c1->reality.server_name, "www.microsoft.com") == 0, "c1 server_name"); - CHECK(c1->reality.fingerprint == REALITY_FP_CHROME, "c1 fingerprint"); + CHECK(c1->reality.fingerprint == REALITY_FP_OPENSSL, "c1 fingerprint"); CHECK(c1->reality.version[0] == 2 && c1->reality.version[1] == 3 && c1->reality.version[2] == 4, "c1 version parsed"); { uint8_t sid[8] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}; CHECK(memcmp(c1->reality.short_id, sid, 8) == 0, "c1 short_id"); } @@ -320,7 +320,7 @@ int main(void) { } if (c2) { CHECK(c2->reality_enabled == 1, "c2 reality enabled"); - CHECK(c2->reality.version[0] == 1 && c2->reality.version[1] == 0 && c2->reality.version[2] == 0, "c2 version default 1.0.0"); + CHECK(c2->reality.version[0] == 2 && c2->reality.version[1] == 0 && c2->reality.version[2] == 0, "c2 version default 2.0.0"); CHECK(c2->links != NULL && c2->links->local_srv != NULL, "c2 link с bind (local_srv set)"); if (c2->links && c2->links->local_srv) CHECK(strcmp(c2->links->local_srv->name, "bind_srv") == 0, "c2 bind socket name"); diff --git a/tests/test_reality_record.c b/tests/test_reality_record.c new file mode 100644 index 00000000..87e0960d --- /dev/null +++ b/tests/test_reality_record.c @@ -0,0 +1,118 @@ +/* Независимые HKDF/GCM-векторы и проверка привязки записей к обоим Hello. */ +#include "reality.h" +#include "../lib/debug_config.h" +#include +#include +#include +#include + +#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "FAIL line=%d: %s", __LINE__, #x); return 1; } } while (0) +static size_t unhex(const char *s, uint8_t *out) { + size_t n = 0; + while (s[0] && s[1]) { + unsigned value; + if (sscanf(s, "%2x", &value) != 1) break; + out[n++] = (uint8_t)value; + s += 2; + } + return n; +} + +static int vectors(void) { + struct reality_traffic send = {0}, recv = {0}; + uint8_t secret[32], wire[REALITY_RECORD_MAX], plain[REALITY_INNER_MAX], expected[64], type; + for (unsigned i = 0; i < 32; i++) secret[i] = i; + CHECK(!reality_traffic_init(&send, secret)); + CHECK(unhex("1519da96243cb6965fbfa3b28ae25a27", expected) == 16 && !memcmp(send.key, expected, 16)); + CHECK(unhex("16e5473250aa12b61fbacec7", expected) == 12 && !memcmp(send.iv, expected, 12)); + const char *golden[] = { + "1703030018a128808be00c4bdf042cf5ae940649c8f0d4701075afa09b", + "170303001811fa393d1f1db1a3ee840870bedcc6f5c9387a00c9b41c84" + }; + const uint8_t data[] = {1, 2, 3, 0}; + size_t n, decoded; + for (unsigned i = 0; i < 2; i++) { + CHECK(!reality_traffic_init(&send, secret)); + recv = send; + send.sequence = recv.sequence = i ? 7 : 0; + CHECK(!reality_record_seal(&send, 23, data, sizeof(data), 3, wire, sizeof(wire), &n)); + CHECK(unhex(golden[i], expected) == n && !memcmp(wire, expected, n)); + CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type)); + CHECK(decoded == sizeof(data) && type == 23 && !memcmp(plain, data, decoded)); + CHECK(reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type) < 0); // повтор sequence + recv.sequence--; + wire[n - 1] ^= 1; + CHECK(reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type) < 0); + CHECK(recv.sequence == (i ? 7 : 0)); + } + CHECK(!reality_traffic_init(&send, secret)); recv = send; + memset(plain, 0xaa, sizeof(plain)); + CHECK(!reality_record_seal(&send, 23, plain, 16384, 0, wire, sizeof(wire), &n)); + CHECK(n == REALITY_RECORD_MAX); + CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type) && decoded == 16384); + CHECK(reality_record_seal(&send, 23, plain, 16384, 1, wire, sizeof(wire), &n) < 0); + CHECK(reality_record_seal(&send, 22, NULL, 0, 0, wire, sizeof(wire), &n) < 0); + CHECK(!reality_record_seal(&send, 23, NULL, 0, 0, wire, sizeof(wire), &n)); + CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type) && decoded == 0); + send.sequence = REALITY_KEY_LIMIT; + CHECK(reality_record_seal(&send, 23, data, sizeof(data), 0, wire, sizeof(wire), &n) < 0); + uint8_t old_key[16]; memcpy(old_key, send.key, 16); + CHECK(!reality_traffic_update(&send) && send.sequence == 0 && send.generation == 1 && memcmp(old_key, send.key, 16)); + recv.sequence = REALITY_KEY_LIMIT; + CHECK(!reality_traffic_update(&recv)); + CHECK(!reality_record_seal(&send, 23, data, sizeof(data), 0, wire, sizeof(wire), &n)); + CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type)); + DEBUG_INFO(DEBUG_CATEGORY_REALITY, "HKDF, GCM nonce/AAD, replay, limits, empty DATA and KeyUpdate vectors passed"); + return 0; +} + +static int hello_session(void) { + struct reality_session client = {0}, server = {0}; + struct reality_client_config cc; + struct reality_server_config sc = {0}; + reality_client_config_set_defaults(&cc); + CHECK(!reality_generate_keypair(sc.static_privkey, cc.server_static_pubkey)); + strcpy(cc.server_name, "www.microsoft.com"); + sc.version[0] = 2; sc.short_id_count = 1; sc.time_window_sec = 30; + uint8_t ch[REALITY_MAX_CH_SIZE], sh[REALITY_MAX_SH_SIZE], finished[32], check[32]; + size_t cn, sn; + CHECK(!reality_session_client_start(&client, &cc, 1000, ch, sizeof(ch), &cn)); + CHECK(!reality_session_server_start(&server, &sc, 1000, ch, cn, sh, sizeof(sh), &sn)); + const size_t offsets[] = {0, 4, 5, 8, 10, 43, 44, 76, 77, 78, 80, 84, 86, 88, 92, 94}; + for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) { + sh[offsets[i]] ^= 1; + CHECK(reality_session_client_accept(&client, sh, sn) != REALITY_OK); + sh[offsets[i]] ^= 1; + } + CHECK(!reality_session_client_accept(&client, sh, sn)); + CHECK(!memcmp(client.send.key, server.recv.key, 16) && !memcmp(client.recv.key, server.send.key, 16)); + CHECK(memcmp(client.send.key, client.recv.key, 16)); + CHECK(!reality_finished(&server, 1, finished) && !reality_finished(&client, 0, check) && !memcmp(finished, check, 32)); + const uint8_t ee[] = {8, 0, 0, 2, 0, 0}; + CHECK(!reality_transcript_add(&client, ee, sizeof(ee))); + CHECK(!reality_finished(&client, 0, check) && memcmp(finished, check, 32)); + CHECK(!reality_transcript_add(&server, ee, sizeof(ee))); + CHECK(!reality_finished(&server, 1, finished) && !reality_finished(&client, 0, check) && !memcmp(finished, check, 32)); + for (unsigned i = 0; i < 32; i++) CHECK(!client.auth_key[i] && !server.auth_key[i] && !client.ephemeral_private[i]); + reality_session_cleanup(&client); reality_session_cleanup(&server); + + /* Все открытые байты совпадают с локальным эталоном OpenSSL; динамические поля исключены. */ + FILE *f = fopen("fixtures/reality_openssl_hello.hex", "r"); + if (!f) f = fopen("tests/fixtures/reality_openssl_hello.hex", "r"); + CHECK(f); + char line[2048]; uint8_t reference[1024]; size_t rn = 0; + while (fgets(line, sizeof(line), f)) if (line[0] != '#') { rn = unhex(line, reference); break; } + fclose(f); + CHECK(rn == cn && cn == 263); + memset(ch + 11, 0, 32); memset(reference + 11, 0, 32); + memset(ch + 44, 0, 32); memset(reference + 44, 0, 32); + memset(ch + cn - 41, 0, 32); memset(reference + cn - 41, 0, 32); + CHECK(!memcmp(ch, reference, cn)); + DEBUG_INFO(DEBUG_CATEGORY_REALITY, "Hello validation, independent traffic keys, transcript Finished and OpenSSL profile passed"); + return 0; +} + +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); + return vectors() || hello_session(); +} diff --git a/tests/test_reality_tls.c b/tests/test_reality_tls.c new file mode 100644 index 00000000..456e838f --- /dev/null +++ b/tests/test_reality_tls.c @@ -0,0 +1,147 @@ +/* Обычный TLS 1.3 и HTTP-запрос проходят через REALITY fallback на локальный HTTPS. */ +#define OPENSSL_API_COMPAT 0x10100000L +#include "stcp_server.h" +#include "utun_instance.h" +#include "../lib/debug_config.h" +#include "../lib/mem.h" +#include +#include +#include +#include +#include + +#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "FAIL line=%d: %s", __LINE__, #x); return 1; } } while (0) +struct https_peer { + struct UASYNC *ua; + SSL *ssl; + void *socket_id; + socket_t sock; + uint8_t ready, done, failed; + uint8_t response[256]; size_t response_len; +}; +struct https_server { struct UASYNC *ua; SSL_CTX *ctx; socket_t sock; struct https_peer peer; }; +static const char response[] = "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nConnection: close\r\n\r\nhello"; + +static void https_progress(socket_t sock, void *arg) { + struct https_peer *p = arg; + int rc; + if (!p->ready) { + rc = SSL_accept(p->ssl); + if (rc == 1) p->ready = 1; + else { + int error = SSL_get_error(p->ssl, rc); + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { + uasync_set_socket_write(p->ua, p->socket_id, error == SSL_ERROR_WANT_WRITE); + return; + } + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS handshake failed: ssl=%d", error); + p->failed = 1; + return; + } + } + if (!p->done) { + uint8_t request[512]; + rc = SSL_read(p->ssl, request, sizeof(request)); + if (rc > 0) { + if (rc < 4 || memcmp(request, "GET ", 4)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid HTTP request"); p->failed = 1; return; } + rc = SSL_write(p->ssl, response, sizeof(response) - 1); + if (rc != sizeof(response) - 1) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS response write failed"); p->failed = 1; return; } + p->done = 1; + uasync_set_socket_write(p->ua, p->socket_id, 0); + DEBUG_INFO(DEBUG_CATEGORY_REALITY, "local HTTPS received HTTP request through relay"); + } else { + int error = SSL_get_error(p->ssl, rc); + if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) { + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS read failed: ssl=%d openssl=%lu", error, ERR_get_error()); + p->failed = 1; + } + uasync_set_socket_write(p->ua, p->socket_id, error == SSL_ERROR_WANT_WRITE); + } + } + (void)sock; +} +static void https_accept(socket_t sock, void *arg) { + struct https_server *s = arg; + struct https_peer *p = &s->peer; + p->sock = accept(sock, NULL, NULL); p->ua = s->ua; + if (p->sock == SOCKET_INVALID) { p->failed = 1; return; } + socket_set_nonblocking(p->sock); + p->ssl = SSL_new(s->ctx); + if (!p->ssl || SSL_set_fd(p->ssl, p->sock) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS SSL setup failed"); p->failed = 1; return; } + p->socket_id = uasync_add_socket_t(s->ua, p->sock, https_progress, https_progress, NULL, "test_https", p); + if (!p->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS socket registration failed"); p->failed = 1; } +} +static void unexpected_stcp(struct stcp_conn *c, void *arg) { + (void)c; (void)arg; + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "ordinary HTTPS client entered STCP"); +} +static int client_step(SSL *client, int rc) { + if (rc > 0) return rc; + int error = SSL_get_error(client, rc); + return error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE ? 0 : -1; +} + +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); + size_t allocated = u_get_allocated_count(); + struct UASYNC *ua = uasync_create(); CHECK(ua); + struct UTUN_INSTANCE inst = {0}; inst.ua = ua; + struct SC_MYKEYS keys; CHECK(sc_generate_keypair(&keys) == SC_OK); + EVP_PKEY_CTX *key_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); EVP_PKEY *key = NULL; + CHECK(key_ctx && EVP_PKEY_keygen_init(key_ctx) == 1 && EVP_PKEY_keygen(key_ctx, &key) == 1); + X509 *cert = X509_new(); CHECK(cert); + CHECK(X509_set_version(cert, 2) && ASN1_INTEGER_set(X509_get_serialNumber(cert), 1)); + CHECK(X509_gmtime_adj(X509_getm_notBefore(cert), 0) && X509_gmtime_adj(X509_getm_notAfter(cert), 3600)); + X509_NAME *name = X509_get_subject_name(cert); + CHECK(X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)"localhost", -1, -1, 0)); + CHECK(X509_set_issuer_name(cert, name) && X509_set_pubkey(cert, key) && X509_sign(cert, key, NULL) > 0); + struct https_server server = {0}; server.ua = ua; server.peer.sock = SOCKET_INVALID; + server.ctx = SSL_CTX_new(TLS_server_method()); SSL_CTX *client_ctx = SSL_CTX_new(TLS_client_method()); + CHECK(server.ctx && client_ctx && SSL_CTX_use_certificate(server.ctx, cert) && SSL_CTX_use_PrivateKey(server.ctx, key)); + CHECK(SSL_CTX_set_min_proto_version(server.ctx, TLS1_3_VERSION) && SSL_CTX_set_min_proto_version(client_ctx, TLS1_3_VERSION)); + CHECK(SSL_CTX_set1_groups_list(client_ctx, "X25519") && SSL_CTX_set_ciphersuites(client_ctx, "TLS_AES_128_GCM_SHA256")); + SSL_CTX_set_num_tickets(server.ctx, 0); SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE, NULL); + server.sock = socket(AF_INET, SOCK_STREAM, 0); CHECK(server.sock != SOCKET_INVALID); + struct sockaddr_in address = {0}; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + CHECK(!bind(server.sock, (struct sockaddr *)&address, sizeof(address)) && !listen(server.sock, 1)); + socklen_t size = sizeof(address); CHECK(!getsockname(server.sock, (struct sockaddr *)&address, &size)); + socket_set_nonblocking(server.sock); + CHECK(uasync_add_socket_t(ua, server.sock, https_accept, NULL, NULL, "test_https_listener", &server)); + struct reality_config cfg; reality_config_set_defaults(&cfg); + cfg.enabled = 1; cfg.short_id_count = 1; + uint8_t pub[32]; CHECK(!reality_generate_keypair(cfg.private_key, pub)); + snprintf(cfg.dest, sizeof(cfg.dest), "127.0.0.1:%u", ntohs(address.sin_port)); + uint16_t port = 34000 + (uint16_t)(time(NULL) % 1000); + struct stcp_server *reality = stcp_server_create(ua, port, &keys, NULL, &inst, unexpected_stcp, NULL, NULL, NULL, AF_INET); + CHECK(reality); stcp_server_set_reality(reality, &cfg); + socket_t sock = socket(AF_INET, SOCK_STREAM, 0); CHECK(sock != SOCKET_INVALID); + address.sin_port = htons(port); CHECK(!connect(sock, (struct sockaddr *)&address, sizeof(address))); + socket_set_nonblocking(sock); + SSL *client = SSL_new(client_ctx); CHECK(client && SSL_set_fd(client, sock)); + CHECK(SSL_set_tlsext_host_name(client, "localhost")); + int ready = 0; + for (unsigned i = 0; !ready && !server.peer.failed && i < 1000; i++) { + ready = client_step(client, SSL_connect(client)); CHECK(ready >= 0); uasync_poll(ua, 1); + } + CHECK(ready == 1 && SSL_version(client) == TLS1_3_VERSION); + const char request[] = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"; + CHECK(SSL_write(client, request, sizeof(request) - 1) == sizeof(request) - 1); + uint8_t data[256]; size_t received = 0; + for (unsigned i = 0; received < sizeof(response) - 1 && !server.peer.failed && i < 1000; i++) { + int n = client_step(client, SSL_read(client, data + received, sizeof(data) - received)); + CHECK(n >= 0); received += n; uasync_poll(ua, 1); + } + DEBUG_INFO(DEBUG_CATEGORY_REALITY, "HTTPS result: ready=%u done=%u failed=%u received=%zu", server.peer.ready, server.peer.done, server.peer.failed, received); + CHECK(!server.peer.failed && received == sizeof(response) - 1 && !memcmp(data, response, received)); + CHECK(inst.reality_owner.relay_count == 1); + SSL_free(client); socket_close_wrapper(sock); + stcp_server_destroy(reality); reality_owner_cleanup(&inst.reality_owner); + if (server.peer.sock != SOCKET_INVALID) { + uasync_remove_socket_t(ua, server.peer.sock); SSL_free(server.peer.ssl); socket_close_wrapper(server.peer.sock); + } + uasync_remove_socket_t(ua, server.sock); socket_close_wrapper(server.sock); + SSL_CTX_free(client_ctx); SSL_CTX_free(server.ctx); X509_free(cert); EVP_PKEY_free(key); EVP_PKEY_CTX_free(key_ctx); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); CHECK(u_get_allocated_count() == allocated); + DEBUG_INFO(DEBUG_CATEGORY_REALITY, "real TLS 1.3 + HTTP over fallback passed without leaked resources"); + return 0; +} diff --git a/tests/test_stcp.c b/tests/test_stcp.c index 963795ca..c5e854e2 100644 --- a/tests/test_stcp.c +++ b/tests/test_stcp.c @@ -12,6 +12,7 @@ #include #include #include +#include static int tests_passed = 0, tests_total = 0; static struct SC_MYKEYS s_keys, c_keys; @@ -22,6 +23,9 @@ static struct SC_MYKEYS s_keys, c_keys; #define WIRE_PADDING 32 #define WIRE_HANDSHAKE_SIZE (STCP_HS_CLIENT_MIN + WIRE_PADDING) static int capture_wire, salt_number; +static int reality_capture, reality_send_calls, reality_tamper, reality_fault, reality_block_recv; +static size_t reality_wire_len[2]; +static uint8_t reality_wire[2][1600000]; static uint16_t capture_port; static size_t wire_count; static struct { @@ -30,6 +34,21 @@ static struct { uint8_t bytes[2][WIRE_HANDSHAKE_SIZE]; } wire[2]; +void *__real_u_malloc_impl(uint32_t, const char *); +void *__real_u_calloc_impl(uint32_t, uint32_t, const char *); +static int reality_allocation_fail(const char *location) { + if (!reality_fault || !strstr(location, "reality_io.c:")) return 0; + if (--reality_fault) return 0; + DEBUG_WARN(DEBUG_CATEGORY_REALITY, "injected allocation failure: %s", location); + return 1; +} +void *__wrap_u_malloc_impl(uint32_t size, const char *location) { + return reality_allocation_fail(location) ? NULL : __real_u_malloc_impl(size, location); +} +void *__wrap_u_calloc_impl(uint32_t count, uint32_t size, const char *location) { + return reality_allocation_fail(location) ? NULL : __real_u_calloc_impl(count, size, location); +} + int __real_random_bytes(uint8_t*, size_t); int __wrap_random_bytes(uint8_t* out, size_t len) { if (capture_wire && len == SC_PUBKEY_ENC_SALT_SIZE) { @@ -39,8 +58,37 @@ int __wrap_random_bytes(uint8_t* out, size_t len) { return __real_random_bytes(out, len); } +ssize_t __real_recv(socket_t, void *, size_t, int); +ssize_t __wrap_recv(socket_t sock, void *buf, size_t len, int flags) { + if (reality_block_recv) { + struct sockaddr_in local; socklen_t size = sizeof(local); + if (!getsockname(sock, (struct sockaddr *)&local, &size) && ntohs(local.sin_port) == capture_port) { + errno = EAGAIN; return -1; + } + } + return __real_recv(sock, buf, len, flags); +} + ssize_t __real_send(socket_t, const void*, size_t, int); ssize_t __wrap_send(socket_t sock, const void* buf, size_t len, int flags) { + if (reality_capture) { + struct sockaddr_in local; socklen_t alen = sizeof(local); + if (getsockname(sock, (struct sockaddr *)&local, &alen)) return -1; + int direction = ntohs(local.sin_port) == capture_port; + if (++reality_send_calls % 17 == 0) { errno = EAGAIN; return -1; } + uint8_t part[37]; size_t take = len > sizeof(part) ? sizeof(part) : len; + memcpy(part, buf, take); + if (reality_tamper && direction && len >= 7 && part[0] == 23 && part[1] == 3 && part[2] == 3) { + part[6] ^= 1; + reality_tamper = 0; + } + ssize_t n = __real_send(sock, part, take, flags); + if (n > 0 && reality_wire_len[direction] + (size_t)n <= sizeof(reality_wire[direction])) { + memcpy(reality_wire[direction] + reality_wire_len[direction], part, (size_t)n); + reality_wire_len[direction] += (size_t)n; + } + return n; + } if (!capture_wire) return __real_send(sock, buf, len, flags); struct sockaddr_in local, peer; socklen_t alen = sizeof(local); if (getsockname(sock, (struct sockaddr*)&local, &alen) != 0) { @@ -102,6 +150,7 @@ static void peer_rx_cb(struct ll_queue *q, void *arg) { if (need > p->accum_cap) { p->accum_cap = need + 4096; p->accum = u_realloc(p->accum, p->accum_cap); } memcpy(p->accum + p->accum_len, e->dgram, e->len); p->accum_len += e->len; + queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(q); if (p->conn) stcp_rx_resume_if_needed(p->conn); @@ -187,7 +236,7 @@ static int test1_sizes(void) { peer_cleanup(&srv); peer_cleanup(&cli); if (srv.conn) stcp_conn_free(srv.conn); stcp_client_destroy(sc); stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -225,7 +274,7 @@ static int test2_many(void) { peer_cleanup(&srv); peer_cleanup(&cli); if (srv.conn) stcp_conn_free(srv.conn); stcp_client_destroy(sc); stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -253,7 +302,7 @@ static int test3_wrong_key(void) { peer_cleanup(&srv); peer_cleanup(&cli); stcp_client_destroy(sc); stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -284,7 +333,7 @@ static int test4_close(void) { peer_cleanup(&srv); peer_cleanup(&cli); stcp_client_destroy(sc); stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -352,7 +401,7 @@ static int test5_multi(void) { stcp_client_destroy(clients[i]); } stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -386,7 +435,7 @@ static int test6_interleaved(void) { peer_cleanup(&srv); peer_cleanup(&cli); if (srv.conn) stcp_conn_free(srv.conn); stcp_client_destroy(sc); stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -425,7 +474,7 @@ static int test7_bulk_4mb(void) { peer_cleanup(&srv); peer_cleanup(&cli); if (srv.conn) stcp_conn_free(srv.conn); stcp_client_destroy(sc); stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -455,7 +504,7 @@ static int test8_srv_recv_close(void) { peer_cleanup(&srv); peer_cleanup(&cli); stcp_client_destroy(sc); stcp_server_destroy(ss); - uasync_destroy(ua, 1); // flush deferred → stcp_conn_free(srv_conn) + uasync_drain_immediate(ua); uasync_destroy(ua, 1); // flush deferred → stcp_conn_free(srv_conn) return 0; } @@ -543,7 +592,7 @@ static int test9_raw_handshake_ok(void) { peer_cleanup(&srv); if (srv.conn) stcp_conn_free(srv.conn); stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -570,7 +619,7 @@ static int test10_oversized_padding(void) { peer_cleanup(&srv); srv.conn = NULL; // уже освобождён deferred-close (allocated=1) stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -597,7 +646,7 @@ static int test11_garbage_handshake(void) { peer_cleanup(&srv); srv.conn = NULL; stcp_server_destroy(ss); - uasync_destroy(ua, 1); + uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } @@ -627,7 +676,7 @@ static int test12_padding_boundaries(void) { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "padding boundary: client=%u server=%u byte-exact DATA OK", ci.padding.max, si.padding.max); peer_cleanup(&srv); peer_cleanup(&cli); if (srv.conn) stcp_conn_free(srv.conn); - stcp_client_destroy(sc); stcp_server_destroy(ss); uasync_destroy(ua, 1); + stcp_client_destroy(sc); stcp_server_destroy(ss); uasync_drain_immediate(ua); uasync_destroy(ua, 1); } return 0; } @@ -706,7 +755,7 @@ static int test13_salted_handshakes(void) { peer_cleanup(&srv[i]); peer_cleanup(&cli[i]); stcp_conn_free(srv[i].conn); stcp_client_destroy(clients[i]); } - stcp_server_destroy(ss); uasync_destroy(ua, 1); + stcp_server_destroy(ss); uasync_drain_immediate(ua); uasync_destroy(ua, 1); } return 0; } @@ -726,16 +775,141 @@ static int test14_modified_salt(void) { socket_t sock = raw_connect_send(port, packet, sizeof(packet)); TASSERT(sock != SOCKET_INVALID); for (int i = 0; !srv.closed && i < 1000; i++) uasync_poll(ua, 10); TASSERT(srv.closed && !srv.ready && srv.close_err == 2); - socket_close_wrapper(sock); stcp_server_destroy(ss); uasync_destroy(ua, 1); + socket_close_wrapper(sock); stcp_server_destroy(ss); uasync_drain_immediate(ua); uasync_destroy(ua, 1); return 0; } +static int test15_reality_records(void) { + for (int round = 0; round < 4; round++) { + struct UASYNC *ua = uasync_create(); TASSERT(ua); + struct test_peer srv = {0}, cli = {0}; + struct UTUN_INSTANCE si = {0}, ci = {0}; + si.ua = ci.ua = ua; si.my_keys = s_keys; ci.my_keys = c_keys; + si.padding.min = si.padding.max = round ? ETCP_PADDING_MAX : 0; + ci.padding.min = ci.padding.max = round ? 0 : ETCP_PADDING_MAX; + struct reality_config cfg; reality_config_set_defaults(&cfg); + struct reality_client_config cc; reality_client_config_set_defaults(&cc); + cfg.enabled = 1; cfg.short_id_count = 1; + TASSERT(!reality_generate_keypair(cfg.private_key, cc.server_static_pubkey)); + strcpy(cc.server_name, "www.microsoft.com"); + uint16_t port = BASE_PORT + 17; + struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, &si, + server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss); + stcp_server_set_reality(ss, &cfg); +#ifdef __linux__ + capture_port = port; reality_capture = 1; reality_send_calls = 0; reality_tamper = round == 2; + memset(reality_wire_len, 0, sizeof(reality_wire_len)); +#endif + struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, + NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, + NULL, NULL, peer_close_cb, &cli, NULL, 3000, &cc, NULL, &ci); TASSERT(sc); + for (int j = 0; !cli.closed && (!srv.ready || !cli.ready) && j < 10000; j++) uasync_poll(ua, 1); +#ifdef __linux__ + if (round == 2) { + TASSERT(cli.closed && !cli.ready && !srv.ready); + reality_capture = 0; + stcp_client_destroy(sc); stcp_server_destroy(ss); + reality_owner_cleanup(&si.reality_owner); uasync_drain_immediate(ua); uasync_destroy(ua, 1); + continue; + } +#endif + TASSERT(srv.ready && cli.ready && !srv.closed && !cli.closed); + uint8_t payload[65535]; + for (unsigned i = 0; i < sizeof(payload); i++) payload[i] = (uint8_t)(i * 7); + int messages = round == 3 ? 100 : 1; + size_t payload_len = round == 3 ? 1024 : sizeof(payload); +#ifdef __linux__ + if (round == 3) reality_capture = 0; +#endif + if (round == 3) queue_set_callback(srv.rx, NULL, NULL); + for (int i = 0; i < messages; i++) { + TASSERT(!peer_send(&cli, payload, payload_len) && !peer_send(&srv, payload, payload_len)); + } + TASSERT(!peer_send(&cli, payload, 0) && !peer_send(&srv, payload, 0)); + if (round == 3) { + for (int j = 0; !srv.conn->rx_paused && j < 100000; j++) uasync_poll(ua, 0); + TASSERT(srv.conn->rx_paused && srv.rx->count > STCP_RX_QUEUE_MAX_PACKETS); +#ifdef __linux__ + reality_block_recv = 1; +#endif + queue_set_callback(srv.rx, peer_rx_cb, &srv); queue_resume_callback(srv.rx); + for (unsigned i = 0; i < 100; i++) uasync_poll(ua, 0); +#ifdef __linux__ + TASSERT(srv.msg_count > STCP_RX_QUEUE_MAX_PACKETS + 1); + reality_block_recv = 0; +#endif + DEBUG_INFO(DEBUG_CATEGORY_REALITY, "backpressure resume delivered %d frames with socket recv blocked", srv.msg_count); + } + for (int j = 0; (srv.msg_count < messages + 1 || cli.msg_count < messages + 1) && j < 100000; j++) uasync_poll(ua, 0); + TASSERT(srv.accum_len == payload_len * messages && cli.accum_len == payload_len * messages); + for (int i = 0; i < messages; i++) { + TASSERT(!memcmp(srv.accum + i * payload_len, payload, payload_len)); + TASSERT(!memcmp(cli.accum + i * payload_len, payload, payload_len)); + } +#ifdef __linux__ + reality_capture = 0; + for (int direction = 0; round != 3 && direction < 2; direction++) { + size_t offset = 0, records = 0, ccs = 0, updates = 0; + while (offset < reality_wire_len[direction]) { + const uint8_t *record = reality_wire[direction] + offset; + TASSERT(reality_wire_len[direction] - offset >= 5); + size_t len = 5 + ((size_t)record[3] << 8) + record[4]; + TASSERT(len <= reality_wire_len[direction] - offset); + if (!records) TASSERT(record[0] == 22 && len == (direction ? 127 : 263)); + else if (record[0] == 20) { TASSERT(len == 6 && record[5] == 1 && !ccs++); } + else TASSERT(record[0] == 23 && record[1] == 3 && record[2] == 3 && len >= 22 && len <= REALITY_RECORD_MAX); + if (record[0] == 23 && len == 27) updates++; + offset += len; records++; + } + TASSERT(records >= 8 && ccs == 1); +#if REALITY_KEY_LIMIT < 16 + TASSERT(updates >= 1); +#endif + } +#endif + peer_cleanup(&srv); peer_cleanup(&cli); + stcp_conn_free(srv.conn); stcp_client_destroy(sc); stcp_server_destroy(ss); + reality_owner_cleanup(&si.reality_owner); uasync_drain_immediate(ua); uasync_destroy(ua, 1); + } + return 0; +} + +#ifdef __linux__ +static int test16_reality_allocation_failures(void) { + for (int failure = 1; failure <= 18; failure++) { + size_t allocated = u_get_allocated_count(); + struct UASYNC *ua = uasync_create(); TASSERT(ua); + struct test_peer srv = {0}, cli = {0}; + struct UTUN_INSTANCE si = {0}; si.ua = ua; si.my_keys = s_keys; + struct reality_config cfg; reality_config_set_defaults(&cfg); + struct reality_client_config cc; reality_client_config_set_defaults(&cc); + cfg.enabled = 1; cfg.short_id_count = 1; + TASSERT(!reality_generate_keypair(cfg.private_key, cc.server_static_pubkey)); + strcpy(cc.server_name, "www.microsoft.com"); + struct stcp_server *ss = stcp_server_create(ua, BASE_PORT + 18, &s_keys, NULL, &si, + server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss); + stcp_server_set_reality(ss, &cfg); + reality_fault = failure; + struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", BASE_PORT + 18, &c_keys, s_keys.public_key, + NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, + NULL, NULL, peer_close_cb, &cli, NULL, 3000, &cc, NULL, NULL); TASSERT(sc); + for (unsigned i = 0; !cli.closed && !srv.closed && i < 1000; i++) uasync_poll(ua, 1); + TASSERT(!reality_fault && !cli.ready && !srv.ready && (cli.closed || srv.closed)); + stcp_client_destroy(sc); stcp_server_destroy(ss); + reality_owner_cleanup(&si.reality_owner); uasync_drain_immediate(ua); uasync_destroy(ua, 1); + TASSERT(u_get_allocated_count() == allocated); + } + return 0; +} +#endif + // ======================= main ======================= int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); - debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO); + debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO | DEBUG_CATEGORY_REALITY); + size_t allocated = u_get_allocated_count(); DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "============================================"); DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== STCP Integration Tests ==="); @@ -759,8 +933,13 @@ int main(void) { TRUN(test13_salted_handshakes); #endif TRUN(test14_modified_salt); + TRUN(test15_reality_records); +#ifdef __linux__ + TRUN(test16_reality_allocation_failures); +#endif DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "============================================"); DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", tests_passed, tests_total); + TASSERT(u_get_allocated_count() == allocated); return tests_passed == tests_total ? 0 : 1; } diff --git a/tests/test_stcp_ping.c b/tests/test_stcp_ping.c index 184caf48..2962f74f 100644 --- a/tests/test_stcp_ping.c +++ b/tests/test_stcp_ping.c @@ -1,5 +1,6 @@ // test_stcp_ping.c — TCP-ping (etcp_send_tcp_ping / stcp_ping_send) end-to-end check #include "stcp_client.h" +#include "stcp_server.h" #include "etcp_connections.h" #include "etcp.h" #include "topo_group.h" @@ -21,6 +22,12 @@ static void ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce, DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "TCP ping cb: success=%d rtt=%u", success, rtt); } +static void unexpected_ready(struct stcp_conn *c, void *arg) { + (void)c; (void)arg; + DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "TCP ping unexpectedly entered DATA callback"); + ping_ok = 0; +} + static char *build_config(const char *node_id, const char *priv, const char *pub, int port) { static char buf[512]; snprintf(buf, sizeof(buf), @@ -71,7 +78,7 @@ int main(void) { struct sockaddr_in *sin = (struct sockaddr_in *)&addr; sin->sin_family = AF_INET; sin->sin_addr.s_addr = htonl(0x7f000001); sin->sin_port = htons(port); - int rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL); + int rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL, NULL); if (rc != 0) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "etcp_send_tcp_ping failed rc=%d", rc); return 1; } int ticks = 0; @@ -83,7 +90,7 @@ int main(void) { ping_ok = -1; cli->padding.min = cli->padding.max = ETCP_PADDING_MAX; srv->padding.min = srv->padding.max = 0; - rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL); + rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL, NULL); if (rc != 0) return 1; ticks = 0; while (ping_ok < 0 && ticks < 5000) { uasync_poll(ua, 10); ticks++; } @@ -95,21 +102,33 @@ int main(void) { struct sockaddr_storage bad_addr; memset(&bad_addr, 0, sizeof(bad_addr)); struct sockaddr_in *bsin = (struct sockaddr_in *)&bad_addr; bsin->sin_family = AF_INET; bsin->sin_addr.s_addr = htonl(0x7f000001); bsin->sin_port = htons(port + 5000); - rc = etcp_send_tcp_ping(cli, srv_pub, &bad_addr, 1000, ping_cb, NULL, NULL); + rc = etcp_send_tcp_ping(cli, srv_pub, &bad_addr, 1000, ping_cb, NULL, NULL, NULL); ticks = 0; while (ping_ok < 0 && ticks < 3000) { uasync_poll(ua, 10); ticks++; } if (ping_ok != 0) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "=== FAILED (failure path): success=%d rtt=%u rc=%d ===", ping_ok, ping_rtt, rc); return 1; } DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== failure path PASSED: success=%d ===", ping_ok); + struct reality_config reality; reality_config_set_defaults(&reality); + struct reality_client_config cc; reality_client_config_set_defaults(&cc); + reality.enabled = 1; reality.short_id_count = 1; + if (reality_generate_keypair(reality.private_key, cc.server_static_pubkey)) return 1; + strcpy(cc.server_name, "www.microsoft.com"); + struct stcp_server *rs = stcp_server_create(ua, port + 2, &srv->my_keys, srv->my_ed25519_pubkey, + srv, unexpected_ready, NULL, NULL, NULL, AF_INET); + if (!rs) return 1; + stcp_server_set_reality(rs, &reality); + sin->sin_port = htons(port + 2); + /* Одноразовый TCP-пинг не удерживает instance: длина выбирается до асинхронного connect. */ ping_ok = -1; - rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL); + rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL, &cc); if (rc != 0) return 1; cli->running = 0; utun_instance_destroy(cli); cli = NULL; ticks = 0; while (ping_ok < 0 && ticks < 5000) { uasync_poll(ua, 10); ticks++; } if (ping_ok != 1) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "TCP ping outliving instance failed"); return 1; } DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== TCP ping without retained instance PASSED ==="); + stcp_server_destroy(rs); srv->running = 0; utun_instance_destroy(srv); uasync_destroy(ua, 0);