You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

323 lines
17 KiB

// stcp_client.c — STCP client implementation
#include "stcp_client.h"
#include "secure_channel.h"
#include "crc32.h"
#include "etcp.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#endif
struct stcp_client {
struct stcp_conn conn;
struct UASYNC *ua;
stcp_ready_cb ready_cb;
void *ready_arg;
stcp_ping_cb ping_cb;
void *ping_arg;
uint8_t ping_done;
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
uint8_t my_ed25519_pubkey[SC_PUBKEY_SIZE];
// reality-камуфляж (клиент): перед STCP-хендшейком шлём ClientHello и читаем ServerHello
uint8_t reality_enabled;
struct reality_client_config reality_cfg;
};
static void client_connect_write_cb(socket_t sock, void *arg);
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 int client_derive_session(struct stcp_conn *c, const uint8_t *peer_pubkey) {
struct secure_channel sc;
sc_init_ctx(&sc, &c->my_keys);
if (sc_set_peer_public_key(&sc, peer_pubkey, SC_PEER_PUBKEY_BIN) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client ECDH failed"); return -1; }
memcpy(c->session_key, sc.session_key, SC_SESSION_KEY_SIZE);
memcpy(c->peer_pubkey, peer_pubkey, SC_PUBKEY_SIZE); c->peer_pubkey_set = 1;
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "stcp_client session_key", c->session_key, 16);
if (sc_stream_init(&sc, &c->stream_send, STCP_STREAM_CLIENT_SEND) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client stream_send init failed"); return -1; }
if (sc_stream_init(&sc, &c->stream_recv, STCP_STREAM_SERVER_SEND) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client stream_recv init failed"); return -1; }
return 0;
}
static void client_send_handshake(struct stcp_conn *c, const uint8_t *server_pubkey, const uint8_t *my_ed25519) {
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
if (random_bytes(salt, SC_PUBKEY_ENC_SALT_SIZE) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client random_bytes failed"); stcp_conn_do_close(c, 1); return; }
uint16_t padding = 8;
size_t total = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + padding;
uint8_t *hs = u_malloc(total);
if (!hs) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client hs malloc failed"); stcp_conn_do_close(c, 1); return; }
memcpy(hs, salt, SC_PUBKEY_ENC_SALT_SIZE);
sc_obfuscate_pubkey(salt, server_pubkey, c->my_keys.public_key, hs + SC_PUBKEY_ENC_SALT_SIZE);
uint8_t gop = c->etcp_conn ? c->etcp_conn->got_initial_pkt : c->got_initial_pkt;
uint32_t sid = c->etcp_conn ? c->etcp_conn->session_id : c->session_id;
uint64_t rid = c->etcp_conn ? c->etcp_conn->reset_id : c->reset_id;
uint8_t plain[STCP_HS_PLAIN_SIZE]; memcpy(plain, my_ed25519, 32);
plain[32] = gop;
memcpy(plain + 33, &sid, 4);
{ uint64_t rid_be = htobe64(rid); memcpy(plain + 37, &rid_be, 8); }
plain[45] = (uint8_t)padding; plain[46] = (uint8_t)(padding >> 8);
plain[47] = c->device_type;
*(uint16_t*)(plain + 48) = htobe16(c->keepalive_interval);
plain[50] = c->hs_flags;
uint32_t crc = crc32_calc(plain, STCP_HS_PLAIN_SIZE);
uint8_t *enc_dst = hs + SC_PUBKEY_ENC_SIZE;
memcpy(enc_dst, plain, STCP_HS_PLAIN_SIZE);
enc_dst[51] = (uint8_t)(crc >> 0); enc_dst[52] = (uint8_t)(crc >> 8); enc_dst[53] = (uint8_t)(crc >> 16); enc_dst[54] = (uint8_t)(crc >> 24);
if (sc_stream_xor(&c->stream_send, enc_dst, STCP_HS_ENC_CLIENT) != SC_OK) { u_free(hs); stcp_conn_do_close(c, 2); return; }
for (int i = 0; i < padding; i++) hs[SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + i] = (uint8_t)(salt[0] ^ i);
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 sid=%08x flags=%02x, entering HS_CLIENT_SENT", total, gop, sid, 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);
}
static void client_hs_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
(void)len;
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_ETCP, "stcp_client process_srv_resp recv_buf", data, len);
const uint8_t *salt = data;
const uint8_t *enc_pubkey = salt + SC_PUBKEY_ENC_SALT_SIZE;
uint8_t server_pubkey[SC_PUBKEY_SIZE];
sc_obfuscate_pubkey(salt, c->my_keys.public_key, enc_pubkey, server_pubkey);
if (c->peer_pubkey_set && memcmp(server_pubkey, c->peer_pubkey, SC_PUBKEY_SIZE) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: server pubkey mismatch — possible MITM");
stcp_conn_do_close(c, 4); return;
}
uint8_t enc_hs[STCP_HS_ENC_SERVER];
memcpy(enc_hs, data + SC_PUBKEY_ENC_SIZE, STCP_HS_ENC_SERVER);
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "stcp_client enc_hs BEFORE xor", enc_hs, STCP_HS_ENC_SERVER);
size_t hs_data_len;
if (stcp_frame_decrypt(enc_hs, STCP_HS_ENC_SERVER, &c->stream_recv, &hs_data_len)) { stcp_conn_do_close(c, 3); return; }
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "stcp_client enc_hs AFTER xor", enc_hs, STCP_HS_ENC_SERVER);
memcpy(c->peer_ed25519_pubkey, enc_hs, SC_PUBKEY_SIZE); c->peer_ed25519_set = 1;
c->peer_got_initial_pkt = enc_hs[32];
memcpy(&c->peer_session_id, enc_hs + 33, 4);
{ uint64_t rid_be; memcpy(&rid_be, enc_hs + 37, 8); c->peer_reset_id = be64toh(rid_be); }
uint16_t padding_size = (uint16_t)enc_hs[45] | ((uint16_t)enc_hs[46] << 8);
c->peer_device_type = enc_hs[47];
c->peer_keepalive_interval = ((uint16_t)enc_hs[48] << 8) | enc_hs[49];
c->peer_flags = enc_hs[50];
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: server response OK gop=%d sid=%08x rid=%016llx padding=%u dev=%d ka=%u flags=%02x",
c->peer_got_initial_pkt, c->peer_session_id, (unsigned long long)c->peer_reset_id, padding_size, c->peer_device_type, c->peer_keepalive_interval, c->peer_flags);
stcp_recv_set(c, padding_size, 0, client_hs_padding_cb);
}
static void client_hs_padding_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
(void)data; (void)len;
if (c->hs_timer) { uasync_cancel_timeout(c->ua, c->hs_timer); c->hs_timer = NULL; }
if (c->hs_flags & STCP_HANDSHAKE_FLAG_PING) {
struct stcp_client *cli = (struct stcp_client *)c;
uint16_t rtt = (uint16_t)(get_time_tb() - c->hs_send_time);
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);
return;
}
c->state = STCP_STATE_DATA;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: handshake OK, entering DATA state");
if (c->on_ready) { void (*cb)(struct stcp_conn*, void*) = c->on_ready; c->on_ready = NULL; cb(c, c->ready_arg); }
stcp_recv_set(c, 0, 1, client_data_cb);
}
static void client_data_cb(struct stcp_conn *c, uint8_t *plain_data, size_t data_len) {
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;
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; }
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);
}
// После завершения 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;
if (reality_client_hello_build(&cli->reality_cfg, 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);
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);
}
static void client_conn_read_cb(socket_t sock, void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
(void)sock;
int r = stcp_conn_read(c);
if (r == -1) return;
if (r == 0) return;
stcp_recv_try(c);
}
static void client_connect_write_cb(socket_t sock, void *arg) {
struct stcp_client *cli = (struct stcp_client *)arg;
struct stcp_conn *c = &cli->conn;
int err = 0;
socklen_t len = sizeof(err);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &len) < 0 || err != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client connect failed err=%d", err);
stcp_conn_do_close(c, err); return;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: TCP connected, starting handshake");
uasync_remove_socket_t(cli->ua, sock);
c->socket_id = uasync_add_socket_t(cli->ua, sock, client_conn_read_cb, stcp_write_cb, NULL, c);
if (!c->socket_id) { stcp_conn_do_close(c, ENOMEM); return; }
client_after_connect(c);
}
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port,
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
const uint8_t *my_ed25519_pubkey,
uint8_t got_initial_pkt, uint32_t session_id,
struct ETCP_CONN *etcp_conn,
uint8_t device_type, uint16_t keepalive_interval,
uint8_t hs_flags,
stcp_ready_cb ready_cb, void *arg,
stcp_ping_cb ping_cb, void *ping_arg,
stcp_close_cb close_cb, void *close_arg,
const struct sockaddr_storage *local_addr,
int timeout_ms,
const struct reality_client_config *reality) {
if (!ua || !addr || !keys || !peer_pubkey || (!ready_cb && !ping_cb)) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "invalid args"); return NULL; }
struct stcp_client *cli = u_calloc(1, sizeof(struct stcp_client));
if (!cli) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "calloc failed"); return NULL; }
cli->ua = ua; cli->ready_cb = ready_cb; cli->ready_arg = arg;
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; }
struct stcp_conn *c = &cli->conn;
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;
c->on_write_error = stcp_conn_do_close;
c->tx_cb = stcp_tx_queue_cb;
c->got_initial_pkt = got_initial_pkt;
c->session_id = session_id;
c->etcp_conn = etcp_conn;
c->device_type = device_type;
c->keepalive_interval = keepalive_interval;
c->hs_flags = hs_flags;
int hs_tb = (timeout_ms > 0) ? timeout_ms * 10 : STCP_CONNECT_TIMEOUT;
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
char port_str[16]; snprintf(port_str, sizeof(port_str), "%u", port);
struct addrinfo *res;
if (getaddrinfo(addr, port_str, &hints, &res) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client getaddrinfo %s:%u failed", addr, port);
u_free(cli); return NULL;
}
c->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (c->sock == SOCKET_INVALID) { freeaddrinfo(res); u_free(cli); return NULL; }
socket_set_nonblocking(c->sock);
if (local_addr && local_addr->ss_family) {
if (bind(c->sock, (const struct sockaddr*)local_addr, local_addr->ss_family == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) < 0)
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "stcp_client bind to %s port=0 failed err=%d(%s), continuing to %s:%u",
sockaddr_storage_to_str(local_addr).str, socket_get_error(), socket_strerror(socket_get_error()), addr, port);
}
int conn_ret = connect(c->sock, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
if (conn_ret < 0) {
int sock_err = socket_get_error();
if (sock_err != EINPROGRESS && sock_err != ERR_WOULDBLOCK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client connect to %s:%u failed err=%d(%s)", addr, port, sock_err, socket_strerror(sock_err));
socket_close_wrapper(c->sock); u_free(cli); return NULL;
}
c->socket_id = uasync_add_socket_t(ua, c->sock, NULL, client_connect_write_cb, NULL, cli);
if (!c->socket_id) { socket_close_wrapper(c->sock); u_free(cli); return NULL; }
c->hs_timer = uasync_set_timeout(ua, hs_tb, c, hs_timeout_cb, "stcp_hs");
} else {
c->socket_id = uasync_add_socket_t(ua, c->sock, client_conn_read_cb, stcp_write_cb, NULL, c);
if (!c->socket_id) { socket_close_wrapper(c->sock); u_free(cli); return NULL; }
c->hs_timer = uasync_set_timeout(ua, hs_tb, c, hs_timeout_cb, "stcp_hs");
client_after_connect(c);
}
return cli;
}
void stcp_client_destroy(struct stcp_client *cli) {
if (!cli) return;
cli->conn.free_on_close = cli;
stcp_conn_do_close(&cli->conn, 0);
}
struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli) {
return cli ? &cli->conn : NULL;
}
static void ping_close_cb(struct stcp_conn *c, int err, void *arg) {
(void)arg;
struct stcp_client *cli = (struct stcp_client *)c;
if (cli->ping_cb && !cli->ping_done) {
cli->ping_done = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_client: ping failed err=%d sock=%d", err, (int)c->sock);
cli->ping_cb(0, 0, cli->ping_arg);
}
}
struct stcp_client *stcp_ping_send(struct UASYNC *ua, const char *addr, uint16_t port,
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
const uint8_t *my_ed25519_pubkey,
uint8_t device_type, uint16_t keepalive_interval,
int timeout_ms, stcp_ping_cb cb, void *arg) {
struct stcp_client *cli = stcp_client_connect(ua, addr, port, keys, peer_pubkey, my_ed25519_pubkey,
0, 0, NULL, device_type, keepalive_interval,
STCP_HANDSHAKE_FLAG_PING,
NULL, NULL, cb, arg, ping_close_cb, NULL, NULL, timeout_ms, NULL);
if (cli) cli->conn.free_on_close = cli;
return cli;
}