Expose real tracker state to web UI

This commit is contained in:
ookami125 2026-06-17 02:14:10 -04:00
parent 6be239943a
commit 6ec5b19a73
4 changed files with 249 additions and 20 deletions

View file

@ -256,6 +256,28 @@ static json_t *torrent_json(torrent_task *task) {
}
json_object_set_new(result, "peer_list", peer_list);
}
json_t *trackers = json_array();
if (trackers) {
uint32_t tracker_count = task->stats.tracker_count;
if (tracker_count > NAUT_SWARM_MAX_TRACKER_STATS)
tracker_count = NAUT_SWARM_MAX_TRACKER_STATS;
for (uint32_t i = 0; i < tracker_count; i++) {
const naut_swarm_tracker_stats *tracker =
&task->stats.tracker_stats[i];
json_t *item = json_pack(
"{s:s,s:i,s:s,s:i,s:i,s:i,s:i,s:s}",
"url", tracker->url,
"tier", tracker->tier,
"status", tracker->status,
"seeds", tracker->seeds,
"peers", tracker->peers,
"leeches", tracker->leeches,
"downloaded", tracker->downloaded,
"message", tracker->message);
if (item) json_array_append_new(trackers, item);
}
json_object_set_new(result, "trackers", trackers);
}
json_object_set_new(result, "elapsed_seconds",
json_real(task->stats.elapsed_seconds));
json_object_set_new(result, "pending_moves",

View file

@ -41,6 +41,10 @@
#define EXT_RESERVED 0x0000000000100000ULL
#define DEFAULT_TARGET_PEERS 80
#define MAX_TARGET_PEERS 512
#define TRACKER_DEFAULT_INTERVAL 1800.0
#define TRACKER_MIN_INTERVAL 60.0
#define TRACKER_FAILURE_RETRY_INTERVAL 300.0
#define DHT_REFRESH_INTERVAL 300.0
typedef struct {
uint32_t piece, begin, length;
@ -85,6 +89,13 @@ static uint32_t target_peer_count(void) {
return (uint32_t)NAUT_MIN(value, MAX_TARGET_PEERS);
}
static double tracker_delay_seconds(int32_t interval) {
if (interval <= 0) return TRACKER_DEFAULT_INTERVAL;
if (interval < (int32_t)TRACKER_MIN_INTERVAL)
return TRACKER_MIN_INTERVAL;
return (double)interval;
}
static void random_bytes(uint8_t *output, size_t length) {
int fd = open("/dev/urandom", O_RDONLY);
size_t offset = 0;
@ -225,12 +236,63 @@ static void snapshot_peer_stats(naut_swarm_stats *stats,
}
}
static uint32_t init_tracker_stats(char *const *trackers,
const uint32_t *tracker_tiers,
size_t num_trackers,
naut_swarm_tracker_stats *stats,
uint32_t capacity) {
uint32_t count = 0;
if (!stats) return 0;
for (size_t i = 0; i < num_trackers && count < capacity; i++) {
naut_swarm_tracker_stats *out = &stats[count++];
memset(out, 0, sizeof(*out));
snprintf(out->url, sizeof out->url, "%s", trackers[i]);
out->tier = tracker_tiers ? (int32_t)tracker_tiers[i] : (int32_t)i;
snprintf(out->status, sizeof out->status, "not contacted");
out->seeds = -1;
out->peers = -1;
out->leeches = -1;
out->downloaded = -1;
}
return count;
}
static naut_swarm_tracker_stats *tracker_stat_for(
naut_swarm_tracker_stats *stats, uint32_t count, const char *url) {
if (!stats || !url) return NULL;
for (uint32_t i = 0; i < count; i++)
if (strcmp(stats[i].url, url) == 0) return &stats[i];
return NULL;
}
static void tracker_set_status(naut_swarm_tracker_stats *tracker,
const char *status, const char *message) {
if (!tracker) return;
snprintf(tracker->status, sizeof tracker->status, "%s",
status ? status : "");
snprintf(tracker->message, sizeof tracker->message, "%s",
message ? message : "");
}
static void snapshot_tracker_stats(naut_swarm_stats *stats,
const naut_swarm_tracker_stats *trackers,
uint32_t tracker_count) {
if (!stats || !trackers) return;
if (tracker_count > NAUT_SWARM_MAX_TRACKER_STATS)
tracker_count = NAUT_SWARM_MAX_TRACKER_STATS;
stats->tracker_count = tracker_count;
for (uint32_t i = 0; i < tracker_count; i++)
stats->tracker_stats[i] = trackers[i];
}
static void report_progress(const naut_swarm_config *config,
const naut_download *download,
const naut_metainfo *metainfo,
uint32_t peers_total, uint32_t peers_connecting,
uint32_t peers_active, uint32_t peers_failed,
peer_t *peers, int npeers,
const naut_swarm_tracker_stats *trackers,
uint32_t tracker_count,
double started_at) {
if (!config->on_progress) return;
naut_swarm_stats stats = {
@ -245,6 +307,7 @@ static void report_progress(const naut_swarm_config *config,
.elapsed_seconds = now() - started_at,
};
snapshot_peer_stats(&stats, peers, npeers, metainfo->num_pieces);
snapshot_tracker_stats(&stats, trackers, tracker_count);
if (download) {
stats.piece_state_count = (uint32_t)naut_download_piece_states(
download, stats.piece_states, NAUT_SWARM_MAX_PIECE_STATS);
@ -340,16 +403,24 @@ static bool discover_trackers(const uint8_t info_hash[20], uint64_t total_length
char *const *trackers, size_t num_trackers,
const uint32_t *tracker_tiers,
const uint8_t peerid[20],
endpoint_t **eps, size_t *neps, size_t *cap) {
uint64_t downloaded, uint64_t left,
naut_tracker_event event,
endpoint_t **eps, size_t *neps, size_t *cap,
int32_t *announce_interval,
naut_swarm_tracker_stats *tracker_stats,
uint32_t tracker_count) {
naut_announce_req req;
memset(&req, 0, sizeof req);
memcpy(req.info_hash, info_hash, sizeof req.info_hash);
memcpy(req.peer_id, peerid, sizeof req.peer_id);
req.port = 6881;
req.downloaded = downloaded;
req.left = total_length;
req.event = NAUT_TEV_STARTED;
if (left <= total_length) req.left = left;
req.event = event;
req.numwant = 100;
memcpy(&req.key, peerid + 8, sizeof req.key);
if (announce_interval) *announce_interval = 0;
size_t tier_start = 0;
while (tier_start < num_trackers) {
@ -370,6 +441,10 @@ static bool discover_trackers(const uint8_t info_hash[20], uint64_t total_length
size_t i = tier_start + (first + n) % tier_count;
const char *tracker = trackers[i];
naut_tracker_response response;
memset(&response, 0, sizeof response);
response.seeders = response.leechers = -1;
naut_swarm_tracker_stats *tracker_stat =
tracker_stat_for(tracker_stats, tracker_count, tracker);
naut_err e = NAUT_ERR_INVAL;
if (strncmp(tracker, "http://", 7) == 0) {
char url[4096];
@ -382,14 +457,30 @@ static bool discover_trackers(const uint8_t info_hash[20], uint64_t total_length
if (parse_udp_tracker(tracker, host, sizeof host, &port))
e = naut_tracker_announce_udp(host, port, &req, &response);
} else {
tracker_set_status(tracker_stat, "unsupported",
"unsupported tracker scheme");
NAUT_WARN("tracker scheme unsupported: %s", tracker);
continue;
}
if (e != NAUT_OK) {
tracker_set_status(tracker_stat, "error",
response.failure
? response.failure : "announce failed");
NAUT_WARN("tracker announce failed: %s", tracker);
naut_tracker_response_free(&response);
continue;
}
tier_succeeded = true;
if (announce_interval && response.interval > 0)
*announce_interval = response.interval;
if (tracker_stat) {
tracker_set_status(tracker_stat, "working", "");
tracker_stat->seeds = response.seeders;
tracker_stat->leeches = response.leechers;
tracker_stat->peers = response.num_peers > INT32_MAX
? INT32_MAX : (int32_t)response.num_peers;
tracker_stat->downloaded = -1;
}
NAUT_INFO("tracker %s returned %zu peers",
tracker, response.num_peers);
for (size_t p = 0; p < response.num_peers; p++) {
@ -934,6 +1025,9 @@ naut_err naut_swarm_run(const naut_swarm_config *config) {
endpoint_t *endpoints = NULL;
size_t neps = 0, epcap = 0;
uint32_t target_peers = target_peer_count();
int32_t tracker_interval = 0;
naut_swarm_tracker_stats tracker_stats[NAUT_SWARM_MAX_TRACKER_STATS];
uint32_t tracker_count = 0;
if (config->num_peers > 0) {
for (size_t i = 0; i < config->num_peers; i++) {
naut_peer_addr addr;
@ -958,10 +1052,15 @@ naut_err naut_swarm_run(const naut_swarm_config *config) {
from_magnet ? NULL : mi.tracker_tiers;
size_t num_trackers =
from_magnet ? magnet.num_trackers : mi.num_trackers;
tracker_count = init_tracker_stats(trackers, tracker_tiers,
num_trackers, tracker_stats,
NAUT_SWARM_MAX_TRACKER_STATS);
uint64_t total = from_magnet ? 0 : (uint64_t)mi.total_length;
if (!discover_trackers(hash, total, trackers, num_trackers,
tracker_tiers, peerid, &endpoints, &neps,
&epcap) ||
tracker_tiers, peerid, 0, total,
NAUT_TEV_STARTED, &endpoints, &neps, &epcap,
&tracker_interval, tracker_stats,
tracker_count) ||
(neps < target_peers &&
!discover_dht(hash, &endpoints, &neps, &epcap))) {
NAUT_ERROR("out of memory collecting discovered peers");
@ -1088,6 +1187,11 @@ naut_err naut_swarm_run(const naut_swarm_config *config) {
for (int i = 0; i < npeers; i++) peers[i].fd = -1;
double t0 = now();
double next_tracker_announce =
t0 + (tracker_interval > 0
? tracker_delay_seconds(tracker_interval)
: TRACKER_FAILURE_RETRY_INTERVAL);
double next_dht_lookup = t0 + DHT_REFRESH_INTERVAL;
naut_err run_error = NAUT_OK;
bool cancelled = false;
uint32_t connecting = 0;
@ -1096,7 +1200,7 @@ naut_err naut_swarm_run(const naut_swarm_config *config) {
struct pollfd *connect_fds = pfd;
for (int i = 0; i < npeers; i++) connect_fds[i].fd = -1;
report_progress(config, d, &mi, (uint32_t)npeers, 0, 0, 0,
peers, npeers, t0);
peers, npeers, tracker_stats, tracker_count, t0);
for (int i = 0; i < npeers; i++) {
bool connected = false;
int fd = connect_start(&endpoints[i], &connected);
@ -1130,7 +1234,8 @@ naut_err naut_swarm_run(const naut_swarm_config *config) {
while (connecting > 0 && now() < connect_deadline &&
!stop_requested(config)) {
report_progress(config, d, &mi, (uint32_t)npeers, connecting,
active, failed, peers, npeers, t0);
active, failed, peers, npeers,
tracker_stats, tracker_count, t0);
int ready = poll(connect_fds, (nfds_t)neps, 100);
if (ready < 0) {
if (errno == EINTR) continue;
@ -1181,13 +1286,53 @@ naut_err naut_swarm_run(const naut_swarm_config *config) {
emit_event(config, NAUT_EVENT_TORRENT_ADDED, 0, NULL, NULL);
report_progress(config, d, &mi, (uint32_t)npeers, 0, active, failed,
peers, npeers, t0);
peers, npeers, tracker_stats, tracker_count, t0);
while (!naut_download_complete(d) && run_error == NAUT_OK) {
service_control(config, st);
if (stop_requested(config)) {
cancelled = true;
break;
}
if (config->num_peers == 0 && npending == 0 &&
peer_count_active(peers, npeers, true) < target_peers) {
double t = now();
if (mi.num_trackers > 0 && t >= next_tracker_announce) {
uint64_t downloaded = naut_download_bytes_done(d);
uint64_t left = (uint64_t)mi.total_length > downloaded
? (uint64_t)mi.total_length - downloaded : 0;
int32_t interval = 0;
size_t before = npending;
if (!discover_trackers(mi.infohash_v1,
(uint64_t)mi.total_length,
mi.trackers, mi.num_trackers,
mi.tracker_tiers, peerid,
downloaded, left, NAUT_TEV_NONE,
&pending, &npending, &pending_cap,
&interval, tracker_stats,
tracker_count)) {
run_error = NAUT_ERR_NOMEM;
break;
}
next_tracker_announce = t + (interval > 0
? tracker_delay_seconds(interval)
: TRACKER_FAILURE_RETRY_INTERVAL);
if (npending > before)
NAUT_INFO("tracker refresh queued %zu peers",
npending - before);
}
if (npending == 0 && t >= next_dht_lookup) {
size_t before = npending;
if (!discover_dht(mi.infohash_v1, &pending, &npending,
&pending_cap)) {
run_error = NAUT_ERR_NOMEM;
break;
}
next_dht_lookup = t + DHT_REFRESH_INTERVAL;
if (npending > before)
NAUT_INFO("DHT refresh queued %zu peers",
npending - before);
}
}
if (!connect_pending_peers(config, d, &mi, peerid, &peers, &npeers,
&peer_capacity, &pfd, &idx_map,
pending, &npending, target_peers, &failed)) {
@ -1226,7 +1371,7 @@ naut_err naut_swarm_run(const naut_swarm_config *config) {
(uint32_t)connecting_peers,
(uint32_t)live_peers,
failed,
peers, npeers, t0);
peers, npeers, tracker_stats, tracker_count, t0);
int r = poll(pfd, nf, 200);
if (r < 0) {
if (errno == EINTR) continue;
@ -1304,7 +1449,8 @@ naut_err naut_swarm_run(const naut_swarm_config *config) {
naut_download_pieces_done(d), mi.num_pieces, dt, mb/dt,
naut_download_in_endgame(d) ? " (passed through endgame)" : "");
report_progress(config, d, &mi, (uint32_t)npeers, 0, 0,
(uint32_t)npeers, peers, npeers, t0);
(uint32_t)npeers, peers, npeers,
tracker_stats, tracker_count, t0);
emit_event(config, NAUT_EVENT_TORRENT_FINISHED, 0, NULL, NULL);
while (config->keep_alive && !stop_requested(config)) {
service_control(config, st);