@ -28,104 +28,16 @@ struct stcp_client {
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_conn_write_cb ( socket_t sock , void * arg ) ;
static void client_tx_queue_cb ( struct ll_queue * q , void * arg ) ;
static void stcp_conn_process_recv ( struct stcp_conn * c ) ;
static int client_try_send ( struct stcp_conn * c , const uint8_t * data , size_t len ) ;
static void client_do_close ( struct stcp_conn * c , int err ) ;
static int encrypt_and_crc ( struct stcp_conn * c , const uint8_t * data , size_t data_len ,
uint8_t * output , size_t * output_len ) {
uint32_t crc = crc32_calc ( data , data_len ) ;
output [ 0 ] = ( uint8_t ) ( data_len > > 0 ) ;
output [ 1 ] = ( uint8_t ) ( data_len > > 8 ) ;
memcpy ( output + 2 , data , data_len ) ;
output [ 2 + data_len + 0 ] = ( uint8_t ) ( crc > > 0 ) ;
output [ 2 + data_len + 1 ] = ( uint8_t ) ( crc > > 8 ) ;
output [ 2 + data_len + 2 ] = ( uint8_t ) ( crc > > 16 ) ;
output [ 2 + data_len + 3 ] = ( uint8_t ) ( crc > > 24 ) ;
if ( sc_stream_xor ( & c - > stream_send , output + 2 , data_len + 4 ) ! = SC_OK ) {
DEBUG_ERROR ( DEBUG_CATEGORY_ETCP , " stream_xor failed len=%zu " , data_len ) ;
return - 1 ;
}
* output_len = 2 + data_len + 4 ;
return 0 ;
}
static int decrypt_and_check ( uint8_t * data , size_t len , struct sc_stream_state * stream , size_t * out_len ) {
if ( sc_stream_xor ( stream , data , len ) ! = SC_OK ) { DEBUG_ERROR ( DEBUG_CATEGORY_ETCP , " stream_xor failed " ) ; return - 1 ; }
log_dump ( DEBUG_LEVEL_ERROR , DEBUG_CATEGORY_DEBUG , " decrypt_and_check AFTER xor " , data , len ) ;
size_t data_len = len - SC_CRC32_SIZE ;
uint32_t recv_crc = ( ( uint32_t ) data [ data_len ] ) | ( ( uint32_t ) data [ data_len + 1 ] < < 8 ) |
( ( uint32_t ) data [ data_len + 2 ] < < 16 ) | ( ( uint32_t ) data [ data_len + 3 ] < < 24 ) ;
uint32_t calc_crc = crc32_calc ( data , data_len ) ;
if ( recv_crc ! = calc_crc ) { DEBUG_ERROR ( DEBUG_CATEGORY_ETCP , " CRC mismatch recv=%08x calc=%08x " , recv_crc , calc_crc ) ; return - 1 ; }
* out_len = data_len ;
return 0 ;
}
static void client_do_close ( struct stcp_conn * c , int err ) {
if ( c - > state = = STCP_STATE_CLOSED | | c - > state = = STCP_STATE_ERROR ) return ;
int prev = c - > state ;
c - > state = STCP_STATE_CLOSED ;
DEBUG_INFO ( DEBUG_CATEGORY_ETCP , " stcp_client: closed is_server=%d err=%d prev_state=%d " , c - > is_server , err , prev ) ;
DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close sock=%d sock_id=%p recv_buf=%p len=%zu cap=%zu send_buf=%p on_close=%p " ,
( int ) c - > sock , ( void * ) c - > socket_id , ( void * ) c - > recv_buf , c - > recv_buf_len , c - > recv_buf_cap , ( void * ) c - > send_buf , ( void * ) c - > on_close ) ;
if ( c - > socket_id ) { uasync_remove_socket_t ( c - > ua , c - > sock ) ; c - > socket_id = NULL ; }
if ( c - > sock ! = SOCKET_INVALID ) { socket_close_wrapper ( c - > sock ) ; c - > sock = SOCKET_INVALID ; }
if ( c - > recv_buf ) { void * p = c - > recv_buf ; u_free ( c - > recv_buf ) ; c - > recv_buf = NULL ; c - > recv_buf_len = 0 ; c - > recv_buf_cap = 0 ; DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close u_free recv_buf=%p " , p ) ; }
if ( c - > send_buf ) { void * p = c - > send_buf ; u_free ( c - > send_buf ) ; c - > send_buf = NULL ; c - > send_len = 0 ; DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close u_free send_buf=%p " , p ) ; }
if ( c - > on_close ) { void ( * cb ) ( struct stcp_conn * , int , void * ) = c - > on_close ; c - > on_close = NULL ; DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close calling on_close=%p " , ( void * ) cb ) ; cb ( c , err , c - > close_arg ) ; DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close on_close returned " ) ; }
}
static int client_try_send ( struct stcp_conn * c , const uint8_t * data , size_t len ) {
if ( c - > sock = = SOCKET_INVALID ) return - 1 ;
if ( c - > send_buf ) return - 1 ;
ssize_t sent = send ( c - > sock , data , len , 0 ) ;
if ( sent < 0 ) {
int e = socket_get_error ( ) ;
if ( e = = ERR_AGAIN | | e = = ERR_WOULDBLOCK ) { c - > send_buf = ( uint8_t * ) data ; c - > send_len = len ; c - > send_offset = 0 ; uasync_set_socket_write ( c - > ua , c - > socket_id , 1 ) ; return 0 ; }
DEBUG_ERROR ( DEBUG_CATEGORY_ETCP , " send failed err=%d " , e ) ;
u_free ( ( uint8_t * ) data ) ; client_do_close ( c , e ) ; return - 1 ;
}
if ( ( size_t ) sent < len ) { c - > send_buf = ( uint8_t * ) data ; c - > send_len = len ; c - > send_offset = ( size_t ) sent ; uasync_set_socket_write ( c - > ua , c - > socket_id , 1 ) ; return 0 ; }
u_free ( ( uint8_t * ) data ) ;
return 0 ;
}
static void client_conn_write_cb ( socket_t sock , void * arg ) {
struct stcp_conn * c = ( struct stcp_conn * ) arg ;
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 ) {
int e = socket_get_error ( ) ;
if ( e = = ERR_AGAIN | | e = = ERR_WOULDBLOCK ) return ;
client_do_close ( c , e ) ; return ;
}
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 ; uasync_set_socket_write ( c - > ua , c - > socket_id , 0 ) ; }
}
static void client_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 ) {
size_t need ;
uint8_t * buf = u_malloc ( 2 + e - > len + SC_CRC32_SIZE ) ;
if ( buf & & ! encrypt_and_crc ( c , e - > dgram , e - > len , buf , & need ) ) client_try_send ( c , buf , need ) ;
else if ( buf ) u_free ( buf ) ;
}
queue_dgram_free ( e ) ;
queue_entry_free ( e ) ;
queue_resume_callback ( q ) ;
}
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_ERROR , DEBUG_CATEGORY_DEBUG , " 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 ; }
@ -152,7 +64,8 @@ static void client_send_handshake(struct stcp_conn *c, const uint8_t *server_pub
c - > state = STCP_STATE_HS_CLIENT_SENT ;
DEBUG_INFO ( DEBUG_CATEGORY_ETCP , " stcp_client: handshake sent (%zu bytes), entering HS_CLIENT_SENT " , total ) ;
client_try_send ( c , hs , total ) ;
c - > send_buf = hs ; c - > send_len = total ; c - > send_offset = 0 ;
uasync_set_socket_write ( c - > ua , c - > socket_id , 1 ) ;
}
static void process_server_response ( struct stcp_conn * c ) {
@ -162,13 +75,17 @@ static void process_server_response(struct stcp_conn *c) {
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 " ) ;
client_do_close ( c , 4 ) ; return ;
}
c - > hs_key_processed = 1 ;
}
uint8_t enc_hs [ STCP_HS_ENC_SERVER ] ;
memcpy ( enc_hs , c - > recv_buf + SC_PUBKEY_ENC_SIZE , STCP_HS_ENC_SERVER ) ;
log_dump ( DEBUG_LEVEL_ERROR , DEBUG_CATEGORY_DEBUG , " stcp_client enc_hs BEFORE xor " , enc_hs , STCP_HS_ENC_SERVER ) ;
size_t hs_data_len ;
if ( decrypt_and_check ( enc_hs , STCP_HS_ENC_SERVER , & c - > stream_recv , & hs_data_len ) ) { client_do_close ( c , 3 ) ; return ; }
if ( stcp_frame_ decrypt( enc_hs , STCP_HS_ENC_SERVER , & c - > stream_recv , & hs_data_len ) ) { client_do_close ( c , 3 ) ; return ; }
log_dump ( DEBUG_LEVEL_ERROR , DEBUG_CATEGORY_DEBUG , " 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 ;
uint8_t status = enc_hs [ 32 ] ;
@ -213,7 +130,7 @@ static void stcp_conn_process_recv(struct stcp_conn *c) {
if ( c - > recv_buf_len < total ) return ;
uint8_t * enc_data = c - > recv_buf + 2 ;
size_t data_len ;
if ( decrypt_and_check ( enc_data , msg_size + SC_CRC32_SIZE , & c - > stream_recv , & data_len ) ) { client_do_close ( c , 6 ) ; return ; }
if ( stcp_frame_ decrypt( enc_data , msg_size + SC_CRC32_SIZE , & c - > stream_recv , & data_len ) ) { client_do_close ( c , 6 ) ; return ; }
if ( c - > rx_queue ) {
struct ll_entry * e = queue_entry_new ( 0 ) ;
if ( e ) { e - > dgram = u_malloc ( data_len ) ; if ( e - > dgram ) { if ( data_len ) memcpy ( e - > dgram , enc_data , data_len ) ; e - > len = ( uint16_t ) data_len ; queue_data_put ( c - > rx_queue , e ) ; } else { queue_entry_free ( e ) ; } }
@ -267,13 +184,40 @@ static void client_connect_write_cb(socket_t sock, void *arg) {
}
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 , client_conn _write_cb, NULL , c ) ;
c - > socket_id = uasync_add_socket_t ( cli - > ua , sock , client_conn_read_cb , stcp _write_cb, NULL , c ) ;
if ( ! c - > socket_id ) { client_do_close ( c , ENOMEM ) ; return ; }
int opt = 1 ; setsockopt ( sock , IPPROTO_TCP , TCP_NODELAY , ( const char * ) & opt , sizeof ( opt ) ) ;
if ( client_derive_session ( c , cli - > peer_pubkey ) ) { client_do_close ( c , 1 ) ; return ; }
client_send_handshake ( c , cli - > peer_pubkey , cli - > my_ed25519_pubkey ) ;
}
static void client_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 )
stcp_pending_queue ( c , e - > dgram , e - > len ) ;
queue_dgram_free ( e ) ;
queue_entry_free ( e ) ;
queue_resume_callback ( q ) ;
stcp_flush_pending ( c ) ;
}
static void client_do_close ( struct stcp_conn * c , int err ) {
if ( c - > state = = STCP_STATE_CLOSED | | c - > state = = STCP_STATE_ERROR ) return ;
int prev = c - > state ;
c - > state = STCP_STATE_CLOSED ;
DEBUG_INFO ( DEBUG_CATEGORY_ETCP , " stcp_client: closed is_server=%d err=%d prev_state=%d " , c - > is_server , err , prev ) ;
DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close sock=%d sock_id=%p recv_buf=%p len=%zu cap=%zu send_buf=%p on_close=%p " ,
( int ) c - > sock , ( void * ) c - > socket_id , ( void * ) c - > recv_buf , c - > recv_buf_len , c - > recv_buf_cap , ( void * ) c - > send_buf , ( void * ) c - > on_close ) ;
if ( c - > socket_id ) { uasync_remove_socket_t ( c - > ua , c - > sock ) ; c - > socket_id = NULL ; }
if ( c - > sock ! = SOCKET_INVALID ) { socket_close_wrapper ( c - > sock ) ; c - > sock = SOCKET_INVALID ; }
if ( c - > recv_buf ) { void * p = c - > recv_buf ; u_free ( c - > recv_buf ) ; c - > recv_buf = NULL ; c - > recv_buf_len = 0 ; c - > recv_buf_cap = 0 ; DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close u_free recv_buf=%p " , p ) ; }
if ( c - > send_buf ) { void * p = c - > send_buf ; u_free ( c - > send_buf ) ; c - > send_buf = NULL ; c - > send_len = 0 ; DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close u_free send_buf=%p " , p ) ; }
stcp_pending_clear ( c ) ;
if ( c - > on_close ) { void ( * cb ) ( struct stcp_conn * , int , void * ) = c - > on_close ; c - > on_close = NULL ; DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close calling on_close=%p " , ( void * ) cb ) ; cb ( c , err , c - > close_arg ) ; DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " stcp_client: do_close on_close returned " ) ; }
}
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 ,
@ -289,6 +233,7 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
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 = client_do_close ;
c - > tx_cb = client_tx_queue_cb ;
struct addrinfo hints = { 0 } ;
@ -314,9 +259,8 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
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 ; }
} else {
/* immediate connect (localhost) — запускаем handshake сразу */
int opt = 1 ; setsockopt ( c - > sock , IPPROTO_TCP , TCP_NODELAY , ( const char * ) & opt , sizeof ( opt ) ) ;
c - > socket_id = uasync_add_socket_t ( ua , c - > sock , client_conn_read_cb , client_conn _write_cb, NULL , c ) ;
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 ; }
if ( client_derive_session ( c , cli - > peer_pubkey ) ) { client_do_close ( c , 1 ) ; return cli ; }
client_send_handshake ( c , cli - > peer_pubkey , cli - > my_ed25519_pubkey ) ;