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

162
src/tracker/fetch.c Normal file
View file

@ -0,0 +1,162 @@
#include "naut/tracker.h"
#include "naut/log.h"
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/time.h>
#define TRACKER_RESPONSE_MAX (16u << 20)
static int dial(const char *host, const char *port, int socktype) {
struct addrinfo hints, *res = NULL, *ai;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; /* IPv4 for now (compact peers are v4) */
hints.ai_socktype = socktype;
if (getaddrinfo(host, port, &hints, &res) != 0) return -1;
int fd = -1;
for (ai = res; ai; ai = ai->ai_next) {
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (fd < 0) continue;
struct timeval tv = { .tv_sec = 10, .tv_usec = 0 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) break;
close(fd); fd = -1;
}
freeaddrinfo(res);
return fd;
}
/* split "http://host[:port]/path" */
static bool parse_http_url(const char *url, char *host, size_t hostsz,
char *port, size_t portsz, const char **path) {
if (strncmp(url, "http://", 7) != 0) return false;
const char *h = url + 7;
const char *slash = strchr(h, '/');
const char *hostend = slash ? slash : h + strlen(h);
const char *colon = memchr(h, ':', (size_t)(hostend - h));
size_t hlen = colon ? (size_t)(colon - h) : (size_t)(hostend - h);
if (hlen >= hostsz) return false;
memcpy(host, h, hlen); host[hlen] = 0;
if (colon) {
size_t plen = (size_t)(hostend - colon - 1);
if (plen >= portsz) return false;
memcpy(port, colon + 1, plen); port[plen] = 0;
} else { snprintf(port, portsz, "80"); }
*path = slash ? slash : "/";
return true;
}
static bool write_all(int fd, const void *data, size_t len) {
const uint8_t *p = data;
while (len) {
ssize_t n = write(fd, p, len);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
p += (size_t)n;
len -= (size_t)n;
}
return true;
}
naut_err naut_tracker_announce_http(const char *url, naut_tracker_response *out) {
char host[256], port[16]; const char *path;
if (!parse_http_url(url, host, sizeof host, port, sizeof port, &path))
return NAUT_ERR_INVAL;
int fd = dial(host, port, SOCK_STREAM);
if (fd < 0) { NAUT_WARN("tracker connect %s:%s failed", host, port); return NAUT_ERR_IO; }
char req[2048];
int rn = snprintf(req, sizeof req,
"GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: Naut/0.1\r\nAccept: */*\r\n\r\n",
path, host);
if (rn < 0 || (size_t)rn >= sizeof req ||
!write_all(fd, req, (size_t)rn)) {
close(fd);
return NAUT_ERR_IO;
}
/* read whole response (server closes on HTTP/1.0) */
size_t cap = 1 << 16, len = 0;
uint8_t *buf = malloc(cap);
if (!buf) { close(fd); return NAUT_ERR_NOMEM; }
naut_err read_error = NAUT_OK;
for (;;) {
if (len == cap) {
if (cap == TRACKER_RESPONSE_MAX) {
read_error = NAUT_ERR_FULL;
break;
}
size_t next_cap = NAUT_MIN(cap * 2, (size_t)TRACKER_RESPONSE_MAX);
uint8_t *next = realloc(buf, next_cap);
if (!next) {
read_error = NAUT_ERR_NOMEM;
break;
}
buf = next;
cap = next_cap;
}
ssize_t r = read(fd, buf + len, cap - len);
if (r < 0) {
if (errno == EINTR) continue;
read_error = NAUT_ERR_IO;
break;
}
if (r == 0) break;
len += (size_t)r;
}
close(fd);
if (read_error != NAUT_OK) {
free(buf);
return read_error;
}
/* find body after CRLFCRLF */
uint8_t *body = NULL; size_t blen = 0;
for (size_t i = 0; i + 3 < len; i++)
if (buf[i]=='\r'&&buf[i+1]=='\n'&&buf[i+2]=='\r'&&buf[i+3]=='\n') {
body = buf + i + 4; blen = len - (i + 4); break;
}
bool success = len >= 12 && memcmp(buf, "HTTP/", 5) == 0 &&
buf[9] == '2';
naut_err e = success && body
? naut_tracker_parse_http(body, blen, out)
: NAUT_ERR_PROTO;
free(buf);
return e;
}
naut_err naut_tracker_announce_udp(const char *host, uint16_t port,
const naut_announce_req *req,
naut_tracker_response *out) {
char portstr[16]; snprintf(portstr, sizeof portstr, "%u", port);
int fd = dial(host, portstr, SOCK_DGRAM);
if (fd < 0) return NAUT_ERR_IO;
srand((unsigned)time(NULL) ^ (unsigned)getpid());
uint32_t txid = (uint32_t)rand();
uint8_t pkt[98], resp[1500];
naut_udp_build_connect(pkt, txid);
if (write(fd, pkt, 16) != 16) { close(fd); return NAUT_ERR_IO; }
ssize_t r = read(fd, resp, sizeof resp);
uint64_t cid;
if (r < 0 || naut_udp_parse_connect(resp, (size_t)r, txid, &cid) != NAUT_OK) {
close(fd); return NAUT_ERR_IO;
}
txid++;
naut_udp_build_announce(pkt, cid, txid, req);
if (write(fd, pkt, 98) != 98) { close(fd); return NAUT_ERR_IO; }
r = read(fd, resp, sizeof resp);
naut_err e = (r < 0) ? NAUT_ERR_IO
: naut_udp_parse_announce(resp, (size_t)r, txid, out);
close(fd);
return e;
}

120
src/tracker/tracker.c Normal file
View file

@ -0,0 +1,120 @@
#include "naut/tracker.h"
#include "naut/bencode.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void naut_tracker_response_free(naut_tracker_response *r) {
free(r->peers); r->peers = NULL; r->num_peers = 0;
free(r->failure); r->failure = NULL;
}
/* percent-encode raw bytes per RFC 3986 (unreserved chars pass through) */
static size_t pct_encode(const uint8_t *in, size_t n, char *out, size_t outsz) {
static const char hx[] = "0123456789ABCDEF";
size_t o = 0;
for (size_t i = 0; i < n; i++) {
uint8_t c = in[i];
bool unreserved = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_' ||
c == '.' || c == '~';
if (unreserved) {
if (o + 1 >= outsz) return 0;
out[o++] = (char)c;
} else {
if (o + 3 >= outsz) return 0;
out[o++] = '%'; out[o++] = hx[c >> 4]; out[o++] = hx[c & 15];
}
}
return o;
}
size_t naut_tracker_http_url(const char *base, const naut_announce_req *req,
char *out, size_t outsz) {
static const char *ev[] = { "", "completed", "started", "stopped" };
if (req->event < NAUT_TEV_NONE || req->event > NAUT_TEV_STOPPED) return 0;
char ih[61], pid[61]; /* 20*3 = 60 worst case + NUL */
size_t ihn = pct_encode(req->info_hash, 20, ih, sizeof ih);
size_t pidn = pct_encode(req->peer_id, 20, pid, sizeof pid);
if (!ihn || !pidn) return 0;
ih[ihn] = 0; pid[pidn] = 0;
const char *sep = strchr(base, '?') ? "&" : "?";
int n = snprintf(out, outsz,
"%s%sinfo_hash=%s&peer_id=%s&port=%u&uploaded=%llu&downloaded=%llu"
"&left=%llu&compact=1&numwant=%d%s%s&key=%u",
base, sep, ih, pid, req->port,
(unsigned long long)req->uploaded, (unsigned long long)req->downloaded,
(unsigned long long)req->left, req->numwant < 0 ? 50 : req->numwant,
req->event ? "&event=" : "", ev[req->event], req->key);
if (n < 0 || (size_t)n >= outsz) return 0;
return (size_t)n;
}
static naut_err parse_peers(const naut_bc *peers, naut_tracker_response *out) {
const uint8_t *p; size_t n;
if (naut_bc_get_str(peers, &p, &n)) { /* compact: 6 bytes each */
if (n % 6 != 0) return NAUT_ERR_PROTO;
out->num_peers = n / 6;
out->peers = calloc(out->num_peers ? out->num_peers : 1, sizeof(naut_peer_addr));
if (!out->peers) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < out->num_peers; i++) {
memcpy(out->peers[i].ip, p + i*6, 4);
out->peers[i].port = ((uint16_t)p[i*6+4] << 8) | p[i*6+5];
}
return NAUT_OK;
}
if (peers && peers->type == NAUT_BC_LIST) { /* dict form */
out->peers = calloc(peers->v.list.count ? peers->v.list.count : 1, sizeof(naut_peer_addr));
if (!out->peers) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < peers->v.list.count; i++) {
const naut_bc *pe = naut_bc_list_at(peers, i);
const uint8_t *ips; size_t ipn; int64_t port;
if (!naut_bc_get_str(naut_bc_dict_get(pe, "ip"), &ips, &ipn)) continue;
if (!naut_bc_get_int(naut_bc_dict_get(pe, "port"), &port)) continue;
unsigned a, b, c, dd;
char tmp[64];
if (ipn >= sizeof tmp) continue;
memcpy(tmp, ips, ipn); tmp[ipn] = 0;
if (sscanf(tmp, "%u.%u.%u.%u", &a, &b, &c, &dd) != 4) continue;
if (a > 255 || b > 255 || c > 255 || dd > 255 ||
port <= 0 || port > UINT16_MAX) continue;
naut_peer_addr *pa = &out->peers[out->num_peers++];
pa->ip[0]=(uint8_t)a; pa->ip[1]=(uint8_t)b; pa->ip[2]=(uint8_t)c; pa->ip[3]=(uint8_t)dd;
pa->port = (uint16_t)port;
}
return NAUT_OK;
}
return NAUT_ERR_PROTO;
}
naut_err naut_tracker_parse_http(const uint8_t *body, size_t len,
naut_tracker_response *out) {
memset(out, 0, sizeof(*out));
out->seeders = out->leechers = -1;
naut_bc_doc *doc = NULL;
naut_err e = naut_bc_parse(body, len, &doc);
if (e != NAUT_OK) return e;
const naut_bc *root = naut_bc_root(doc);
const uint8_t *fp; size_t fn;
if (naut_bc_get_str(naut_bc_dict_get(root, "failure reason"), &fp, &fn)) {
out->failure = malloc(fn + 1);
if (out->failure) { memcpy(out->failure, fp, fn); out->failure[fn] = 0; }
naut_bc_free(doc);
return NAUT_ERR_PROTO; /* tracker reported failure */
}
int64_t iv = 0;
naut_bc_get_int(naut_bc_dict_get(root, "interval"), &iv);
out->interval = (int32_t)iv;
int64_t sc;
if (naut_bc_get_int(naut_bc_dict_get(root, "complete"), &sc)) out->seeders = (int32_t)sc;
if (naut_bc_get_int(naut_bc_dict_get(root, "incomplete"), &sc)) out->leechers = (int32_t)sc;
e = parse_peers(naut_bc_dict_get(root, "peers"), out);
naut_bc_free(doc);
if (e != NAUT_OK) { naut_tracker_response_free(out); return e; }
return NAUT_OK;
}

81
src/tracker/udp.c Normal file
View file

@ -0,0 +1,81 @@
#include "naut/tracker.h"
#include <stdlib.h>
#include <string.h>
#define UDP_PROTOCOL_ID 0x41727101980ULL /* BEP-15 magic */
#define ACTION_CONNECT 0
#define ACTION_ANNOUNCE 1
#define ACTION_ERROR 3
static void wr16(uint8_t *p, uint16_t v) { p[0]=(uint8_t)(v>>8); p[1]=(uint8_t)v; }
static void wr32(uint8_t *p, uint32_t v) {
p[0]=(uint8_t)(v>>24); p[1]=(uint8_t)(v>>16); p[2]=(uint8_t)(v>>8); p[3]=(uint8_t)v;
}
static void wr64(uint8_t *p, uint64_t v) { wr32(p, (uint32_t)(v>>32)); wr32(p+4, (uint32_t)v); }
static uint32_t rd32(const uint8_t *p) {
return ((uint32_t)p[0]<<24)|((uint32_t)p[1]<<16)|((uint32_t)p[2]<<8)|p[3];
}
static uint64_t rd64(const uint8_t *p) { return ((uint64_t)rd32(p)<<32) | rd32(p+4); }
void naut_udp_build_connect(uint8_t out[16], uint32_t txid) {
wr64(out, UDP_PROTOCOL_ID);
wr32(out + 8, ACTION_CONNECT);
wr32(out + 12, txid);
}
naut_err naut_udp_parse_connect(const uint8_t *in, size_t len, uint32_t txid,
uint64_t *connection_id) {
if (len < 16) return NAUT_ERR_PROTO;
if (rd32(in) != ACTION_CONNECT) return NAUT_ERR_PROTO;
if (rd32(in + 4) != txid) return NAUT_ERR_PROTO;
*connection_id = rd64(in + 8);
return NAUT_OK;
}
void naut_udp_build_announce(uint8_t out[98], uint64_t connection_id,
uint32_t txid, const naut_announce_req *req) {
wr64(out + 0, connection_id);
wr32(out + 8, ACTION_ANNOUNCE);
wr32(out + 12, txid);
memcpy(out + 16, req->info_hash, 20);
memcpy(out + 36, req->peer_id, 20);
wr64(out + 56, req->downloaded);
wr64(out + 64, req->left);
wr64(out + 72, req->uploaded);
wr32(out + 80, (uint32_t)req->event);
wr32(out + 84, 0); /* IP: 0 = source */
wr32(out + 88, req->key);
wr32(out + 92, (uint32_t)(req->numwant < 0 ? 50 : req->numwant));
wr16(out + 96, req->port);
}
naut_err naut_udp_parse_announce(const uint8_t *in, size_t len, uint32_t txid,
naut_tracker_response *out) {
memset(out, 0, sizeof(*out));
out->seeders = out->leechers = -1;
if (len < 8) return NAUT_ERR_PROTO;
uint32_t action = rd32(in);
if (rd32(in + 4) != txid) return NAUT_ERR_PROTO;
if (action == ACTION_ERROR) {
size_t mn = len - 8;
out->failure = malloc(mn + 1);
if (out->failure) { memcpy(out->failure, in + 8, mn); out->failure[mn] = 0; }
return NAUT_ERR_PROTO;
}
if (action != ACTION_ANNOUNCE || len < 20 || (len - 20) % 6 != 0)
return NAUT_ERR_PROTO;
out->interval = (int32_t)rd32(in + 8);
out->leechers = (int32_t)rd32(in + 12);
out->seeders = (int32_t)rd32(in + 16);
size_t avail = (len - 20) / 6;
out->peers = calloc(avail ? avail : 1, sizeof(naut_peer_addr));
if (!out->peers) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < avail; i++) {
const uint8_t *p = in + 20 + i*6;
memcpy(out->peers[i].ip, p, 4);
out->peers[i].port = ((uint16_t)p[4] << 8) | p[5];
}
out->num_peers = avail;
return NAUT_OK;
}