Enforce authenticated service admission in the router
This commit is contained in:
1 parent
66f817e0b5
commit
5f2eb2ef70
5 files changed
+90
-7
No files matched your search
@@ -70,6 +70,15 @@ static uint32_t router_effective_max_inflight(struct ETCP_ROUTER_CONN* rconn);
|
||||
static void router_update_inflight_limit(struct ETCP_ROUTER_CONN* rconn);
|
||||
static void router_update_minrtt(struct ETCP_ROUTER_CONN* rconn);
|
||||
|
||||
/* Политика не изменяет router; отказ не закрывает канал из чужого стека отправки. */
|
||||
static int router_allowed(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t peer, uint8_t svc) {
|
||||
int (*authorize)(struct UTUN_INSTANCE*, uint64_t, uint64_t) = inst->router_bindings.authorize[svc];
|
||||
if (!authorize || authorize(inst, group, peer) == 1) return 1;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router access denied: group=%016llx peer=%016llx svc=%u",
|
||||
(unsigned long long)group, (unsigned long long)peer, svc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Трейлер меняется независимо от подписанного тела. Не realloc: входной буфер
|
||||
* может принадлежать пулу normalizer. При ошибке entry остаётся у вызывающего. */
|
||||
static int router_path_append(struct ll_entry* e, uint64_t node_id, uint8_t count) {
|
||||
@@ -86,6 +95,14 @@ static int router_path_append(struct ll_entry* e, uint64_t node_id, uint8_t coun
|
||||
}
|
||||
|
||||
static int router_source_send(struct ETCP_CONN* conn, struct ll_entry* e) {
|
||||
struct UTUN_INSTANCE* inst = conn->instance;
|
||||
const struct SVC_ROUTE_HDR* header = (const struct SVC_ROUTE_HDR*)e->dgram;
|
||||
if (!router_allowed(inst, header->group_id, header->dst_node_id, header->svc_id)) return -1;
|
||||
if (e->len == SVC_ROUTE_HDR_SIZE && (inst->router_bindings.required_crypto[header->svc_id] & ROUTER_FLAG_SIGNED)) {
|
||||
uint8_t* signed_wire = NULL; size_t size = 0;
|
||||
if (route_crypto_encode(inst, header->dst_node_id, e->dgram, e->len, ROUTE_CRYPTO_SIGN, &signed_wire, &size)) return -1;
|
||||
queue_dgram_free(e); e->dgram = signed_wire; e->len = size;
|
||||
}
|
||||
if (router_path_append(e, conn->instance->node_id, 0) < 0) return -1;
|
||||
const struct SVC_ROUTE_HDR* h = (const struct SVC_ROUTE_HDR*)e->dgram;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "route TX conn=%s svc=%u flags=%02x seq=%u len=%u src=%016llx dst=%016llx group=%016llx epoch=%016llx",
|
||||
@@ -641,6 +658,9 @@ static void router_send_drain_cb(struct ll_queue* q, void* arg) {
|
||||
struct ETCP_ROUTER_CONN* rconn = arg;
|
||||
rconn->send_waiter_q = NULL;
|
||||
if (rconn->closed || rconn->no_route) return;
|
||||
if (!router_allowed(rconn->inst, rconn->group_id, rconn->remote_node_id, rconn->svc_id)) {
|
||||
router_close_and_notify(rconn); return;
|
||||
}
|
||||
struct ETCP_CONN* conn = router_send_conn(rconn);
|
||||
if (!conn || conn->send_input_q != q) { router_send_kick(rconn); return; }
|
||||
int retry_pending = rconn->retrans_seq != rconn->retrans_end;
|
||||
@@ -892,6 +912,7 @@ static void router_retrans_timer_cb(void* arg) {
|
||||
|
||||
// Скопировать plaintext и mode в send_q, выделить seq; кодирование выполняется при отправке после handshake.
|
||||
static int router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t len, int force, int mode) {
|
||||
if (!router_allowed(rconn->inst, rconn->group_id, rconn->remote_node_id, rconn->svc_id)) return -1;
|
||||
uint8_t required = rconn->inst->router_bindings.required_crypto[rconn->svc_id];
|
||||
if ((mode & required) != required) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router send: insufficient crypto svc=%u peer=%016llx flags=%02x required=%02x",
|
||||
@@ -1128,6 +1149,7 @@ static void router_idle_ack_timer_cb(void* arg) {
|
||||
// Доставка сервисной кодограммы в едином формате [svc_id][src][dst][rx_flags][group_id][payload].
|
||||
// Для loopback (dst == self) src и dst оба = self, rx_flags = 0.
|
||||
static int router_deliver_loopback(struct UTUN_INSTANCE* inst, uint64_t group_id, uint8_t svc_id, struct ll_entry* entry, int mode) {
|
||||
if (!router_allowed(inst, group_id, inst->node_id, svc_id)) { free_entry(entry); return -1; }
|
||||
size_t payload_len = entry->len - 1;
|
||||
if (!payload_len || payload_len > UINT16_MAX - ROUTER_SVC_HDR_SIZE || (mode & ~(ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT))) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router loopback: invalid payload svc=%u bytes=%zu mode=%x", svc_id, payload_len, mode);
|
||||
@@ -1195,6 +1217,9 @@ static int router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn
|
||||
|
||||
// Собрать доставку из recv_q: последовательно доставлять пакеты по возрастанию rx_seq, пока есть следующий.
|
||||
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn) {
|
||||
if (!router_allowed(rconn->inst, rconn->group_id, rconn->remote_node_id, rconn->svc_id)) {
|
||||
router_close_and_notify(rconn); return;
|
||||
}
|
||||
struct ETCP_CONN local = {.instance = rconn->inst, .peer_node_id = rconn->inst->node_id};
|
||||
if (!conn) conn = &local;
|
||||
uint32_t next = rconn->rx_seq;
|
||||
@@ -1477,7 +1502,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
}
|
||||
entry->len = body;
|
||||
uint8_t required = inst->router_bindings.required_crypto[h->svc_id];
|
||||
if (body > SVC_ROUTE_HDR_SIZE && (h->flags & required) != required) {
|
||||
if ((h->flags & (required & ROUTER_FLAG_SIGNED)) != (required & ROUTER_FLAG_SIGNED)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router receive: insufficient crypto svc=%u peer=%016llx flags=%02x required=%02x",
|
||||
h->svc_id, (unsigned long long)h->src_node_id, h->flags, required);
|
||||
free_entry(entry); return;
|
||||
@@ -1494,6 +1519,11 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
}
|
||||
uint8_t control = h->flags & ~(ROUTER_FLAG_ENCRYPTED | ROUTER_FLAG_SIGNED);
|
||||
size_t payload_len = entry->len - SVC_ROUTE_HDR_SIZE;
|
||||
if (payload_len && (h->flags & required) != required) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router data crypto rejected: svc=%u flags=%02x required=%02x", h->svc_id, h->flags, required);
|
||||
free_entry(entry); return;
|
||||
}
|
||||
if (!router_allowed(inst, h->group_id, h->src_node_id, h->svc_id)) { free_entry(entry); return; }
|
||||
if (!h->reset_id || (payload_len && (control || h->challenge))
|
||||
|| (!payload_len && control != 0 && control != ROUTER_FLAG_START && control != ROUTER_FLAG_RST
|
||||
&& control != (ROUTER_FLAG_START | ROUTER_FLAG_RST) && control != ROUTER_FLAG_CLOSE && control != ROUTER_FLAG_RECEIVE_BUSY)) {
|
||||
@@ -1586,6 +1616,7 @@ int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn ca
|
||||
if (!callback) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "unbind svc_id=%u", svc_id);
|
||||
inst->router_bindings.callbacks[svc_id] = NULL;
|
||||
inst->router_bindings.authorize[svc_id] = NULL;
|
||||
inst->router_bindings.receive_ready[svc_id] = NULL;
|
||||
inst->router_bindings.required_crypto[svc_id] = 0;
|
||||
return 0;
|
||||
@@ -1605,6 +1636,7 @@ int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id) {
|
||||
return -1;
|
||||
}
|
||||
inst->router_bindings.callbacks[svc_id] = NULL;
|
||||
inst->router_bindings.authorize[svc_id] = NULL;
|
||||
inst->router_bindings.receive_ready[svc_id] = NULL;
|
||||
inst->router_bindings.required_crypto[svc_id] = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "svc_id=%u", svc_id);
|
||||
@@ -1612,7 +1644,8 @@ int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id) {
|
||||
}
|
||||
|
||||
int etcp_router_require_crypto(struct UTUN_INSTANCE* inst, uint8_t svc_id, uint8_t flags) {
|
||||
if (!inst || !inst->router_bindings.callbacks[svc_id] || (flags & ~(ROUTER_FLAG_ENCRYPTED | ROUTER_FLAG_SIGNED))) {
|
||||
if (!inst || !inst->router_bindings.callbacks[svc_id] || (flags & ~(ROUTER_FLAG_ENCRYPTED | ROUTER_FLAG_SIGNED)) ||
|
||||
(inst->router_bindings.authorize[svc_id] && !(flags & ROUTER_FLAG_SIGNED))) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router crypto policy invalid inst=%p svc=%u flags=%02x", (void*)inst, svc_id, flags);
|
||||
return -1;
|
||||
}
|
||||
@@ -1621,6 +1654,30 @@ int etcp_router_require_crypto(struct UTUN_INSTANCE* inst, uint8_t svc_id, uint8
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_router_set_authorize(struct UTUN_INSTANCE* inst, uint8_t svc_id,
|
||||
int (*authorize)(struct UTUN_INSTANCE*, uint64_t, uint64_t)) {
|
||||
if (!inst || !inst->router_bindings.callbacks[svc_id] ||
|
||||
(authorize && !(inst->router_bindings.required_crypto[svc_id] & ROUTER_FLAG_SIGNED))) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router access policy requires bound signed service svc=%u", svc_id); return -1;
|
||||
}
|
||||
inst->router_bindings.authorize[svc_id] = authorize;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router access policy svc=%u enabled=%d", svc_id, authorize != NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void etcp_router_revalidate_group(struct UTUN_INSTANCE* inst, uint64_t group_id) {
|
||||
if (!inst) return;
|
||||
for (;;) {
|
||||
struct ETCP_ROUTER_CONN* denied = NULL;
|
||||
for (struct ll_entry* e = inst->router_conns ? inst->router_conns->head : NULL; e; e = e->next) {
|
||||
struct ETCP_ROUTER_CONN* c = (struct ETCP_ROUTER_CONN*)e;
|
||||
if (c->group_id == group_id && !router_allowed(inst, group_id, c->remote_node_id, c->svc_id)) { denied = c; break; }
|
||||
}
|
||||
if (!denied) return;
|
||||
router_close_and_notify(denied);
|
||||
}
|
||||
}
|
||||
|
||||
void etcp_router_set_receive_ready(struct UTUN_INSTANCE* inst, uint8_t svc_id,
|
||||
int (*ready)(struct UTUN_INSTANCE*, uint64_t, uint64_t, const uint8_t*, size_t)) {
|
||||
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router receive readiness: NULL instance"); return; }
|
||||
@@ -1778,6 +1835,7 @@ 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) {
|
||||
if (!inst || !inst->router_conns) return NULL;
|
||||
if (!router_allowed(inst, group_id, remote_node_id, svc_id)) return NULL;
|
||||
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, group_id, remote_node_id, svc_id);
|
||||
if (rconn) return rconn;
|
||||
|
||||
|
||||
@@ -258,6 +258,7 @@ struct ROUTER_INFLIGHT {
|
||||
// Bindings для сервисов внутри etcp_router (аналогично ETCP_BINDINGS)
|
||||
struct ETCP_ROUTER_BINDINGS {
|
||||
etcp_recv_fn callbacks[SVC_ROUTE_MAX_BINDINGS];
|
||||
int (*authorize[SVC_ROUTE_MAX_BINDINGS])(struct UTUN_INSTANCE*, uint64_t, uint64_t); // Допуск group/peer; 1 — разрешено.
|
||||
int (*receive_ready[SVC_ROUTE_MAX_BINDINGS])(struct UTUN_INSTANCE*, uint64_t, uint64_t, const uint8_t*, size_t);
|
||||
uint8_t required_crypto[SVC_ROUTE_MAX_BINDINGS]; // Обязательные ENCRYPTED/SIGNED для сервисных данных; 0 — без требования.
|
||||
};
|
||||
@@ -284,11 +285,23 @@ int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn c
|
||||
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id);
|
||||
|
||||
// Задать обязательные ROUTER_FLAG_ENCRYPTED/SIGNED для зарегистрированного сервиса, в обе стороны сетевого обмена.
|
||||
// Входящие данные проверяются до состояния/очередей/receive_ready, на конечном узле; header-only control исключён.
|
||||
// Входящие данные проверяются до состояния/очередей/receive_ready; SIGNED обязателен также для header-only control.
|
||||
// Control подписывается без шифрования пустого payload; для данных применяются оба заданных требования.
|
||||
// Доверенная синхронная доставка dst=self обходит crypto policy и даёт rx_flags=0.
|
||||
// Работает в uasync; unbind очищает требование. Возвращает 0 при успехе, -1 при неверном instance/сервисе/маске.
|
||||
int etcp_router_require_crypto(struct UTUN_INSTANCE* inst, uint8_t svc_id, uint8_t flags);
|
||||
|
||||
/* Допуск конечных участников сервиса; NULL снимает. Требует зарегистрированный сервис с обязательным SIGNED.
|
||||
* authorize(inst,group,peer) возвращает строго 1 при разрешении; остальные значения запрещают доступ.
|
||||
* Вызывается в uasync: разрешены чтения БД, запрещены callbacks, мутации и закрытие router.
|
||||
* Проверяется при открытии, сетевом приёме, доставке очереди и отправке/ретрансмите, также при dst=self.
|
||||
* Транзит не проверяется. Unbind очищает callback. 0/-1 с диагностикой. */
|
||||
int etcp_router_set_authorize(struct UTUN_INSTANCE* inst, uint8_t svc_id,
|
||||
int (*authorize)(struct UTUN_INSTANCE*, uint64_t, uint64_t));
|
||||
/* После commit изменения допуска: закрыть запрещённые каналы группы, уведомить сервисы, очистить очереди.
|
||||
* Не затрагивает общий ETCP и сервисы без authorize; callbacks могут менять список каналов. */
|
||||
void etcp_router_revalidate_group(struct UTUN_INSTANCE* inst, uint64_t group_id);
|
||||
|
||||
// Проверяется до доставки/ACK. 0 оставляет пакет в recv_q, сохраняя порядок; CLOSE не блокируется.
|
||||
// ready получает заимствованный plaintext, работает в uasync без I/O и изменения состояния/закрытия роутера.
|
||||
// NULL снимает ограничение; для уже приостановленных каналов вызвать resume_receive отдельно. Локальная доставка не проверяется.
|
||||
|
||||
@@ -407,13 +407,20 @@ static int topo_group_has_sender(struct TOPO_GROUP* group, struct ETCP_CONN* con
|
||||
/* Транспорт не даёт членства: CHAT сверяет локальную таблицу участников. */
|
||||
static int topo_group_peer_allowed(struct TOPO_GROUP* group, uint64_t node_id) {
|
||||
if (group->group_type != TOPO_GROUP_TYPE_CHAT) return 1;
|
||||
sqlite3* db = group->instance->topo_sqlite_db;
|
||||
return db && group->channel_id[0] && topo_node_sqlite_member_allowed(db, group->channel_id, group->instance->node_id) &&
|
||||
topo_node_sqlite_member_allowed(db, group->channel_id, node_id);
|
||||
return topo_group_chat_allowed(group->instance, group->group_id, node_id);
|
||||
}
|
||||
|
||||
int topo_group_chat_allowed(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t peer) {
|
||||
struct TOPO_GROUP* group = inst && inst->topo_groups ? topo_groups_find(inst->topo_groups, group_id) : NULL;
|
||||
if (!group || group->stopping || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0] || !inst->topo_sqlite_db)
|
||||
return 0;
|
||||
return topo_node_sqlite_member_allowed(inst->topo_sqlite_db, group->channel_id, inst->node_id) &&
|
||||
(peer == inst->node_id || topo_node_sqlite_member_allowed(inst->topo_sqlite_db, group->channel_id, peer));
|
||||
}
|
||||
|
||||
void topo_group_revalidate_members(struct TOPO_GROUP* group) {
|
||||
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || group->stopping) return;
|
||||
etcp_router_revalidate_group(group->instance, group->group_id);
|
||||
/* Удаление вызывает callbacks и меняет список; каждый раз ищем следующего заново. */
|
||||
for (;;) {
|
||||
struct TOPO_GROUP_CONN_ITEM* invalid = NULL;
|
||||
|
||||
@@ -389,6 +389,9 @@ enum topo_group_remove_reason {
|
||||
void topo_group_remove_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn, enum topo_group_remove_reason reason);
|
||||
/* После пересчёта admitted: закрыть недопущенные сессии/запросы только этой группы. Общий транспорт сохраняется. */
|
||||
void topo_group_revalidate_members(struct TOPO_GROUP* group);
|
||||
/* Допуск сервиса CHAT: существующая работающая группа, self и peer имеют активное admitted-членство.
|
||||
* Готовность прямой сессии не требуется. Только чтение БД в uasync; 1 — разрешено, 0 — отказ/ошибка. */
|
||||
int topo_group_chat_allowed(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t peer);
|
||||
|
||||
/**
|
||||
* @brief Обрабатывает пакет NODEINFO.
|
||||
|
||||
@@ -976,7 +976,9 @@ static int member_access(sqlite3* db, const char* channel_id, uint64_t node_id,
|
||||
if (rc != SQLITE_ROW && rc != SQLITE_DONE)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member admission read failed channel=%s node=%016llx rc=%d: %s",
|
||||
channel_id, (unsigned long long)node_id, rc, sqlite3_errmsg(db));
|
||||
sqlite3_finalize(statement); return allowed;
|
||||
int final = sqlite3_finalize(statement);
|
||||
if (final != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member admission finalize failed channel=%s rc=%d", channel_id, final);
|
||||
return allowed && final == SQLITE_OK;
|
||||
}
|
||||
|
||||
int topo_node_sqlite_member_allowed(sqlite3* db, const char* channel_id, uint64_t node_id) {
|
||||
|
||||
Reference in new issue
Block a user