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.
 
 
 
 
 
 

134 lines
6.9 KiB

#include "etcp_session.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include <string.h>
#define SESSION_RETRY_TB 5000
#define SESSION_TIMEOUT_TB 100000
static uint64_t read_id(const uint8_t* p) { uint64_t v; memcpy(&v,p,8); return be64toh(v); }
static void write_id(uint8_t* p, uint64_t v) { v = htobe64(v); memcpy(p,&v,8); }
void etcp_session_encode(uint8_t* data, uint8_t code, uint64_t sender, uint64_t target) {
data[0] = code; write_id(data+1,sender); write_id(data+9,target);
}
static void session_send(struct ETCP_CONN* c, uint8_t code, uint64_t peer, uint64_t cookie) {
for (struct ETCP_LINK* link = c->links; link; link = link->next) {
if (!link->initialized) continue;
struct ETCP_DGRAM* pkt = memory_pool_alloc(c->instance->pkt_pool);
if (!pkt) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP,"[%s] session: packet allocation failed",c->log_name); return; }
pkt->link = link; pkt->data_len = ETCP_SESSION_CONTROL_SIZE; pkt->noencrypt_len = 0;
etcp_session_encode(pkt->data,code,c->reset_id,peer); write_id(pkt->data+17,cookie);
if (etcp_encrypt_send(pkt) < 0)
DEBUG_WARN(DEBUG_CATEGORY_ETCP,"[%s] session: send failed code=%u link=%u",c->log_name,code,link->local_link_id);
memory_pool_free(c->instance->pkt_pool,pkt);
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] session TX code=%u local=%016llx peer=%016llx cookie=%016llx",
c->log_name,code,(unsigned long long)c->reset_id,(unsigned long long)peer,(unsigned long long)cookie);
}
void etcp_session_cancel(struct ETCP_CONN* c) {
if (c->session_timer) { uasync_cancel_timeout(c->instance->ua,c->session_timer); c->session_timer = NULL; }
c->session_candidate = c->session_challenge = 0;
}
static void session_tick(void* arg) {
struct ETCP_CONN* c = arg;
c->session_timer = NULL;
if (c->close_requested) return;
if (get_time_tb() - c->session_started >= SESSION_TIMEOUT_TB) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP,"[%s] session confirmation timeout required=%u candidate=%016llx",c->log_name,
c->session_required,(unsigned long long)c->session_candidate);
etcp_session_cancel(c);
if (c->session_required) etcp_connection_close(c);
return;
}
if (c->session_required) session_send(c,ETCP_SESSION_HELLO,c->peer_reset_id,0);
if (c->session_candidate) session_send(c,ETCP_SESSION_CHALLENGE,c->session_candidate,c->session_challenge);
c->session_timer = uasync_set_timeout(c->instance->ua,SESSION_RETRY_TB,c,session_tick,"etcp_session");
}
static void session_schedule(struct ETCP_CONN* c) {
if (c->session_timer) return;
c->session_started = get_time_tb();
c->session_timer = uasync_set_timeout(c->instance->ua,1,c,session_tick,"etcp_session");
}
void etcp_session_start(struct ETCP_CONN* c) {
etcp_session_cancel(c); c->session_required = 1;
DEBUG_INFO(DEBUG_CATEGORY_ETCP,"[%s] session negotiation started local=%016llx peer=%016llx",c->log_name,
(unsigned long long)c->reset_id,(unsigned long long)c->peer_reset_id);
session_schedule(c);
}
void etcp_session_observe(struct ETCP_CONN* c, uint64_t peer_epoch) {
if (!peer_epoch || c->close_requested) return;
if (c->session_candidate != peer_epoch) {
c->session_candidate = peer_epoch;
do {
if (random_bytes((uint8_t*)&c->session_challenge,8) != 0) {
c->session_candidate = 0;
DEBUG_ERROR(DEBUG_CATEGORY_ETCP,"[%s] session challenge RNG failed",c->log_name); return;
}
} while (!c->session_challenge);
}
session_schedule(c);
}
int etcp_session_receive(struct ETCP_CONN* c, const uint8_t* data, size_t len) {
if (!len || data[0] < ETCP_SESSION_HELLO || data[0] > ETCP_SESSION_CONFIRM) return 0;
if (len != ETCP_SESSION_CONTROL_SIZE || c->close_requested) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP,"[%s] invalid session control size=%zu",c->log_name,len); return 1;
}
uint64_t peer = read_id(data+1), target = read_id(data+9), cookie = read_id(data+17);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] session RX code=%u peer=%016llx target=%016llx cookie=%016llx",
c->log_name,data[0],(unsigned long long)peer,(unsigned long long)target,(unsigned long long)cookie);
if (!peer) { DEBUG_WARN(DEBUG_CATEGORY_ETCP,"[%s] session: zero peer epoch",c->log_name); return 1; }
if (data[0] == ETCP_SESSION_HELLO) {
etcp_session_observe(c,peer);
if (c->session_challenge) session_send(c,ETCP_SESSION_CHALLENGE,peer,c->session_challenge);
} else if (target != c->reset_id || !cookie) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] session: stale target or empty cookie",c->log_name);
} else if (data[0] == ETCP_SESSION_CHALLENGE) {
session_send(c,ETCP_SESSION_CONFIRM,peer,cookie);
if (c->session_required || peer != c->peer_reset_id) {
etcp_session_observe(c,peer);
if (c->session_challenge) session_send(c,ETCP_SESSION_CHALLENGE,peer,c->session_challenge);
}
} else if (peer == c->session_candidate && cookie == c->session_challenge) {
etcp_session_cancel(c);
if (peer != c->peer_reset_id) etcp_conn_reinit_id(c,"confirmed peer epoch",c->reset_id);
c->peer_reset_id = peer; c->session_required = 0;
DEBUG_INFO(DEBUG_CATEGORY_ETCP,"[%s] session confirmed local=%016llx peer=%016llx",c->log_name,
(unsigned long long)c->reset_id,(unsigned long long)peer);
etcp_conn_ready(c);
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] session: stale confirmation",c->log_name);
}
return 1;
}
int etcp_session_accept_data(struct ETCP_CONN* c, const uint8_t* data, size_t len) {
if (len <= ETCP_SESSION_HEADER_SIZE || data[0] != ETCP_SESSION_DATA || data[ETCP_SESSION_HEADER_SIZE] > 1) {
c->session_data_errors++;
if (c->session_data_errors <= 3 || c->session_data_errors % 1024 == 0) {
int code = len ? data[0] : -1;
int section = len > ETCP_SESSION_HEADER_SIZE ? data[ETCP_SESSION_HEADER_SIZE] : -1;
const char* reason = len <= ETCP_SESSION_HEADER_SIZE ? "short header" :
(code != ETCP_SESSION_DATA ? "missing session envelope" : "invalid stream section");
DEBUG_WARN(DEBUG_CATEGORY_ETCP,
"[%s] invalid session data: %s size=%zu code=%d expected=%u section=%d count=%llu",
c->log_name, reason, len, code, ETCP_SESSION_DATA, section, (unsigned long long)c->session_data_errors);
}
return 0;
}
uint64_t peer = read_id(data+1), target = read_id(data+9);
if (peer != c->peer_reset_id || target != c->reset_id || c->reinit_pending || !c->initialized) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] discard stale data peer=%016llx target=%016llx pending=%u",c->log_name,
(unsigned long long)peer,(unsigned long long)target,c->reinit_pending);
return 0;
}
return 1;
}