From 0babdde10161cccf7227b7e8229cdb3f24cd1b3b Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Sun, 16 Aug 2015 20:48:11 +0200 Subject: [PATCH] Disable DHT and PEX when the torrent is flagged as private Add test for private torrent - should disable DHT Fail public torrent test when using a private torrent standard Disable DHT for the torrent's swarm and discovery. --- lib/torrent.js | 12 +- test/download-private-dht.js | 118 +++++++++++++++++++ test/torrents/big-buck-bunny-private.torrent | Bin 0 -> 17270 bytes 3 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 test/download-private-dht.js create mode 100644 test/torrents/big-buck-bunny-private.torrent diff --git a/lib/torrent.js b/lib/torrent.js index 9d4ecdf5..0275d70c 100644 --- a/lib/torrent.js +++ b/lib/torrent.js @@ -196,7 +196,9 @@ Torrent.prototype._onParsedTorrent = function (parsedTorrent) { // create swarm self.swarm = new Swarm(self.infoHash, self.client.peerId, { - handshake: { dht: !!self.client.dht } + handshake: { + dht: self.private ? false : !!self.client.dht + } }) self.swarm.on('error', self._onError.bind(self)) self.swarm.on('wire', self._onWire.bind(self)) @@ -263,7 +265,9 @@ Torrent.prototype._onSwarmListening = function () { // begin discovering peers via the DHT and tracker servers self.discovery = new Discovery({ announce: self.announce, - dht: self.client.dht, + dht: self.private + ? false + : self.client.dht, tracker: self.client.tracker, peerId: self.client.peerId, port: self.client.torrentPort, @@ -587,8 +591,8 @@ Torrent.prototype._onWire = function (wire, addr) { wire.ut_metadata.fetch() } - // use ut_pex extension - if (typeof ut_pex === 'function') { + // use ut_pex extension if the torrent is not flagged as private + if (typeof ut_pex === 'function' && !self.private) { wire.use(ut_pex()) // wire.ut_pex.start() // TODO two-way communication diff --git a/test/download-private-dht.js b/test/download-private-dht.js new file mode 100644 index 00000000..14419265 --- /dev/null +++ b/test/download-private-dht.js @@ -0,0 +1,118 @@ +var auto = require('run-auto') +var DHT = require('bittorrent-dht/server') +var fs = require('fs') +var parseTorrent = require('parse-torrent') +var test = require('tape') +var WebTorrent = require('../') + +var bunnyTorrent = fs.readFileSync(__dirname + '/torrents/big-buck-bunny-private.torrent') +var bunnyParsed = parseTorrent(bunnyTorrent) + +var leavesTorrent = fs.readFileSync(__dirname + '/torrents/leaves.torrent') +var leavesParsed = parseTorrent(leavesTorrent) + +// remove trackers from .torrent file +bunnyParsed.announce = [] +leavesParsed.announce = [] + +test('private torrent should not use DHT', function (t) { + t.plan(3) + + var dhtServer = new DHT({ bootstrap: false }) + + dhtServer.on('error', function (err) { t.fail(err) }) + dhtServer.on('warning', function (err) { t.fail(err) }) + + auto({ + dhtPort: function (cb) { + dhtServer.listen(function () { + var port = dhtServer.address().port + cb(null, port) + }) + }, + + client: ['dhtPort', function (cb, r) { + var client = new WebTorrent({ + tracker: false, + dht: { bootstrap: '127.0.0.1:' + r.dhtPort } + }) + client.on('error', function (err) { t.fail(err) }) + client.on('warning', function (err) { t.fail(err) }) + + var torrent = client.add(bunnyParsed) + + torrent.on('dhtAnnounce', function () { + t.fail('client announced to dht') + }) + + client.on('torrent', function () { + if (!torrent.discovery.dht && !torrent.swarm.handshakeOpts.dht) { + t.pass('dht is disabled for this torrent') + cb(null, client) + } + }) + + }] + + }, function (err, r) { + if (err) throw err + + dhtServer.destroy(function () { + t.pass('dht server destroyed') + }) + r.client.destroy(function () { + t.pass('client destroyed') + }) + }) +}) + +test('public torrent should use DHT', function (t) { + t.plan(3) + + var dhtServer = new DHT({ bootstrap: false }) + + dhtServer.on('error', function (err) { t.fail(err) }) + dhtServer.on('warning', function (err) { t.fail(err) }) + + auto({ + dhtPort: function (cb) { + dhtServer.listen(function () { + var port = dhtServer.address().port + cb(null, port) + }) + }, + + client: ['dhtPort', function (cb, r) { + var client = new WebTorrent({ + tracker: false, + dht: { bootstrap: '127.0.0.1:' + r.dhtPort } + }) + client.on('error', function (err) { t.fail(err) }) + client.on('warning', function (err) { t.fail(err) }) + + var torrent = client.add(leavesParsed) + + torrent.on('dhtAnnounce', function () { + t.pass('client announced to dht') + cb(null, client) + }) + + client.on('torrent', function () { + if (!torrent.client.dht) { + t.fail('dht server is null') + } + }) + + }] + + }, function (err, r) { + if (err) throw err + + dhtServer.destroy(function () { + t.pass('dht server destroyed') + }) + r.client.destroy(function () { + t.pass('client destroyed') + }) + }) +}) diff --git a/test/torrents/big-buck-bunny-private.torrent b/test/torrents/big-buck-bunny-private.torrent new file mode 100644 index 0000000000000000000000000000000000000000..25018e7ddc8c39a81fd0816dc8da39e7f00aac37 GIT binary patch literal 17270 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 literal 0 HcmV?d00001