mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-08-11 14:16:41 +03:00
155 lines
5.7 KiB
C
155 lines
5.7 KiB
C
|
|
/* test_sock.c — out-of-process control: wire codec + authentication/admission.
|
||
|
|
* Bring up two listeners (one admitting, one rejecting) on abstract sockets, run
|
||
|
|
* the core in a separate thread, connect clients and check: policy invoked,
|
||
|
|
* valid poller admitted, unauthorized rejected (EOF), reap without a crash. */
|
||
|
|
#define _GNU_SOURCE
|
||
|
|
#include "vmsig.h"
|
||
|
|
#include "vmsig_socket.h"
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
#include <pthread.h>
|
||
|
|
#include <stdatomic.h>
|
||
|
|
#include <sys/socket.h>
|
||
|
|
#include <sys/un.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <stddef.h>
|
||
|
|
#include <time.h>
|
||
|
|
|
||
|
|
static int g_fail = 0;
|
||
|
|
#define CHECK(cond, msg) do { \
|
||
|
|
if (!(cond)) { printf(" FAIL: %s\n", (msg)); g_fail = 1; } \
|
||
|
|
} while (0)
|
||
|
|
|
||
|
|
static atomic_int g_auth = 0;
|
||
|
|
static atomic_int g_deny = 0;
|
||
|
|
static atomic_int g_admit = 0;
|
||
|
|
static atomic_int g_reject = 0;
|
||
|
|
|
||
|
|
static void audit_cb(void* ud, const vmsig_audit* a) {
|
||
|
|
(void)ud;
|
||
|
|
if (a->kind == VMSIG_AUDIT_ADMIT) atomic_fetch_add(&g_admit, 1);
|
||
|
|
else if (a->kind == VMSIG_AUDIT_REJECT) atomic_fetch_add(&g_reject, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
static vmsig_grant pol_ok(uint32_t uid, uint32_t pid, void* ud) {
|
||
|
|
(void)pid; (void)ud;
|
||
|
|
atomic_fetch_add(&g_auth, 1);
|
||
|
|
vmsig_grant g; memset(&g, 0, sizeof g);
|
||
|
|
g.principal = uid; g.endpoint_mask = 1u << 0;
|
||
|
|
g.source_mask = 0xFFFFFFFFu; g.cap_mask = VMSIG_CAP_OBSERVE;
|
||
|
|
return g;
|
||
|
|
}
|
||
|
|
static vmsig_grant pol_deny(uint32_t uid, uint32_t pid, void* ud) {
|
||
|
|
(void)uid; (void)pid; (void)ud;
|
||
|
|
atomic_fetch_add(&g_deny, 1);
|
||
|
|
vmsig_grant g; memset(&g, 0, sizeof g); /* empty => reject */
|
||
|
|
return g;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int connect_abstract(const char* name) {
|
||
|
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
|
if (fd < 0) return -1;
|
||
|
|
struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX;
|
||
|
|
size_t n = strlen(name);
|
||
|
|
a.sun_path[0] = 0;
|
||
|
|
memcpy(a.sun_path + 1, name + 1, n - 1);
|
||
|
|
socklen_t alen = (socklen_t)(offsetof(struct sockaddr_un, sun_path) + n);
|
||
|
|
if (connect(fd, (struct sockaddr*)&a, alen) < 0) { close(fd); return -1; }
|
||
|
|
return fd;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void* loop_main(void* p) { vmsig_core_run((vmsig_core*)p); return NULL; }
|
||
|
|
|
||
|
|
static void wait_atomic(atomic_int* a, int want, int ms) {
|
||
|
|
for (int i = 0; i < ms; i++) {
|
||
|
|
if (atomic_load(a) >= want) return;
|
||
|
|
struct timespec t = { .tv_sec = 0, .tv_nsec = 1000000 };
|
||
|
|
nanosleep(&t, NULL);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void test_wire(void) {
|
||
|
|
printf("test_wire\n");
|
||
|
|
vmsig_event ev; memset(&ev, 0, sizeof ev);
|
||
|
|
ev.kind = VMSIG_EV_CMD_VM; ev.source = VMSIG_SRC_VMHOST; ev.dir = VMSIG_DIR_DOWN;
|
||
|
|
ev.prio = VMSIG_PRIO_HIGH; ev.endpoint = 0; ev.corr = 0xABCD;
|
||
|
|
for (int i = 0; i < 48; i++) ev.inln[i] = (uint8_t)i;
|
||
|
|
|
||
|
|
vmsig_wire w; vmsig_wire_encode(&w, &ev);
|
||
|
|
vmsig_event d;
|
||
|
|
CHECK(vmsig_wire_decode(&w, &d) == 0, "decode ok");
|
||
|
|
CHECK(d.kind == ev.kind && d.source == ev.source &&
|
||
|
|
d.endpoint == ev.endpoint && d.corr == ev.corr, "frame fields match");
|
||
|
|
CHECK(memcmp(d.inln, ev.inln, 48) == 0, "inln matches");
|
||
|
|
|
||
|
|
vmsig_wire bad = w; bad.magic = 0; vmsig_event x;
|
||
|
|
CHECK(vmsig_wire_decode(&bad, &x) == -1, "bad magic rejected");
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void) {
|
||
|
|
test_wire();
|
||
|
|
|
||
|
|
printf("test_socket\n");
|
||
|
|
vmsig_ctx* ctx = vmsig_ctx_new();
|
||
|
|
vmsig_core* core = vmsig_core_new(ctx);
|
||
|
|
vmsig_core_set_audit(core, audit_cb, NULL);
|
||
|
|
const char* OK = "@vmsig-sock-ok-test";
|
||
|
|
const char* DENY = "@vmsig-sock-deny-test";
|
||
|
|
CHECK(vmsig_socket_attach(core, OK, pol_ok, NULL) == 0, "attach ok listener");
|
||
|
|
CHECK(vmsig_socket_attach(core, DENY, pol_deny, NULL) == 0, "attach deny listener");
|
||
|
|
|
||
|
|
pthread_t th;
|
||
|
|
pthread_create(&th, NULL, loop_main, core);
|
||
|
|
|
||
|
|
/* valid poller: connect -> policy -> admission */
|
||
|
|
int c1 = connect_abstract(OK);
|
||
|
|
CHECK(c1 >= 0, "client connected (ok)");
|
||
|
|
wait_atomic(&g_auth, 1, 1000);
|
||
|
|
CHECK(atomic_load(&g_auth) >= 1, "policy invoked — poller authenticated/admitted");
|
||
|
|
if (c1 >= 0) close(c1); /* disconnect -> deferred reap (no crash) */
|
||
|
|
|
||
|
|
/* unauthorized: connect -> server closes -> EOF on the client */
|
||
|
|
int c2 = connect_abstract(DENY);
|
||
|
|
CHECK(c2 >= 0, "client connected (deny)");
|
||
|
|
wait_atomic(&g_deny, 1, 1000);
|
||
|
|
CHECK(atomic_load(&g_deny) >= 1, "deny policy invoked");
|
||
|
|
if (c2 >= 0) {
|
||
|
|
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
|
||
|
|
setsockopt(c2, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
|
||
|
|
char b; ssize_t r = read(c2, &b, 1);
|
||
|
|
CHECK(r == 0, "connection rejected by server (EOF)");
|
||
|
|
close(c2);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* slot reuse: churn > MAX_CONTROLS(64). Without returning slots the listener
|
||
|
|
* would die after 64 cycles. Each cycle: connect(ok) -> wait auth++ -> close. */
|
||
|
|
int base = atomic_load(&g_auth);
|
||
|
|
const int churn = 70;
|
||
|
|
for (int k = 0; k < churn; k++) {
|
||
|
|
int fc = connect_abstract(OK);
|
||
|
|
if (fc < 0) { CHECK(0, "churn connect"); break; }
|
||
|
|
wait_atomic(&g_auth, base + k + 1, 1000);
|
||
|
|
close(fc);
|
||
|
|
struct timespec ts = { .tv_sec = 0, .tv_nsec = 2 * 1000000 };
|
||
|
|
nanosleep(&ts, NULL); /* let the loop reap before the next connection */
|
||
|
|
}
|
||
|
|
CHECK(atomic_load(&g_auth) >= base + churn,
|
||
|
|
"slots reused: churn > MAX_CONTROLS admitted");
|
||
|
|
|
||
|
|
/* audit recorded admissions and rejections */
|
||
|
|
CHECK(atomic_load(&g_admit) >= 1, "audit: poller admission");
|
||
|
|
CHECK(atomic_load(&g_reject) >= 1, "audit: rejection (deny listener)");
|
||
|
|
|
||
|
|
struct timespec t = { .tv_sec = 0, .tv_nsec = 50 * 1000000 };
|
||
|
|
nanosleep(&t, NULL); /* let the loop process the reaps */
|
||
|
|
vmsig_core_stop(core);
|
||
|
|
pthread_join(th, NULL);
|
||
|
|
vmsig_core_free(core);
|
||
|
|
vmsig_ctx_free(ctx);
|
||
|
|
|
||
|
|
printf("socket tests: %s\n", g_fail ? "FAIL" : "PASS");
|
||
|
|
return g_fail ? 1 : 0;
|
||
|
|
}
|