Check remote and local member-file ACL refusals separately

This commit is contained in:
evgeny committed 2026-10-25 18:10:58 +01:00
1 parent b3d3df5c57
commit 59b3b9bd34
1 file changed
+58 -6
+58 -6
View File
@@ -53,6 +53,8 @@ struct network_test {
unsigned preservation_case;
char preserved_temp[864];
unsigned mutation_index, busy_responses, dropped_response;
unsigned deleted_wire, deleted_replies; // биты STAT/QUOTA: запрос дошёл до сервера и получил отказ
uint64_t deletion_started; // монотонное время публикации удаления для ожидания репликации
etcp_recv_fn reader_receive;
struct api_case api[8];
void* timer;
@@ -91,6 +93,16 @@ static void capture_log(int level, const char* category, const char* message) {
/* Задержка диска начинается с первого DATA; сценарий не зависит от текста и уровня логов. */
static int server_receive_ready(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t peer, const uint8_t* payload, size_t bytes) {
struct network_test* t = active;
if (bytes >= MF_WIRE_HEADER && payload[1] == MF_REQUEST && mf_get64(payload + 5) >= 3000 && mf_get64(payload + 5) <= 3001) {
struct mf_message q;
if (mf_decode(payload, bytes, &q) || group != GROUP || peer != t->ids[READER] || q.owner != t->ids[AUTHOR] ||
q.command != (q.request_id == 3000 ? MF_STAT : MF_QUOTA) || mf_server_allowed(inst->member_files, group, peer, &q)) {
fail(t, "deleted reader wire request identity or server ACL"); return 0;
}
t->deleted_wire |= 1U << (q.request_id - 3000);
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files test: deleted reader reached server cmd=%s id=%llu allowed=0 wire_mask=%u",
mf_command_text(q.command), (unsigned long long)q.request_id, t->deleted_wire);
}
if (!t->slow_queued && bytes >= MF_WIRE_HEADER && payload[1] == MF_REQUEST && payload[2] == MF_STREAM_DATA) {
t->slow_queued = 1;
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files test: first DATA received; delaying disk worker");
@@ -166,6 +178,20 @@ static void response(void* arg, const struct mf_message* r) {
struct network_test* t = arg; t->response = *r; t->answered = 1;
}
/* Ответы двух независимых запросов могут прийти в любом порядке; каждый обязан быть пустым отказом. */
static void deleted_response(void* arg, const struct mf_message* r) {
struct network_test* t = arg;
if (r->request_id < 3000 || r->request_id > 3001 || r->owner != t->ids[AUTHOR] ||
r->command != (r->request_id == 3000 ? MF_STAT : MF_QUOTA) || r->status != MF_ACCESS_DENIED || r->data_len) {
fail(t, "deleted reader response identity/status or leaked data"); return;
}
unsigned bit = 1U << (r->request_id - 3000);
if (t->deleted_replies & bit) { fail(t, "duplicate deleted reader response"); return; }
t->deleted_replies |= bit;
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files test: deleted reader denied cmd=%s id=%llu replies_mask=%u",
mf_command_text(r->command), (unsigned long long)r->request_id, t->deleted_replies);
}
/* Потеря первого результата проверяет повтор с тем же ID без повторного mkdir. */
static void mutation_receive(struct ETCP_CONN* conn, struct ll_entry* e) {
struct network_test* t = active;
@@ -418,12 +444,38 @@ static void tick(void* arg) {
if (!t->api[0].finished || !t->api[1].finished || t->nodes[SERVER]->member_files->server_transfer_count) goto again;
if (t->api[0].status != MF_IO_ERROR || t->api[1].status != MF_IO_ERROR) { fail(t, "restart completion status"); return; }
if (put_member(t, SERVER, READER, 1)) { fail(t, "signed deletion"); return; }
t->phase = 7; request(t, MF_STAT, t->ids[AUTHOR]);
} else if (t->phase == 7 && t->answered) {
if (t->response.status != MF_ACCESS_DENIED) { fail(t, "deleted reader accepted"); return; }
t->phase = 21; request(t, MF_QUOTA, t->ids[AUTHOR]);
} else if (t->phase == 21 && t->answered) {
if (t->response.status != MF_ACCESS_DENIED || t->response.data_len) { fail(t, "quota leaked to deleted reader"); return; }
int reader_allowed = member_files_member(t->nodes[READER], GROUP, t->ids[READER], 0);
int server_allowed = member_files_member(t->nodes[SERVER], GROUP, t->ids[READER], 0);
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files test: deletion published reader_local_allowed=%d server_reader_allowed=%d",
reader_allowed, server_allowed);
if (!reader_allowed || server_allowed) { fail(t, "deleted reader ACL fixture"); return; }
t->phase = 7; t->deletion_started = get_time_tb();
// Оба запроса стартуют до следующего poll: последующая репликация не меняет проверяемый серверный отказ.
for (unsigned i = 0; i < 2; i++) {
struct mf_message q = { .command = i ? MF_QUOTA : MF_STAT, .owner = t->ids[AUTHOR], .request_id = 3000 + i };
if (!i) snprintf(q.path, sizeof(q.path), "file.bin");
if (!member_files_request(t->nodes[READER], GROUP, t->ids[SERVER], &q, deleted_response, t)) {
fail(t, "deleted reader remote request start"); return;
}
}
} else if (t->phase == 7 || t->phase == 21) {
if (get_time_tb() - t->deletion_started >= 100000) { fail(t, "deleted reader replies/replication timeout"); return; }
if (t->deleted_replies != 3) goto again;
if (t->deleted_wire != 3) { fail(t, "deleted reader denial did not exercise server ACL"); return; }
t->phase = 21;
if (member_files_member(t->nodes[READER], GROUP, t->ids[READER], 0)) goto again;
if (!member_files_member(t->nodes[READER], GROUP, t->ids[SERVER], 1)) { fail(t, "storage role lost during deletion"); return; }
unsigned pending = t->nodes[READER]->member_files->pending_count;
for (unsigned i = 0; i < 2; i++) {
struct mf_message q = { .command = i ? MF_QUOTA : MF_STAT, .owner = t->ids[AUTHOR], .request_id = 4000 + i };
if (!i) snprintf(q.path, sizeof(q.path), "file.bin");
if (member_files_request(t->nodes[READER], GROUP, t->ids[SERVER], &q, deleted_response, t)) {
fail(t, "deleted reader local request accepted"); return;
}
}
if (t->nodes[READER]->member_files->pending_count != pending) { fail(t, "local ACL refusal created pending requests"); return; }
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files test: deleted reader local_allowed=0 storage_allowed=1 wire=%u replies=%u pending=%u",
t->deleted_wire, t->deleted_replies, pending);
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files network: PASS progress=%u ticks=%u peak_inflight=%u pauses=%d",
t->progress, t->ticks, t->peak_inflight, t->receive_pauses);
uasync_stop(t->ua); return;