diff --git a/index.js b/index.js index c0662887..1afff0b6 100644 --- a/index.js +++ b/index.js @@ -106,51 +106,106 @@ function WebTorrent (opts) { if (global.WRTC && !self.tracker.wrtc) self.tracker.wrtc = global.WRTC } - if (typeof TCPPool === 'function') { - self._tcpPool = new TCPPool(self) + // Proxy + self.proxyOpts = opts.proxyOpts + if (self.proxyOpts) { + self.proxyOpts.proxyTrackerConnections = self.proxyOpts.proxyTrackerConnections !== false + self.proxyOpts.proxyPeerConnections = self.proxyOpts.proxyPeerConnections !== false + + if (self.tracker && self.proxyOpts.proxyTrackerConnections && !self.tracker.proxyOpts) { + self.tracker.proxyOpts = self.proxyOpts + } + + var socksProxy = self.proxyOpts.socksProxy + if (socksProxy) { + if (!socksProxy.proxy) socksProxy.proxy = {} + if (!socksProxy.proxy.type) socksProxy.proxy.type = 5 + + // Ensure electron-wrtc is used in electron with socks proxy + if (self.tracker && self.proxyOpts.proxyPeerConnections && + process && process.versions['electron'] && + self.tracker.wrtc && !self.tracker.wrtc.electronDaemon) { + console.warn('You need to provide an electron-wrtc instance in opts.wrtc to use Socks proxy in electron -> WebRTC is disabled') + self.tracker.wrtc = false + } + + // Convert proxy opts to electron API in webtorrent-hybrid + if (self.tracker && self.tracker.wrtc && self.tracker.wrtc.electronDaemon && + socksProxy && self.proxyOpts.proxyPeerConnections) { + if (!socksProxy.proxy.authentication && !socksProxy.proxy.userid && socksProxy.proxy.type === 5) { + var electronConfig = { + proxyRules: 'socks' + socksProxy.proxy.type + '://' + socksProxy.proxy.ipAddress + ':' + socksProxy.proxy.port + } + self.tracker.wrtc.electronDaemon.eval('window.webContents.session.setProxy(' + + JSON.stringify(electronConfig) + ', function(){})', {mainProcess: true}, networkSettingsReady) + } else { + console.warn('SOCKS Proxy must be version 5 with no authentication to work in electron-wrtc -> WebRTC is disabled') + self.tracker.wrtc = false + networkSettingsReady(null) + } + } else { + networkSettingsReady(null) + } + } else { + networkSettingsReady(null) + } } else { - process.nextTick(function () { - self._onListening() - }) + networkSettingsReady(null) } - // stats - self._downloadSpeed = speedometer() - self._uploadSpeed = speedometer() + function networkSettingsReady (err) { + if (err) { + self._destroy(err) + } - if (opts.dht !== false && typeof DHT === 'function' /* browser exclude */) { - // use a single DHT instance for all torrents, so the routing table can be reused - self.dht = new DHT(extend({ nodeId: self.nodeId }, opts.dht)) + if (typeof TCPPool === 'function') { + self._tcpPool = new TCPPool(self) + } else { + process.nextTick(function () { + self._onListening() + }) + } - self.dht.once('error', function (err) { - self._destroy(err) - }) + // stats + self._downloadSpeed = speedometer() + self._uploadSpeed = speedometer() - self.dht.once('listening', function () { - var address = self.dht.address() - if (address) self.dhtPort = address.port - }) + if (opts.dht !== false && typeof DHT === 'function' /* browser exclude */) { + // use a single DHT instance for all torrents, so the routing table can be reused + self.dht = new DHT(extend({nodeId: self.nodeId}, opts.dht)) - // Ignore warning when there are > 10 torrents in the client - self.dht.setMaxListeners(0) + self.dht.once('error', function (err) { + self._destroy(err) + }) - self.dht.listen(self.dhtPort) - } else { - self.dht = false - } + self.dht.once('listening', function () { + var address = self.dht.address() + if (address) self.dhtPort = address.port + }) - if (typeof loadIPSet === 'function' && opts.blocklist != null) { - loadIPSet(opts.blocklist, { - headers: { - 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' - } - }, function (err, ipSet) { - if (err) return self.error('Failed to load blocklist: ' + err.message) - self.blocked = ipSet - ready() - }) - } else { - process.nextTick(ready) + // Ignore warning when there are > 10 torrents in the client + self.dht.setMaxListeners(0) + + self.dht.listen(self.dhtPort) + } else { + self.dht = false + } + + debug('new webtorrent (peerId %s, nodeId %s)', self.peerId, self.nodeId) + + if (typeof loadIPSet === 'function' && opts.blocklist != null) { + loadIPSet(opts.blocklist, { + headers: { + 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' + } + }, function (err, ipSet) { + if (err) return self.error('Failed to load blocklist: ' + err.message) + self.blocked = ipSet + ready() + }) + } else { + process.nextTick(ready) + } } function ready () { diff --git a/lib/torrent.js b/lib/torrent.js index 78c63754..5379a4e0 100644 --- a/lib/torrent.js +++ b/lib/torrent.js @@ -5,6 +5,7 @@ module.exports = Torrent var addrToIPPort = require('addr-to-ip-port') var BitField = require('bitfield') var ChunkStoreWriteStream = require('chunk-store-stream/write') +var clone = require('clone') var debug = require('debug')('webtorrent:torrent') var Discovery = require('torrent-discovery') var EventEmitter = require('events').EventEmitter @@ -26,6 +27,7 @@ var Piece = require('torrent-piece') var pump = require('pump') var randomIterate = require('random-iterate') var sha1 = require('simple-sha1') +var Socks = require('socks') var speedometer = require('speedometer') var uniq = require('uniq') var utMetadata = require('ut_metadata') @@ -1658,38 +1660,56 @@ Torrent.prototype._drain = function () { port: parts[1] } - var conn = peer.conn = net.connect(opts) + if (self.client.proxyOpts && self.client.proxyOpts.socksProxy && self.client.proxyOpts.proxyPeerConnections) { + var proxyOpts = extend(self.client.proxyOpts.socksProxy, { + command: 'connect', + target: opts + }) - conn.once('connect', function () { peer.onConnect() }) - conn.once('error', function (err) { peer.destroy(err) }) - peer.startConnectTimeout() + Socks.createConnection(clone(proxyOpts), onGotSocket) + } else { + onGotSocket(null, net.connect(opts)) + } - // When connection closes, attempt reconnect after timeout (with exponential backoff) - conn.on('close', function () { - if (self.destroyed) return + function onGotSocket (err, socket) { + if (err) return peer.destroy(err) - // TODO: If torrent is done, do not try to reconnect after a timeout + var conn = peer.conn = socket - if (peer.retries >= RECONNECT_WAIT.length) { - self._debug( - 'conn %s closed: will not re-add (max %s attempts)', - peer.addr, RECONNECT_WAIT.length - ) - return + if (self.client.proxyOpts && self.client.proxyOpts.proxyPeerConnections) { + peer.onConnect() } + conn.once('connect', function () { peer.onConnect() }) + conn.once('error', function (err) { peer.destroy(err) }) + peer.startConnectTimeout() - var ms = RECONNECT_WAIT[peer.retries] - self._debug( - 'conn %s closed: will re-add to queue in %sms (attempt %s)', - peer.addr, ms, peer.retries + 1 - ) + // When connection closes, attempt reconnect after timeout (with exponential backoff) + conn.on('close', function () { + if (self.destroyed) return - var reconnectTimeout = setTimeout(function reconnectTimeout () { - var newPeer = self._addPeer(peer.addr) - if (newPeer) newPeer.retries = peer.retries + 1 - }, ms) - if (reconnectTimeout.unref) reconnectTimeout.unref() - }) + // TODO: If torrent is done, do not try to reconnect after a timeout + + if (peer.retries >= RECONNECT_WAIT.length) { + self._debug( + 'conn %s closed: will not re-add (max %s attempts)', + peer.addr, RECONNECT_WAIT.length + ) + return + } + + var ms = RECONNECT_WAIT[peer.retries] + self._debug( + 'conn %s closed: will re-add to queue in %sms (attempt %s)', + peer.addr, ms, peer.retries + 1 + ) + + var reconnectTimeout = setTimeout(function reconnectTimeout () { + var newPeer = self._addPeer(peer.addr) + if (newPeer) newPeer.retries = peer.retries + 1 + }, ms) + if (reconnectTimeout.unref) reconnectTimeout.unref() + }) + } } /** diff --git a/lib/webconn.js b/lib/webconn.js index c52dad6d..3311e32a 100644 --- a/lib/webconn.js +++ b/lib/webconn.js @@ -2,10 +2,13 @@ module.exports = WebConn var BitField = require('bitfield') var Buffer = require('safe-buffer').Buffer +var clone = require('clone') var debug = require('debug')('webtorrent:webconn') var get = require('simple-get') var inherits = require('inherits') var sha1 = require('simple-sha1') +var url = require('url') +var Socks = require('socks') var Wire = require('bittorrent-protocol') var VERSION = require('../package.json').version @@ -109,20 +112,31 @@ WebConn.prototype.httpRequest = function (pieceIndex, offset, length, cb) { } requests.forEach(function (request) { - var url = request.url + var parsedUrl = url.parse(request.url) var start = request.start var end = request.end debug( 'Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d', - url, pieceIndex, offset, length, start, end + request.url, pieceIndex, offset, length, start, end ) + + var agent + var proxyOpts = self._torrent.client.proxyOpts + if (proxyOpts && proxyOpts.proxyPeerConnections) { + agent = parsedUrl.protocol === 'https:' + ? proxyOpts.httpsAgent : proxyOpts.httpAgent + if (!agent && proxyOpts.socksProxy) { + agent = new Socks.Agent(clone(proxyOpts.socksProxy), (parsedUrl.protocol === 'https:')) + } + } var opts = { - url: url, + url: parsedUrl, method: 'GET', headers: { 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)', range: 'bytes=' + start + '-' + end - } + }, + agent: agent } get.concat(opts, function (err, res, data) { if (hasError) return diff --git a/package.json b/package.json index 320014be..20d4121a 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "load-ip-set": false, "net": false, "os": false, + "socks": false, "ut_pex": false }, "browserify": { @@ -31,6 +32,7 @@ "bittorrent-dht": "^7.2.2", "bittorrent-protocol": "^2.1.5", "chunk-store-stream": "^2.0.2", + "clone": "^1.0.2", "create-torrent": "^3.24.5", "debug": "^2.2.0", "end-of-stream": "^1.1.0", @@ -56,6 +58,7 @@ "simple-get": "^2.2.1", "simple-peer": "^6.0.4", "simple-sha1": "^2.0.8", + "socks": "^1.1.9", "speedometer": "^1.0.0", "stream-to-blob": "^1.0.0", "stream-to-blob-url": "^2.1.0",