Browse Source

etcp_router: вынести подпись/шифрование в route_crypto, одна функция отправки с флагами

- route_crypto_encode/decode: автономный пред/пост-обработчик пакета
  (encrypt-then-sign, стандартный Ed25519 вместо sc_stream_sign)
- одна функция отправки etcp_route_send/etcp_router_conn_send с bitmask mode
- формат доставки: [svc_id][src][dst][rx_flags][payload] (статус подписи/шифрования)
- ROUTER_INFLIGHT хранит финальный wire-пакет (чинит потерю подписи при ретрансмите)
- дедуп инициализации: router_conn_reset/free_queues
- учёт inflight через inflight_q count (seq присваивается на enqueue)
v2
evgeny 4 weeks ago
parent
commit
2d04bf941b
  1. 2
      cross-build-win.sh
  2. 2
      src/Makefile.am
  3. 2
      src/media_delivery/media_delivery.c
  4. 2
      src/media_delivery/media_download.c
  5. 4
      src/nat_transport.c
  6. 6
      src/proxy/icmp_proxy.c
  7. 2
      src/proxy/socks_proxy.c
  8. 2
      src/proxy/tcp_proxy_client.c
  9. 2
      src/proxy/tcp_proxy_server.c
  10. 4
      src/proxy/udp_proxy.c
  11. 6
      src/routing_layer/conn_mgr_core.c
  12. 6
      src/routing_layer/conn_mgr_indirect.c
  13. 712
      src/routing_layer/etcp_router.c
  14. 36
      src/routing_layer/etcp_router.h
  15. 70
      src/routing_layer/etcp_router_doc.md
  16. 160
      src/routing_layer/route_crypto.c
  17. 46
      src/routing_layer/route_crypto.h
  18. 2
      src/routing_layer/routing.c
  19. 4
      tests/test_etcp_router.c
  20. 2
      tests/test_etcp_router_reconnect.c
  21. 35
      tests/test_etcp_router_unit.c
  22. 2
      tests/test_media_delivery_full.c
  23. 2
      tests/test_media_delivery_integration.c
  24. 4
      tests/test_nat_transport.c
  25. 2
      tools/chatgui/transport/utun_node.cpp

2
cross-build-win.sh

@ -203,7 +203,7 @@ SRC_SOURCES=(
transport_layer/pkt_normalizer.c transport_layer/packet_dump.c transport_layer/etcp_api.c
control_server.c
firewall.c eim_nat.c nat_transport.c transport_layer/dummynet.c
etcp_router.c
etcp_router.c route_crypto.c
proxy/udp_proxy.c proxy/socks_proxy.c
lwip_tcp/lwip_pbuf.c
)

2
src/Makefile.am

@ -61,6 +61,7 @@ utun_CORE_SOURCES = \
ntp_node_time.c \
proxy/tcp_proxy_client.c \
routing_layer/etcp_router.c \
routing_layer/route_crypto.c \
proxy/tcp_proxy_server.c \
proxy/udp_proxy.c \
proxy/socks_proxy.c \
@ -143,6 +144,7 @@ libutun_a_SOURCES = \
ntp_node_time.c \
proxy/tcp_proxy_client.c \
routing_layer/etcp_router.c \
routing_layer/route_crypto.c \
proxy/tcp_proxy_server.c \
proxy/udp_proxy.c \
proxy/socks_proxy.c \

2
src/media_delivery/media_delivery.c

@ -412,7 +412,7 @@ static int md_send(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_n
if (!e->dgram) { queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_RT_ID_MEDIA_DELIVERY;
memcpy(e->dgram + 1, data, len); e->len = (uint16_t)(len + 1);
int rc = etcp_route_send(inst, group_id, dst_node_id, e, 1);
int rc = etcp_route_send(inst, group_id, dst_node_id, e, 1, 0);
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "%s: md_send FAIL rc=%d dst=0x%016llx group=0x%016llx",
MD_ID, rc, (unsigned long long)dst_node_id, (unsigned long long)group_id);

2
src/media_delivery/media_download.c

@ -72,7 +72,7 @@ static int md_dl_send(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t ds
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "%s: u_malloc(%zu) failed for send to 0x%016llx", MDL_ID, len + 1, (unsigned long long)dst); queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_RT_ID_MEDIA_DELIVERY;
memcpy(e->dgram + 1, data, len); e->len = (uint16_t)(len + 1);
int rc = etcp_route_send(inst, group_id, dst, e, 1);
int rc = etcp_route_send(inst, group_id, dst, e, 1, 0);
if (rc != 0) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "%s: etcp_route_send failed rc=%d to 0x%016llx group=0x%016llx", MDL_ID, rc, (unsigned long long)dst, (unsigned long long)group_id); u_free(e->dgram); queue_entry_free(e); }
return rc;
}

4
src/nat_transport.c

@ -47,7 +47,7 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
int ret = etcp_route_send(inst, TOPO_GROUP_UTUN, tr->nat_via_node_id, new_entry, 0);
int ret = etcp_route_send(inst, TOPO_GROUP_UTUN, tr->nat_via_node_id, new_entry, 0, 0);
if (ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT client: etcp_route_send to provider %016llx failed",
(unsigned long long)tr->nat_via_node_id);
@ -89,7 +89,7 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
int send_ret = etcp_route_send(inst, TOPO_GROUP_UTUN, entry->src_node_id, new_entry, 0);
int send_ret = etcp_route_send(inst, TOPO_GROUP_UTUN, entry->src_node_id, new_entry, 0, 0);
if (send_ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT provider: etcp_route_send back to node %016llx failed",
(unsigned long long)entry->src_node_id);

6
src/proxy/icmp_proxy.c

@ -143,7 +143,7 @@ static void raw_read_cb(socket_t sock, void* arg) {
memcpy(e->dgram + 12, &icmp_hdr->icmp_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
int ret = etcp_route_send(g_icmp_ctx->inst, TOPO_GROUP_UTUN, r->client_node_id, e, 0);
int ret = etcp_route_send(g_icmp_ctx->inst, TOPO_GROUP_UTUN, r->client_node_id, e, 0, 0);
if (ret != 0) DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: etcp_route_send reply failed: %d", ret);
else DEBUG_INFO(DEBUG_CATEGORY_PROXY, "icmp_proxy: reply forwarded to client %016llx", (unsigned long long)r->client_node_id);
u_free(r);
@ -180,7 +180,7 @@ static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry)
memcpy(e->dgram + 12, &echo_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
etcp_route_send(inst, TOPO_GROUP_UTUN, client_node_id, e, 0);
etcp_route_send(inst, TOPO_GROUP_UTUN, client_node_id, e, 0, 0);
} else queue_entry_free(e);
}
} else {
@ -244,7 +244,7 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
memcpy(e->dgram + 12, &echo_seq, 2);
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
return etcp_route_send(inst, TOPO_GROUP_UTUN, exit_node_id, e, 0);
return etcp_route_send(inst, TOPO_GROUP_UTUN, exit_node_id, e, 0, 0);
}
// ====================================================================

2
src/proxy/socks_proxy.c

@ -70,7 +70,7 @@ static int send_msg(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst,
queue_dgram_free(e); queue_entry_free(e); return -1;
}
e->len = (uint16_t)(TCP_PROXY_HDR_SIZE + len);
int ret = etcp_route_send(inst, group_id, dst, e, force);
int ret = etcp_route_send(inst, group_id, dst, e, force, 0);
DEBUG_TRACE(DEBUG_CATEGORY_PROXY, "SOCKS send_msg subcmd=%02x sid=%08x len=%zu force=%d → ret=%d",
subcmd, sid, len, force, ret);
return ret;

2
src/proxy/tcp_proxy_client.c

@ -79,7 +79,7 @@ static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t group_
memcpy(e->dgram + 2, &sid, 4);
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
e->len = TCP_PROXY_HDR_SIZE + len;
return etcp_route_send(inst, group_id, dst, e, force);
return etcp_route_send(inst, group_id, dst, e, force, 0);
}
static int tcp_proxy_client_send_connect(struct tcp_proxy_client_conn* pc) {

2
src/proxy/tcp_proxy_server.c

@ -65,7 +65,7 @@ static int send_msg(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst,
queue_dgram_free(e); queue_entry_free(e); return -1;
}
e->len = TCP_PROXY_HDR_SIZE + len;
return etcp_route_send(inst, group_id, dst, e, force);
return etcp_route_send(inst, group_id, dst, e, force, 0);
}
static void close_retry_cb(void* arg) {

4
src/proxy/udp_proxy.c

@ -64,7 +64,7 @@ static void flow_read_cb(socket_t sock, void* arg) {
memcpy(e->dgram + UDP_PROXY_HDR_SIZE, buf, n);
e->len = UDP_PROXY_HDR_SIZE + n;
etcp_route_send(g_udp_ctx->inst, TOPO_GROUP_UTUN, f->client_node_id, e, 0);
etcp_route_send(g_udp_ctx->inst, TOPO_GROUP_UTUN, f->client_node_id, e, 0, 0);
}
// ====================================================================
@ -168,7 +168,7 @@ int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
memcpy(e->dgram + 12, &dst_port, 2);
if (payload_len > 0) memcpy(e->dgram + UDP_PROXY_HDR_SIZE, payload, payload_len);
e->len = UDP_PROXY_HDR_SIZE + payload_len;
return etcp_route_send(inst, TOPO_GROUP_UTUN, exit_node_id, e, 0);
return etcp_route_send(inst, TOPO_GROUP_UTUN, exit_node_id, e, 0, 0);
}
// ====================================================================

6
src/routing_layer/conn_mgr_core.c

@ -440,7 +440,7 @@ void conn_mgr_close(struct CONN_MGR_HANDLE* h) {
pkt.cmd = ETCP_RT_ID_CONN_MGR; pkt.subcmd = CM_SUBCMD_DISCONNECT; pkt.node_id = entry->node_id;
struct ll_entry* qe = queue_entry_new(0);
if (qe) { qe->dgram = u_malloc(sizeof(pkt)); memcpy(qe->dgram, &pkt, sizeof(pkt)); qe->len = sizeof(pkt);
etcp_route_send(entry->mgr->instance, entry->mgr->group->group_id, entry->node_id, qe, 1); }
etcp_route_send(entry->mgr->instance, entry->mgr->group->group_id, entry->node_id, qe, 1, 0); }
/* NCD CLOSE — транспортный уровень (отправит CLOSE/KEEP_ALIVE через node_conn_direct) */
}
cm_entry_cleanup(entry); u_free(h);
@ -458,7 +458,7 @@ int conn_mgr_send(struct CONN_MGR_HANDLE* h, struct ll_entry* e) {
entry->last_traffic_tb = get_time_tb();
struct ETCP_ROUTER_CONN* r = etcp_router_conn_get(entry->mgr->instance, entry->mgr->group->group_id, entry->node_id, ETCP_RT_ID_CONN_MGR);
if (!r) { queue_entry_free(e); return -1; }
int ret = etcp_router_conn_send(r, e->dgram, e->len); queue_entry_free(e); return ret;
int ret = etcp_router_conn_send(r, e->dgram, e->len, 0); queue_entry_free(e); return ret;
}
/* ═══════ локальное сканирование ═══════ */
@ -676,7 +676,7 @@ void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) {
for (uint8_t i=0;i<dc;i++) { *p++=addrs[i].t; memcpy(p,addrs[i].ip,4);p+=4; memcpy(p,&addrs[i].port,2);p+=2; *p++=addrs[i].sid; }
struct ll_entry* qe = queue_entry_new(0);
if (!qe) { u_free(pkt); cm_deliver_event(entry, CONN_EVENT_TIMEOUT); return; }
qe->dgram=pkt; qe->len=(uint16_t)sz; etcp_route_send(entry->mgr->instance, group->group_id, entry->node_id, qe, 1);
qe->dgram=pkt; qe->len=(uint16_t)sz; etcp_route_send(entry->mgr->instance, group->group_id, entry->node_id, qe, 1, 0);
struct cm_reverse_pending* rp = u_calloc(1, sizeof(*rp));
if (rp) { rp->request_id=req_id; rp->entry=entry; rp->next=entry->mgr->reverse_pending; entry->mgr->reverse_pending=rp; }
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS*10,

6
src/routing_layer/conn_mgr_indirect.c

@ -26,7 +26,7 @@ void cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) {
for (uint8_t i=0;i<req.candidate_count;i++) req.candidates[i]=mgr->best_candidates[i];
struct ll_entry* qe = queue_entry_new(0); if (!qe) return;
qe->dgram=u_malloc(sizeof(req)); memcpy(qe->dgram,&req,sizeof(req)); qe->len=sizeof(req);
etcp_route_send(mgr->instance, mgr->group->group_id, entry->node_id, qe, 1);
etcp_route_send(mgr->instance, mgr->group->group_id, entry->node_id, qe, 1, 0);
entry->main.timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS*10, entry, cm_exchange_timeout_cb, "conn_mgr_interm_exch");
struct cm_exchange_pending* ep = u_calloc(1,sizeof(*ep));
if (ep) { ep->request_id=entry->main.request_id; ep->entry=entry; ep->timer=entry->main.timer; ep->next=mgr->exchange_pending; mgr->exchange_pending=ep; }
@ -67,7 +67,7 @@ void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CM_EXCHANGE_
for(uint8_t i=0;i<sel;i++){pkt.selected[i].node_id=all[i].nid;pkt.selected[i].rtt=0;
struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(entry->mgr->group,all[i].nid); if(nq)pkt.selected[i].rtt=topo_get_chain_rtt(nq);}
struct ll_entry* qe=queue_entry_new(0);
if(qe){qe->dgram=u_malloc(sizeof(pkt));memcpy(qe->dgram,&pkt,sizeof(pkt));qe->len=sizeof(pkt);etcp_route_send(entry->mgr->instance,entry->mgr->group->group_id,entry->node_id,qe,1);}
if(qe){qe->dgram=u_malloc(sizeof(pkt));memcpy(qe->dgram,&pkt,sizeof(pkt));qe->len=sizeof(pkt);etcp_route_send(entry->mgr->instance,entry->mgr->group->group_id,entry->node_id,qe,1,0);}
entry->conn_type=CONN_TYPE_INDIRECT; entry->state=CONN_MGR_STATE_CONNECTED;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: indirect OK for 0x%016llx via %u intermediaries",(unsigned long long)entry->node_id,sel);
cm_update_nodeinfo(entry); cm_deliver_event(entry,CONN_EVENT_UP);
@ -140,6 +140,6 @@ void cm_handle_interm_exchange_req(struct ETCP_CONN* conn, struct CM_EXCHANGE_RE
}
size_t sz=offsetof(struct CM_EXCHANGE_RESP,your_candidates)+(size_t)resp.your_count*sizeof(struct CONN_MGR_CANDIDATE);
struct ll_entry* qe=queue_entry_new(0);
if(qe){qe->dgram=u_malloc(sz);memcpy(qe->dgram,&resp,sz);qe->len=(uint16_t)sz;etcp_route_send(mgr->instance,mgr->group->group_id,conn->peer_node_id,qe,1);}
if(qe){qe->dgram=u_malloc(sz);memcpy(qe->dgram,&resp,sz);qe->len=(uint16_t)sz;etcp_route_send(mgr->instance,mgr->group->group_id,conn->peer_node_id,qe,1,0);}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: sent EXCHANGE_RESP to 0x%016llx my=%u your=%u",(unsigned long long)conn->peer_node_id,resp.my_count,resp.your_count);
}

712
src/routing_layer/etcp_router.c

File diff suppressed because it is too large Load Diff

36
src/routing_layer/etcp_router.h

@ -1,10 +1,11 @@
// etcp_router.h — Сервисный слой маршрутизации поверх ETCP
// Маршрутизирует сервисные пакеты до целевой ноды, на промежуточных нодах ретранслирует
// Упрощённый TCP поверх ETCP: восстановление порядка, дедупликация, без переповторов
// Упрощённый TCP поверх ETCP: восстановление порядка, дедупликация, ретрансмиты, inflight-контроль.
// Подпись/шифрование вынесены в автономный модуль route_crypto (encode/decode).
//
// Формат SVC_ROUTE пакета:
// [cmd:1] [group_id:8] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [flags:1] [timestamp:2] [payload...]
// flags: bit7=START, bit6=RST, bit5-4=sess_id(0-3), bit2=ENCRYPTED, bit1=CLOSE
// flags: bit7=START, bit6=RST, bit5-4=sess_id(0-3), bit3=SIGNED, bit2=ENCRYPTED, bit1=CLOSE
// ACK-пакет: тот же заголовок, seq=rx_seq, payload_len=0
#ifndef ETCP_ROUTER_H
#define ETCP_ROUTER_H
@ -35,12 +36,14 @@ struct SVC_ROUTE_HDR {
#define SVC_ROUTE_MAX_BINDINGS 256
// Единый формат доставки сервисной кодограммы (router → сервис):
// [svc_id:1][src_node_id:8][dst_node_id:8][payload...]
// [svc_id:1][src_node_id:8][dst_node_id:8][rx_flags:1][payload...]
// src/dst — реальные end-to-end узлы из SVC_ROUTE заголовка (не промежуточные).
// rx_flags — биты ROUTER_FLAG_ENCRYPTED/SIGNED, бывшие на wire (статус для сервиса).
#define ROUTER_SVC_SRC_OFF 1
#define ROUTER_SVC_DST_OFF 9
#define ROUTER_SVC_PAYLOAD_OFF 17
#define ROUTER_SVC_HDR_SIZE 17 // svc_id(1) + src_node_id(8) + dst_node_id(8)
#define ROUTER_SVC_FLAGS_OFF 17
#define ROUTER_SVC_PAYLOAD_OFF 18
#define ROUTER_SVC_HDR_SIZE 18 // svc_id(1) + src_node_id(8) + dst_node_id(8) + rx_flags(1)
// Биты в flags
#define ROUTER_FLAG_START 0x80
@ -158,15 +161,14 @@ struct ETCP_ROUTER_CONN {
#define MINRTT_WINDOW_SIZE 10
#define MINRTT_DEFAULT_TB 100 // default minRTT = 10ms (заменяется первым RTT)
// Inflight запись — копия отправленного пакета для возможного ретрансмита
// Inflight запись — копия финального wire-пакета (уже закодирован encode'ом) для ретрансмита
struct ROUTER_INFLIGHT {
struct ll_entry ll; // индекс по seq (4 байта, offset 0)
uint32_t seq;
uint64_t last_sent_tb; // время последней отправки (0.1ms)
uint8_t send_count; // число переотправок
uint8_t is_encrypted; // 1 = E2E-шифрованный пакет
uint8_t* payload; // копия данных
size_t payload_len;
uint8_t* dgram; // копия финального пакета [SVC_ROUTE_HDR][payload][sig?]
size_t dgram_len;
};
// Bindings для сервисов внутри etcp_router (аналогично ETCP_BINDINGS)
@ -184,24 +186,18 @@ void etcp_router_destroy(struct UTUN_INSTANCE* inst);
int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn callback);
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id);
// Отправить сервисный пакет (авто-conn, seq, inflight-контроль через send_q)
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id, struct ll_entry* entry, int force);
// Отправить сервисный пакет с E2E-шифрованием (AES-128-CCM через X25519 ECDH)
int etcp_route_send_encrypted(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id, struct ll_entry* entry, int force);
// Отправить сервисный пакет (авто-conn, seq, inflight-контроль через send_q).
// mode — битовая маска ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT (см. route_crypto.h), 0 = обычный пакет.
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id, struct ll_entry* entry, int force, int mode);
// Найти/создать состояние seq-подключения по (group_id, remote_node_id, svc_id)
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);
// Отправить данные с авто-seq и контролем inflight
// data: payload без svc_id, flags: битовые флаги (зарезервировано)
// data: payload без svc_id, mode: ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT (0 = обычный)
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len);
// Отправить данные с Ed25519 подписью (аналог etcp_router_conn_send с флагом ROUTER_FLAG_SIGNED)
int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len);
const uint8_t* data, size_t len, int mode);
// Закрыть seq-подключение
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);

70
src/routing_layer/etcp_router_doc.md

@ -22,20 +22,21 @@ e->dgram = u_malloc(1 + payload_len);
e->dgram[0] = svc_id;
memcpy(e->dgram + 1, payload, payload_len);
e->len = 1 + payload_len;
etcp_route_send(inst, dst_node_id, e, force);
etcp_route_send(inst, group_id, dst_node_id, e, force, mode); // mode: 0 / ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT
// Или через существующий ROUTER_CONN:
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(inst, remote_node_id, svc_id);
etcp_router_conn_send(rconn, data, len);
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(inst, group_id, remote_node_id, svc_id);
etcp_router_conn_send(rconn, data, len, mode);
```
### Приём
```c
void my_service_callback(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry) { /* соединение закрыто */ return; }
uint8_t svc_id = entry->dgram[0];
uint64_t from = *(uint64_t*)(entry->dgram + 1); // remote_node_id
// ... данные начиная с entry->dgram[9] ...
uint8_t svc_id = entry->dgram[0];
uint64_t from = *(uint64_t*)(entry->dgram + ROUTER_SVC_SRC_OFF); // remote_node_id
uint8_t rx_flags = entry->dgram[ROUTER_SVC_FLAGS_OFF]; // был ли SIGNED/ENCRYPTED на wire
// ... данные начиная с entry->dgram[ROUTER_SVC_PAYLOAD_OFF] ...
queue_dgram_free(entry);
queue_entry_free(entry);
}
@ -45,34 +46,40 @@ void my_service_callback(struct ETCP_CONN* conn, struct ll_entry* entry) {
```c
// На send_q (inflight переполнен):
struct queue_waiter_handle h;
etcp_router_on_send_ready(inst, node_id, svc_id, &h, my_ready_cb, my_arg);
etcp_router_on_send_ready(inst, group_id, node_id, svc_id, &h, my_ready_cb, my_arg);
// Когда send_q освободится — вызовется my_ready_cb
// На normalizer->input (сетевая очередь):
etcp_router_waiter_register(inst, node_id, &h, my_ready_cb, my_arg);
etcp_router_waiter_register(inst, group_id, node_id, &h, my_ready_cb, my_arg);
```
### Подписанные сообщения
### Подпись / шифрование (модуль route_crypto)
```c
etcp_router_conn_send_signed(rconn, data, len); // добавляет Ed25519-подпись
// mode — битовая маска, применяется в начале функции отправки (route_crypto_encode),
// при приёме проверяется/расшифровывается перед вызовом коллбэка (route_crypto_decode).
#include "route_crypto.h"
etcp_route_send(inst, group_id, dst, e, force, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT);
```
## 3. API
### Ключевые структуры
**SVC_ROUTE_HDR** (25 байт) — заголовок пакета:
`cmd(1) + dst_node_id(8) + src_node_id(8) + seq(4) + svc_id(1) + flags(1) + timestamp(2)`
**SVC_ROUTE_HDR** (33 байта) — заголовок пакета:
`cmd(1) + group_id(8) + dst_node_id(8) + src_node_id(8) + seq(4) + svc_id(1) + flags(1) + timestamp(2)`
Флаги: `ROUTER_FLAG_START` (0x80), `ROUTER_FLAG_RST` (0x40), `ROUTER_FLAG_SIGNED` (0x08), `ROUTER_FLAG_CLOSE` (0x02). Sess_id в bits 5-4.
Флаги: `ROUTER_FLAG_START` (0x80), `ROUTER_FLAG_RST` (0x40), `ROUTER_FLAG_SIGNED` (0x08), `ROUTER_FLAG_ENCRYPTED` (0x04), `ROUTER_FLAG_CLOSE` (0x02). Sess_id в bits 5-4.
**ETCP_ROUTER_CONN** — состояние логического подключения (remote_node_id + svc_id):
**Формат доставки сервису** (router → callback):
`[svc_id:1][src_node_id:8][dst_node_id:8][rx_flags:1][payload...]` (ROUTER_SVC_HDR_SIZE = 18).
**ETCP_ROUTER_CONN** — состояние логического подключения (group_id + remote_node_id + svc_id):
- `tx_seq`, `rx_seq`, `tx_acked` — seq-нумерация для порядка и inflight
- `recv_q` — reorder-очередь с хеш-индексом по seq (восстановление порядка)
- `send_q` — очередь ожидания при полном inflight (backpressure)
- `send_waiter` — waiter на send_input_q (по 1 пакету, round-robin между сервисами)
- `watchdog_timer` — медленная проверка инварианта drain (страховка от заклинивания)
- `inflight_q` — копии отправленных пакетов для ретрансмита (хеш по seq)
- `inflight_q` — копии финальных отправленных пакетов для ретрансмита (хеш по seq)
- `incoming_q` — FIFO между сетевым приёмом и recv_q (защита от гонок)
- `rtt`, `rtt_jitter`, `minrtt` — измерения задержки
- `inflight_limit` — текущий лимит пакетов в полёте (minrtt_probe снижает до 4)
@ -80,9 +87,9 @@ etcp_router_conn_send_signed(rconn, data, len); // добавляет Ed25519-
- `start_sent`, `peer_sync_done` — синхронизация после (пере)подключения
- `closed` — флаг закрытия (игнорирование таймеров)
**ROUTER_INFLIGHT** — запись в inflight_q: `seq, last_sent_tb, send_count, payload*`
**ROUTER_INFLIGHT** — запись в inflight_q: `seq, last_sent_tb, send_count, dgram*` (финальный wire-пакет)
**TRANSIT_QUEUE** — per (src,dst) пара на промежуточном узле:
**TRANSIT_QUEUE** — per (group_id, src, dst) пара на промежуточном узле:
- `q` — FIFO транзитных пакетов
- `waiter` — backpressure на send_input_q next_hop'а
- `conn` — ETCP_CONN следующего шага
@ -91,11 +98,11 @@ etcp_router_conn_send_signed(rconn, data, len); // добавляет Ed25519-
```
Приём: сеть → incoming_q (FIFO) → router_incoming_q_cb → recv_q (хеш по seq)
↓
router_try_assembly → deliver (rx_seq++)
router_try_assembly → deliver → route_crypto_decode → cb
Отправка: router_enqueue_send → send_q (FIFO, backpressure)
Отправка: router_enqueue_send → router_build_packet (seq + route_crypto_encode) → send_q (FIFO, backpressure)
router_send_kick → waiter на send_input_q → router_send_drain_cb (по 1 пакету, round-robin)
→ router_send_one_flags → etcp_send + inflight_q (копия)
→ etcp_send + inflight_q (копия финального пакета)
↓
router_track_inflight_state + retrans_schedule
ACK: периодический (10ms) + idle (500ms) → router_send_ack(rx_seq)
@ -119,17 +126,22 @@ ACK: периодический (10ms) + idle (500ms) → router_send_ack(rx_seq
| `etcp_router_destroy(inst)` | Деинициализация: unbind + close_all + free |
| `etcp_router_bind(inst, svc_id, cb)` | Зарегистрировать обработчик сервиса |
| `etcp_router_unbind(inst, svc_id)` | Удалить обработчик сервиса |
| `etcp_route_send(inst, dst, entry, force)` | Отправить пакет (loopback/transit/direct) |
| `etcp_router_conn_get(inst, remote, svc_id)` | Найти или создать ROUTER_CONN |
| `etcp_router_conn_send(rconn, data, len)` | Отправить данные с авто-seq и inflight-контролем |
| `etcp_router_conn_send_signed(rconn, data, len)` | Отправить с Ed25519-подписью |
| `etcp_route_send(inst, group, dst, entry, force, mode)` | Отправить пакет (loopback/transit/direct); mode = ROUTE_CRYPTO_SIGN/ENCRYPT |
| `etcp_router_conn_get(inst, group, remote, svc_id)` | Найти или создать ROUTER_CONN |
| `etcp_router_conn_send(rconn, data, len, mode)` | Отправить данные с авто-seq и inflight-контролем |
| `etcp_router_conn_close(rconn)` | Закрыть seq-подключение (CLOSE + уведомление сервиса) |
| `etcp_router_conn_close_async(rconn, cb, arg)` | Асинхронное закрытие с коллбэком |
| `etcp_router_conn_close_all_for_node(inst, node_id)` | Закрыть все conn к узлу |
| `etcp_router_conn_restart(inst, remote, svc_id)` | Сброс состояния (перезапуск удалённой стороны) |
| `etcp_router_conn_close_all_for_node(inst, group, node_id)` | Закрыть все conn к узлу |
| `etcp_router_conn_restart(inst, group, remote, svc_id)` | Сброс состояния (перезапуск удалённой стороны) |
| `etcp_router_pause_retrans_for_node(inst, node_id)` | Сбросить ретрансмиты без закрытия (для conn reinit) |
| `router_set_max_inflight(rconn, new_max)` | Установить рабочий max_inflight |
| `etcp_router_input_q_count(inst, node_id)` | Размер normalizer->input очереди узла |
| `etcp_router_waiter_register/cancel(inst, node_id, h, cb, arg)` | Backpressure на normalizer->input |
| `etcp_router_on_send_ready/cancel_send_ready(inst, node, svc, h, cb, arg)` | Backpressure на send_q |
| `etcp_router_input_q_count(inst, group, node_id)` | Размер normalizer->input очереди узла |
| `etcp_router_waiter_register/cancel(inst, group, node_id, h, cb, arg)` | Backpressure на normalizer->input |
| `etcp_router_on_send_ready/cancel_send_ready(inst, group, node, svc, h, cb, arg)` | Backpressure на send_q |
| `etcp_router_transit_queues_destroy(conn)` | Удалить все транзитные очереди ETCP_CONN |
### Модуль route_crypto (пред/пост-обработка пакета)
| Функция | Описание |
|---|---|
| `route_crypto_encode(inst, peer, base, len, mode, &out, &len)` | Шифрование/подпись в начале отправки → финальный пакет |
| `route_crypto_decode(inst, wire, len, &out, &len, &rx_flags)` | Проверка подписи/расшифровка перед коллбэком; ошибка → дроп |

160
src/routing_layer/route_crypto.c

@ -0,0 +1,160 @@
// route_crypto.c — пред/пост-обработка SVC_ROUTE пакетов (шифрование + подпись)
#include "route_crypto.h"
#include "utun_instance.h"
#include "topo_node.h"
#include "../transport_layer/secure_channel.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include <string.h>
// ECDH-контекст для пира (кэш в inst->e2e_ctx_cache). NULL = нет NODEINFO / ECDH не удался.
static struct secure_channel* route_crypto_e2e_ctx(struct UTUN_INSTANCE* inst, uint64_t peer_node_id) {
for (int i = 0; i < E2E_CTX_CACHE_SIZE; i++) {
if (inst->e2e_ctx_cache[i].valid && inst->e2e_ctx_cache[i].peer_node_id == peer_node_id)
return &inst->e2e_ctx_cache[i].ctx;
}
struct TOPO_NODE* tn = topo_node_registry_find(inst->topo_groups, peer_node_id);
if (!tn) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto: no NODEINFO for peer=%016llx", (unsigned long long)peer_node_id);
return NULL;
}
int slot = -1;
for (int i = 0; i < E2E_CTX_CACHE_SIZE; i++) {
if (!inst->e2e_ctx_cache[i].valid) { slot = i; break; }
}
if (slot < 0) slot = 0;
sc_init_ctx(&inst->e2e_ctx_cache[slot].ctx, &inst->my_keys);
if (sc_set_peer_public_key(&inst->e2e_ctx_cache[slot].ctx, tn->public_key, SC_PEER_PUBKEY_BIN) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto: ECDH failed peer=%016llx", (unsigned long long)peer_node_id);
inst->e2e_ctx_cache[slot].valid = 0;
return NULL;
}
inst->e2e_ctx_cache[slot].peer_node_id = peer_node_id;
inst->e2e_ctx_cache[slot].valid = 1;
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "route_crypto: new context for peer=%016llx slot=%d",
(unsigned long long)peer_node_id, slot);
return &inst->e2e_ctx_cache[slot].ctx;
}
int route_crypto_encode(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
const uint8_t* base, size_t base_len, int mode,
uint8_t** out, size_t* out_len) {
if (!inst || !base || base_len < SVC_ROUTE_HDR_SIZE || !out || !out_len) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_encode: invalid args inst=%p base=%p len=%zu mode=%d",
(void*)inst, (const void*)base, base_len, mode);
return -1;
}
const uint8_t* pl = base + SVC_ROUTE_HDR_SIZE;
size_t pl_len = base_len - SVC_ROUTE_HDR_SIZE;
const uint8_t* body = pl;
size_t body_len = pl_len;
uint8_t* enc_buf = NULL;
if (mode & ROUTE_CRYPTO_ENCRYPT) {
struct secure_channel* e2e = route_crypto_e2e_ctx(inst, peer_node_id);
if (!e2e) return -1;
size_t enc_cap = pl_len + SC_NONCE_SIZE + SC_CRC32_SIZE + SC_TAG_SIZE;
enc_buf = u_malloc(enc_cap);
if (!enc_buf) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_encode: enc_buf alloc fail"); return -1; }
size_t enc_len = 0;
if (sc_encrypt(e2e, pl, pl_len, enc_buf, &enc_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_encode: sc_encrypt FAIL peer=%016llx len=%zu",
(unsigned long long)peer_node_id, pl_len);
u_free(enc_buf); return -1;
}
body = enc_buf; body_len = enc_len;
}
size_t sig_len = (mode & ROUTE_CRYPTO_SIGN) ? SC_SIGN_SIZE : 0;
size_t total = SVC_ROUTE_HDR_SIZE + body_len + sig_len;
uint8_t* dgram = u_malloc(total);
if (!dgram) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_encode: out alloc fail"); if (enc_buf) u_free(enc_buf); return -1; }
memcpy(dgram, base, SVC_ROUTE_HDR_SIZE);
memcpy(dgram + SVC_ROUTE_HDR_SIZE, body, body_len);
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
if (mode & ROUTE_CRYPTO_ENCRYPT) hdr->flags |= ROUTER_FLAG_ENCRYPTED;
if (mode & ROUTE_CRYPTO_SIGN) {
hdr->flags |= ROUTER_FLAG_SIGNED;
if (sc_ed25519_sign(inst->my_ed25519_privkey, dgram, SVC_ROUTE_HDR_SIZE + body_len,
dgram + SVC_ROUTE_HDR_SIZE + body_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_encode: sign FAIL peer=%016llx", (unsigned long long)peer_node_id);
u_free(dgram); if (enc_buf) u_free(enc_buf); return -1;
}
}
if (enc_buf) u_free(enc_buf);
*out = dgram; *out_len = total;
return 0;
}
int route_crypto_decode(struct UTUN_INSTANCE* inst,
const uint8_t* wire, size_t wire_len,
uint8_t** out, size_t* out_len, uint8_t* rx_flags) {
if (!inst || !wire || wire_len < SVC_ROUTE_HDR_SIZE || !out || !out_len || !rx_flags) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_decode: invalid args inst=%p wire=%p len=%zu",
(void*)inst, (const void*)wire, wire_len);
return -1;
}
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)wire;
uint8_t fl = hdr->flags & (ROUTER_FLAG_ENCRYPTED | ROUTER_FLAG_SIGNED);
*rx_flags = fl;
const uint8_t* body = wire + SVC_ROUTE_HDR_SIZE;
size_t body_len = wire_len - SVC_ROUTE_HDR_SIZE;
if (fl & ROUTER_FLAG_SIGNED) {
if (body_len < SC_SIGN_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_decode: SIGNED packet too short len=%zu from %016llx",
wire_len, (unsigned long long)hdr->src_node_id);
return -1;
}
size_t signed_len = SVC_ROUTE_HDR_SIZE + body_len - SC_SIGN_SIZE;
const uint8_t* sig = wire + signed_len;
struct TOPO_NODE* sni = topo_node_registry_find(inst->topo_groups, hdr->src_node_id);
if (!sni) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_decode: SIGNED from unknown node %016llx — dropping",
(unsigned long long)hdr->src_node_id);
return -1;
}
uint8_t zero[SC_PUBKEY_SIZE] = {0};
if (memcmp(sni->ed25519_public_key, zero, SC_PUBKEY_SIZE) == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_decode: no Ed25519 pubkey for node %016llx — dropping",
(unsigned long long)hdr->src_node_id);
return -1;
}
if (sc_ed25519_verify(sni->ed25519_public_key, wire, signed_len, sig) != SC_OK) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_decode: signature FAILED for node %016llx svc_id=%u — dropping",
(unsigned long long)hdr->src_node_id, hdr->svc_id);
return -1;
}
body_len -= SC_SIGN_SIZE;
}
uint8_t* pt_buf = NULL;
if (fl & ROUTER_FLAG_ENCRYPTED) {
struct secure_channel* e2e = route_crypto_e2e_ctx(inst, hdr->src_node_id);
if (!e2e) return -1;
pt_buf = u_malloc(body_len);
size_t pt_len = 0;
if (!pt_buf || sc_decrypt(e2e, body, body_len, pt_buf, &pt_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_decode: decrypt FAIL src=%016llx svc=%u len=%zu",
(unsigned long long)hdr->src_node_id, hdr->svc_id, body_len);
if (pt_buf) u_free(pt_buf);
return -1;
}
body = pt_buf; body_len = pt_len;
}
size_t total = SVC_ROUTE_HDR_SIZE + body_len;
uint8_t* dgram = u_malloc(total);
if (!dgram) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "route_crypto_decode: out alloc fail"); if (pt_buf) u_free(pt_buf); return -1; }
memcpy(dgram, wire, SVC_ROUTE_HDR_SIZE);
memcpy(dgram + SVC_ROUTE_HDR_SIZE, body, body_len);
((struct SVC_ROUTE_HDR*)dgram)->flags &= ~(ROUTER_FLAG_ENCRYPTED | ROUTER_FLAG_SIGNED);
if (pt_buf) u_free(pt_buf);
*out = dgram; *out_len = total;
return 0;
}

46
src/routing_layer/route_crypto.h

@ -0,0 +1,46 @@
// route_crypto.h — пред/пост-обработка SVC_ROUTE пакетов (шифрование + подпись)
// Автономный модуль: из одного пакета формирует другой, пригодный для отправки/обработки.
//
// encode(): вызывается в начале функции отправки.
// [SVC_ROUTE_HDR][plaintext] + mode → [SVC_ROUTE_HDR][payload][sig?]
// decode(): вызывается перед вызовом сервисного коллбэка.
// [SVC_ROUTE_HDR][payload][sig?] → [SVC_ROUTE_HDR][plaintext]
//
// Порядок кодирования: encrypt-then-sign (подпись поверх всего протокола, включая шифртекст).
// При ошибке декодирования (плохая подпись / не удалось расшифровать) пакет дропается.
#ifndef ROUTE_CRYPTO_H
#define ROUTE_CRYPTO_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include "etcp_router.h"
struct UTUN_INSTANCE;
// Флаги трансформации (bitmask), совпадают с битами SVC_ROUTE_HDR.flags
#define ROUTE_CRYPTO_ENCRYPT ROUTER_FLAG_ENCRYPTED // 0x04
#define ROUTE_CRYPTO_SIGN ROUTER_FLAG_SIGNED // 0x08
// Кодировать пакет: base = [SVC_ROUTE_HDR][plaintext payload].
// mode — битовая маска ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT.
// Возвращает новый буфер (u_malloc) с финальным пакетом. 0 = успех, <0 = ошибка.
int route_crypto_encode(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
const uint8_t* base, size_t base_len, int mode,
uint8_t** out, size_t* out_len);
// Декодировать пакет: wire = [SVC_ROUTE_HDR][payload][sig?].
// Проверяет подпись и/или расшифровывает. Через *rx_flags сообщает, что было на wire
// (ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT). Возвращает [SVC_ROUTE_HDR][plaintext] с очищенными флагами.
// 0 = успех, <0 = ошибка (пакет дропается).
int route_crypto_decode(struct UTUN_INSTANCE* inst,
const uint8_t* wire, size_t wire_len,
uint8_t** out, size_t* out_len, uint8_t* rx_flags);
#ifdef __cplusplus
}
#endif
#endif // ROUTE_CRYPTO_H

2
src/routing_layer/routing.c

@ -145,7 +145,7 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t
} else {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sending %zu bytes to node %016llx dst=%s",
ip_len, (unsigned long long)nq->node_id, ip_to_str(&addr, AF_INET).str);
int send_err = etcp_route_send(instance, TOPO_GROUP_UTUN, nq->node_id, entry, 1);
int send_err = etcp_route_send(instance, TOPO_GROUP_UTUN, nq->node_id, entry, 1, 0);
if (send_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: etcp_route_send failed: dst=%s err=%d",
ip_to_str(&addr, AF_INET).str, send_err);

4
tests/test_etcp_router.c

@ -243,7 +243,7 @@ static int send_one_pkt(uint32_t seq, int data_len) {
memcpy(e->dgram, buf, 10 + data_len);
e->len = 10 + data_len;
return etcp_route_send(cli, TOPO_GROUP_UTUN, server_node_id, e, 0);
return etcp_route_send(cli, TOPO_GROUP_UTUN, server_node_id, e, 0, 0);
}
// ======================== Server handler ========================
@ -338,7 +338,7 @@ static int test_loopback(void) {
uint8_t data[6] = { 0xF0, 0xAA, 0xBB, 0xCC, 0x00, 0x00 };
struct ll_entry* e = queue_entry_new(0);
e->dgram = u_malloc(6); memcpy(e->dgram, data, 6); e->len = 6;
etcp_route_send(srv, TOPO_GROUP_UTUN, srv->node_id, e, 0);
etcp_route_send(srv, TOPO_GROUP_UTUN, srv->node_id, e, 0, 0);
etcp_router_unbind(srv, 0xF0);
if (loop_rcvd != 1 || !loop_ok) {
printf("[FAIL] loopback: rcvd=%d ok=%d\n", loop_rcvd, loop_ok);

2
tests/test_etcp_router_reconnect.c

@ -148,7 +148,7 @@ static int send_one_pkt(void) {
gen_payload(g_seq, buf+7, len);
struct ll_entry* e=queue_entry_new(0); if(!e){u_free(buf);return -1;}
e->dgram=buf; e->len=7+len;
int ret = etcp_route_send(g_a, TOPO_GROUP_UTUN, node_c, e, 0);
int ret = etcp_route_send(g_a, TOPO_GROUP_UTUN, node_c, e, 0, 0);
if (ret == 0) g_seq++;
return ret;
}

35
tests/test_etcp_router_unit.c

@ -18,6 +18,7 @@
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../src/transport_layer/secure_channel.h"
#include <openssl/sha.h>
#define TEST_SVC_ID 0x42
#define TEST_SVC_ID2 0x43
@ -131,6 +132,7 @@ static int g_sign_ready = 0;
static struct SC_MYKEYS g_sign_keys;
static sc_context_t g_sign_ctx;
static uint8_t g_sign_ed25519_pubkey[SC_PUBKEY_SIZE];
static uint8_t g_sign_ed25519_privkey[SC_PRIVKEY_SIZE];
static const uint64_t SIGNER_NODE = 0xCCCC000000000001ULL;
static int setup_sign_keys(void) {
@ -138,6 +140,7 @@ static int setup_sign_keys(void) {
if (sc_generate_keypair(&g_sign_keys) != SC_OK) { printf(" setup_sign_keys: generate_keypair failed\n"); return -1; }
sc_init_ctx(&g_sign_ctx, &g_sign_keys);
if (sc_derive_ed25519_pubkey(g_sign_keys.private_key, g_sign_ed25519_pubkey) != SC_OK) { printf(" setup_sign_keys: derive_ed25519_pubkey failed\n"); return -1; }
{ unsigned char h[SHA512_DIGEST_LENGTH]; SHA512(g_sign_keys.private_key, SC_PRIVKEY_SIZE, h); memcpy(g_sign_ed25519_privkey, h, SC_PRIVKEY_SIZE); }
g_sign_ready = 1;
return 0;
}
@ -208,11 +211,7 @@ static void inject_signed(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
hdr->flags = flags | ROUTER_FLAG_SIGNED;
if (pl_len > 0) memcpy(dgram + SVC_ROUTE_HDR_SIZE, pl, pl_len);
struct sc_stream_sign_state sign_state;
if (sc_stream_sign_init(&g_sign_ctx, &sign_state) != SC_OK) { u_free(dgram); return; }
if (sc_stream_sign_update(&sign_state, dgram, hdr_pl) != SC_OK) { sc_stream_sign_cleanup(&sign_state); u_free(dgram); return; }
size_t sig_len = SC_SIGN_SIZE;
if (sc_stream_sign_final(&sign_state, dgram + hdr_pl, &sig_len) != SC_OK) { u_free(dgram); return; }
if (sc_ed25519_sign(g_sign_ed25519_privkey, dgram, hdr_pl, dgram + hdr_pl) != SC_OK) { u_free(dgram); return; }
if (tamper_sig) dgram[hdr_pl] ^= 0x01;
@ -565,19 +564,25 @@ static int test_send_q(void) {
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
// Simulate full inflight: set tx_acked far behind tx_seq
c->tx_seq = ROUTER_MAX_INFLIGHT;
c->tx_acked = 0;
// Заполняем inflight_q до лимита — канал «загружен»
for (int i = 0; i < ROUTER_MAX_INFLIGHT; i++) {
struct ROUTER_INFLIGHT* inf = u_malloc(sizeof(struct ROUTER_INFLIGHT));
if (!inf) FAIL("inflight alloc");
memset(&inf->ll, 0, sizeof(inf->ll));
inf->ll.size = 4; inf->seq = (uint32_t)i; *(uint32_t*)inf->ll.data = (uint32_t)i;
inf->dgram = u_malloc(1); inf->dgram_len = 1;
queue_data_put_with_index(c->inflight_q, &inf->ll);
}
if (c->send_q == NULL) FAIL("send_q is NULL");
// Send via conn_send — should queue, not drop
uint8_t data[4] = { 1, 2, 3, 4 };
int ret = etcp_router_conn_send(c, data, sizeof(data));
int ret = etcp_router_conn_send(c, data, sizeof(data), 0);
if (ret != 0) FAIL("conn_send should return 0 (queued in send_q)");
if (c->send_blocked == 0) FAIL("send_blocked not set");
if (c->watchdog_timer == NULL) FAIL("watchdog_timer not set");
if (queue_entry_count(c->send_q) != 1) FAIL("send_q count != 1");
if (c->tx_seq != ROUTER_MAX_INFLIGHT) FAIL("tx_seq advanced while queued");
if (c->tx_seq != 1) FAIL("tx_seq should advance to 1 at enqueue");
// Cancel timers manually
if (c->watchdog_timer) { uasync_cancel_timeout(ua, c->watchdog_timer); c->watchdog_timer = NULL; }
@ -602,7 +607,7 @@ static int test_loopback(void) {
memcpy(e->dgram, buf, 4);
e->len = 4;
if (etcp_route_send(&inst, TOPO_GROUP_UTUN, inst.node_id, e, 0) != 0) FAIL("loopback send failed");
if (etcp_route_send(&inst, TOPO_GROUP_UTUN, inst.node_id, e, 0, 0) != 0) FAIL("loopback send failed");
if (rx.delivered != 1) FAIL("loopback not delivered");
if (rx.errors != 0) FAIL("loopback data mismatch");
@ -642,7 +647,7 @@ static int test_conn_send_no_bgp(void) {
if (!c) FAIL("conn_get failed");
uint8_t data[4] = { 1, 2, 3, 4 };
int ret = etcp_router_conn_send(c, data, sizeof(data));
int ret = etcp_router_conn_send(c, data, sizeof(data), 0);
if (ret != 0) FAIL("conn_send should buffer (return 0) without BGP");
if (c->no_route == 0) FAIL("no_route not set");
if (queue_entry_count(c->send_q) != 1) FAIL("send_q count != 1");
@ -1144,11 +1149,7 @@ static int test_e2e_combined_signed(void) {
hdr->flags = ROUTER_FLAG_ENCRYPTED | ROUTER_FLAG_SIGNED | ROUTER_FLAG_START;
memcpy(dgram + SVC_ROUTE_HDR_SIZE, enc_buf, enc_len);
struct sc_stream_sign_state sign_state;
if (sc_stream_sign_init(&g_sign_ctx, &sign_state) != SC_OK) { u_free(dgram); u_free(enc_buf); FAIL("sign init"); }
if (sc_stream_sign_update(&sign_state, dgram, hdr_pl) != SC_OK) { sc_stream_sign_cleanup(&sign_state); u_free(dgram); u_free(enc_buf); FAIL("sign update"); }
size_t sig_len = SC_SIGN_SIZE;
if (sc_stream_sign_final(&sign_state, dgram + hdr_pl, &sig_len) != SC_OK) { u_free(dgram); u_free(enc_buf); FAIL("sign final"); }
if (sc_ed25519_sign(g_sign_ed25519_privkey, dgram, hdr_pl, dgram + hdr_pl) != SC_OK) { u_free(dgram); u_free(enc_buf); FAIL("sign"); }
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(dgram); u_free(enc_buf); FAIL("entry"); }

2
tests/test_media_delivery_full.c

@ -104,7 +104,7 @@ static int msend(struct UTUN_INSTANCE* inst, uint64_t dst, const uint8_t* data,
e->dgram[0] = ETCP_RT_ID_MEDIA_DELIVERY; memcpy(e->dgram + 1, data, len); e->len = (uint16_t)(len + 1);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "[msend] 0x%016llx → 0x%016llx svc=0x%02x subcmd=0x%02x len=%zu",
(unsigned long long)inst->node_id, (unsigned long long)dst, e->dgram[0], data[0], len);
int rc = etcp_route_send(inst, TOPO_GROUP_UTUN, dst, e, 1);
int rc = etcp_route_send(inst, TOPO_GROUP_UTUN, dst, e, 1, 0);
if (rc != 0) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "[msend] FAILED rc=%d", rc); u_free(e->dgram); queue_entry_free(e); }
return rc;
}

2
tests/test_media_delivery_integration.c

@ -117,7 +117,7 @@ static int msend(struct UTUN_INSTANCE* inst, uint64_t dst, const uint8_t* data,
e->dgram = u_malloc(len + 1); if (!e->dgram) { queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_RT_ID_MEDIA_DELIVERY;
memcpy(e->dgram + 1, data, len); e->len = (uint16_t)(len + 1);
int rc = etcp_route_send(inst, TOPO_GROUP_UTUN, dst, e, 1);
int rc = etcp_route_send(inst, TOPO_GROUP_UTUN, dst, e, 1, 0);
if (rc != 0) { u_free(e->dgram); queue_entry_free(e); }
return rc;
}

4
tests/test_nat_transport.c

@ -306,7 +306,7 @@ static int test_provider_egress(void) {
entry->dgram = dgram;
entry->len = total;
int ret = etcp_route_send(inst_client, TOPO_GROUP_UTUN, g_provider_node_id, entry, 0);
int ret = etcp_route_send(inst_client, TOPO_GROUP_UTUN, g_provider_node_id, entry, 0, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "etcp_route_send failed");
queue_dgram_free(entry);
@ -474,7 +474,7 @@ static int test_full_roundtrip(void) {
struct ll_entry* entry = queue_entry_new(0);
entry->dgram = dgram;
entry->len = total;
int ret = etcp_route_send(inst_client, TOPO_GROUP_UTUN, g_provider_node_id, entry, 0);
int ret = etcp_route_send(inst_client, TOPO_GROUP_UTUN, g_provider_node_id, entry, 0, 0);
if (ret != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "etcp_route_send failed"); queue_dgram_free(entry); queue_entry_free(entry); return 0; }
// Poll for provider to process egress

2
tools/chatgui/transport/utun_node.cpp

@ -112,7 +112,7 @@ void UtunNode::send(uint64_t dstNodeId, const QByteArray& data) {
struct ll_entry* entry = queue_entry_new(data.size());
if (!entry) return;
memcpy(entry->data, data.constData(), data.size());
etcp_route_send(m_instance, TOPO_GROUP_UTUN, dstNodeId, entry, 0);
etcp_route_send(m_instance, TOPO_GROUP_UTUN, dstNodeId, entry, 0, 0);
}
QString UtunNode::nodeIdHex() const {

Loading…
Cancel
Save