Initial commit: Naut-Torrent — from-scratch 10 GbE BitTorrent client

A maintainable, extensible BitTorrent client (C11, Linux/io_uring) targeting
10 GbE saturation. All torrent functionality is built from scratch; liburing
is the only linked third-party dependency on the data path.

Implements Phases 1-7 of the roadmap:
- core: page-aligned buffer pool, MPMC/Treiber queues, bitfields, worker pool
- crypto: SHA-1/256 (SHA-NI + scalar), Merkle (BEP-52), RC4 (MSE)
- bencode/metainfo: zero-copy parser, v1/v2/hybrid .torrent + magnet
- peer: sans-IO wire codec, MSE/PE handshake state machine, BEP-10, ut_metadata, PEX
- piece/storage: block-level multi-peer engine, rarest-first + endgame,
  per-file completion events + single-file relocate (move-as-you-finish)
- tracker/dht: HTTP + UDP (BEP-15) trackers, BEP-5 KRPC iterative lookup
- platform: io_uring reactor (SQPOLL, registered buffers, SEND_ZC)
- surface: versioned RPC, native plugin ABI, sandboxed Lua scripting, nautd/nautctl

Verified against libtorrent (single/multi/hybrid, MSE, magnet-via-DHT, swarm);
unit + interop tests green; ASan/UBSan/TSan clean. Scripting reference in
docs/scripting.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ookami125 2026-06-15 12:12:00 -04:00
commit 2178d6a70c
121 changed files with 12644 additions and 0 deletions

312
src/script/script.c Normal file
View file

@ -0,0 +1,312 @@
#include "naut/script.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <limits.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
naut_event event;
char message[256];
char path[PATH_MAX];
} script_job;
struct naut_script {
naut_event_bus *events;
uint64_t subscription;
lua_State *lua;
pthread_t thread;
pthread_mutex_t lock;
pthread_cond_t ready;
script_job *queue;
size_t capacity;
size_t head;
size_t count;
bool stopping;
naut_script_move_file_cb move_file;
void *move_context;
_Atomic uint64_t queued;
_Atomic uint64_t handled;
_Atomic uint64_t dropped;
_Atomic uint64_t errors;
_Atomic uint64_t move_requests;
char last_error[256];
};
static const char *hook_names[] = {
[NAUT_EVENT_TORRENT_ADDED] = "on_torrent_added",
[NAUT_EVENT_PIECE_COMPLETE] = "on_piece_complete",
[NAUT_EVENT_FILE_COMPLETE] = "on_file_complete",
[NAUT_EVENT_TORRENT_FINISHED] = "on_torrent_finished",
[NAUT_EVENT_PEER_CONNECTED] = "on_peer_connected",
[NAUT_EVENT_ALERT] = "on_alert",
};
static void set_last_error(naut_script *script, const char *message) {
pthread_mutex_lock(&script->lock);
snprintf(script->last_error, sizeof script->last_error, "%s",
message ? message : "unknown Lua error");
pthread_mutex_unlock(&script->lock);
}
static naut_script *lua_script(lua_State *lua) {
return lua_touserdata(lua, lua_upvalueindex(1));
}
static int lua_move_file(lua_State *lua) {
naut_script *script = lua_script(lua);
lua_Integer torrent_id = luaL_checkinteger(lua, 1);
lua_Integer file_index = luaL_checkinteger(lua, 2);
const char *destination = luaL_checkstring(lua, 3);
if (torrent_id < 0 || file_index < 0 ||
(uint64_t)file_index > UINT32_MAX)
return luaL_error(lua, "move_file arguments out of range");
if (!script->move_file)
return luaL_error(lua, "move_file is unavailable");
naut_err error = script->move_file(script->move_context,
(uint64_t)torrent_id,
(uint32_t)file_index,
destination);
if (error != NAUT_OK)
return luaL_error(lua, "move_file failed: %d", error);
atomic_fetch_add_explicit(&script->move_requests, 1,
memory_order_relaxed);
return 0;
}
static void sandbox(lua_State *lua) {
/* Remove every documented route to the filesystem, subprocesses, native
* module loading, and raw chunk compilation. `load`/`loadstring` are
* blocked too: with the default "bt" mode they accept *binary* chunks, and
* a crafted bytecode chunk can escape the VM entirely so even though
* scripts are operator-supplied, we deny the bytecode-loader as
* defense-in-depth. */
static const char *blocked[] = {
"debug", "dofile", "io", "load", "loadfile", "loadstring",
"os", "package", "require",
};
for (size_t i = 0; i < NAUT_ARRAY_LEN(blocked); i++) {
lua_pushnil(lua);
lua_setglobal(lua, blocked[i]);
}
}
static void install_api(naut_script *script) {
lua_State *lua = script->lua;
lua_newtable(lua);
lua_pushlightuserdata(lua, script);
lua_pushcclosure(lua, lua_move_file, 1);
lua_setfield(lua, -2, "move_file");
lua_setglobal(lua, "naut");
}
static void push_event(lua_State *lua, const naut_event *event) {
lua_createtable(lua, 0, 5);
lua_pushstring(lua, naut_event_type_name(event->type));
lua_setfield(lua, -2, "type");
lua_pushinteger(lua, (lua_Integer)event->torrent_id);
lua_setfield(lua, -2, "torrent_id");
lua_pushinteger(lua, (lua_Integer)event->index);
lua_setfield(lua, -2, "index");
if (event->message) {
lua_pushstring(lua, event->message);
lua_setfield(lua, -2, "message");
}
if (event->path) {
lua_pushstring(lua, event->path);
lua_setfield(lua, -2, "path");
}
}
static void run_hook(naut_script *script, const naut_event *event) {
if ((size_t)event->type >= NAUT_ARRAY_LEN(hook_names)) {
atomic_fetch_add_explicit(&script->errors, 1, memory_order_relaxed);
set_last_error(script, "unknown event type");
return;
}
const char *hook = hook_names[event->type];
lua_getglobal(script->lua, hook);
if (lua_isnil(script->lua, -1)) {
lua_pop(script->lua, 1);
return;
}
if (!lua_isfunction(script->lua, -1)) {
lua_pop(script->lua, 1);
atomic_fetch_add_explicit(&script->errors, 1, memory_order_relaxed);
set_last_error(script, "event hook is not a function");
return;
}
push_event(script->lua, event);
if (lua_pcall(script->lua, 1, 0, 0) != LUA_OK) {
atomic_fetch_add_explicit(&script->errors, 1, memory_order_relaxed);
set_last_error(script, lua_tostring(script->lua, -1));
lua_pop(script->lua, 1);
return;
}
atomic_fetch_add_explicit(&script->handled, 1, memory_order_relaxed);
}
static bool pop_job(naut_script *script, script_job *job) {
pthread_mutex_lock(&script->lock);
while (!script->stopping && script->count == 0)
pthread_cond_wait(&script->ready, &script->lock);
if (script->count == 0) {
pthread_mutex_unlock(&script->lock);
return false;
}
*job = script->queue[script->head];
if (job->event.message) job->event.message = job->message;
if (job->event.path) job->event.path = job->path;
script->head = (script->head + 1) % script->capacity;
script->count--;
pthread_mutex_unlock(&script->lock);
return true;
}
static void *script_worker(void *opaque) {
naut_script *script = opaque;
script_job job;
while (pop_job(script, &job))
run_hook(script, &job.event);
return NULL;
}
static void queue_event(void *opaque, const naut_event *event) {
naut_script *script = opaque;
pthread_mutex_lock(&script->lock);
if (script->stopping || script->count == script->capacity) {
pthread_mutex_unlock(&script->lock);
atomic_fetch_add_explicit(&script->dropped, 1, memory_order_relaxed);
return;
}
size_t tail = (script->head + script->count) % script->capacity;
script_job *job = &script->queue[tail];
memset(job, 0, sizeof(*job));
job->event = *event;
if (event->message) {
snprintf(job->message, sizeof job->message, "%s", event->message);
job->event.message = job->message;
}
if (event->path) {
snprintf(job->path, sizeof job->path, "%s", event->path);
job->event.path = job->path;
}
script->count++;
pthread_cond_signal(&script->ready);
pthread_mutex_unlock(&script->lock);
atomic_fetch_add_explicit(&script->queued, 1, memory_order_relaxed);
}
naut_script *naut_script_create(naut_event_bus *events,
const char *script_path,
size_t queue_capacity,
naut_script_move_file_cb move_file,
void *move_context,
naut_err *error) {
if (error) *error = NAUT_ERR_INVAL;
if (!events || !script_path || !*script_path || queue_capacity == 0)
return NULL;
naut_script *script = calloc(1, sizeof(*script));
if (!script) {
if (error) *error = NAUT_ERR_NOMEM;
return NULL;
}
script->events = events;
script->capacity = queue_capacity;
script->move_file = move_file;
script->move_context = move_context;
script->queue = calloc(queue_capacity, sizeof(*script->queue));
if (!script->queue) {
if (error) *error = NAUT_ERR_NOMEM;
free(script);
return NULL;
}
if (pthread_mutex_init(&script->lock, NULL) != 0) {
if (error) *error = NAUT_ERR_NOMEM;
free(script->queue);
free(script);
return NULL;
}
if (pthread_cond_init(&script->ready, NULL) != 0) {
if (error) *error = NAUT_ERR_NOMEM;
pthread_mutex_destroy(&script->lock);
free(script->queue);
free(script);
return NULL;
}
script->lua = luaL_newstate();
if (!script->lua) goto fail;
luaL_openlibs(script->lua);
sandbox(script->lua);
install_api(script);
if (luaL_loadfile(script->lua, script_path) != LUA_OK ||
lua_pcall(script->lua, 0, 0, 0) != LUA_OK) {
set_last_error(script, lua_tostring(script->lua, -1));
if (error) *error = NAUT_ERR_PROTO;
goto fail;
}
if (naut_event_subscribe(events, queue_event, script,
&script->subscription) != NAUT_OK)
goto fail;
if (pthread_create(&script->thread, NULL, script_worker, script) != 0) {
naut_event_unsubscribe(events, script->subscription);
script->subscription = 0;
goto fail;
}
if (error) *error = NAUT_OK;
return script;
fail:
if (error && *error == NAUT_ERR_INVAL) *error = NAUT_ERR_NOMEM;
if (script->lua) lua_close(script->lua);
pthread_cond_destroy(&script->ready);
pthread_mutex_destroy(&script->lock);
free(script->queue);
free(script);
return NULL;
}
void naut_script_destroy(naut_script *script) {
if (!script) return;
naut_event_unsubscribe(script->events, script->subscription);
pthread_mutex_lock(&script->lock);
script->stopping = true;
pthread_cond_broadcast(&script->ready);
pthread_mutex_unlock(&script->lock);
pthread_join(script->thread, NULL);
lua_close(script->lua);
pthread_cond_destroy(&script->ready);
pthread_mutex_destroy(&script->lock);
free(script->queue);
free(script);
}
void naut_script_get_stats(const naut_script *script,
naut_script_stats *stats) {
if (!script || !stats) return;
*stats = (naut_script_stats) {
.queued = atomic_load_explicit(&script->queued, memory_order_relaxed),
.handled = atomic_load_explicit(&script->handled,
memory_order_relaxed),
.dropped = atomic_load_explicit(&script->dropped,
memory_order_relaxed),
.errors = atomic_load_explicit(&script->errors, memory_order_relaxed),
.move_requests = atomic_load_explicit(&script->move_requests,
memory_order_relaxed),
};
}
const char *naut_script_last_error(naut_script *script) {
if (!script) return "";
pthread_mutex_lock(&script->lock);
static _Thread_local char copy[256];
snprintf(copy, sizeof copy, "%s", script->last_error);
pthread_mutex_unlock(&script->lock);
return copy;
}