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

507
apps/nautd/main.c Normal file
View file

@ -0,0 +1,507 @@
#include "naut/event.h"
#include "naut/log.h"
#include "naut/metainfo.h"
#include "naut/plugin.h"
#include "naut/rpc.h"
#include "naut/script.h"
#include "naut/session.h"
#include "naut/storage.h"
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define DEFAULT_SOCKET "/tmp/nautd.sock"
#define MOVE_QUEUE_CAPACITY 64
#define MAX_SUBSCRIBERS 64
typedef struct {
uint64_t torrent_id;
uint32_t file_index;
char destination[PATH_MAX];
} move_command;
typedef struct {
naut_event_bus *events;
naut_rpc_registry *rpc;
naut_plugin_manager *plugins;
naut_script *script;
naut_session *session;
pthread_mutex_t move_lock;
move_command moves[MOVE_QUEUE_CAPACITY];
size_t move_head;
size_t move_count;
uint64_t moves_processed;
pthread_mutex_t subscriber_lock;
int subscribers[MAX_SUBSCRIBERS];
size_t subscriber_count;
bool stopping;
} daemon_state;
static volatile sig_atomic_t interrupted;
static void on_signal(int signal_number) {
(void)signal_number;
interrupted = 1;
}
static json_t *rpc_ping(void *opaque, const json_t *params, naut_err *error) {
(void)opaque;
(void)params;
json_t *result = json_object();
if (!result) {
*error = NAUT_ERR_NOMEM;
return NULL;
}
json_object_set_new(result, "protocol", json_integer(NAUT_RPC_VERSION));
json_object_set_new(result, "service", json_string("nautd"));
*error = NAUT_OK;
return result;
}
static json_t *rpc_status(void *opaque, const json_t *params,
naut_err *error) {
(void)params;
daemon_state *state = opaque;
naut_script_stats stats = {0};
if (state->script) naut_script_get_stats(state->script, &stats);
pthread_mutex_lock(&state->move_lock);
uint64_t moves = state->moves_processed;
size_t pending = state->move_count;
pthread_mutex_unlock(&state->move_lock);
json_t *result = json_object();
json_t *script = json_object();
if (!result || !script) {
json_decref(result);
json_decref(script);
*error = NAUT_ERR_NOMEM;
return NULL;
}
json_object_set_new(result, "protocol", json_integer(NAUT_RPC_VERSION));
json_object_set_new(result, "plugins",
json_integer((json_int_t)naut_plugin_count(
state->plugins)));
json_object_set_new(result, "storage_backends",
json_integer((json_int_t)naut_plugin_storage_count(
state->plugins)));
json_object_set_new(script, "queued", json_integer(stats.queued));
json_object_set_new(script, "handled", json_integer(stats.handled));
json_object_set_new(script, "dropped", json_integer(stats.dropped));
json_object_set_new(script, "errors", json_integer(stats.errors));
json_object_set_new(script, "move_requests",
json_integer(stats.move_requests));
json_object_set_new(result, "script", script);
json_object_set_new(result, "move_commands", json_integer(moves));
json_object_set_new(result, "pending_move_commands",
json_integer((json_int_t)pending));
*error = NAUT_OK;
return result;
}
static json_t *rpc_plugins(void *opaque, const json_t *params,
naut_err *error) {
(void)params;
daemon_state *state = opaque;
json_t *plugins = json_array();
json_t *storage = json_array();
if (!plugins || !storage) {
json_decref(plugins);
json_decref(storage);
*error = NAUT_ERR_NOMEM;
return NULL;
}
for (size_t i = 0; i < naut_plugin_count(state->plugins); i++)
json_array_append_new(plugins,
json_string(naut_plugin_name(state->plugins, i)));
for (size_t i = 0; i < naut_plugin_storage_count(state->plugins); i++)
json_array_append_new(storage,
json_string(naut_plugin_storage_name(state->plugins, i)));
json_t *result = json_object();
if (!result) {
json_decref(plugins);
json_decref(storage);
*error = NAUT_ERR_NOMEM;
return NULL;
}
json_object_set_new(result, "plugins", plugins);
json_object_set_new(result, "storage_backends", storage);
*error = NAUT_OK;
return result;
}
static json_t *rpc_emit(void *opaque, const json_t *params, naut_err *error) {
daemon_state *state = opaque;
if (!json_is_object(params)) {
*error = NAUT_ERR_INVAL;
return NULL;
}
const char *type_name = json_string_value(json_object_get(params, "type"));
naut_event_type type;
if (!type_name || !naut_event_type_parse(type_name, &type)) {
*error = NAUT_ERR_INVAL;
return NULL;
}
json_int_t torrent_id =
json_integer_value(json_object_get(params, "torrent_id"));
json_int_t index = json_integer_value(json_object_get(params, "index"));
if (torrent_id < 0 || index < 0 || (uint64_t)index > UINT32_MAX) {
*error = NAUT_ERR_RANGE;
return NULL;
}
naut_event event = {
.type = type,
.torrent_id = (uint64_t)torrent_id,
.index = (uint32_t)index,
.message = json_string_value(json_object_get(params, "message")),
.path = json_string_value(json_object_get(params, "path")),
};
naut_event_emit(state->events, &event);
*error = NAUT_OK;
return json_true();
}
static json_t *rpc_shutdown(void *opaque, const json_t *params,
naut_err *error) {
(void)params;
daemon_state *state = opaque;
state->stopping = true;
*error = NAUT_OK;
return json_true();
}
static uint8_t *read_file(const char *path, size_t *len) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; }
long n = ftell(f);
if (n < 0 || fseek(f, 0, SEEK_SET) != 0) { fclose(f); return NULL; }
uint8_t *buf = malloc((size_t)n);
if (!buf) { fclose(f); return NULL; }
if (fread(buf, 1, (size_t)n, f) != (size_t)n) {
free(buf); fclose(f); return NULL;
}
fclose(f);
*len = (size_t)n;
return buf;
}
/* add_torrent {torrent_id, torrent: <.torrent path>, root: <output dir>} opens
* the torrent's storage and registers it so move_file can later relocate one of
* its files. This is the control-plane seam that binds a script's move command
* to a concrete naut_storage; it runs on the daemon owner thread. */
static json_t *rpc_add_torrent(void *opaque, const json_t *params,
naut_err *error) {
daemon_state *state = opaque;
if (!json_is_object(params)) { *error = NAUT_ERR_INVAL; return NULL; }
json_int_t torrent_id =
json_integer_value(json_object_get(params, "torrent_id"));
const char *torrent_path =
json_string_value(json_object_get(params, "torrent"));
const char *root = json_string_value(json_object_get(params, "root"));
if (torrent_id < 0 || !torrent_path || !root) {
*error = NAUT_ERR_INVAL;
return NULL;
}
if (naut_session_has(state->session, (uint64_t)torrent_id)) {
*error = NAUT_ERR_INVAL;
return NULL;
}
size_t len = 0;
uint8_t *raw = read_file(torrent_path, &len);
if (!raw) { *error = NAUT_ERR_IO; return NULL; }
naut_metainfo mi;
naut_err err = naut_metainfo_parse(raw, len, &mi);
free(raw);
if (err != NAUT_OK) { *error = err; return NULL; }
naut_storage *storage =
naut_storage_open(mi.files, mi.num_files, root, &err);
if (!storage) {
naut_metainfo_free(&mi);
*error = err != NAUT_OK ? err : NAUT_ERR_IO;
return NULL;
}
naut_metainfo_free(&mi);
err = naut_session_add(state->session, (uint64_t)torrent_id, storage);
if (err != NAUT_OK) {
naut_storage_close(storage);
*error = err;
return NULL;
}
*error = NAUT_OK;
return json_true();
}
static naut_err queue_move(void *opaque, uint64_t torrent_id,
uint32_t file_index, const char *destination) {
daemon_state *state = opaque;
pthread_mutex_lock(&state->move_lock);
if (state->move_count == MOVE_QUEUE_CAPACITY) {
pthread_mutex_unlock(&state->move_lock);
return NAUT_ERR_FULL;
}
size_t tail = (state->move_head + state->move_count) %
MOVE_QUEUE_CAPACITY;
state->moves[tail] = (move_command) {
.torrent_id = torrent_id,
.file_index = file_index,
};
snprintf(state->moves[tail].destination,
sizeof state->moves[tail].destination, "%s", destination);
state->move_count++;
pthread_mutex_unlock(&state->move_lock);
return NAUT_OK;
}
static void drain_moves(daemon_state *state) {
/* Copy each pending command out under the lock, then perform the relocate
* with the lock released (so the script thread can keep enqueuing). All
* relocates run on this, the owner thread, as the session requires. */
for (;;) {
move_command command;
pthread_mutex_lock(&state->move_lock);
if (state->move_count == 0) {
pthread_mutex_unlock(&state->move_lock);
return;
}
command = state->moves[state->move_head];
state->move_head = (state->move_head + 1) % MOVE_QUEUE_CAPACITY;
state->move_count--;
state->moves_processed++;
pthread_mutex_unlock(&state->move_lock);
naut_err err = naut_session_move_file(state->session,
command.torrent_id,
command.file_index,
command.destination);
if (err == NAUT_OK)
NAUT_INFO("moved torrent=%llu file=%u -> %s",
(unsigned long long)command.torrent_id,
command.file_index, command.destination);
else
NAUT_WARN("move torrent=%llu file=%u -> %s failed: %s",
(unsigned long long)command.torrent_id,
command.file_index, command.destination,
naut_strerror(err));
}
}
static void broadcast_event(void *opaque, const naut_event *event) {
daemon_state *state = opaque;
json_t *payload = naut_rpc_event_json(event);
if (!payload) return;
pthread_mutex_lock(&state->subscriber_lock);
for (size_t i = 0; i < state->subscriber_count;) {
if (naut_rpc_send_json(state->subscribers[i], NAUT_RPC_EVENT,
payload) == NAUT_OK) {
i++;
continue;
}
close(state->subscribers[i]);
state->subscribers[i] =
state->subscribers[--state->subscriber_count];
}
pthread_mutex_unlock(&state->subscriber_lock);
json_decref(payload);
}
static int listen_unix(const char *path) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct sockaddr_un address;
memset(&address, 0, sizeof address);
address.sun_family = AF_UNIX;
if (strlen(path) >= sizeof address.sun_path) {
close(fd);
return -1;
}
strcpy(address.sun_path, path);
unlink(path);
if (bind(fd, (struct sockaddr *)&address, sizeof address) != 0 ||
listen(fd, 32) != 0) {
close(fd);
return -1;
}
return fd;
}
static json_t *response(bool ok, json_t *result, naut_err error) {
json_t *reply = json_object();
if (!reply) return NULL;
json_object_set_new(reply, "ok", json_boolean(ok));
if (ok) {
json_object_set(reply, "result", result ? result : json_null());
} else {
json_object_set_new(reply, "code", json_integer(error));
json_object_set_new(reply, "error",
json_string(naut_strerror(error)));
}
return reply;
}
static void add_subscriber(daemon_state *state, int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags >= 0) fcntl(fd, F_SETFL, flags | O_NONBLOCK);
pthread_mutex_lock(&state->subscriber_lock);
if (state->subscriber_count < MAX_SUBSCRIBERS) {
state->subscribers[state->subscriber_count++] = fd;
fd = -1;
}
pthread_mutex_unlock(&state->subscriber_lock);
if (fd >= 0) close(fd);
}
static void handle_client(daemon_state *state, int fd) {
struct timeval timeout = {.tv_sec = 2};
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);
naut_rpc_frame_type type;
json_t *request = NULL;
naut_err error = naut_rpc_recv_json(fd, &type, &request);
if (error != NAUT_OK || type != NAUT_RPC_REQUEST ||
!json_is_object(request)) {
json_decref(request);
close(fd);
return;
}
const char *method =
json_string_value(json_object_get(request, "method"));
json_t *params = json_object_get(request, "params");
if (method && strcmp(method, "subscribe") == 0) {
json_t *subscribed = json_string("subscribed");
json_t *reply = response(true, subscribed, NAUT_OK);
json_decref(subscribed);
if (reply &&
naut_rpc_send_json(fd, NAUT_RPC_RESPONSE, reply) == NAUT_OK)
add_subscriber(state, fd);
else
close(fd);
json_decref(reply);
json_decref(request);
return;
}
if (!method) error = NAUT_ERR_INVAL;
json_t *result = method
? naut_rpc_dispatch(state->rpc, method, params, &error) : NULL;
json_t *reply = response(error == NAUT_OK && result, result, error);
json_decref(result);
if (reply) {
naut_rpc_send_json(fd, NAUT_RPC_RESPONSE, reply);
json_decref(reply);
}
json_decref(request);
close(fd);
}
static bool register_commands(daemon_state *state) {
return naut_rpc_register(state->rpc, "ping", rpc_ping, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "status", rpc_status, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "plugins", rpc_plugins, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "emit", rpc_emit, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "add_torrent", rpc_add_torrent, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "shutdown", rpc_shutdown, state) == NAUT_OK;
}
static void usage(const char *program) {
fprintf(stderr,
"usage: %s [--socket PATH] [--plugin PATH]... [--script PATH]\n",
program);
}
int main(int argc, char **argv) {
const char *socket_path = DEFAULT_SOCKET;
const char *script_path = NULL;
const char *plugin_paths[64];
size_t plugin_count = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--socket") == 0 && i + 1 < argc)
socket_path = argv[++i];
else if (strcmp(argv[i], "--plugin") == 0 && i + 1 < argc &&
plugin_count < NAUT_ARRAY_LEN(plugin_paths))
plugin_paths[plugin_count++] = argv[++i];
else if (strcmp(argv[i], "--script") == 0 && i + 1 < argc)
script_path = argv[++i];
else {
usage(argv[0]);
return 2;
}
}
signal(SIGINT, on_signal);
signal(SIGTERM, on_signal);
signal(SIGPIPE, SIG_IGN);
daemon_state state = {0};
pthread_mutex_init(&state.move_lock, NULL);
pthread_mutex_init(&state.subscriber_lock, NULL);
state.events = naut_event_bus_create();
state.rpc = naut_rpc_registry_create();
state.plugins = naut_plugin_manager_create(state.rpc, state.events);
state.session = naut_session_create();
if (!state.events || !state.rpc || !state.plugins || !state.session ||
!register_commands(&state)) {
fprintf(stderr, "nautd: failed to initialize control plane\n");
return 1;
}
for (size_t i = 0; i < plugin_count; i++) {
if (naut_plugin_load(state.plugins, plugin_paths[i]) != NAUT_OK) {
fprintf(stderr, "nautd: failed to load plugin %s\n",
plugin_paths[i]);
return 1;
}
}
if (script_path) {
naut_err error;
state.script = naut_script_create(state.events, script_path, 256,
queue_move, &state, &error);
if (!state.script) {
fprintf(stderr, "nautd: failed to load script %s: %s\n",
script_path, naut_strerror(error));
return 1;
}
}
uint64_t event_subscription;
if (naut_event_subscribe(state.events, broadcast_event, &state,
&event_subscription) != NAUT_OK)
return 1;
int listener = listen_unix(socket_path);
if (listener < 0) {
perror("nautd: listen");
return 1;
}
NAUT_INFO("nautd listening on %s", socket_path);
while (!state.stopping && !interrupted) {
struct pollfd pollfd = {.fd = listener, .events = POLLIN};
int ready = poll(&pollfd, 1, 100);
if (ready > 0 && (pollfd.revents & POLLIN)) {
int client = accept(listener, NULL, NULL);
if (client >= 0) handle_client(&state, client);
} else if (ready < 0 && errno != EINTR) {
break;
}
drain_moves(&state);
}
close(listener);
unlink(socket_path);
naut_event_unsubscribe(state.events, event_subscription);
pthread_mutex_lock(&state.subscriber_lock);
for (size_t i = 0; i < state.subscriber_count; i++)
close(state.subscribers[i]);
pthread_mutex_unlock(&state.subscriber_lock);
naut_script_destroy(state.script); /* joins the script thread */
drain_moves(&state); /* flush any moves it left queued */
naut_plugin_manager_destroy(state.plugins);
naut_rpc_registry_destroy(state.rpc);
naut_session_destroy(state.session);
naut_event_bus_destroy(state.events);
pthread_mutex_destroy(&state.subscriber_lock);
pthread_mutex_destroy(&state.move_lock);
return 0;
}