Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ ifdef KBOX_HAS_SLIRP
SLIRP_OBJS = $(SLIRP_SRCS:.c=.o)
endif

# Optional: Web observatory (set KBOX_HAS_WEB=1 to enable)
ifdef KBOX_HAS_WEB
CFLAGS += -DKBOX_HAS_WEB
WEB_ASSET_SRC = $(SRC_DIR)/web-assets.c
endif

# Source files
SRC_DIR = src
SRCS = $(SRC_DIR)/main.c \
Expand All @@ -53,7 +59,14 @@ SRCS = $(SRC_DIR)/main.c \
$(SRC_DIR)/shadow-fd.c \
$(SRC_DIR)/seccomp-dispatch.c \
$(SRC_DIR)/seccomp-supervisor.c \
$(SRC_DIR)/net-slirp.c
$(SRC_DIR)/net-slirp.c \
$(SRC_DIR)/web-telemetry.c \
$(SRC_DIR)/web-events.c \
$(SRC_DIR)/web-server.c

ifdef KBOX_HAS_WEB
SRCS += $(WEB_ASSET_SRC)
endif

ifdef KBOX_HAS_SLIRP
SRCS += $(SLIRP_SRCS)
Expand Down Expand Up @@ -97,7 +110,7 @@ ROOTFS = alpine.ext4

# ---- Top-level targets ----

.PHONY: all clean check check-unit check-integration check-stress guest-bins stress-bins rootfs fetch-lkl install-hooks
.PHONY: all clean check check-unit check-integration check-stress guest-bins stress-bins rootfs fetch-lkl install-hooks web-assets

all: $(TARGET)
ifneq ($(wildcard .git),)
Expand Down Expand Up @@ -177,9 +190,18 @@ install-hooks:
fi; \
done

# Generate compiled-in web assets from web/ directory.
# Re-run when any web/ file changes.
ifdef KBOX_HAS_WEB
WEB_SRCS_ALL = $(shell find web -type f \( -name '*.html' -o -name '*.css' -o -name '*.js' -o -name '*.svg' \) 2>/dev/null)
$(WEB_ASSET_SRC): $(WEB_SRCS_ALL) scripts/gen-web-assets.sh
./scripts/gen-web-assets.sh
web-assets: $(WEB_ASSET_SRC)
endif

clean:
rm -f $(OBJS) $(TARGET) $(TEST_TARGET) $(TEST_DIR)/*.o
rm -f src/*.o
rm -f src/*.o src/web-assets.c
rm -f $(GUEST_BINS) $(STRESS_BINS)

# ---- Dependencies ----
Expand All @@ -194,3 +216,6 @@ $(SRC_DIR)/seccomp-supervisor.o: include/kbox/seccomp.h include/kbox/seccomp-def
$(SRC_DIR)/seccomp-bpf.o: include/kbox/seccomp.h include/kbox/seccomp-defs.h include/kbox/syscall-nr.h
$(SRC_DIR)/seccomp-notify.o: include/kbox/seccomp.h include/kbox/seccomp-defs.h
$(SRC_DIR)/net-slirp.o: include/kbox/net.h include/kbox/lkl-wrap.h include/kbox/syscall-nr.h
$(SRC_DIR)/web-telemetry.o: include/kbox/web.h include/kbox/lkl-wrap.h include/kbox/syscall-nr.h
$(SRC_DIR)/web-events.o: include/kbox/web.h
$(SRC_DIR)/web-server.o: include/kbox/web.h include/kbox/fd-table.h include/kbox/lkl-wrap.h include/kbox/syscall-nr.h
201 changes: 140 additions & 61 deletions README.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions include/kbox/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ struct kbox_image_args {
bool verbose; /* --forward-verbose */
bool net; /* --net: enable SLIRP networking */
enum kbox_mount_profile mount_profile; /* --mount-profile */
bool web; /* --web: enable web observatory */
int web_port; /* --web=PORT (default 8080) */
const char *web_bind; /* --web-bind ADDR */
const char *trace_format; /* --trace-format=json */
const char *const *extra_args; /* remaining args after -- */
int extra_argc; /* count of extra_args */
};
Expand Down
6 changes: 5 additions & 1 deletion include/kbox/seccomp.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ struct kbox_dispatch {
* Supervisor context. Replaces 9+ parameter function signatures
* from the Rust code.
*/
struct kbox_web_ctx; /* forward declaration */

struct kbox_supervisor_ctx {
const struct kbox_sysnrs *sysnrs;
const struct kbox_host_nrs *host_nrs;
Expand All @@ -46,6 +48,7 @@ struct kbox_supervisor_ctx {
uid_t override_uid;
gid_t override_gid;
int normalize;
struct kbox_web_ctx *web; /* NULL if telemetry disabled */
};

/* --- BPF filter (seccomp-bpf.c) --- */
Expand Down Expand Up @@ -103,7 +106,8 @@ int kbox_run_supervisor(const struct kbox_sysnrs *sysnrs,
int exec_memfd,
int verbose,
int root_identity,
int normalize);
int normalize,
struct kbox_web_ctx *web);

/* I/O chunk size for forwarding read/write through LKL. */
#define KBOX_IO_CHUNK_LEN (128 * 1024)
Expand Down
264 changes: 264 additions & 0 deletions include/kbox/web.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
/* SPDX-License-Identifier: MIT */
#ifndef KBOX_WEB_H
#define KBOX_WEB_H

/*
* web.h - Web-based kernel observatory.
*
* Optional component activated by --web[=PORT]. When enabled, the
* supervisor spawns an embedded HTTP server that streams LKL kernel
* telemetry to a browser dashboard via SSE.
*
* Compile with KBOX_HAS_WEB to include web support.
* The enable_trace config flag allows --trace-format=json to work
* without --web (telemetry-only mode, no HTTP server).
*/

#include <stdint.h>

/* ------------------------------------------------------------------ */
/* Telemetry counters (updated from dispatch loop, lock-free) */
/* ------------------------------------------------------------------ */

/*
* Syscall family classification for dashboard grouping.
*/
enum kbox_syscall_family {
KBOX_FAM_FILE_IO, /* read, write, pread64, pwrite64, sendfile */
KBOX_FAM_DIR, /* getdents, mkdir, unlink, rename, readlink */
KBOX_FAM_FD_OPS, /* open, close, dup, fcntl, fstat, statx */
KBOX_FAM_IDENTITY, /* getuid, setuid, getgid, setgid, etc. */
KBOX_FAM_MEMORY, /* mmap, mprotect, mremap, brk */
KBOX_FAM_SIGNALS, /* rt_sigaction, rt_sigprocmask, kill, tgkill */
KBOX_FAM_SCHEDULER, /* sched_yield, sched_setscheduler, etc. */
KBOX_FAM_OTHER, /* everything else */
KBOX_FAM_COUNT,
};

/*
* Dispatch disposition (for per-syscall tracking).
*/
enum kbox_disposition {
KBOX_DISP_CONTINUE,
KBOX_DISP_RETURN,
KBOX_DISP_ENOSYS,
KBOX_DISP_COUNT,
};

/*
* Supervisor-internal counters. Updated atomically from the
* single-threaded dispatch loop. Read by the web server thread
* under a snapshot copy.
*/
struct kbox_telemetry_counters {
/* Aggregate dispatch stats */
uint64_t syscall_total;
uint64_t disp_continue;
uint64_t disp_return;
uint64_t disp_enosys;

/* Per-family dispatch counts */
uint64_t family[KBOX_FAM_COUNT];

/* Latency tracking (nanoseconds, cumulative) */
uint64_t latency_total_ns;
uint64_t latency_max_ns;

/* ENOSYS hit counters: per-syscall-number */
uint64_t enosys_hits[1024];
uint64_t enosys_overflow;
int enosys_overflow_last_nr;

/* Notification lifecycle */
uint64_t recv_enoent;
uint64_t send_enoent;

/* Cross-memory failures */
uint64_t vm_readv_efault;
uint64_t vm_readv_esrch;
uint64_t vm_readv_eperm;
uint64_t vm_writev_efault;
uint64_t vm_writev_esrch;

/* ADDFD operations */
uint64_t addfd_ok;
uint64_t addfd_enoent;
uint64_t addfd_ebadf;
uint64_t addfd_emfile;
uint64_t addfd_other_err;
};

/* ------------------------------------------------------------------ */
/* Telemetry snapshot (sampled from LKL /proc) */
/* ------------------------------------------------------------------ */

#define KBOX_SNAPSHOT_VERSION 1

struct kbox_telemetry_snapshot {
uint32_t version;
uint64_t timestamp_ns;
uint64_t uptime_ns;

/* /proc/stat */
uint64_t context_switches;
uint64_t softirqs[10]; /* HI, TIMER, NET_TX, NET_RX, BLOCK,
IRQ_POLL, TASKLET, SCHED, HRTIMER, RCU */
uint64_t softirq_total;

/* /proc/meminfo (kB) */
uint64_t mem_total;
uint64_t mem_free;
uint64_t mem_available;
uint64_t buffers;
uint64_t cached;
uint64_t slab;

/* /proc/vmstat */
uint64_t pgfault;
uint64_t pgmajfault;

/* /proc/loadavg */
uint32_t loadavg_1; /* fixed-point: value * 100 */
uint32_t loadavg_5;
uint32_t loadavg_15;

/* FD table (supervisor internal) */
uint32_t fd_table_used;
uint32_t fd_table_max;

/* Copy of dispatch counters at sample time */
struct kbox_telemetry_counters counters;
};

/* ------------------------------------------------------------------ */
/* Event ring buffer */
/* ------------------------------------------------------------------ */

#define KBOX_EVENT_RING_SIZE 1024
#define KBOX_EVENT_RING_ROUTINE 768
#define KBOX_EVENT_RING_ERROR 256

enum kbox_event_type {
KBOX_EVT_SYSCALL,
KBOX_EVT_PROCESS,
KBOX_EVT_COUNTER_DELTA,
};

struct kbox_syscall_event {
uint64_t timestamp_ns;
uint32_t pid;
int syscall_nr;
const char *syscall_name; /* static string, not owned */
uint64_t args[6];
enum kbox_disposition disposition;
int64_t return_value;
int error_nr;
uint64_t latency_ns;
};

struct kbox_process_event {
uint64_t timestamp_ns;
uint32_t pid;
int is_exit; /* 1 = exit, 0 = exec */
int exit_code;
char command[128];
};

struct kbox_event {
enum kbox_event_type type;
uint64_t seq; /* monotonic sequence number for SSE dedup */
union {
struct kbox_syscall_event syscall;
struct kbox_process_event process;
};
};

/*
* Event ring buffer.
* Split: [0, ROUTINE) for sampled routine events,
* [ROUTINE, SIZE) for errors/rare events.
*/
struct kbox_event_ring {
struct kbox_event entries[KBOX_EVENT_RING_SIZE];
uint64_t write_seq;
int routine_head;
int error_head;
};

/* ------------------------------------------------------------------ */
/* Web context (opaque) */
/* ------------------------------------------------------------------ */

struct kbox_web_ctx;
struct kbox_sysnrs;

/*
* Configuration for the web observatory.
*/
struct kbox_web_config {
int port; /* HTTP port (0 = default 8080) */
const char *bind; /* Bind address (NULL = "127.0.0.1") */
int sample_ms; /* Fast tick interval (0 = default 100) */
int slow_sample_ms; /* Slow tick interval (0 = default 500) */
int enable_web; /* Start HTTP server */
int enable_trace; /* Enable --trace-format=json to fd */
int trace_fd; /* FD for JSON trace output */
const char *guest_name; /* Guest binary name for /stats */
};

/*
* Initialize the web observatory.
* The sysnrs pointer must remain valid for the lifetime of the context.
* Returns an opaque context or NULL on error.
*/
struct kbox_web_ctx *kbox_web_init(const struct kbox_web_config *cfg,
const struct kbox_sysnrs *sysnrs);

/*
* Shut down the web observatory. Stops the HTTP server thread
* and frees all resources.
*/
void kbox_web_shutdown(struct kbox_web_ctx *ctx);

/*
* Called from the supervisor loop on each iteration.
* Checks if a sampling tick is due and reads LKL /proc files.
* Must be called from the main thread.
*/
void kbox_web_tick(struct kbox_web_ctx *ctx);

/*
* Record a dispatched syscall event.
* Called from the supervisor loop after dispatch + send.
*/
void kbox_web_record_syscall(struct kbox_web_ctx *ctx,
uint32_t pid,
int syscall_nr,
const char *syscall_name,
const uint64_t args[6],
enum kbox_disposition disp,
int64_t ret_val,
int error_nr,
uint64_t latency_ns);

/*
* Record a process lifecycle event (exec or exit).
*/
void kbox_web_record_process(struct kbox_web_ctx *ctx,
uint32_t pid,
int is_exit,
int exit_code,
const char *command);

/*
* Get a pointer to the live counters (for direct increment
* from the dispatch loop without function call overhead).
*/
struct kbox_telemetry_counters *kbox_web_counters(struct kbox_web_ctx *ctx);

/*
* Monotonic clock reading in nanoseconds (CLOCK_MONOTONIC).
*/
uint64_t kbox_clock_ns(void);

#endif /* KBOX_WEB_H */
Loading
Loading