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

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Minimal BEP-5 get_peers responder for the trackerless magnet gate."""
import socket
import struct
import sys
peer_port = int(sys.argv[1])
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 0))
print("DHT_PORT %d" % sock.getsockname()[1], flush=True)
node_id = bytes(range(20))
compact_peer = socket.inet_aton("127.0.0.1") + struct.pack("!H", peer_port)
while True:
packet, address = sock.recvfrom(65535)
marker = b"1:t"
start = packet.find(marker)
if start < 0:
continue
length_start = start + len(marker)
colon = packet.find(b":", length_start)
if colon < 0:
continue
try:
tx_len = int(packet[length_start:colon])
except ValueError:
continue
tx = packet[colon + 1:colon + 1 + tx_len]
if len(tx) != tx_len:
continue
response = (
b"d1:rd2:id20:" + node_id +
b"6:valuesl6:" + compact_peer +
b"ee1:t" + str(tx_len).encode() + b":" + tx +
b"1:y1:re"
)
sock.sendto(response, address)

View file

@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Exercise the io_uring fixed-buffer/SEND_ZC echo path with exact data."""
import socket
import sys
import threading
import time
port = int(sys.argv[1])
total = int(sys.argv[2]) if len(sys.argv) > 2 else 64 * 1024 * 1024
connections = int(sys.argv[3]) if len(sys.argv) > 3 else 1
chunk = bytes((i * 31 + 7) & 0xFF for i in range(128 * 1024))
start = time.monotonic()
errors = []
def connection_worker(connection_bytes):
sock = socket.create_connection(("127.0.0.1", port), timeout=5)
send_error = []
def sender():
try:
sent = 0
while sent < connection_bytes:
payload = chunk[:min(len(chunk), connection_bytes - sent)]
sock.sendall(payload)
sent += len(payload)
sock.shutdown(socket.SHUT_WR)
except Exception as error:
send_error.append(error)
thread = threading.Thread(target=sender)
thread.start()
try:
done = 0
while done < connection_bytes:
part = sock.recv(min(1024 * 1024, connection_bytes - done))
if not part:
raise RuntimeError("echo server closed early")
offset = 0
while offset < len(part):
pattern_offset = (done + offset) % len(chunk)
count = min(len(part) - offset, len(chunk) - pattern_offset)
if part[offset:offset + count] != chunk[pattern_offset:pattern_offset + count]:
raise RuntimeError("echo content mismatch")
offset += count
done += len(part)
thread.join()
if send_error:
raise send_error[0]
except Exception as error:
errors.append(error)
finally:
sock.close()
per_connection = total // connections
workers = [
threading.Thread(target=connection_worker, args=(per_connection,))
for _ in range(connections)
]
for worker in workers:
worker.start()
for worker in workers:
worker.join()
if errors:
raise errors[0]
elapsed = time.monotonic() - start
print("ECHO %.2f Gbit/s (%d connections)" %
(per_connection * connections * 8 / elapsed / 1e9, connections),
flush=True)

View file

@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""Minimal compact-peer HTTP tracker for the Phase 4 integration test."""
from http.server import BaseHTTPRequestHandler, HTTPServer
import socket
import struct
import sys
peer_port = int(sys.argv[1])
body = (
b"d8:intervali1800e5:peers6:"
+ socket.inet_aton("127.0.0.1")
+ struct.pack("!H", peer_port)
+ b"e"
)
class Tracker(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, fmt, *args):
print("REQUEST " + (fmt % args), flush=True)
server = HTTPServer(("127.0.0.1", 0), Tracker)
print("PORT %d" % server.server_port, flush=True)
server.serve_forever()

View file

@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Phase 6 local platform gate: fixed-buffer recv + SEND_ZC echo integrity.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
ECHO="${1:-$ROOT/build/naut_echo}"
PORT="${NAUT_ECHO_PORT:-39127}"
LOG="$(mktemp /tmp/naut_echo_scale.XXXXXX)"
cleanup() {
[ -n "${server:-}" ] && kill -TERM "$server" 2>/dev/null || true
[ -n "${server:-}" ] && wait "$server" 2>/dev/null || true
rm -f "$LOG"
}
trap cleanup EXIT
NAUT_SQPOLL=1 "$ECHO" "$PORT" >"$LOG" 2>&1 &
server=$!
for _ in $(seq 1 100); do
grep -q "echo listening" "$LOG" && break
kill -0 "$server" 2>/dev/null || {
echo "FAIL: echo server exited"; cat "$LOG"; exit 1;
}
sleep 0.05
done
grep -q "echo listening" "$LOG" || {
echo "FAIL: echo server did not listen"; cat "$LOG"; exit 1;
}
BYTES=$((256 * 1024 * 1024))
CONNS=8
python3 "$ROOT/tests/integration/echo_client.py" "$PORT" "$BYTES" "$CONNS"
kill -TERM "$server"
wait "$server"
server=""
grep -q "bytes echoed" "$LOG" || {
echo "FAIL: echo server did not shut down cleanly"; cat "$LOG"; exit 1;
}
grep "bytes echoed" "$LOG"
# Server-side accounting must match exactly: every byte the client sent was
# echoed back, and no connection was dropped mid-stream. (A truncated echo is
# also caught client-side, but asserting the count here makes a server-side
# drop a hard, deterministic failure rather than a timing-dependent one.)
echoed=$(grep -oE '[0-9]+ bytes echoed' "$LOG" | grep -oE '^[0-9]+')
if [ "$echoed" != "$BYTES" ]; then
echo "FAIL: echoed $echoed bytes, expected $BYTES (a connection was dropped)"
cat "$LOG"; exit 1
fi
conns=$(grep -oE '[0-9]+ conns' "$LOG" | grep -oE '^[0-9]+')
if [ "$conns" != "$CONNS" ]; then
echo "FAIL: served $conns connections, expected $CONNS"; cat "$LOG"; exit 1
fi
# Report whether the SQPOLL path was actually exercised (it falls back cleanly
# where the kernel/privileges disallow it — informational, not a failure).
if grep -q "sqpoll" "$LOG"; then
echo "PASS: io_uring SQPOLL fixed-buffer/SEND_ZC echo path is byte-correct"
else
echo "PASS: io_uring echo path byte-correct (SQPOLL unavailable, used fallback)"
fi

View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Phase 3 interop gate: seed each fixture torrent with libtorrent and download
# it with the from-scratch naut_leech, asserting a byte-identical content tree.
# Skips (exit 77 = ctest SKIP) if python libtorrent is unavailable.
set -u
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
LEECH="${1:-$ROOT/build/naut_leech}"
SEEDER="$ROOT/tests/integration/seeder.py"
DATA="$ROOT/tests/fixtures/data"
python3 -c 'import libtorrent' 2>/dev/null || { echo "SKIP: python libtorrent not available"; exit 77; }
[ -x "$LEECH" ] || { echo "FAIL: $LEECH not built"; exit 1; }
fail=0
run_one() {
local tor="$1" cmp="$2"
local out; out="$(mktemp -d /tmp/naut_interop.XXXXXX)"
local log; log="$(mktemp /tmp/naut_seed.XXXXXX)"
python3 "$SEEDER" "$ROOT/tests/fixtures/$tor" "$DATA" > "$log" 2>&1 &
local seed=$!
local port=""
for _ in $(seq 1 100); do port=$(grep -oP 'PORT \K[0-9]+' "$log" 2>/dev/null); [ -n "$port" ] && break; sleep 0.1; done
if [ -z "$port" ]; then echo "FAIL[$tor]: seeder did not start"; cat "$log"; kill "$seed" 2>/dev/null; fail=1; rm -rf "$out" "$log"; return; fi
if timeout 30 "$LEECH" "$ROOT/tests/fixtures/$tor" "$out" 127.0.0.1 "$port" 2>&1 | grep -q "COMPLETE"; then
if diff -r "$out/$cmp" "$DATA/$cmp" >/dev/null; then
echo "PASS[$tor]: byte-identical content tree"
else
echo "FAIL[$tor]: content mismatch"; fail=1
fi
else
echo "FAIL[$tor]: download did not complete"; fail=1
fi
kill "$seed" 2>/dev/null; rm -rf "$out" "$log"
}
run_one single_v1.torrent single.bin
run_one multi_v1.torrent multi
run_one hybrid.torrent multi
exit $fail

View file

@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Phase 5 gate: trackerless magnet -> DHT peer -> ut_metadata -> verified data.
set -u
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
SWARM="${1:-$ROOT/build/naut_swarm}"
SEEDER="$ROOT/tests/integration/seeder.py"
DHT="$ROOT/tests/integration/dht_fixture.py"
TORRENT="$ROOT/tests/fixtures/single_v1.torrent"
DATA="$ROOT/tests/fixtures/data"
MAGNET="magnet:?xt=urn:btih:7f2555dfd18ba1c4a024264e939d7a5f8b25311b"
python3 -c 'import libtorrent' 2>/dev/null || {
echo "SKIP: python libtorrent not available"; exit 77;
}
[ -x "$SWARM" ] || { echo "FAIL: $SWARM not built"; exit 1; }
out="$(mktemp -d /tmp/naut_magnet.XXXXXX)"
seed_log="$(mktemp /tmp/naut_magnet_seed.XXXXXX)"
dht_log="$(mktemp /tmp/naut_magnet_dht.XXXXXX)"
client_log="$(mktemp /tmp/naut_magnet_client.XXXXXX)"
cleanup() {
[ -n "${seed:-}" ] && kill "$seed" 2>/dev/null || true
[ -n "${dht_pid:-}" ] && kill "$dht_pid" 2>/dev/null || true
rm -rf "$out" "$seed_log" "$dht_log" "$client_log"
}
trap cleanup EXIT
python3 "$SEEDER" "$TORRENT" "$DATA" >"$seed_log" 2>&1 &
seed=$!
peer_port=""
for _ in $(seq 1 100); do
peer_port=$(grep -oP 'PORT \K[0-9]+' "$seed_log" 2>/dev/null)
[ -n "$peer_port" ] && break
sleep 0.1
done
[ -n "$peer_port" ] || {
echo "FAIL: seeder did not start"; cat "$seed_log"; exit 1;
}
python3 "$DHT" "$peer_port" >"$dht_log" 2>&1 &
dht_pid=$!
dht_port=""
for _ in $(seq 1 100); do
dht_port=$(grep -oP 'DHT_PORT \K[0-9]+' "$dht_log" 2>/dev/null)
[ -n "$dht_port" ] && break
sleep 0.1
done
[ -n "$dht_port" ] || {
echo "FAIL: DHT fixture did not start"; cat "$dht_log"; exit 1;
}
if ! NAUT_DHT_BOOTSTRAP="127.0.0.1:$dht_port" \
timeout 30 "$SWARM" "$MAGNET" "$out" >"$client_log" 2>&1; then
echo "FAIL: trackerless magnet download did not complete"
cat "$client_log"
cat "$seed_log"
exit 1
fi
grep -q "magnet metadata verified" "$client_log" || {
echo "FAIL: metadata was not verified"; cat "$client_log"; exit 1;
}
diff -r "$out/single.bin" "$DATA/single.bin" >/dev/null || {
echo "FAIL: magnet content mismatch"; exit 1;
}
echo "PASS: DHT-only magnet completed with verified metadata and content"

View file

@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Phase 5 MSE gate: a libtorrent seed requires encrypted incoming connections.
set -u
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
LEECH="${1:-$ROOT/build/naut_leech}"
SEEDER="$ROOT/tests/integration/seeder.py"
TORRENT="$ROOT/tests/fixtures/single_v1.torrent"
DATA="$ROOT/tests/fixtures/data"
python3 -c 'import libtorrent' 2>/dev/null || {
echo "SKIP: python libtorrent not available"; exit 77;
}
[ -x "$LEECH" ] || { echo "FAIL: $LEECH not built"; exit 1; }
out="$(mktemp -d /tmp/naut_mse.XXXXXX)"
log="$(mktemp /tmp/naut_mse_seed.XXXXXX)"
client_log="$(mktemp /tmp/naut_mse_client.XXXXXX)"
cleanup() {
[ -n "${seed:-}" ] && kill "$seed" 2>/dev/null || true
rm -rf "$out" "$log" "$client_log"
}
trap cleanup EXIT
NAUT_FORCE_MSE=1 python3 "$SEEDER" "$TORRENT" "$DATA" > "$log" 2>&1 &
seed=$!
port=""
for _ in $(seq 1 100); do
port=$(grep -oP 'PORT \K[0-9]+' "$log" 2>/dev/null)
[ -n "$port" ] && break
sleep 0.1
done
[ -n "$port" ] || { echo "FAIL: encrypted seeder did not start"; cat "$log"; exit 1; }
if ! timeout 30 "$LEECH" --mse "$TORRENT" "$out" 127.0.0.1 "$port" \
>"$client_log" 2>&1; then
echo "FAIL: MSE download did not complete"
cat "$client_log"
cat "$log"
exit 1
fi
grep -q "COMPLETE" "$client_log" || {
echo "FAIL: client exited without completing"
cat "$client_log"
exit 1
}
diff -r "$out/single.bin" "$DATA/single.bin" >/dev/null || {
echo "FAIL: MSE content mismatch"; exit 1;
}
echo "PASS: forced MSE/RC4 download is byte-identical"

View file

@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -euo pipefail
daemon=$1
ctl=$2
plugin=$3
script=$4
tmp=$(mktemp -d)
socket="$tmp/nautd.sock"
daemon_log="$tmp/nautd.log"
events_log="$tmp/events.log"
cleanup() {
result=$?
if [[ -n "${daemon_pid:-}" ]]; then
kill "$daemon_pid" 2>/dev/null || true
wait "$daemon_pid" 2>/dev/null || true
fi
if [[ -n "${events_pid:-}" ]]; then
kill "$events_pid" 2>/dev/null || true
wait "$events_pid" 2>/dev/null || true
fi
if [[ "$result" -ne 0 ]]; then
cat "$daemon_log" >&2 2>/dev/null || true
cat "$events_log" >&2 2>/dev/null || true
fi
rm -rf "$tmp"
return "$result"
}
trap cleanup EXIT
"$daemon" --socket "$socket" --plugin "$plugin" --script "$script" \
>"$daemon_log" 2>&1 &
daemon_pid=$!
for _ in $(seq 1 100); do
[[ -S "$socket" ]] && break
sleep 0.02
done
[[ -S "$socket" ]]
"$ctl" --socket "$socket" ping | grep -q '"service": "nautd"'
"$ctl" --socket "$socket" plugins | grep -q '"memory"'
timeout 5 "$ctl" --socket "$socket" events >"$events_log" &
events_pid=$!
sleep 0.1
"$ctl" --socket "$socket" emit \
'{"type":"torrent_finished","torrent_id":7}' >/dev/null
status=
for _ in $(seq 1 100); do
status=$("$ctl" --socket "$socket" status)
if grep -q '"move_commands": 1' <<<"$status" &&
grep -q '"handled": 1' <<<"$status"; then
break
fi
sleep 0.02
done
grep -q '"move_commands": 1' <<<"$status"
grep -q '"handled": 1' <<<"$status"
grep -q '"errors": 0' <<<"$status"
plugin_status=$("$ctl" --socket "$socket" example.events)
grep -q '"finished": 1' <<<"$plugin_status"
for _ in $(seq 1 100); do
grep -q '"event": "torrent_finished"' "$events_log" && break
sleep 0.02
done
grep -q '"event": "torrent_finished"' "$events_log"
# --- end-to-end move-as-you-finish: register a real torrent's storage, fire a
# file_complete event, and confirm the script-driven move actually relocates the
# file on disk (script thread -> bounded queue -> owner thread -> storage). ----
fixtures=$(dirname "$script")
root="$tmp/torrent-data"
"$ctl" --socket "$socket" add_torrent \
"{\"torrent_id\":42,\"torrent\":\"$fixtures/single_v1.torrent\",\"root\":\"$root\"}" \
| grep -q '"ok": true'
src=$(find "$root" -type f | head -n1)
[[ -n "$src" ]]
"$ctl" --socket "$socket" emit \
"{\"type\":\"file_complete\",\"torrent_id\":42,\"index\":0,\"path\":\"$src\"}" \
>/dev/null
for _ in $(seq 1 100); do
[[ -f "$src.moved" ]] && break
sleep 0.02
done
[[ -f "$src.moved" ]]
[[ ! -f "$src" ]]
"$ctl" --socket "$socket" shutdown >/dev/null
wait "$daemon_pid"
daemon_pid=

View file

@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Phase 4 interop gate: download one torrent concurrently from two independent
# libtorrent seeds and require both peers to contribute blocks.
set -u
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
SWARM="${1:-$ROOT/build/naut_swarm}"
SEEDER="$ROOT/tests/integration/seeder.py"
TOR="$ROOT/tests/fixtures/single_v1.torrent"
DATA="$ROOT/tests/fixtures/data"
python3 -c 'import libtorrent' 2>/dev/null || {
echo "SKIP: python libtorrent not available"
exit 77
}
[ -x "$SWARM" ] || { echo "FAIL: $SWARM not built"; exit 1; }
out="$(mktemp -d /tmp/naut_swarm.XXXXXX)"
log1="$(mktemp /tmp/naut_seed1.XXXXXX)"
log2="$(mktemp /tmp/naut_seed2.XXXXXX)"
slog="$(mktemp /tmp/naut_swarm_log.XXXXXX)"
seed1=""
seed2=""
cleanup() {
[ -n "$seed1" ] && kill "$seed1" 2>/dev/null || true
[ -n "$seed2" ] && kill "$seed2" 2>/dev/null || true
rm -rf "$out" "$log1" "$log2" "$slog"
}
trap cleanup EXIT
python3 "$SEEDER" "$TOR" "$DATA" > "$log1" 2>&1 &
seed1=$!
python3 "$SEEDER" "$TOR" "$DATA" > "$log2" 2>&1 &
seed2=$!
port1=""
port2=""
for _ in $(seq 1 100); do
port1="$(grep -oP 'PORT \K[0-9]+' "$log1" 2>/dev/null || true)"
port2="$(grep -oP 'PORT \K[0-9]+' "$log2" 2>/dev/null || true)"
[ -n "$port1" ] && [ "$port1" != 0 ] &&
[ -n "$port2" ] && [ "$port2" != 0 ] && break
sleep 0.1
done
if [ -z "$port1" ] || [ "$port1" = 0 ] ||
[ -z "$port2" ] || [ "$port2" = 0 ]; then
echo "FAIL: seeders did not start"
cat "$log1" "$log2"
exit 1
fi
if ! timeout 30 "$SWARM" "$TOR" "$out" \
"127.0.0.1:$port1" "127.0.0.1:$port2" > "$slog" 2>&1; then
echo "FAIL: swarm download did not complete"
cat "$slog"
exit 1
fi
if ! diff -r "$out/single.bin" "$DATA/single.bin" >/dev/null; then
echo "FAIL: content mismatch"
exit 1
fi
contributors="$(grep -Ec 'delivered [1-9][0-9]* blocks' "$slog" || true)"
if [ "$contributors" -lt 2 ]; then
echo "FAIL: expected both peers to contribute"
cat "$slog"
exit 1
fi
echo "PASS: two-peer swarm produced byte-identical output"

View file

@ -0,0 +1,129 @@
#!/usr/bin/env bash
# Phase 4 tracker gate: discover a real libtorrent seed from a localhost HTTP
# tracker, then complete the download without explicit peer arguments.
set -u
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
SWARM="${1:-$ROOT/build/naut_swarm}"
MODE="${2:-http}"
SEEDER="$ROOT/tests/integration/seeder.py"
if [ "$MODE" = "udp" ]; then
TRACKER="$ROOT/tests/integration/udp_tracker.py"
else
TRACKER="$ROOT/tests/integration/http_tracker.py"
fi
SOURCE_TOR="$ROOT/tests/fixtures/single_v1.torrent"
DATA="$ROOT/tests/fixtures/data"
python3 -c 'import libtorrent' 2>/dev/null || {
echo "SKIP: python libtorrent not available"
exit 77
}
[ -x "$SWARM" ] || { echo "FAIL: $SWARM not built"; exit 1; }
out="$(mktemp -d /tmp/naut_tracker_swarm.XXXXXX)"
seed_log="$(mktemp /tmp/naut_tracker_seed.XXXXXX)"
tracker_log="$(mktemp /tmp/naut_http_tracker.XXXXXX)"
swarm_log="$(mktemp /tmp/naut_tracker_client.XXXXXX)"
tor="$(mktemp /tmp/naut_tracker.XXXXXX.torrent)"
seed=""
tracker=""
cleanup() {
[ -n "$seed" ] && kill "$seed" 2>/dev/null || true
[ -n "$tracker" ] && kill "$tracker" 2>/dev/null || true
rm -rf "$out" "$seed_log" "$tracker_log" "$swarm_log" "$tor"
}
trap cleanup EXIT
python3 "$SEEDER" "$SOURCE_TOR" "$DATA" > "$seed_log" 2>&1 &
seed=$!
seed_port=""
for _ in $(seq 1 100); do
seed_port="$(grep -oP 'PORT \K[0-9]+' "$seed_log" 2>/dev/null || true)"
[ -n "$seed_port" ] && [ "$seed_port" != 0 ] && break
sleep 0.1
done
if [ -z "$seed_port" ] || [ "$seed_port" = 0 ]; then
echo "FAIL: seeder did not start"
cat "$seed_log"
exit 1
fi
python3 "$TRACKER" "$seed_port" > "$tracker_log" 2>&1 &
tracker=$!
tracker_port=""
for _ in $(seq 1 100); do
tracker_port="$(grep -oP 'PORT \K[0-9]+' "$tracker_log" 2>/dev/null || true)"
[ -n "$tracker_port" ] && break
sleep 0.05
done
if [ -z "$tracker_port" ]; then
echo "FAIL: tracker did not start"
cat "$tracker_log"
exit 1
fi
# Replace only the top-level dictionary. The raw info dictionary is copied
# byte-for-byte so its SHA-1 info hash remains unchanged.
if [ "$MODE" = "udp" ]; then
announce="udp://127.0.0.1:$tracker_port/announce"
else
announce="http://127.0.0.1:$tracker_port/announce"
fi
python3 - "$SOURCE_TOR" "$tor" "$announce" <<'PY'
import sys
def skip(data, pos):
token = data[pos]
if token == ord("i"):
return data.index(b"e", pos) + 1
if token in (ord("l"), ord("d")):
pos += 1
while data[pos] != ord("e"):
pos = skip(data, pos)
if token == ord("d"):
pos = skip(data, pos)
return pos + 1
colon = data.index(b":", pos)
size = int(data[pos:colon])
return colon + 1 + size
source, target, announce = sys.argv[1], sys.argv[2], sys.argv[3].encode()
data = open(source, "rb").read()
pos = 1
raw_info = None
while data[pos] != ord("e"):
colon = data.index(b":", pos)
key_len = int(data[pos:colon])
key_start = colon + 1
key = data[key_start:key_start + key_len]
pos = key_start + key_len
value_start = pos
pos = skip(data, pos)
if key == b"info":
raw_info = data[value_start:pos]
assert raw_info is not None
rewritten = (
b"d8:announce" + str(len(announce)).encode() + b":" + announce
+ b"4:info" + raw_info + b"e"
)
open(target, "wb").write(rewritten)
PY
if ! timeout 30 "$SWARM" "$tor" "$out" > "$swarm_log" 2>&1; then
echo "FAIL: tracker-discovered swarm did not complete"
cat "$swarm_log" "$tracker_log"
exit 1
fi
if ! diff -r "$out/single.bin" "$DATA/single.bin" >/dev/null; then
echo "FAIL: content mismatch"
exit 1
fi
if ! grep -q "REQUEST" "$tracker_log"; then
echo "FAIL: tracker received no announce"
exit 1
fi
echo "PASS: $MODE tracker discovery produced byte-identical output"

View file

@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""Seed a .torrent with libtorrent on 127.0.0.1 and print the listen port.
Usage: seeder.py <file.torrent> <save_path>
Runs until killed. Prints 'PORT <n>' once it is seeding."""
import libtorrent as lt, os, sys, time
enc_policy = 0 if os.environ.get("NAUT_FORCE_MSE") == "1" else 1
torrent, save_path = sys.argv[1], sys.argv[2]
ses = lt.session({
"listen_interfaces": "127.0.0.1:0",
"unchoke_slots_limit": 64, # unchoke leechers fast
"in_enc_policy": enc_policy,
"out_enc_policy": enc_policy,
"alert_mask": lt.alert_category.all,
})
atp = lt.add_torrent_params()
atp.ti = lt.torrent_info(torrent)
atp.save_path = save_path
atp.flags |= lt.torrent_flags.seed_mode # data already present; skip recheck
atp.flags &= ~lt.torrent_flags.paused # must be active to accept peers
atp.flags &= ~lt.torrent_flags.auto_managed # don't let the queue re-pause it
h = ses.add_torrent(atp)
h.resume()
deadline = time.time() + 30
while not h.status().is_seeding and time.time() < deadline:
time.sleep(0.05)
if not h.status().is_seeding:
print("ERROR: not seeding", flush=True); sys.exit(1)
print("PORT %d" % ses.listen_port(), flush=True)
while True:
for a in ses.pop_alerts():
print("ALERT %s: %s" % (type(a).__name__, a.message()), flush=True)
time.sleep(0.2)

View file

@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""Minimal BEP-15 UDP tracker for the Phase 4 integration test."""
import socket
import struct
import sys
peer_port = int(sys.argv[1])
connection_id = 0x0102030405060708
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 0))
print("PORT %d" % sock.getsockname()[1], flush=True)
while True:
packet, addr = sock.recvfrom(2048)
if len(packet) >= 16 and packet[8:12] == b"\x00\x00\x00\x00":
sock.sendto(struct.pack("!IIQ", 0, struct.unpack("!I", packet[12:16])[0],
connection_id), addr)
continue
if len(packet) >= 98 and packet[8:12] == b"\x00\x00\x00\x01":
txid = struct.unpack("!I", packet[12:16])[0]
response = (
struct.pack("!IIIII", 1, txid, 1800, 0, 1)
+ socket.inet_aton("127.0.0.1")
+ struct.pack("!H", peer_port)
)
sock.sendto(response, addr)
print("REQUEST announce", flush=True)