Chapter 2

Tracker Communication

What Is a Tracker?

A tracker is an HTTP (or UDP) server that knows which peers are currently downloading a given torrent. You tell it you exist, it gives you a list of other peers. That’s the entire job.

The HTTP Announce Request

Send a GET with these query parameters:

ParameterValue
info_hashURL-encoded 20-byte SHA-1 of info dict
peer_idYour 20-byte random client ID
portPort you’re listening on (e.g. 6881)
uploadedBytes uploaded so far
downloadedBytes downloaded so far
leftBytes remaining
compact1 (request compact peer list)
eventstarted / completed / stopped
char url[2048];
snprintf(url, sizeof(url),
    "%s?info_hash=%s&peer_id=%s&port=6881"
    "&uploaded=0&downloaded=0&left=%zu&compact=1&event=started",
    tracker_url,
    url_encode(info_hash, 20),   // percent-encode all 20 bytes
    peer_id_str,
    total_size
);

URL-Encoding the Info Hash

The info_hash is raw binary, but URL query strings need percent-encoding. Every byte becomes %XX where XX is the hex value — even printable ASCII chars.

char *url_encode(const uint8_t *data, size_t len) {
    char *out = malloc(len * 3 + 1);
    char *p = out;
    for (size_t i = 0; i < len; i++) {
        p += sprintf(p, "%%%02X", data[i]);
    }
    *p = '\0';
    return out;
}

Parsing the Tracker Response

The tracker responds with a bencoded dict. Success looks like:

d
  8:interval  i1800e        ← re-announce every 30 minutes
  5:peers     <binary>      ← compact format: 6 bytes per peer
e

In compact mode (compact=1), peers is a binary string where every 6 bytes is one peer: 4 bytes IP (big-endian) + 2 bytes port (big-endian).

BencNode *resp  = benc_parse(response_body, response_len, &consumed);
BencNode *peers_raw = dict_get(resp, "peers");

size_t peer_count = peers_raw->string.len / 6;
for (size_t i = 0; i < peer_count; i++) {
    uint8_t *p = (uint8_t *)peers_raw->string.data + i * 6;
    struct in_addr ip = { .s_addr = *(uint32_t *)p };
    uint16_t port = ntohs(*(uint16_t *)(p + 4));
    printf("Peer: %s:%u\n", inet_ntoa(ip), port);
}

What I Got Wrong First

My initial URL encoding only encoded non-ASCII bytes — treating printable bytes like . and - as safe. The tracker rejected the request. The BitTorrent spec requires encoding all 20 bytes of the info_hash unconditionally, regardless of their value.

Also: the interval field is the minimum time between announces, not a suggestion. Some trackers ban clients that announce too frequently.

Next: Connecting to Peers

With a list of peer IP:port pairs, we can start opening TCP connections and doing the BitTorrent handshake. That’s the next chapter.