I'm trying to perform a UDP scrape from public UDP trackers such as tracker.publicbt.com or tracker.openbittorrent.com using the BitTorrent UDP Tracker Protocol. My app sends a request to the tracker for a connection_id and uses that id to perform a scrape. The scrape response is returned from the tracker, with no errors to indicate a badly formed packet, but no matter what info_hash I use, I get "0" returned for the numbers of seeders, leechers and completed.
I've thoroughly checked that the packet is the right size, that the info_hash starts at the correct offset, and that the data are all correct. As far as I can see, there are no problems creating and sending the packet. This question's been open and unanswered for a few days, so I've updated and edited the code example below in the hope someone can help.
I've hardcoded an info_hash into the following example. When run on the command line this code should connect to the tracker, get a connection_id and then perform a scrape on an Ubuntu torrent info_hash, outputting various bits of info to the console.
The connection_id is split into 2 parts because it is a 64 bit integer.
var dgram = require('dgram'), server = dgram.createSocket("udp4"), connectionIdHigh = 0x417, connectionIdLow = 0x27101980, transactionId, action, trackerHost = "tracker.publicbt.com", trackerPort = 80, infoHash = "", ACTION_CONNECT = 0, ACTION_ANNOUNCE = 1, ACTION_SCRAPE = 2, ACTION_ERROR = 3, sendPacket = function (buf, host, port) { "use strict"; server.send(buf, 0, buf.length, port, host, function(err, bytes) { if (err) { console.log(err.message); } }); }, startConnection = function (host, port) { "use strict"; var buf = new Buffer(16); transactionId = Math.floor((Math.random()*100000)+1); buf.fill(0); buf.writeUInt32BE(connectionIdHigh, 0); buf.writeUInt32BE(connectionIdLow, 4); buf.writeUInt32BE(ACTION_CONNECT, 8); buf.writeUInt32BE(transactionId, 12); sendPacket(buf, host, port); }, scrapeTorrent = function (host, port, hash) { "use strict"; var buf = new Buffer(56), tmp = ''; infoHash = hash; if (!transactionId) { startConnection(host, port); } else { buf.fill(0); buf.writeUInt32BE(connectionIdHigh, 0); buf.writeUInt32BE(connectionIdLow, 4); buf.writeUInt32BE(ACTION_SCRAPE, 8); buf.writeUInt32BE(transactionId, 12); buf.write(infoHash, 16, buf.length); console.log(infoHash); console.log(buf.toString('utf8', 16, buf.length)); // do scrape sendPacket(buf, host, port); transactionId = null; infoHash = null; } }; server.on("message", function (msg, rinfo) { "use strict"; var buf = new Buffer(msg), seeders, completed, leechers; console.log(rinfo); action = buf.readUInt32BE(0, 4); transactionId = buf.readUInt32BE(4, 4); console.log("returned action: " + action); console.log("returned transactionId: " + transactionId); if (action === ACTION_CONNECT) { console.log("connect response"); connectionIdHigh = buf.readUInt32BE(8, 4); connectionIdLow = buf.readUInt32BE(12, 4); scrapeTorrent(trackerHost, trackerPort, infoHash); } else if (action === ACTION_SCRAPE) { console.log("scrape response"); seeders = buf.readUInt32BE(8, 4); completed = buf.readUInt32BE(12, 4); leechers = buf.readUInt32BE(16, 4); console.log(seeders); console.log(completed); console.log(leechers); } else if (action === ACTION_ERROR) { console.log("error response"); } }); server.on("listening", function () { "use strict"; var address = server.address(); console.log("server listening " + address.address + ":" + address.port); }); server.bind(); scrapeTorrent(trackerHost, trackerPort, "335990D615594B9BE409CCFEB95864E24EC702C7");
I finally worked this out, and kicked myself for not realising sooner.
An info_hash is a hex encoded string, so when it's written to the buffer needs to have it's encoding set. For example:
buf.write(infoHash, 16, buf.length, 'hex');
The UDP tracker protocol doesn't mention the encoding required, it just describes it as a 20 byte string. Hopefully this Q&A might help someone else who encounters the same problem.