Skip to content
HTTP Tracker Protocol
step 1/5

Reading — step 1 of 5

Read

~3 min readTracker Communication

HTTP Tracker Protocol

A tracker is a phone book, not a relay. It never touches file data; it keeps, per infohash, a list of who is currently in the swarm. You announce yourself, you get peers back, and you re-announce on an interval so the list stays fresh. The original BEP 3 design is a single HTTP GET:

GET /announce?info_hash=%01%02...&peer_id=-qB4500-abc123&port=6881
    &uploaded=0&downloaded=0&left=4200000000&compact=1&event=started
ParamWhy it exists
info_hashWhich swarm — the 20 raw hash bytes, percent-encoded (see below)
peer_idYour 20-byte identity; conventionally -qB4500- + random (client code + version)
portThe port you listen on — peers will dial you back
uploaded / downloadedSession byte counters (stats; ratio enforcement on private trackers)
leftBytes you still need. left=0 marks you a seeder
compact1 = send the packed binary peer list (BEP 23)
eventstarted on first contact, completed when done, stopped on exit; omitted for periodic re-announces

The info_hash encoding trap

info_hash is not text. It is 20 raw bytes, and an HTTP query string cannot carry raw bytes — so BEP 3 says: percent-encode per byte. Bytes in the unreserved set [a-zA-Z0-9._~-] pass through literally; every other byte becomes %XX.

Walk the hash 00 11 22 33 44 55 66 77 ... through that rule: 0x00%00, 0x11%11, 0x22 (the quote character) → %22 — but 0x33 is ASCII 3, unreserved, so it passes through literally, and 0x44 is D. The encoded result %00%11%223DUfw... interleaves escapes with literal characters. It looks broken; it is correct.

The classic bug is sending the 40-char hex string instead. The tracker does not "decode" it — it percent-decodes and sees a 40-byte identifier that matches no torrent. You get an empty peer list or a failure reason, and nothing tells you why. A subtler bug: routing the hash through a string API. Keep it as bytes and build the escape per byte — pushing it through str invites UTF-8 expansion of every byte above 0x7F.

The response

A bencoded dict:

{
  'interval': 1800,        # seconds until you re-announce
  'min interval': 900,     # optional floor
  'complete': 42,          # seeders
  'incomplete': 7,         # leechers
  'peers': <binary blob>   # compact format
}

(On error the dict contains a single failure reason string instead.)

With compact=1, peers is one binary string of 6 * N bytes — per peer, 4 IPv4 bytes then a 2-byte big-endian port. Walk c0 a8 01 01 1a e1: IP 192.168.1.1, port 0x1ae1 = 6881. The older verbose form (a bencoded list of dicts) still exists in the spec, but compact is what real trackers send — at 6 bytes per peer, a 50-peer answer fits in ~300 bytes.

Your exercise

BUILD prints the announce URL with parameters in exactly this order: info_hash, peer_id, port, uploaded, downloaded, left, compact=1. Mistakes the grader catches:

  1. Lowercase escapes. The expected output reads info_hash=%01...%0A%0B%0C... — uppercase hex (%02X formatting). %0a fails the exact string comparison.
  2. Escaping unreserved bytes. The hidden test's hash contains 0x33 and 0x440x77; the grader expects %00%11%223DUfw%88.... Escaping everything (%00%11%22%33%44...) violates the unreserved-set rule and fails.
  3. PARSE input is hex. The tests feed the bencoded response as one hex string (raw tracker bytes cannot ride a text stdin): bytes.fromhex first, then bencode-decode. Print interval=, complete=, incomplete=, then a peers: line followed by one ip:port per line — and when peers is empty, peers: is the final line with nothing after it.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…