call: ретрансмит INVITE каждые 2с + fail-fast при отсутствии маршрута (6с)

This commit is contained in:
evgeny committed 2026-09-22 16:06:52 +03:00
1 parent 93becd36e6
commit 0e625da109
8 files changed
+128 -20

No files matched your search

+54 -13
View File
@@ -57,6 +57,8 @@ struct call_session {
struct CONN_MGR_HANDLE* cm_handle;
struct ETCP_CONN* data_conn; /* прямой data-коннект звонка (NULL для INDIRECT) */
void* ring_timer; /* caller: 45с ожидания ответа */
void* invite_timer; /* caller: периодический ретрансмит INVITE (до RINGING) */
uint8_t no_route_strikes; /* caller: подряд ретрансмитов без маршрута (fail-fast) */
void* traffic_timer; /* ACTIVE: 20с без медиа (запускается по первому медиа) */
void* teardown_timer; /* отложенный разрыв conn_mgr (500мс после завершения) */
void* stats_timer; /* ACTIVE: раз в 1с — CALL_STATS (rtt/buffer/tempo) */
@@ -136,6 +138,22 @@ static int call_send(struct call_ctx* ctx, uint64_t group_id, uint64_t dst,
return rc;
}
/* Собрать и отправить INVITE для сессии вызывающего. */
static int call_send_invite(struct call_ctx* ctx, struct call_session* s) {
uint8_t buf[sizeof(struct call_pkt_invite)];
struct call_pkt_invite* inv = (struct call_pkt_invite*)buf;
inv->subcmd = CALL_SUBCMD_INVITE;
inv->call_id = s->call_id;
inv->group_id = s->group_id;
inv->num_tracks = 1;
inv->tracks[0].type = CALL_TRACK_AUDIO;
inv->tracks[0].codec = CALL_CODEC_OPUS;
inv->tracks[0].sample_rate = CALL_OPUS_SAMPLE_RATE;
inv->tracks[0].channels = 1;
inv->tracks[0].bitrate = 32000;
return call_send(ctx, s->group_id, s->peer_node_id, buf, sizeof(buf), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT);
}
/* ─────────────────── реестр сессий ─────────────────── */
static struct call_session* call_session_find(struct call_ctx* ctx, uint64_t call_id) {
@@ -289,6 +307,7 @@ static void call_end(struct call_ctx* ctx, struct call_session* s, uint8_t reaso
ctx->c_calls_ended++;
if (s->ring_timer) { uasync_cancel_timeout(ctx->inst->ua, s->ring_timer); s->ring_timer = NULL; }
if (s->invite_timer) { uasync_cancel_timeout(ctx->inst->ua, s->invite_timer); s->invite_timer = NULL; }
if (s->traffic_timer) { uasync_cancel_timeout(ctx->inst->ua, s->traffic_timer); s->traffic_timer = NULL; }
if (s->stats_timer) { uasync_cancel_timeout(ctx->inst->ua, s->stats_timer); s->stats_timer = NULL; }
@@ -363,6 +382,37 @@ static void call_ring_timeout_cb(void* arg) {
call_end(ctx, s, CALL_REASON_RING_TIMEOUT);
}
/* Периодический ретрансмит INVITE (caller, до RINGING). Покрывает потерю INVITE/RINGING
* и появление маршрута позже. При стойком отсутствии маршрута — fail-fast CONNECT_FAIL. */
static void call_invite_retry_cb(void* arg) {
struct call_session* s = (struct call_session*)arg;
if (!s || !s->ctx) return;
struct call_ctx* ctx = s->ctx;
s->invite_timer = NULL;
if (s->state == CALL_ST_ENDED) return;
if (s->state != CALL_ST_OUTGOING) return; /* RINGING — INVITE доставлен, ретрансмит не нужен */
if (etcp_router_has_route(ctx->inst, s->group_id, s->peer_node_id)) {
s->no_route_strikes = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE retry id=%016llx (route ok)", CALL_ID, (unsigned long long)s->call_id);
call_send_invite(ctx, s);
} else if (++s->no_route_strikes >= CALL_NO_ROUTE_MAX_STRIKES) {
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: no route after %d retries id=%016llx -> CONNECT_FAIL",
CALL_ID, s->no_route_strikes, (unsigned long long)s->call_id);
uint8_t buf[9];
memcpy(buf, &s->call_id, 8);
buf[8] = 2; /* err: peer unreachable (no route) */
chat_event_post(ctx->inst, CHAT_EVT_CALL_ERROR, buf, sizeof(buf));
call_end(ctx, s, CALL_REASON_CONNECT_FAIL);
return;
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE retry id=%016llx no route (strike %d/%d)",
CALL_ID, (unsigned long long)s->call_id, s->no_route_strikes, CALL_NO_ROUTE_MAX_STRIKES);
}
s->invite_timer = uasync_set_timeout(ctx->inst->ua, CALL_INVITE_RETRY_TB, s, call_invite_retry_cb, "call_invite_retry");
}
static void call_traffic_timeout_cb(void* arg) {
struct call_session* s = (struct call_session*)arg;
if (!s || !s->ctx) return;
@@ -519,6 +569,7 @@ static void call_handle_ringing(struct call_ctx* ctx, uint64_t from, const uint8
if (!s || s->role != CALL_ROLE_CALLER || s->state == CALL_ST_ENDED) return;
if (s->state == CALL_ST_OUTGOING) {
s->state = CALL_ST_RINGING;
if (s->invite_timer) { uasync_cancel_timeout(ctx->inst->ua, s->invite_timer); s->invite_timer = NULL; }
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: RINGING id=%016llx", CALL_ID, (unsigned long long)call_id);
call_event_id(ctx, CHAT_EVT_CALL_RINGING, call_id, 0);
}
@@ -692,6 +743,7 @@ void call_destroy(struct UTUN_INSTANCE* inst) {
struct call_session* s = (struct call_session*)e->data;
s->state = CALL_ST_ENDED;
if (s->ring_timer) { uasync_cancel_timeout(inst->ua, s->ring_timer); s->ring_timer = NULL; }
if (s->invite_timer) { uasync_cancel_timeout(inst->ua, s->invite_timer); s->invite_timer = NULL; }
if (s->traffic_timer) { uasync_cancel_timeout(inst->ua, s->traffic_timer); s->traffic_timer = NULL; }
if (s->stats_timer) { uasync_cancel_timeout(inst->ua, s->stats_timer); s->stats_timer = NULL; }
if (s->teardown_timer) { uasync_cancel_timeout(inst->ua, s->teardown_timer); s->teardown_timer = NULL; }
@@ -827,27 +879,16 @@ void call_start(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_nod
s->state = CALL_ST_OUTGOING;
ctx->c_calls_started++;
uint8_t buf[sizeof(struct call_pkt_invite)];
struct call_pkt_invite* inv = (struct call_pkt_invite*)buf;
inv->subcmd = CALL_SUBCMD_INVITE;
inv->call_id = out_call_id;
inv->group_id = group_id;
inv->num_tracks = 1;
inv->tracks[0].type = CALL_TRACK_AUDIO;
inv->tracks[0].codec = CALL_CODEC_OPUS;
inv->tracks[0].sample_rate = CALL_OPUS_SAMPLE_RATE;
inv->tracks[0].channels = 1;
inv->tracks[0].bitrate = 32000;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: call_start id=%016llx peer=0x%016llx group=0x%016llx",
CALL_ID, (unsigned long long)out_call_id, (unsigned long long)peer_node_id, (unsigned long long)group_id);
if (call_send(ctx, group_id, peer_node_id, buf, sizeof(buf), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT) != 0) {
if (call_send_invite(ctx, s) != 0) {
call_end(ctx, s, CALL_REASON_CONNECT_FAIL);
return;
}
call_post_bgp_path(ctx, s);
s->ring_timer = uasync_set_timeout(inst->ua, CALL_RING_TIMEOUT_TB, s, call_ring_timeout_cb, "call_ring");
s->invite_timer = uasync_set_timeout(inst->ua, CALL_INVITE_RETRY_TB, s, call_invite_retry_cb, "call_invite_retry");
}
void call_accept(struct UTUN_INSTANCE* inst, uint64_t call_id) {
+2
View File
@@ -85,6 +85,8 @@ struct call_pkt_media {
/* ── таймауты и ёмкости (timebase 0.1ms) ── */
#define CALL_RING_TIMEOUT_TB 450000 /* 45с — нет ответа → CANCEL */
#define CALL_INVITE_RETRY_TB 20000 /* 2с — период ретрансмита INVITE (до RINGING) */
#define CALL_NO_ROUTE_MAX_STRIKES 3 /* 3 × 2с ≈ 6с без маршрута → fail-fast CONNECT_FAIL */
#define CALL_NO_TRAFFIC_TB 200000 /* 20с — нет входящих медиа → HANGUP */
#define CALL_TEARDOWN_DELAY_TB 5000 /* 500мс — задержка разрыва conn_mgr после завершения */
#define CALL_MAX_TX_FRAMES 50 /* ~1с буфер отправки (50 кадров × 20мс) */
+5
View File
@@ -891,6 +891,11 @@ int merkle_sync_start(struct UTUN_INSTANCE* inst, uint64_t peer,
}
}
/* я в SLEEP-фазе — не инициирую синхронизацию (client-side throttling) */
#ifdef UTUN_HAVE_STANDBY
if (standby_get_sleep_tb() > 0) return 0;
#endif
struct ms_session* s = _session_find(ms, peer, ns);
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: start peer=%016llx ns=%s new=%d", MS_ID, (unsigned long long)peer, ns, s ? 0 : 1);
if (!s) {
+15 -6
View File
@@ -194,21 +194,30 @@ static struct ETCP_ROUTER_CONN* router_conn_find(struct UTUN_INSTANCE* inst,
// ====================================================================
// Найти ETCP_CONN для отправки (учитывая indirect-посредников). NULL = нет маршрута.
static struct ETCP_CONN* router_send_conn(struct ETCP_ROUTER_CONN* rconn) {
struct UTUN_INSTANCE* inst = rconn->inst;
struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, rconn->group_id);
static struct ETCP_CONN* router_route_conn(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t remote_node_id) {
struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, group_id);
if (group) {
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, rconn->remote_node_id);
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, remote_node_id);
if (nq && nq->conn_mgr_type == CONN_TYPE_INDIRECT && nq->conn_mgr_intermediariy_count > 0) {
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++) {
struct ETCP_CONN* c = topo_group_find_conn_for_node(group, nq->conn_mgr_intermediaries[i]);
if (c) return c;
}
}
return topo_group_find_conn_for_node(group, rconn->remote_node_id);
return topo_group_find_conn_for_node(group, remote_node_id);
}
/* group_id=0 (или группа не найдена) — глобальная маршрутизация: прямое соединение по node_id */
return instance_find_conn(inst, rconn->remote_node_id);
return instance_find_conn(inst, remote_node_id);
}
static struct ETCP_CONN* router_send_conn(struct ETCP_ROUTER_CONN* rconn) {
return router_route_conn(rconn->inst, rconn->group_id, rconn->remote_node_id);
}
int etcp_router_has_route(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id) {
if (!inst) return 0;
if (dst_node_id == inst->node_id) return 1;
return router_route_conn(inst, group_id, dst_node_id) != NULL;
}
// Собрать и закодировать (подпись/шифрование) пакет в начале отправки.
+4
View File
@@ -207,6 +207,10 @@ int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
uint64_t group_id, uint64_t remote_node_id, uint8_t svc_id);
// Есть ли сейчас физический маршрут до узла (прямой, indirect-посредник или глобальный).
// 1 = есть ETCP-соединение для отправки, 0 = нет (передача встанет в no_route).
int etcp_router_has_route(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id);
// Отправить данные с авто-seq и контролем inflight
// data: payload без svc_id, mode: ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT (0 = обычный)
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,