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:
commit
2178d6a70c
121 changed files with 12644 additions and 0 deletions
296
src/peer/extension.c
Normal file
296
src/peer/extension.c
Normal 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
235
src/peer/metadata.c
Normal 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
466
src/peer/mse.c
Normal 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
64
src/peer/pipeline.c
Normal 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
113
src/peer/wire.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue