/* tracker_client.c — HTTP/UDP tracker announce client. * * The wire codec (query building, bencode/UDP packet encode+decode) comes from * the sibling `torrent-tracker` library; this file owns only the socket glue and * the conversion between Naut's announce types and torrent-tracker's. */ #include "naut/tracker.h" #include "naut/log.h" #include "tracker.h" /* torrent-tracker public ABI */ #include #include #include #include #include #include #include #include #include #define TRACKER_RESPONSE_MAX (16u << 20) void naut_tracker_response_free(naut_tracker_response *r) { if (!r) return; free(r->peers); free(r->failure); r->peers = NULL; r->failure = NULL; r->num_peers = 0; } /* naut_announce_req -> torrent-tracker request (compact IPv4 announce). */ static void to_tracker_request(const naut_announce_req *req, tracker_announce_request *out) { memset(out, 0, sizeof *out); memcpy(out->info_hash, req->info_hash, 20); memcpy(out->peer_id, req->peer_id, 20); out->port = req->port; out->uploaded = req->uploaded; out->downloaded = req->downloaded; out->left = req->left; out->numwant = req->numwant; out->key = req->key; out->has_key = 1; out->compact = 1; out->event = (tracker_event)req->event; /* codes match BEP-15 */ } /* Copy torrent-tracker IPv4 peers into a freshly malloc'd naut_peer_addr array. */ static naut_err collect_peers(const tracker_peer *peers, size_t count, const tracker_announce_response *resp, naut_tracker_response *out) { out->interval = (int32_t)resp->interval; out->min_interval = (int32_t)resp->min_interval; out->seeders = (int32_t)resp->complete; out->leechers = (int32_t)resp->incomplete; out->peers = NULL; out->num_peers = 0; if (count == 0) return NAUT_OK; naut_peer_addr *v = malloc(count * sizeof *v); if (!v) return NAUT_ERR_NOMEM; size_t n = 0; for (size_t i = 0; i < count; i++) { if (peers[i].family != TRACKER_ADDR_IPV4) continue; /* IPv4 only */ memcpy(v[n].ip, peers[i].addr, 4); v[n].port = peers[i].port; n++; } out->peers = v; out->num_peers = n; return NAUT_OK; } size_t naut_tracker_http_url(const char *base, const naut_announce_req *req, char *out, size_t outsz) { tracker_announce_request treq; to_tracker_request(req, &treq); char query[2048]; size_t qlen = 0; if (tracker_http_write_announce_query(&treq, query, sizeof query, &qlen) != TRACKER_OK) return 0; const char sep = strchr(base, '?') ? '&' : '?'; int n = snprintf(out, outsz, "%s%c%.*s", base, sep, (int)qlen, query); if (n < 0 || (size_t)n >= outsz) return 0; return (size_t)n; } /* --- HTTP --------------------------------------------------------------- */ static int dial(const char *host, const char *port, int socktype) { struct addrinfo hints, *res = NULL, *ai; memset(&hints, 0, sizeof hints); hints.ai_family = AF_INET; /* IPv4 (compact peers are v4) */ hints.ai_socktype = socktype; if (getaddrinfo(host, port, &hints, &res) != 0) return -1; int fd = -1; for (ai = res; ai; ai = ai->ai_next) { fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd < 0) continue; struct timeval tv = { .tv_sec = 10, .tv_usec = 0 }; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) break; close(fd); fd = -1; } freeaddrinfo(res); return fd; } /* split "http://host[:port]/path" */ static bool parse_http_url(const char *url, char *host, size_t hostsz, char *port, size_t portsz, const char **path) { if (strncmp(url, "http://", 7) != 0) return false; const char *h = url + 7; const char *slash = strchr(h, '/'); const char *hostend = slash ? slash : h + strlen(h); const char *colon = memchr(h, ':', (size_t)(hostend - h)); size_t hlen = colon ? (size_t)(colon - h) : (size_t)(hostend - h); if (hlen >= hostsz) return false; memcpy(host, h, hlen); host[hlen] = 0; if (colon) { size_t plen = (size_t)(hostend - colon - 1); if (plen >= portsz) return false; memcpy(port, colon + 1, plen); port[plen] = 0; } else { snprintf(port, portsz, "80"); } *path = slash ? slash : "/"; return true; } static bool write_all(int fd, const void *data, size_t len) { const uint8_t *p = data; while (len) { ssize_t n = write(fd, p, len); if (n < 0) { if (errno == EINTR) continue; return false; } p += (size_t)n; len -= (size_t)n; } return true; } naut_err naut_tracker_announce_http(const char *url, naut_tracker_response *out) { char host[256], port[16]; const char *path; if (!parse_http_url(url, host, sizeof host, port, sizeof port, &path)) return NAUT_ERR_INVAL; int fd = dial(host, port, SOCK_STREAM); if (fd < 0) { NAUT_WARN("tracker connect %s:%s failed", host, port); return NAUT_ERR_IO; } char req[4096]; int rn = snprintf(req, sizeof req, "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: Naut/0.1\r\nAccept: */*\r\n\r\n", path, host); if (rn < 0 || (size_t)rn >= sizeof req || !write_all(fd, req, (size_t)rn)) { close(fd); return NAUT_ERR_IO; } /* read whole response (server closes on HTTP/1.0) */ size_t cap = 1 << 16, len = 0; uint8_t *buf = malloc(cap); if (!buf) { close(fd); return NAUT_ERR_NOMEM; } naut_err read_error = NAUT_OK; for (;;) { if (len == cap) { if (cap == TRACKER_RESPONSE_MAX) { read_error = NAUT_ERR_FULL; break; } size_t next_cap = NAUT_MIN(cap * 2, (size_t)TRACKER_RESPONSE_MAX); uint8_t *next = realloc(buf, next_cap); if (!next) { read_error = NAUT_ERR_NOMEM; break; } buf = next; cap = next_cap; } ssize_t r = read(fd, buf + len, cap - len); if (r < 0) { if (errno == EINTR) continue; read_error = NAUT_ERR_IO; break; } if (r == 0) break; len += (size_t)r; } close(fd); if (read_error != NAUT_OK) { free(buf); return read_error; } /* find body after CRLFCRLF */ uint8_t *body = NULL; size_t blen = 0; for (size_t i = 0; i + 3 < len; i++) if (buf[i]=='\r'&&buf[i+1]=='\n'&&buf[i+2]=='\r'&&buf[i+3]=='\n') { body = buf + i + 4; blen = len - (i + 4); break; } bool ok = len >= 12 && memcmp(buf, "HTTP/", 5) == 0 && buf[9] == '2'; if (!ok || !body) { free(buf); return NAUT_ERR_PROTO; } tracker_peer peers[TRACKER_MAX_PEERS]; tracker_announce_response resp; memset(&resp, 0, sizeof resp); naut_err e = NAUT_ERR_PROTO; if (tracker_http_parse_announce_response(body, blen, peers, TRACKER_MAX_PEERS, &resp) == TRACKER_OK) e = collect_peers(resp.peers, resp.peer_count, &resp, out); free(buf); return e; } /* --- UDP (BEP-15) ------------------------------------------------------- */ naut_err naut_tracker_announce_udp(const char *host, uint16_t port, const naut_announce_req *req, naut_tracker_response *out) { char portstr[16]; snprintf(portstr, sizeof portstr, "%u", port); int fd = dial(host, portstr, SOCK_DGRAM); if (fd < 0) return NAUT_ERR_IO; srand((unsigned)time(NULL) ^ (unsigned)getpid()); uint32_t txid = (uint32_t)rand(); uint8_t pkt[128], resp[2048]; size_t written = 0; if (tracker_udp_write_connect_request(txid, pkt, sizeof pkt, &written) != TRACKER_OK || !write_all(fd, pkt, written)) { close(fd); return NAUT_ERR_IO; } ssize_t r = read(fd, resp, sizeof resp); uint64_t cid = 0; if (r < 0 || tracker_udp_parse_connect_response(resp, (size_t)r, txid, &cid) != TRACKER_OK) { close(fd); return NAUT_ERR_IO; } txid++; tracker_announce_request treq; to_tracker_request(req, &treq); if (tracker_udp_write_announce_request(cid, txid, &treq, pkt, sizeof pkt, &written) != TRACKER_OK || !write_all(fd, pkt, written)) { close(fd); return NAUT_ERR_IO; } r = read(fd, resp, sizeof resp); naut_err e = NAUT_ERR_IO; if (r >= 0) { tracker_peer peers[TRACKER_MAX_PEERS]; tracker_announce_response tresp; memset(&tresp, 0, sizeof tresp); e = NAUT_ERR_PROTO; if (tracker_udp_parse_announce_response(resp, (size_t)r, txid, TRACKER_ADDR_IPV4, peers, TRACKER_MAX_PEERS, &tresp) == TRACKER_OK) e = collect_peers(tresp.peers, tresp.peer_count, &tresp, out); } close(fd); return e; }