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.