Reconcile group policy with members and prefer group signatures
This commit is contained in:
1 parent
94ae8231aa
commit
b5d008ee87
2 files changed
+75
-16
No files matched your search
@@ -9,6 +9,8 @@
|
||||
#include "../routing_layer/topo_node_sqlite.h"
|
||||
#include "../routing_layer/topo_group.h"
|
||||
#include "../routing_layer/topo_group_connect.h"
|
||||
#include "../transport_layer/etcp.h"
|
||||
#include "../transport_layer/secure_channel.h"
|
||||
#include "../../lib/mem.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
#include "../../lib/json_flat.h"
|
||||
@@ -105,7 +107,7 @@ int group_policy_apply(sqlite3* db, const char* channel, const struct group_poli
|
||||
|
||||
struct gp_member {
|
||||
uint64_t node, signer;
|
||||
uint8_t x[32], valid, admin, previous, allowed, keep;
|
||||
uint8_t x[32], ed[32], valid, admin, previous, allowed, keep;
|
||||
size_t child, sibling; /* Индексы + 1, 0 — конец списка детей. */
|
||||
char name[128];
|
||||
};
|
||||
@@ -146,6 +148,7 @@ static int gp_load(struct UTUN_INSTANCE* inst, const char* ch, struct gp_member*
|
||||
.signed_by = p->signer, .signature = sqlite3_column_blob(st, 11)};
|
||||
if (!m.x25519 || !m.ed25519 || !m.join_sig || !m.signature) { rc = SQLITE_NOMEM; goto done; }
|
||||
memcpy(p->x, m.x25519, 32);
|
||||
memcpy(p->ed, m.ed25519, 32);
|
||||
int verified = member_sync_verify_local_record(db, ch, &m);
|
||||
if (verified < 0) goto done;
|
||||
int tree = member_sync_verify_signature(db, ch, &m);
|
||||
@@ -292,7 +295,7 @@ int group_policy_command(struct UTUN_INSTANCE* inst, const char* channel, int al
|
||||
for (size_t i = 0; i < count; i++) if (m[i].keep && m[i].signer) {
|
||||
uint8_t signature[64];
|
||||
if (member_sync_sign_pubkey(key, m[i].x, signature) ||
|
||||
member_sync_put(inst, channel, m[i].node, m[i].x, NULL, NULL, 0, NULL, 0, NULL, NULL, NULL, 0, 0, signature) < 0) goto done;
|
||||
member_sync_put(inst, channel, m[i].node, m[i].x, m[i].ed, NULL, 0, NULL, 0, NULL, NULL, NULL, 0, 0, signature) < 0) goto done;
|
||||
}
|
||||
if (policy.version == INT64_MAX) { error = "version_exhausted"; goto done; }
|
||||
int64_t now = ntp_time_get_us(inst); uint64_t version = now > 0 ? (uint64_t)now : 1;
|
||||
|
||||
+70
-14
@@ -2,6 +2,7 @@
|
||||
#include "member_sync.h"
|
||||
#include "db_sync.h"
|
||||
#include "chat_feedback.h"
|
||||
#include "group_policy.h"
|
||||
|
||||
#include "../routing_layer/topo_node_sqlite.h"
|
||||
#include "../routing_layer/topo_group.h"
|
||||
@@ -194,6 +195,17 @@ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level,
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: bucket_hash prepare failed ns=%s", MS_ID, ns); return -1; }
|
||||
|
||||
int count = 0;
|
||||
if (!prefix64) {
|
||||
struct group_policy policy; uint8_t wire[GROUP_POLICY_WIRE_SIZE];
|
||||
if (group_policy_get(db, ns, &policy)) { sqlite3_finalize(stmt); return -1; }
|
||||
if (policy.version) {
|
||||
group_policy_encode(&policy, wire);
|
||||
if (EVP_DigestUpdate(sha_ctx, wire, sizeof(wire)) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "group policy hash failed ns=%s", ns); sqlite3_finalize(stmt); return -1;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
int step;
|
||||
while ((step = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
uint64_t nid = (uint64_t)sqlite3_column_int64(stmt, 0);
|
||||
@@ -261,9 +273,12 @@ static int _member_get_page(void* ctx, const char* ns, uint64_t prefix, int afte
|
||||
sqlite3_bind_int(stmt, 1, after_valid);
|
||||
sqlite3_bind_int64(stmt, 2, (sqlite3_int64)after_id);
|
||||
|
||||
size_t off = 0;
|
||||
if (off + 2 > *len) { sqlite3_finalize(stmt); return -2; }
|
||||
uint16_t cnt = 0; off += 2;
|
||||
size_t off = 2 + GROUP_POLICY_WIRE_SIZE;
|
||||
if (off > *len) { sqlite3_finalize(stmt); return -2; }
|
||||
struct group_policy policy;
|
||||
if (group_policy_get(db, ns, &policy)) { sqlite3_finalize(stmt); return -1; }
|
||||
group_policy_encode(&policy, buf + 2);
|
||||
uint16_t cnt = 0;
|
||||
|
||||
int step;
|
||||
while ((step = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
@@ -732,7 +747,10 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
}
|
||||
|
||||
/* Подпись приглашения не зависит от версии профиля: подтверждаем уже сохранённого мембера. */
|
||||
int signature_changed = has_local && has_signature && _sig_is_zero64(local_signature);
|
||||
int same_join = has_local && (!m->join_sig ||
|
||||
(m->join_ts == local_join_ts && !memcmp(m->join_sig, local_join_sig, 64)));
|
||||
int signature_changed = same_join && has_signature &&
|
||||
(_sig_is_zero64(local_signature) || m->signed_by < local_signed_by);
|
||||
if (signature_changed) {
|
||||
int verified = member_sync_verify_signature(db, ch_id, m);
|
||||
if (verified == MS_VERIFY_ERROR) return MT_ERR_IO;
|
||||
@@ -743,8 +761,9 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
if (block_a_changed) {
|
||||
const uint8_t* jsig = m->join_sig ? m->join_sig : (has_local ? local_join_sig : NULL);
|
||||
/* Нулевой блок на проводе означает отсутствие подписи и не стирает сохранённое подтверждение. */
|
||||
uint64_t w_signed_by = has_signature ? m->signed_by : local_signed_by;
|
||||
const uint8_t* w_signature = has_signature ? m->signature : (has_local ? local_signature : NULL);
|
||||
int keep_signature = same_join && !_sig_is_zero64(local_signature) && (!has_signature || local_signed_by <= m->signed_by);
|
||||
uint64_t w_signed_by = keep_signature ? local_signed_by : m->signed_by;
|
||||
const uint8_t* w_signature = keep_signature ? local_signature : m->signature;
|
||||
if (topo_node_sqlite_member_block_put(db, ch_id, m->node_id, jsig, m->join_ts,
|
||||
m->update_sig, m->update_ts, m->x25519, m->ed25519,
|
||||
m->userinfo ? m->userinfo : "", NULL, w_signed_by, w_signature) != 0) {
|
||||
@@ -860,6 +879,9 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
if (sql_rc != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: record commit ch=%s rc=%d: %s", MS_ID, ch_id, sql_rc, sqlite3_errmsg(db)); goto error;
|
||||
}
|
||||
if (own && rc >= 0 && (rc & (MS_APPLY_CHANGED | MS_APPLY_FEEDBACK)))
|
||||
if (from_peer == inst->node_id && group_policy_refresh(inst, ch_id))
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "local member committed; admission refresh failed ch=%s", ch_id);
|
||||
if (own && rc >= 0 && (rc & (MS_APPLY_CHANGED | MS_APPLY_FEEDBACK)))
|
||||
member_sync_record_committed(inst, ch_id, m->node_id, (unsigned)rc);
|
||||
return rc;
|
||||
@@ -1150,14 +1172,16 @@ static int member_decode(const uint8_t* p, size_t len, struct member_wire* w, si
|
||||
static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, const uint8_t* data, size_t len) {
|
||||
struct UTUN_INSTANCE* inst = ctx;
|
||||
sqlite3* db = _db(inst);
|
||||
if (!db || len < 2) {
|
||||
if (!db || len < 2 + GROUP_POLICY_WIRE_SIZE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: invalid member page ns=%s bytes=%zu", MS_ID, ns, len); return MT_ERR_DATA;
|
||||
}
|
||||
uint16_t count = ((uint16_t)data[0] << 8) | data[1];
|
||||
if (count > (len - 2) / 285) {
|
||||
if (count > (len - 2 - GROUP_POLICY_WIRE_SIZE) / 285) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: impossible page count=%u bytes=%zu", MS_ID, count, len); return MT_ERR_DATA;
|
||||
}
|
||||
size_t off = 2;
|
||||
struct group_policy policy;
|
||||
if (group_policy_decode(db, ns, data + 2, &policy)) return MT_ERR_DATA;
|
||||
size_t off = 2 + GROUP_POLICY_WIRE_SIZE;
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
struct member_wire w; size_t used;
|
||||
if (member_decode(data + off, len - off, &w, &used) < 0) {
|
||||
@@ -1167,15 +1191,16 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, co
|
||||
off += used;
|
||||
}
|
||||
if (off != len) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: trailing page bytes ns=%s bytes=%zu", MS_ID, ns, len - off); return MT_ERR_DATA; }
|
||||
if (!count) return 0;
|
||||
uint8_t* changed = u_calloc(count, 1);
|
||||
uint8_t* changed = u_calloc(count ? count : 1, 1);
|
||||
if (!changed) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: page allocation failed", MS_ID); return MT_ERR_IO; }
|
||||
if (sqlite3_exec(db, "SAVEPOINT member_page", NULL, NULL, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: page begin: %s", MS_ID, sqlite3_errmsg(db)); u_free(changed); return MT_ERR_IO;
|
||||
}
|
||||
int rc = 0;
|
||||
off = 2;
|
||||
int rc = group_policy_apply(db, ns, &policy);
|
||||
if (rc > 0) rc = merkle_sync_recompute_path(inst, MT_DATA_MEMBERS, ns, 0) < 0 ? MT_ERR_IO : 0;
|
||||
off = 2 + GROUP_POLICY_WIRE_SIZE;
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
if (rc < 0) break;
|
||||
struct member_wire w; size_t used;
|
||||
if (member_decode(data + off, len - off, &w, &used) < 0) { rc = MT_ERR_DATA; break; }
|
||||
if (!w.rec.join_sig && topo_node_sqlite_member_get_join(db, ns, w.rec.node_id, w.local_join, &w.rec.join_ts) == 0)
|
||||
@@ -1193,7 +1218,8 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, co
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: page cleanup: %s", MS_ID, sqlite3_errmsg(db));
|
||||
}
|
||||
if (rc == 0) {
|
||||
off = 2;
|
||||
merkle_sync_changed(inst, MT_DATA_MEMBERS, ns);
|
||||
off = 2 + GROUP_POLICY_WIRE_SIZE;
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
struct member_wire w; size_t used;
|
||||
if (member_decode(data + off, len - off, &w, &used) < 0) { rc = MT_ERR_DATA; break; }
|
||||
@@ -1249,12 +1275,38 @@ static int member_finish(void* ctx, const char* ns) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Допуск меняется только после общей контрольной точки политики и всех записей MEMBERS.
|
||||
* Список для JOIN callbacks копируется: получатель вправе изменить группу/таблицу. */
|
||||
static void member_policy_result(void* ctx, const char* ns, uint64_t peer, int result) {
|
||||
(void)peer;
|
||||
struct UTUN_INSTANCE* inst = ctx;
|
||||
if (result != MT_OK || group_policy_refresh(inst, ns)) return;
|
||||
struct ready_member { uint64_t node, signer; uint8_t x[32], signature[64]; };
|
||||
struct ready_member* rows = NULL; size_t count = 0;
|
||||
char sql[192]; sqlite3_stmt* st = NULL;
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id,signed_by,x25519_pubkey,signature FROM peers_%s WHERE admitted=1 AND deleted=0", ns);
|
||||
int rc = sqlite3_prepare_v2(_db(inst), sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
if (sqlite3_column_bytes(st, 2) != 32 || sqlite3_column_bytes(st, 3) != 64) { rc = SQLITE_CORRUPT; break; }
|
||||
if (count >= UINT32_MAX / sizeof(*rows) - 1) { rc = SQLITE_TOOBIG; break; }
|
||||
struct ready_member* p = u_realloc(rows, (count + 1) * sizeof(*rows));
|
||||
if (!p) { rc = SQLITE_NOMEM; break; } rows = p;
|
||||
rows[count].node = (uint64_t)sqlite3_column_int64(st, 0); rows[count].signer = (uint64_t)sqlite3_column_int64(st, 1);
|
||||
memcpy(rows[count].x, sqlite3_column_blob(st, 2), 32); memcpy(rows[count].signature, sqlite3_column_blob(st, 3), 64); count++;
|
||||
}
|
||||
int final = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_DONE || final != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "admission callbacks read ns=%s rc=%d", ns, rc);
|
||||
else for (size_t i = 0; i < count; i++) _fire_apply_cbk(inst, ns, rows[i].node, rows[i].x, rows[i].signer, rows[i].signature);
|
||||
u_free(rows);
|
||||
}
|
||||
|
||||
static const struct merkle_sync_data_ops g_member_ops = {
|
||||
.update_bucket_hash = _member_update_bucket_hash,
|
||||
.get_page = _member_get_page,
|
||||
.apply_items = _member_apply_items,
|
||||
.validate_peer = _member_validate_peer,
|
||||
.finish = member_finish,
|
||||
.on_result = member_policy_result,
|
||||
};
|
||||
|
||||
/* ── Public API ── */
|
||||
@@ -1269,6 +1321,9 @@ static int _rebuild_all_trees(struct UTUN_INSTANCE* inst) {
|
||||
const char* table = (const char*)sqlite3_column_text(tables, 0);
|
||||
if (!table || strncmp(table, "peers_", 6)) continue;
|
||||
const char* ns = table + 6;
|
||||
if (topo_node_sqlite_member_local_init(db, ns) || merkle_sync_recompute_path(inst, MT_DATA_MEMBERS, ns, 0) < 0) {
|
||||
rc = SQLITE_ERROR; break;
|
||||
}
|
||||
uint64_t mask = merkle_sync_level_prefix(UINT64_MAX, MT_MAX_LEVEL);
|
||||
char sql[256];
|
||||
snprintf(sql, sizeof(sql), "SELECT DISTINCT (node_id & %lld) FROM \"%s\" WHERE source=0", (long long)mask, table);
|
||||
@@ -1316,6 +1371,7 @@ int member_sync_init(struct UTUN_INSTANCE* inst) {
|
||||
if (!db || !sqlite3_get_autocommit(db)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: init requires database outside transaction", MS_ID); return -1;
|
||||
}
|
||||
if (group_policy_schema(db)) return -1;
|
||||
int rc = merkle_sync_init(inst, ETCP_ID_MEMBER_SYNC, &g_member_ops, inst);
|
||||
if (rc != 0) return rc;
|
||||
/* очистка битых записей во всех каналах перед построением дерева */
|
||||
|
||||
Reference in new issue
Block a user