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

296
src/peer/extension.c Normal file
View file

@ -0,0 +1,296 @@
#include "naut/extension.h"
#include "naut/bencode.h"
#include "naut/peer.h"
#include <stdlib.h>
#include <string.h>
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 naut_err frame(uint8_t ext_id, const uint8_t *payload, size_t payload_len,
uint8_t **out, size_t *out_len) {
if (!out || !out_len || payload_len > UINT32_MAX - 2) return NAUT_ERR_INVAL;
size_t n = 6 + payload_len;
uint8_t *buf = malloc(n);
if (!buf) return NAUT_ERR_NOMEM;
wr32(buf, (uint32_t)(2 + payload_len));
buf[4] = NAUT_MSG_EXTENDED;
buf[5] = ext_id;
if (payload_len) memcpy(buf + 6, payload, payload_len);
*out = buf;
*out_len = n;
return NAUT_OK;
}
naut_err naut_ext_build_handshake(uint8_t ut_metadata_id, uint8_t ut_pex_id,
uint32_t metadata_size, uint16_t port,
uint8_t **out, size_t *out_len) {
naut_bc_writer w;
naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "m");
naut_bc_w_dict_begin(&w);
if (ut_metadata_id) {
naut_bc_w_cstr(&w, "ut_metadata");
naut_bc_w_int(&w, ut_metadata_id);
}
if (ut_pex_id) {
naut_bc_w_cstr(&w, "ut_pex");
naut_bc_w_int(&w, ut_pex_id);
}
naut_bc_w_end(&w);
if (metadata_size) {
naut_bc_w_cstr(&w, "metadata_size");
naut_bc_w_int(&w, metadata_size);
}
if (port) {
naut_bc_w_cstr(&w, "p");
naut_bc_w_int(&w, port);
}
naut_bc_w_cstr(&w, "reqq");
naut_bc_w_int(&w, 256);
naut_bc_w_cstr(&w, "v");
naut_bc_w_cstr(&w, "Naut/0.1");
naut_bc_w_end(&w);
if (w.err != NAUT_OK) {
naut_err e = w.err;
naut_bc_w_free(&w);
return e;
}
naut_err e = frame(0, w.buf, w.len, out, out_len);
naut_bc_w_free(&w);
return e;
}
static bool get_u32(const naut_bc *dict, const char *key, uint32_t *out) {
int64_t v;
if (!naut_bc_get_int(naut_bc_dict_get(dict, key), &v) ||
v < 0 || v > UINT32_MAX) return false;
*out = (uint32_t)v;
return true;
}
naut_err naut_ext_parse_handshake(const uint8_t *payload, size_t len,
naut_ext_handshake *out) {
if (!payload || !out) return NAUT_ERR_INVAL;
memset(out, 0, sizeof(*out));
naut_bc_doc *doc = NULL;
naut_err e = naut_bc_parse(payload, len, &doc);
if (e != NAUT_OK) return e;
const naut_bc *root = naut_bc_root(doc);
if (!root || root->type != NAUT_BC_DICT) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
const naut_bc *m = naut_bc_dict_get(root, "m");
uint32_t v;
if (m && m->type != NAUT_BC_DICT) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
if (m && get_u32(m, "ut_metadata", &v) && v <= UINT8_MAX)
out->ut_metadata = (uint8_t)v;
if (m && get_u32(m, "ut_pex", &v) && v <= UINT8_MAX)
out->ut_pex = (uint8_t)v;
if (get_u32(root, "metadata_size", &v)) {
if (v == 0 || v > NAUT_METADATA_MAX) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
out->metadata_size = v;
}
if (get_u32(root, "reqq", &v)) out->reqq = v;
if (get_u32(root, "p", &v) && v <= UINT16_MAX) out->port = (uint16_t)v;
naut_bc_free(doc);
return NAUT_OK;
}
naut_err naut_metadata_build(uint8_t ext_id, naut_metadata_type type,
uint32_t piece, uint32_t total_size,
const void *data, size_t data_len,
uint8_t **out, size_t *out_len) {
if (!ext_id || type > NAUT_METADATA_REJECT ||
(type == NAUT_METADATA_DATA && (!data || total_size == 0)) ||
(type != NAUT_METADATA_DATA && data_len != 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, "msg_type"); naut_bc_w_int(&w, type);
naut_bc_w_cstr(&w, "piece"); naut_bc_w_int(&w, piece);
if (type == NAUT_METADATA_DATA) {
naut_bc_w_cstr(&w, "total_size"); naut_bc_w_int(&w, total_size);
}
naut_bc_w_end(&w);
if (w.err != NAUT_OK) {
naut_err e = w.err;
naut_bc_w_free(&w);
return e;
}
if (data_len > SIZE_MAX - w.len) {
naut_bc_w_free(&w);
return NAUT_ERR_RANGE;
}
size_t payload_len = w.len + data_len;
uint8_t *payload = malloc(payload_len ? payload_len : 1);
if (!payload) {
naut_bc_w_free(&w);
return NAUT_ERR_NOMEM;
}
memcpy(payload, w.buf, w.len);
if (data_len) memcpy(payload + w.len, data, data_len);
naut_err e = frame(ext_id, payload, payload_len, out, out_len);
free(payload);
naut_bc_w_free(&w);
return e;
}
naut_err naut_metadata_parse(const uint8_t *payload, size_t len,
naut_metadata_msg *out) {
if (!payload || !out) return NAUT_ERR_INVAL;
memset(out, 0, sizeof(*out));
naut_bc_doc *doc = NULL;
size_t used = 0;
naut_err e = naut_bc_parse_prefix(payload, len, &doc, &used);
if (e != NAUT_OK) return e;
const naut_bc *root = naut_bc_root(doc);
uint32_t type, piece;
if (!root || root->type != NAUT_BC_DICT ||
!get_u32(root, "msg_type", &type) || type > NAUT_METADATA_REJECT ||
!get_u32(root, "piece", &piece)) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
out->type = (naut_metadata_type)type;
out->piece = piece;
if (out->type == NAUT_METADATA_DATA) {
if (!get_u32(root, "total_size", &out->total_size) ||
out->total_size == 0 || out->total_size > NAUT_METADATA_MAX) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
out->data = payload + used;
out->data_len = len - used;
uint32_t pieces = (out->total_size + NAUT_METADATA_BLOCK - 1) /
NAUT_METADATA_BLOCK;
if (piece >= pieces ||
out->data_len != (piece + 1 < pieces
? NAUT_METADATA_BLOCK
: out->total_size - (size_t)piece * NAUT_METADATA_BLOCK)) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
} else if (used != len) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
naut_bc_free(doc);
return NAUT_OK;
}
static naut_err parse_compact(const uint8_t *p, size_t n,
naut_peer_addr **out, size_t *count) {
if (n % 6 != 0 || n / 6 > 200) return NAUT_ERR_PROTO;
size_t num = n / 6;
naut_peer_addr *v = calloc(num ? num : 1, sizeof(*v));
if (!v) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < num; i++) {
memcpy(v[i].ip, p + i * 6, 4);
v[i].port = ((uint16_t)p[i * 6 + 4] << 8) | p[i * 6 + 5];
if (v[i].port == 0) {
free(v);
return NAUT_ERR_PROTO;
}
}
*out = v;
*count = num;
return NAUT_OK;
}
naut_err naut_pex_parse(const uint8_t *payload, size_t len, naut_pex_msg *out) {
if (!payload || !out) return NAUT_ERR_INVAL;
memset(out, 0, sizeof(*out));
naut_bc_doc *doc = NULL;
naut_err e = naut_bc_parse(payload, len, &doc);
if (e != NAUT_OK) return e;
const naut_bc *root = naut_bc_root(doc);
const uint8_t *p; size_t n;
if (!root || root->type != NAUT_BC_DICT) {
e = NAUT_ERR_PROTO;
goto done;
}
if (naut_bc_get_str(naut_bc_dict_get(root, "added"), &p, &n)) {
e = parse_compact(p, n, &out->added, &out->num_added);
if (e != NAUT_OK) goto done;
}
const uint8_t *flags; size_t flags_n;
if (naut_bc_get_str(naut_bc_dict_get(root, "added.f"), &flags, &flags_n)) {
if (flags_n != out->num_added) { e = NAUT_ERR_PROTO; goto done; }
out->added_flags = malloc(flags_n ? flags_n : 1);
if (!out->added_flags) { e = NAUT_ERR_NOMEM; goto done; }
memcpy(out->added_flags, flags, flags_n);
}
if (naut_bc_get_str(naut_bc_dict_get(root, "dropped"), &p, &n)) {
e = parse_compact(p, n, &out->dropped, &out->num_dropped);
if (e != NAUT_OK) goto done;
}
if (out->num_added == 0 && out->num_dropped == 0) e = NAUT_ERR_PROTO;
done:
naut_bc_free(doc);
if (e != NAUT_OK) naut_pex_free(out);
return e;
}
naut_err naut_pex_build(uint8_t ext_id, const naut_pex_msg *msg,
uint8_t **out, size_t *out_len) {
if (!ext_id || !msg || (msg->num_added == 0 && msg->num_dropped == 0) ||
msg->num_added > 200 || msg->num_dropped > 200)
return NAUT_ERR_INVAL;
naut_bc_writer w;
naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
if (msg->num_added) {
uint8_t compact[200 * 6];
for (size_t i = 0; i < msg->num_added; i++) {
memcpy(compact + i * 6, msg->added[i].ip, 4);
compact[i * 6 + 4] = (uint8_t)(msg->added[i].port >> 8);
compact[i * 6 + 5] = (uint8_t)msg->added[i].port;
}
naut_bc_w_cstr(&w, "added");
naut_bc_w_bytes(&w, compact, msg->num_added * 6);
if (msg->added_flags) {
naut_bc_w_cstr(&w, "added.f");
naut_bc_w_bytes(&w, msg->added_flags, msg->num_added);
}
}
if (msg->num_dropped) {
uint8_t compact[200 * 6];
for (size_t i = 0; i < msg->num_dropped; i++) {
memcpy(compact + i * 6, msg->dropped[i].ip, 4);
compact[i * 6 + 4] = (uint8_t)(msg->dropped[i].port >> 8);
compact[i * 6 + 5] = (uint8_t)msg->dropped[i].port;
}
naut_bc_w_cstr(&w, "dropped");
naut_bc_w_bytes(&w, compact, msg->num_dropped * 6);
}
naut_bc_w_end(&w);
if (w.err != NAUT_OK) {
naut_err e = w.err;
naut_bc_w_free(&w);
return e;
}
naut_err e = frame(ext_id, w.buf, w.len, out, out_len);
naut_bc_w_free(&w);
return e;
}
void naut_pex_free(naut_pex_msg *msg) {
if (!msg) return;
free(msg->added);
free(msg->added_flags);
free(msg->dropped);
memset(msg, 0, sizeof(*msg));
}

235
src/peer/metadata.c Normal file
View file

@ -0,0 +1,235 @@
#include "naut/extension.h"
#include "naut/hash.h"
#include "naut/peer.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#define EXT_RESERVED 0x0000000000100000ULL
static bool send_all(int fd, const void *data, size_t len) {
const uint8_t *p = data;
while (len) {
ssize_t n = send(fd, p, len, MSG_NOSIGNAL);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
len -= (size_t)n;
}
return true;
}
static bool recv_exact(int fd, void *data, size_t len) {
uint8_t *p = data;
while (len) {
ssize_t n = recv(fd, p, len, 0);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
len -= (size_t)n;
}
return true;
}
static int connect_peer(const naut_peer_addr *peer) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(peer->port);
memcpy(&addr.sin_addr, peer->ip, sizeof peer->ip);
if (connect(fd, (struct sockaddr *)&addr, sizeof addr) != 0) {
close(fd);
return -1;
}
int one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
struct timeval timeout = { .tv_sec = 10 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof timeout);
return fd;
}
naut_err naut_metadata_fetch(const naut_peer_addr *peer,
const uint8_t info_hash[20],
const uint8_t peer_id[20],
uint8_t **info, size_t *info_len) {
if (!peer || !info_hash || !peer_id || !info || !info_len)
return NAUT_ERR_INVAL;
*info = NULL;
*info_len = 0;
int fd = connect_peer(peer);
if (fd < 0) return NAUT_ERR_IO;
naut_err result = NAUT_ERR_IO;
uint8_t *metadata = NULL, *frame = NULL, *buffer = NULL;
bool *received = NULL;
uint8_t handshake[NAUT_HANDSHAKE_LEN];
naut_peer_handshake_build(handshake, info_hash, peer_id, EXT_RESERVED);
if (!send_all(fd, handshake, sizeof handshake) ||
!recv_exact(fd, handshake, sizeof handshake))
goto done;
uint8_t remote_hash[20], remote_id[20];
uint64_t reserved = 0;
if (!naut_peer_handshake_parse(handshake, remote_hash, remote_id,
&reserved) ||
memcmp(remote_hash, info_hash, 20) != 0 ||
(reserved & EXT_RESERVED) == 0) {
result = NAUT_ERR_PROTO;
goto done;
}
size_t frame_len = 0;
result = naut_ext_build_handshake(NAUT_EXT_UT_METADATA, NAUT_EXT_UT_PEX,
0, 0, &frame, &frame_len);
if (result != NAUT_OK || !send_all(fd, frame, frame_len)) {
result = NAUT_ERR_IO;
goto done;
}
free(frame);
frame = NULL;
size_t cap = 128 * 1024, len = 0;
buffer = malloc(cap);
if (!buffer) {
result = NAUT_ERR_NOMEM;
goto done;
}
naut_ext_handshake remote_ext = {0};
uint32_t piece_count = 0, received_count = 0;
while (!metadata || received_count < piece_count) {
if (len == cap) {
if (cap >= NAUT_METADATA_MAX + (1u << 20)) {
result = NAUT_ERR_PROTO;
goto done;
}
size_t next_cap = cap * 2;
uint8_t *next = realloc(buffer, next_cap);
if (!next) {
result = NAUT_ERR_NOMEM;
goto done;
}
buffer = next;
cap = next_cap;
}
ssize_t n = recv(fd, buffer + len, cap - len, 0);
if (n < 0) {
if (errno == EINTR) continue;
result = NAUT_ERR_IO;
goto done;
}
if (n == 0) {
result = NAUT_ERR_IO;
goto done;
}
len += (size_t)n;
size_t pos = 0;
for (;;) {
naut_msg msg;
int consumed = naut_peer_msg_parse(buffer + pos, len - pos, &msg);
if (consumed == 0) break;
if (consumed < 0) {
result = NAUT_ERR_PROTO;
goto done;
}
pos += (size_t)consumed;
if (msg.type != NAUT_MSG_EXTENDED || msg.payload_len < 1)
continue;
uint8_t ext_id = msg.payload[0];
const uint8_t *payload = msg.payload + 1;
size_t payload_len = msg.payload_len - 1;
if (ext_id == 0) {
result = naut_ext_parse_handshake(payload, payload_len,
&remote_ext);
if (result != NAUT_OK || remote_ext.ut_metadata == 0 ||
remote_ext.metadata_size == 0) {
result = NAUT_ERR_PROTO;
goto done;
}
if (!metadata) {
metadata = malloc(remote_ext.metadata_size);
piece_count =
(remote_ext.metadata_size + NAUT_METADATA_BLOCK - 1) /
NAUT_METADATA_BLOCK;
received = calloc(piece_count, sizeof(*received));
if (!metadata || !received) {
result = NAUT_ERR_NOMEM;
goto done;
}
for (uint32_t piece = 0; piece < piece_count; piece++) {
result = naut_metadata_build(
remote_ext.ut_metadata, NAUT_METADATA_REQUEST,
piece, 0, NULL, 0, &frame, &frame_len);
if (result != NAUT_OK ||
!send_all(fd, frame, frame_len)) {
result = NAUT_ERR_IO;
goto done;
}
free(frame);
frame = NULL;
}
}
} else if (metadata &&
(ext_id == NAUT_EXT_UT_METADATA ||
ext_id == remote_ext.ut_metadata)) {
naut_metadata_msg metadata_msg;
result = naut_metadata_parse(payload, payload_len,
&metadata_msg);
if (result != NAUT_OK ||
metadata_msg.type == NAUT_METADATA_REJECT ||
metadata_msg.total_size != remote_ext.metadata_size ||
metadata_msg.piece >= piece_count) {
result = NAUT_ERR_PROTO;
goto done;
}
if (metadata_msg.type == NAUT_METADATA_DATA &&
!received[metadata_msg.piece]) {
memcpy(metadata +
(size_t)metadata_msg.piece * NAUT_METADATA_BLOCK,
metadata_msg.data, metadata_msg.data_len);
received[metadata_msg.piece] = true;
received_count++;
}
}
}
memmove(buffer, buffer + pos, len - pos);
len -= pos;
}
uint8_t digest[20];
naut_sha1(metadata, remote_ext.metadata_size, digest);
if (memcmp(digest, info_hash, 20) != 0) {
result = NAUT_ERR_PROTO;
goto done;
}
*info = metadata;
*info_len = remote_ext.metadata_size;
metadata = NULL;
result = NAUT_OK;
done:
close(fd);
free(metadata);
free(received);
free(frame);
free(buffer);
return result;
}

466
src/peer/mse.c Normal file
View file

@ -0,0 +1,466 @@
#include "naut/mse.h"
#include "naut/hash.h"
#include <errno.h>
#include <openssl/bn.h>
#include <openssl/rand.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#define MSE_PAD_MAX 512
#define MSE_CRYPTO_RC4 2u
static const char DH_PRIME_HEX[] =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC"
"74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF2"
"5F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3621000000"
"0000090563";
static void wr16(uint8_t *p, uint16_t value) {
p[0] = (uint8_t)(value >> 8);
p[1] = (uint8_t)value;
}
static void wr32(uint8_t *p, uint32_t value) {
p[0] = (uint8_t)(value >> 24);
p[1] = (uint8_t)(value >> 16);
p[2] = (uint8_t)(value >> 8);
p[3] = (uint8_t)value;
}
static uint16_t rd16(const uint8_t *p) {
return ((uint16_t)p[0] << 8) | p[1];
}
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 void hash_parts(const char label[4],
const uint8_t *first, size_t first_len,
const uint8_t *second, size_t second_len,
uint8_t out[20]) {
naut_sha1_ctx sha;
naut_sha1_init(&sha);
naut_sha1_update(&sha, label, 4);
naut_sha1_update(&sha, first, first_len);
if (second && second_len) naut_sha1_update(&sha, second, second_len);
naut_sha1_final(&sha, out);
}
static void init_rc4(const uint8_t secret[NAUT_MSE_DH_LEN],
const uint8_t info_hash[20],
naut_mse_stream *stream) {
uint8_t key_a[20], key_b[20];
hash_parts("keyA", secret, NAUT_MSE_DH_LEN, info_hash, 20, key_a);
hash_parts("keyB", secret, NAUT_MSE_DH_LEN, info_hash, 20, key_b);
naut_rc4_init(&stream->send, key_a, sizeof key_a, 1024);
naut_rc4_init(&stream->recv, key_b, sizeof key_b, 1024);
memset(key_a, 0, sizeof key_a);
memset(key_b, 0, sizeof key_b);
}
/* ---- sans-IO handshake state machine ------------------------------------- */
enum {
PH_RECV_PUBKEY, /* waiting for the peer's 96-byte DH public key */
PH_SYNC_VC, /* scanning past PadB for the encrypted VC */
PH_RECV_SELECT, /* crypto_select + len(PadD) */
PH_RECV_PAD, /* PadD bytes (discarded) */
PH_RECV_HS, /* the peer's encrypted BitTorrent handshake */
};
struct naut_mse_handshake {
int phase;
naut_err err;
bool done;
uint8_t info_hash[20];
uint8_t peer_id[NAUT_PEERID_LEN];
uint64_t reserved;
/* DH state retained until the shared secret is computed. */
BN_CTX *ctx;
BIGNUM *prime;
BIGNUM *priv;
naut_mse_stream stream;
uint8_t expected_vc[8];
size_t vc_scanned;
size_t pad_remaining;
uint8_t remote_handshake[NAUT_HANDSHAKE_LEN];
uint8_t out[256];
size_t out_len, out_off;
uint8_t in[1024];
size_t in_len;
};
static void dh_free(naut_mse_handshake *h) {
BN_CTX_free(h->ctx); h->ctx = NULL;
BN_free(h->prime); h->prime = NULL;
BN_clear_free(h->priv); h->priv = NULL;
}
/* Generate our private key and public value, writing the 96-byte public key
* into the outgoing buffer. Retains prime/priv/ctx for dh_complete(). */
static naut_err dh_begin(naut_mse_handshake *h) {
naut_err result = NAUT_ERR_IO;
BIGNUM *generator = BN_new();
BIGNUM *local = BN_new();
h->ctx = BN_CTX_new();
h->priv = BN_new();
if (!generator || !local || !h->ctx || !h->priv ||
!BN_hex2bn(&h->prime, DH_PRIME_HEX) || !BN_set_word(generator, 2))
goto done;
do {
if (!BN_rand_range(h->priv, h->prime)) goto done;
} while (BN_cmp(h->priv, generator) < 0);
if (!BN_mod_exp(local, generator, h->priv, h->prime, h->ctx) ||
BN_bn2binpad(local, h->out, NAUT_MSE_DH_LEN) != NAUT_MSE_DH_LEN)
goto done;
h->out_len = NAUT_MSE_DH_LEN;
h->out_off = 0;
result = NAUT_OK;
done:
BN_free(generator);
BN_free(local);
if (result != NAUT_OK) dh_free(h);
return result;
}
/* Validate the peer's public key and derive the shared secret. */
static naut_err dh_complete(naut_mse_handshake *h, const uint8_t remote_bytes[96],
uint8_t secret[NAUT_MSE_DH_LEN]) {
naut_err result = NAUT_ERR_IO;
BIGNUM *remote = BN_new();
BIGNUM *shared = BN_new();
BIGNUM *limit = BN_new();
BIGNUM *two = BN_new();
if (!remote || !shared || !limit || !two ||
!BN_bin2bn(remote_bytes, NAUT_MSE_DH_LEN, remote) ||
!BN_set_word(two, 2) || !BN_copy(limit, h->prime) ||
!BN_sub_word(limit, 1))
goto done;
if (BN_cmp(remote, two) < 0 || BN_cmp(remote, limit) >= 0) {
result = NAUT_ERR_PROTO;
goto done;
}
if (!BN_mod_exp(shared, remote, h->priv, h->prime, h->ctx) ||
BN_bn2binpad(shared, secret, NAUT_MSE_DH_LEN) != NAUT_MSE_DH_LEN)
goto done;
result = NAUT_OK;
done:
BN_free(remote);
BN_clear_free(shared);
BN_free(limit);
BN_free(two);
return result;
}
naut_mse_handshake *naut_mse_handshake_begin(
const uint8_t info_hash[20],
const uint8_t peer_id[NAUT_PEERID_LEN],
uint64_t reserved) {
if (!info_hash || !peer_id) return NULL;
naut_mse_handshake *h = calloc(1, sizeof(*h));
if (!h) return NULL;
memcpy(h->info_hash, info_hash, 20);
memcpy(h->peer_id, peer_id, NAUT_PEERID_LEN);
h->reserved = reserved;
h->phase = PH_RECV_PUBKEY;
if (dh_begin(h) != NAUT_OK) {
naut_mse_handshake_free(h);
return NULL;
}
return h;
}
void naut_mse_handshake_free(naut_mse_handshake *h) {
if (!h) return;
dh_free(h);
/* keystream state is sensitive; scrub before release */
memset(h, 0, sizeof(*h));
free(h);
}
static void consume(naut_mse_handshake *h, size_t n) {
memmove(h->in, h->in + n, h->in_len - n);
h->in_len -= n;
}
/* Build req1/req2 + encrypted offer (VC, crypto_provide, PadC, IA) into out. */
static void build_request(naut_mse_handshake *h, const uint8_t secret[96]) {
uint8_t req1[20], req2[20], req3[20];
hash_parts("req1", secret, NAUT_MSE_DH_LEN, NULL, 0, req1);
hash_parts("req2", h->info_hash, 20, NULL, 0, req2);
hash_parts("req3", secret, NAUT_MSE_DH_LEN, NULL, 0, req3);
for (size_t i = 0; i < sizeof req2; i++) req2[i] ^= req3[i];
init_rc4(secret, h->info_hash, &h->stream);
uint8_t *p = h->out;
memcpy(p, req1, 20);
memcpy(p + 20, req2, 20);
p += 40;
uint8_t *offer = p; /* VC(8) crypto_provide(4) padlen(2) ialen(2) IA */
memset(offer, 0, 8);
wr32(offer + 8, MSE_CRYPTO_RC4);
wr16(offer + 12, 0);
wr16(offer + 14, NAUT_HANDSHAKE_LEN);
naut_peer_handshake_build(offer + 16, h->info_hash, h->peer_id, h->reserved);
size_t offer_len = 16 + NAUT_HANDSHAKE_LEN;
naut_rc4_xor(&h->stream.send, offer, offer_len);
h->out_len = 40 + offer_len;
h->out_off = 0;
/* expected_vc = our recv keystream applied to 8 zero bytes at position 0,
* without advancing the real recv state (we resync on it). */
naut_rc4 probe = h->stream.recv;
uint8_t vc[8] = {0};
naut_rc4_xor(&probe, vc, sizeof vc);
memcpy(h->expected_vc, vc, sizeof vc);
h->vc_scanned = 0;
}
static void advance(naut_mse_handshake *h) {
for (;;) {
switch (h->phase) {
case PH_RECV_PUBKEY: {
if (h->in_len < NAUT_MSE_DH_LEN) return;
uint8_t secret[NAUT_MSE_DH_LEN];
naut_err e = dh_complete(h, h->in, secret);
if (e != NAUT_OK) { h->err = e; return; }
consume(h, NAUT_MSE_DH_LEN);
dh_free(h); /* DH no longer needed */
build_request(h, secret);
memset(secret, 0, sizeof secret);
h->phase = PH_SYNC_VC;
return; /* out now holds req+offer: NEED_WRITE */
}
case PH_SYNC_VC: {
while (h->in_len >= sizeof h->expected_vc) {
if (memcmp(h->in, h->expected_vc, sizeof h->expected_vc) == 0) {
uint8_t vc[8];
memcpy(vc, h->in, sizeof vc);
naut_rc4_xor(&h->stream.recv, vc, sizeof vc);
static const uint8_t zero8[8] = {0};
if (memcmp(vc, zero8, sizeof vc) != 0) {
h->err = NAUT_ERR_PROTO;
return;
}
consume(h, sizeof vc);
h->phase = PH_RECV_SELECT;
break;
}
consume(h, 1);
if (++h->vc_scanned > MSE_PAD_MAX) {
h->err = NAUT_ERR_PROTO;
return;
}
}
if (h->phase == PH_SYNC_VC) return; /* need more bytes */
continue;
}
case PH_RECV_SELECT: {
if (h->in_len < 6) return;
uint8_t hdr[6];
memcpy(hdr, h->in, sizeof hdr);
naut_rc4_xor(&h->stream.recv, hdr, sizeof hdr);
consume(h, sizeof hdr);
if (rd32(hdr) != MSE_CRYPTO_RC4) { h->err = NAUT_ERR_PROTO; return; }
h->pad_remaining = rd16(hdr + 4);
if (h->pad_remaining > MSE_PAD_MAX) { h->err = NAUT_ERR_PROTO; return; }
h->phase = PH_RECV_PAD;
continue;
}
case PH_RECV_PAD: {
if (h->pad_remaining > 0) {
size_t n = h->pad_remaining < h->in_len ? h->pad_remaining
: h->in_len;
if (n == 0) return;
naut_rc4_xor(&h->stream.recv, h->in, n); /* advance keystream */
consume(h, n);
h->pad_remaining -= n;
if (h->pad_remaining > 0) return;
}
h->phase = PH_RECV_HS;
continue;
}
case PH_RECV_HS: {
if (h->in_len < NAUT_HANDSHAKE_LEN) return;
memcpy(h->remote_handshake, h->in, NAUT_HANDSHAKE_LEN);
naut_rc4_xor(&h->stream.recv, h->remote_handshake, NAUT_HANDSHAKE_LEN);
consume(h, NAUT_HANDSHAKE_LEN);
uint8_t remote_hash[20], remote_id[20];
if (!naut_peer_handshake_parse(h->remote_handshake, remote_hash,
remote_id, NULL) ||
memcmp(remote_hash, h->info_hash, 20) != 0) {
h->err = NAUT_ERR_PROTO;
return;
}
h->stream.active = true;
h->done = true;
return;
}
default:
h->err = NAUT_ERR_PROTO;
return;
}
}
}
naut_mse_hs_status naut_mse_handshake_status(const naut_mse_handshake *h) {
if (!h || h->err != NAUT_OK) return NAUT_MSE_HS_ERROR;
if (h->done) return NAUT_MSE_HS_DONE;
if (h->out_off < h->out_len) return NAUT_MSE_HS_NEED_WRITE;
return NAUT_MSE_HS_NEED_READ;
}
size_t naut_mse_handshake_pull(naut_mse_handshake *h, uint8_t *buf, size_t cap) {
if (!h || !buf) return 0;
size_t avail = h->out_len - h->out_off;
size_t n = avail < cap ? avail : cap;
if (n) {
memcpy(buf, h->out + h->out_off, n);
h->out_off += n;
if (h->out_off == h->out_len) h->out_len = h->out_off = 0;
}
return n;
}
naut_mse_hs_status naut_mse_handshake_feed(naut_mse_handshake *h,
const uint8_t *data, size_t len,
size_t *consumed) {
if (consumed) *consumed = 0;
if (!h) return NAUT_MSE_HS_ERROR;
if (h->err == NAUT_OK && !h->done && data && len) {
size_t space = sizeof h->in - h->in_len;
size_t take = len < space ? len : space;
memcpy(h->in + h->in_len, data, take);
h->in_len += take;
if (consumed) *consumed = take;
advance(h);
}
return naut_mse_handshake_status(h);
}
naut_err naut_mse_handshake_finish(naut_mse_handshake *h,
naut_mse_stream *stream,
uint8_t remote_handshake[NAUT_HANDSHAKE_LEN]) {
if (!h || !stream || !remote_handshake) return NAUT_ERR_INVAL;
if (h->err != NAUT_OK) return h->err;
if (!h->done) return NAUT_ERR_AGAIN;
*stream = h->stream;
memcpy(remote_handshake, h->remote_handshake, NAUT_HANDSHAKE_LEN);
return NAUT_OK;
}
/* ---- blocking I/O helpers + convenience wrapper -------------------------- */
static bool raw_send_all(int fd, const void *data, size_t len) {
const uint8_t *p = data;
while (len) {
ssize_t n = send(fd, p, len, MSG_NOSIGNAL);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
len -= (size_t)n;
}
return true;
}
static bool raw_recv_exact(int fd, void *data, size_t len) {
uint8_t *p = data;
while (len) {
ssize_t n = recv(fd, p, len, 0);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
len -= (size_t)n;
}
return true;
}
naut_err naut_mse_client_handshake(
int fd,
const uint8_t info_hash[20],
const uint8_t peer_id[NAUT_PEERID_LEN],
uint64_t reserved,
naut_mse_stream *stream,
uint8_t remote_handshake[NAUT_HANDSHAKE_LEN]) {
if (fd < 0 || !info_hash || !peer_id || !stream || !remote_handshake)
return NAUT_ERR_INVAL;
memset(stream, 0, sizeof(*stream));
naut_mse_handshake *h =
naut_mse_handshake_begin(info_hash, peer_id, reserved);
if (!h) return NAUT_ERR_NOMEM;
naut_err rc = NAUT_ERR_PROTO;
for (;;) {
naut_mse_hs_status st = naut_mse_handshake_status(h);
if (st == NAUT_MSE_HS_NEED_WRITE) {
uint8_t buf[256];
size_t n;
bool ok = true;
while ((n = naut_mse_handshake_pull(h, buf, sizeof buf)) > 0)
if (!raw_send_all(fd, buf, n)) { ok = false; break; }
if (!ok) { rc = NAUT_ERR_IO; break; }
} else if (st == NAUT_MSE_HS_NEED_READ) {
/* One byte at a time: the handshake is tiny and one-shot, and this
* keeps the wrapper from over-reading into the payload stream. */
uint8_t byte;
if (!raw_recv_exact(fd, &byte, 1)) { rc = NAUT_ERR_IO; break; }
naut_mse_handshake_feed(h, &byte, 1, NULL);
} else if (st == NAUT_MSE_HS_DONE) {
rc = naut_mse_handshake_finish(h, stream, remote_handshake);
break;
} else {
rc = h->err != NAUT_OK ? h->err : NAUT_ERR_PROTO;
break;
}
}
naut_mse_handshake_free(h);
return rc;
}
/* ---- post-handshake stream I/O ------------------------------------------- */
bool naut_mse_send_all(int fd, naut_mse_stream *stream,
const void *data, size_t len) {
if (!stream || !stream->active) return raw_send_all(fd, data, len);
const uint8_t *p = data;
uint8_t block[16 * 1024];
while (len) {
size_t n = len < sizeof block ? len : sizeof block;
memcpy(block, p, n);
naut_rc4_xor(&stream->send, block, n);
if (!raw_send_all(fd, block, n)) return false;
p += n;
len -= n;
}
return true;
}
ssize_t naut_mse_recv(int fd, naut_mse_stream *stream,
void *data, size_t len) {
ssize_t n;
do {
n = recv(fd, data, len, 0);
} while (n < 0 && errno == EINTR);
if (n > 0 && stream && stream->active)
naut_rc4_xor(&stream->recv, data, (size_t)n);
return n;
}

64
src/peer/pipeline.c Normal file
View file

@ -0,0 +1,64 @@
#include "naut/pipeline.h"
#include <math.h>
static uint32_t clamp_depth(const naut_pipeline *p, uint32_t depth) {
if (depth < p->min_depth) return p->min_depth;
if (depth > p->max_depth) return p->max_depth;
return depth;
}
void naut_pipeline_init(naut_pipeline *p, uint32_t block_size,
uint32_t min_depth, uint32_t max_depth,
uint32_t initial_depth) {
if (!p) return;
if (block_size == 0) block_size = NAUT_BLOCK;
if (min_depth == 0) min_depth = 1;
if (max_depth < min_depth) max_depth = min_depth;
p->rtt_seconds = 0;
p->bytes_per_second = 0;
p->last_sample_at = 0;
p->min_depth = min_depth;
p->max_depth = max_depth;
p->block_size = block_size;
p->depth = clamp_depth(p, initial_depth);
}
void naut_pipeline_on_block(naut_pipeline *p, uint32_t bytes,
double sent_at, double received_at) {
if (!p || bytes == 0 || sent_at <= 0 || received_at <= sent_at) return;
double rtt = received_at - sent_at;
if (rtt > 60.0) return;
if (p->rtt_seconds == 0) p->rtt_seconds = rtt;
else p->rtt_seconds = p->rtt_seconds * 0.875 + rtt * 0.125;
double interval = p->last_sample_at > 0
? received_at - p->last_sample_at : rtt;
if (interval <= 0) interval = rtt;
double rate = bytes / interval;
if (p->bytes_per_second == 0) p->bytes_per_second = rate;
else p->bytes_per_second = p->bytes_per_second * 0.8 + rate * 0.2;
p->last_sample_at = received_at;
double blocks = (2.0 * p->bytes_per_second * p->rtt_seconds) /
p->block_size;
uint32_t target = blocks >= UINT32_MAX ? UINT32_MAX :
(uint32_t)ceil(blocks);
target = clamp_depth(p, target);
/* Grow quickly enough to fill a fast path; shrink one eighth at a time so
* transient delayed samples do not collapse the pipe. */
if (target > p->depth) {
uint32_t step = p->depth / 4 + 1;
p->depth = clamp_depth(p, NAUT_MIN(target, p->depth + step));
} else if (target < p->depth) {
uint32_t step = p->depth / 8 + 1;
p->depth = clamp_depth(p, target > p->depth - step
? target : p->depth - step);
}
}
uint32_t naut_pipeline_depth(const naut_pipeline *p) {
return p ? p->depth : 0;
}

113
src/peer/wire.c Normal file
View file

@ -0,0 +1,113 @@
#include "naut/peer.h"
#include <string.h>
static const char PSTR[] = "BitTorrent protocol"; /* 19 bytes */
#define PSTRLEN 19
static inline 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 inline uint32_t rd32(const uint8_t *p) {
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | (uint32_t)p[3];
}
void naut_peer_handshake_build(uint8_t out[NAUT_HANDSHAKE_LEN],
const uint8_t infohash[20],
const uint8_t peerid[NAUT_PEERID_LEN],
uint64_t reserved) {
out[0] = PSTRLEN;
memcpy(out + 1, PSTR, PSTRLEN);
for (int i = 0; i < 8; i++) out[20 + i] = (uint8_t)(reserved >> (56 - i*8));
memcpy(out + 28, infohash, 20);
memcpy(out + 48, peerid, 20);
}
bool naut_peer_handshake_parse(const uint8_t in[NAUT_HANDSHAKE_LEN],
uint8_t infohash[20],
uint8_t peerid[NAUT_PEERID_LEN],
uint64_t *reserved) {
if (in[0] != PSTRLEN || memcmp(in + 1, PSTR, PSTRLEN) != 0) return false;
if (reserved) {
uint64_t r = 0;
for (int i = 0; i < 8; i++) r = (r << 8) | in[20 + i];
*reserved = r;
}
memcpy(infohash, in + 28, 20);
memcpy(peerid, in + 48, 20);
return true;
}
int naut_peer_msg_parse(const uint8_t *buf, size_t len, naut_msg *out) {
if (len < 4) return 0;
uint32_t n = rd32(buf);
if (n == 0) { out->type = NAUT_MSG_KEEPALIVE; return 4; } /* keep-alive */
if (n > NAUT_MSG_MAX) return NAUT_ERR_PROTO;
if (len < 4 + (size_t)n) return 0; /* need more */
const uint8_t *p = buf + 4;
uint8_t id = p[0];
const uint8_t *body = p + 1;
uint32_t blen = n - 1;
memset(out, 0, sizeof(*out));
out->type = (naut_msg_type)id;
switch (id) {
case NAUT_MSG_CHOKE: case NAUT_MSG_UNCHOKE:
case NAUT_MSG_INTERESTED: case NAUT_MSG_NOT_INTERESTED:
if (blen != 0) return NAUT_ERR_PROTO;
break;
case NAUT_MSG_HAVE:
if (blen != 4) return NAUT_ERR_PROTO;
out->index = rd32(body);
break;
case NAUT_MSG_BITFIELD:
out->payload = body; out->payload_len = blen;
break;
case NAUT_MSG_REQUEST: case NAUT_MSG_CANCEL:
if (blen != 12) return NAUT_ERR_PROTO;
out->index = rd32(body); out->begin = rd32(body + 4); out->length = rd32(body + 8);
break;
case NAUT_MSG_PIECE:
if (blen < 8) return NAUT_ERR_PROTO;
out->index = rd32(body); out->begin = rd32(body + 4);
out->payload = body + 8; out->payload_len = blen - 8;
out->length = blen - 8;
break;
case NAUT_MSG_PORT:
if (blen != 2) return NAUT_ERR_PROTO;
out->index = ((uint32_t)body[0] << 8) | body[1]; /* port in index */
break;
default:
/* unknown/extended: surface type + raw payload, let caller decide */
out->payload = body; out->payload_len = blen;
break;
}
return (int)(4 + n);
}
size_t naut_peer_keepalive(uint8_t out[4]) { wr32(out, 0); return 4; }
size_t naut_peer_msg_simple(uint8_t out[5], naut_msg_type t) {
wr32(out, 1); out[4] = (uint8_t)t; return 5;
}
size_t naut_peer_msg_have(uint8_t out[9], uint32_t index) {
wr32(out, 5); out[4] = NAUT_MSG_HAVE; wr32(out + 5, index); return 9;
}
size_t naut_peer_msg_request(uint8_t out[17], uint32_t index, uint32_t begin, uint32_t length) {
wr32(out, 13); out[4] = NAUT_MSG_REQUEST;
wr32(out + 5, index); wr32(out + 9, begin); wr32(out + 13, length); return 17;
}
size_t naut_peer_msg_cancel(uint8_t out[17], uint32_t index, uint32_t begin, uint32_t length) {
wr32(out, 13); out[4] = NAUT_MSG_CANCEL;
wr32(out + 5, index); wr32(out + 9, begin); wr32(out + 13, length); return 17;
}
size_t naut_peer_msg_piece_header(uint8_t out[13], uint32_t index, uint32_t begin, uint32_t block_len) {
wr32(out, 9 + block_len); out[4] = NAUT_MSG_PIECE;
wr32(out + 5, index); wr32(out + 9, begin); return 13;
}
size_t naut_peer_msg_bitfield(uint8_t *out, const uint8_t *bf, size_t nbytes) {
wr32(out, (uint32_t)(1 + nbytes)); out[4] = NAUT_MSG_BITFIELD;
memcpy(out + 5, bf, nbytes); return 5 + nbytes;
}