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

227
src/dht/dht.c Normal file
View file

@ -0,0 +1,227 @@
#include "naut/dht.h"
#include "naut/bencode.h"
#include <stdlib.h>
#include <string.h>
static naut_err finish(naut_bc_writer *w, uint8_t **out, size_t *out_len) {
if (w->err != NAUT_OK) {
naut_err e = w->err;
naut_bc_w_free(w);
return e;
}
*out = w->buf;
*out_len = w->len;
w->buf = NULL;
naut_bc_w_free(w);
return NAUT_OK;
}
static bool valid_common(const uint8_t *tx, size_t tx_len,
const uint8_t id[20], uint8_t **out, size_t *out_len) {
return tx && tx_len > 0 && tx_len <= 8 && id && out && out_len;
}
naut_err naut_dht_build_ping(const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
uint8_t **out, size_t *out_len) {
if (!valid_common(tx, tx_len, id, out, out_len)) return NAUT_ERR_INVAL;
naut_bc_writer w; naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "a"); naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "id"); naut_bc_w_bytes(&w, id, 20);
naut_bc_w_end(&w);
naut_bc_w_cstr(&w, "q"); naut_bc_w_cstr(&w, "ping");
naut_bc_w_cstr(&w, "t"); naut_bc_w_bytes(&w, tx, tx_len);
naut_bc_w_cstr(&w, "y"); naut_bc_w_cstr(&w, "q");
naut_bc_w_end(&w);
return finish(&w, out, out_len);
}
static naut_err build_target_query(const char *query, const char *target_key,
const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
const uint8_t target[20],
uint8_t **out, size_t *out_len) {
if (!query || !target_key || !target ||
!valid_common(tx, tx_len, id, out, out_len)) return NAUT_ERR_INVAL;
naut_bc_writer w; naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "a"); naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "id"); naut_bc_w_bytes(&w, id, 20);
naut_bc_w_cstr(&w, target_key); naut_bc_w_bytes(&w, target, 20);
naut_bc_w_end(&w);
naut_bc_w_cstr(&w, "q"); naut_bc_w_cstr(&w, query);
naut_bc_w_cstr(&w, "t"); naut_bc_w_bytes(&w, tx, tx_len);
naut_bc_w_cstr(&w, "y"); naut_bc_w_cstr(&w, "q");
naut_bc_w_end(&w);
return finish(&w, out, out_len);
}
naut_err naut_dht_build_find_node(const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
const uint8_t target[20],
uint8_t **out, size_t *out_len) {
return build_target_query("find_node", "target", tx, tx_len, id, target,
out, out_len);
}
naut_err naut_dht_build_get_peers(const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
const uint8_t info_hash[20],
uint8_t **out, size_t *out_len) {
return build_target_query("get_peers", "info_hash", tx, tx_len, id,
info_hash, out, out_len);
}
naut_err naut_dht_build_announce_peer(const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
const uint8_t info_hash[20],
uint16_t port, bool implied_port,
const void *token, size_t token_len,
uint8_t **out, size_t *out_len) {
if (!valid_common(tx, tx_len, id, out, out_len) || !info_hash ||
!token || token_len == 0 || token_len > 64 || (!implied_port && port == 0))
return NAUT_ERR_INVAL;
naut_bc_writer w; naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "a"); naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "id"); naut_bc_w_bytes(&w, id, 20);
naut_bc_w_cstr(&w, "implied_port"); naut_bc_w_int(&w, implied_port ? 1 : 0);
naut_bc_w_cstr(&w, "info_hash"); naut_bc_w_bytes(&w, info_hash, 20);
naut_bc_w_cstr(&w, "port"); naut_bc_w_int(&w, port);
naut_bc_w_cstr(&w, "token"); naut_bc_w_bytes(&w, token, token_len);
naut_bc_w_end(&w);
naut_bc_w_cstr(&w, "q"); naut_bc_w_cstr(&w, "announce_peer");
naut_bc_w_cstr(&w, "t"); naut_bc_w_bytes(&w, tx, tx_len);
naut_bc_w_cstr(&w, "y"); naut_bc_w_cstr(&w, "q");
naut_bc_w_end(&w);
return finish(&w, out, out_len);
}
static naut_err parse_nodes(const uint8_t *p, size_t n,
naut_dht_node **out, size_t *count) {
if (n % 26 != 0 || n / 26 > NAUT_DHT_MAX_NODES) return NAUT_ERR_PROTO;
size_t num = n / 26;
naut_dht_node *nodes = calloc(num ? num : 1, sizeof(*nodes));
if (!nodes) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < num; i++) {
const uint8_t *entry = p + i * 26;
memcpy(nodes[i].id, entry, 20);
memcpy(nodes[i].ip, entry + 20, 4);
nodes[i].port = ((uint16_t)entry[24] << 8) | entry[25];
if (nodes[i].port == 0) {
free(nodes);
return NAUT_ERR_PROTO;
}
}
*out = nodes;
*count = num;
return NAUT_OK;
}
static bool peer_duplicate(const naut_peer_addr *peers, size_t n,
const naut_peer_addr *candidate) {
for (size_t i = 0; i < n; i++)
if (peers[i].port == candidate->port &&
memcmp(peers[i].ip, candidate->ip, 4) == 0)
return true;
return false;
}
static naut_err parse_values(const naut_bc *values,
naut_peer_addr **out, size_t *count) {
if (!values || values->type != NAUT_BC_LIST ||
values->v.list.count > NAUT_DHT_MAX_PEERS) return NAUT_ERR_PROTO;
naut_peer_addr *peers = calloc(values->v.list.count ? values->v.list.count : 1,
sizeof(*peers));
if (!peers) return NAUT_ERR_NOMEM;
size_t num = 0;
for (size_t i = 0; i < values->v.list.count; i++) {
const uint8_t *p; size_t n;
if (!naut_bc_get_str(naut_bc_list_at(values, i), &p, &n) || n != 6) {
free(peers);
return NAUT_ERR_PROTO;
}
naut_peer_addr peer;
memcpy(peer.ip, p, 4);
peer.port = ((uint16_t)p[4] << 8) | p[5];
if (peer.port && !peer_duplicate(peers, num, &peer))
peers[num++] = peer;
}
*out = peers;
*count = num;
return NAUT_OK;
}
naut_err naut_dht_parse_response(const uint8_t *data, size_t len,
naut_dht_response *out) {
if (!data || !out) return NAUT_ERR_INVAL;
memset(out, 0, sizeof(*out));
naut_bc_doc *doc = NULL;
naut_err err = naut_bc_parse(data, len, &doc);
if (err != NAUT_OK) return err;
const naut_bc *root = naut_bc_root(doc);
const uint8_t *p; size_t n;
if (!root || root->type != NAUT_BC_DICT ||
!naut_bc_get_str(naut_bc_dict_get(root, "t"), &p, &n) ||
n == 0 || n > sizeof out->transaction) {
err = NAUT_ERR_PROTO;
goto done;
}
memcpy(out->transaction, p, n);
out->transaction_len = n;
const naut_bc *y = naut_bc_dict_get(root, "y");
if (naut_bc_str_eq(y, "e")) {
const naut_bc *e = naut_bc_dict_get(root, "e");
int64_t code;
if (!e || e->type != NAUT_BC_LIST || e->v.list.count < 1 ||
!naut_bc_get_int(naut_bc_list_at(e, 0), &code)) {
err = NAUT_ERR_PROTO;
goto done;
}
out->type = NAUT_DHT_ERROR;
out->error_code = (int)code;
goto done;
}
if (!naut_bc_str_eq(y, "r")) {
err = NAUT_ERR_PROTO;
goto done;
}
out->type = NAUT_DHT_RESPONSE;
const naut_bc *r = naut_bc_dict_get(root, "r");
if (!r || r->type != NAUT_BC_DICT) {
err = NAUT_ERR_PROTO;
goto done;
}
if (naut_bc_get_str(naut_bc_dict_get(r, "id"), &p, &n)) {
if (n != 20) { err = NAUT_ERR_PROTO; goto done; }
memcpy(out->id, p, 20);
out->has_id = true;
}
if (naut_bc_get_str(naut_bc_dict_get(r, "token"), &p, &n)) {
if (n == 0 || n > sizeof out->token) { err = NAUT_ERR_PROTO; goto done; }
memcpy(out->token, p, n);
out->token_len = n;
}
if (naut_bc_get_str(naut_bc_dict_get(r, "nodes"), &p, &n)) {
err = parse_nodes(p, n, &out->nodes, &out->num_nodes);
if (err != NAUT_OK) goto done;
}
const naut_bc *values = naut_bc_dict_get(r, "values");
if (values) {
err = parse_values(values, &out->peers, &out->num_peers);
if (err != NAUT_OK) goto done;
}
done:
naut_bc_free(doc);
if (err != NAUT_OK) naut_dht_response_free(out);
return err;
}
void naut_dht_response_free(naut_dht_response *response) {
if (!response) return;
free(response->nodes);
free(response->peers);
memset(response, 0, sizeof(*response));
}

154
src/dht/fetch.c Normal file
View file

@ -0,0 +1,154 @@
#include "naut/dht.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
typedef struct {
struct sockaddr_in addr;
bool queried;
} candidate;
static bool parse_endpoint(const char *text, struct sockaddr_in *out) {
const char *colon = strrchr(text, ':');
if (!colon || colon == text) return false;
char host[256], port[16];
size_t host_len = (size_t)(colon - text);
size_t port_len = strlen(colon + 1);
if (host_len >= sizeof host || port_len == 0 || port_len >= sizeof port)
return false;
memcpy(host, text, host_len); host[host_len] = 0;
memcpy(port, colon + 1, port_len + 1);
struct addrinfo hints, *result = NULL;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
if (getaddrinfo(host, port, &hints, &result) != 0) return false;
memcpy(out, result->ai_addr, sizeof(*out));
freeaddrinfo(result);
return true;
}
static bool same_addr(const struct sockaddr_in *a, const struct sockaddr_in *b) {
return a->sin_port == b->sin_port && a->sin_addr.s_addr == b->sin_addr.s_addr;
}
static bool add_candidate(candidate *v, size_t *n, const struct sockaddr_in *addr) {
if (addr->sin_port == 0) return true;
for (size_t i = 0; i < *n; i++)
if (same_addr(&v[i].addr, addr)) return true;
if (*n == NAUT_DHT_MAX_NODES) return false;
v[*n].addr = *addr;
v[*n].queried = false;
(*n)++;
return true;
}
static bool add_peer(naut_peer_addr *v, size_t *n, const naut_peer_addr *peer) {
for (size_t i = 0; i < *n; i++)
if (v[i].port == peer->port && memcmp(v[i].ip, peer->ip, 4) == 0)
return true;
if (*n == NAUT_DHT_MAX_PEERS) return false;
v[(*n)++] = *peer;
return true;
}
static void node_id(uint8_t id[20]) {
int fd = open("/dev/urandom", O_RDONLY);
if (fd >= 0) {
size_t done = 0;
while (done < 20) {
ssize_t n = read(fd, id + done, 20 - done);
if (n <= 0) break;
done += (size_t)n;
}
close(fd);
if (done == 20) return;
}
for (size_t i = 0; i < 20; i++) id[i] = (uint8_t)rand();
}
naut_err naut_dht_get_peers(const char *const *bootstrap, size_t num_bootstrap,
const uint8_t info_hash[20],
naut_peer_addr **peers, size_t *num_peers) {
if (!bootstrap || num_bootstrap == 0 || !info_hash || !peers || !num_peers)
return NAUT_ERR_INVAL;
*peers = NULL; *num_peers = 0;
candidate nodes[NAUT_DHT_MAX_NODES];
size_t node_count = 0;
for (size_t i = 0; i < num_bootstrap; i++) {
struct sockaddr_in addr;
if (parse_endpoint(bootstrap[i], &addr))
add_candidate(nodes, &node_count, &addr);
}
if (node_count == 0) return NAUT_ERR_INVAL;
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) return NAUT_ERR_IO;
naut_peer_addr found[NAUT_DHT_MAX_PEERS];
size_t found_count = 0;
uint8_t id[20];
node_id(id);
uint16_t tx_counter = 1;
size_t queries = 0;
while (queries < 64 && found_count < NAUT_DHT_MAX_PEERS) {
size_t index = SIZE_MAX;
for (size_t i = 0; i < node_count; i++)
if (!nodes[i].queried) { index = i; break; }
if (index == SIZE_MAX) break;
nodes[index].queried = true;
queries++;
uint8_t tx[2] = { (uint8_t)(tx_counter >> 8), (uint8_t)tx_counter };
tx_counter++;
uint8_t *query = NULL; size_t query_len = 0;
if (naut_dht_build_get_peers(tx, sizeof tx, id, info_hash,
&query, &query_len) != NAUT_OK)
continue;
ssize_t sent = sendto(fd, query, query_len, 0,
(struct sockaddr *)&nodes[index].addr,
sizeof(nodes[index].addr));
free(query);
if (sent < 0) continue;
struct pollfd pfd = { .fd = fd, .events = POLLIN };
if (poll(&pfd, 1, 1000) <= 0) continue;
uint8_t packet[65536];
ssize_t received = recv(fd, packet, sizeof packet, 0);
if (received <= 0) continue;
naut_dht_response response;
if (naut_dht_parse_response(packet, (size_t)received, &response) != NAUT_OK)
continue;
if (response.transaction_len != sizeof tx ||
memcmp(response.transaction, tx, sizeof tx) != 0 ||
response.type != NAUT_DHT_RESPONSE) {
naut_dht_response_free(&response);
continue;
}
for (size_t i = 0; i < response.num_peers; i++)
add_peer(found, &found_count, &response.peers[i]);
for (size_t i = 0; i < response.num_nodes; i++) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
memcpy(&addr.sin_addr, response.nodes[i].ip, 4);
addr.sin_port = htons(response.nodes[i].port);
add_candidate(nodes, &node_count, &addr);
}
naut_dht_response_free(&response);
}
close(fd);
if (found_count == 0) return NAUT_ERR_EMPTY;
naut_peer_addr *result = malloc(found_count * sizeof(*result));
if (!result) return NAUT_ERR_NOMEM;
memcpy(result, found, found_count * sizeof(*result));
*peers = result;
*num_peers = found_count;
return NAUT_OK;
}