bittorrent-relay
Advanced tools
+7
-3
@@ -19,3 +19,4 @@ #!/usr/bin/env node | ||
| 'version', | ||
| 'ev' | ||
| 'ev', | ||
| 'status' | ||
| ], | ||
@@ -30,3 +31,4 @@ string: [ | ||
| 'pub', | ||
| 'priv' | ||
| 'priv', | ||
| 'server' | ||
| ], | ||
@@ -47,3 +49,5 @@ default: { | ||
| 'limit': {}, | ||
| 'ev': false | ||
| 'ev': false, | ||
| 'status': true, | ||
| 'server': '0.0.0.0' | ||
| } | ||
@@ -50,0 +54,0 @@ }) |
+1
-1
| { | ||
| "name": "bittorrent-relay", | ||
| "description": "Uses the mainline dht to relay requests to other trackers in a swarm", | ||
| "version": "12.0.5", | ||
| "version": "12.0.6", | ||
| "bin": { | ||
@@ -6,0 +6,0 @@ "bittorrent-relay": "./bin/cmd.js" |
+138
-124
@@ -87,8 +87,12 @@ import Debug from 'debug' | ||
| this.timer.active = this.timer.active || 5 * 60 * 1000 | ||
| this.host = opts.host || '0.0.0.0' | ||
| this.server = opts.server || '0.0.0.0' | ||
| if(!opts.host){ | ||
| throw new Error('must have host') | ||
| } | ||
| this.host = opts.host | ||
| this.port = opts.port || 10509 | ||
| this.address = `${this.host}:${this.port}` | ||
| this._trustProxy = Boolean(opts.trustProxy) | ||
| this.web = `${this.domain}:${this.port}` | ||
| this.hash = crypto.createHash('sha1').update(this.web).digest('hex') | ||
| this.web = `${this.domain || this.host}:${this.port}` | ||
| this.id = crypto.createHash('sha1').update(this.address).digest('hex') | ||
| this.sockets = new Map() | ||
@@ -161,3 +165,3 @@ this.triedAlready = new Map() | ||
| // if(socket.readyState === 1){ | ||
| // socket.send(JSON.stringify({action: 'web', domain: self.domain, host: self.host, port: self.port, web: self.web, hash: self.hash})) | ||
| // socket.send(JSON.stringify({action: 'web', domain: self.domain, host: self.host, port: self.port, web: self.web, id: self.id})) | ||
| // } | ||
@@ -187,8 +191,18 @@ // } | ||
| for(const test in this.sockets.values()){ | ||
| if(!test.relays.length){ | ||
| if(!test.active){ | ||
| test.terminate() | ||
| continue | ||
| } else { | ||
| test.close() | ||
| continue | ||
| } | ||
| } | ||
| if(!test.active){ | ||
| test.terminate() | ||
| continue | ||
| } else { | ||
| test.active = false | ||
| test.send(JSON.stringify({action: 'ping'})) | ||
| } | ||
| test.active = false | ||
| test.send(JSON.stringify({action: 'ping'})) | ||
| } | ||
@@ -257,33 +271,7 @@ }, this.timer.inactive) | ||
| try { | ||
| if(req.url.startsWith('/announce/')){ | ||
| const useInfoHash = req.url.slice(0, req.url.indexOf('?')).replace('/announce/', '') | ||
| if(!this.hashes.has(useInfoHash)){ | ||
| return res.end(bencode.encode({'failure reason': 'infohash is not supported'})) | ||
| } | ||
| // const useQueryString = req.url.replace('/announce/', '').slice(0, req.url.indexOf('?')) | ||
| // if(!this.hashes.has(useQueryString)){} | ||
| return this.onHttpRequest(req, res) | ||
| } else if(req.url.startsWith('/relay/')){ | ||
| const useRelayHash = req.url.slice(0, req.url.indexOf('?')).replace('/relay/', '') | ||
| const para = new URLSearchParams(req.url.slice(req.url.indexOf('?'))) | ||
| if(!this.relays.has(useRelayHash)){ | ||
| return res.end(bencode.encode({'failure reason': 'relayhash is not supported'})) | ||
| } | ||
| let size | ||
| if(para.has('size')){ | ||
| if(JSON.parse(para.get('size'))){ | ||
| size = this.limit.serverConnections | ||
| } | ||
| } | ||
| let keys | ||
| if(para.has('keys')){ | ||
| if(JSON.parse(para.get('keys'))){ | ||
| keys = this.relays.get(useRelayHash).map((data) => {return data.key}) | ||
| } | ||
| } | ||
| // const rh = req.url.replace('/relay/', '').replace(para, '') | ||
| // const useQueryString = req.url.replace('/relay/', '').slice(0, req.url.indexOf('?')) | ||
| // if(!this.relays.has(useQueryString)){} | ||
| res.setHeader('Content-Type', 'text/plain; charset=UTF-8') | ||
| return res.end(bencode.encode({size, keys})) | ||
| if(req.url.startsWith('/announce?')){ | ||
| this.onHttpRequest(req, res) | ||
| } else if(req.url.startsWith('/relay?')){ | ||
| const useParams = new URLSearchParams(req.url.slice(req.url.indexOf('?'))) | ||
| this.onHttpRelay(req, res, useParams) | ||
| } else if(req.method === 'HEAD' && req.url === '/'){ | ||
@@ -395,8 +383,8 @@ res.statusCode = 200 | ||
| res.end(JSON.stringify(Array.from(this.hashes))) | ||
| } else if(req.method === 'GET' && req.url === '/hash.html'){ | ||
| } else if(req.method === 'GET' && req.url === '/id.html'){ | ||
| res.setHeader('Content-Type', 'text/html') | ||
| res.end(`<html><head><title>Relay</title></head><body>${(() => {const arr = [];this.sockets.forEach((data) => {arr.push(data.hash)});return arr;})().join('\n')}</body></html>`) | ||
| } else if(req.method === 'GET' && req.url === '/hash.json'){ | ||
| res.end(`<html><head><title>Relay</title></head><body>${(() => {const arr = [];this.sockets.forEach((data) => {arr.push(data.id)});return arr;})().join('\n')}</body></html>`) | ||
| } else if(req.method === 'GET' && req.url === '/id.json'){ | ||
| res.setHeader('Content-Type', 'application/json') | ||
| res.end(JSON.stringify((() => {const arr = [];this.sockets.forEach((data) => {arr.push(data.hash)});return arr;})())) | ||
| res.end(JSON.stringify((() => {const arr = [];this.sockets.forEach((data) => {arr.push(data.id)});return arr;})())) | ||
| } else if(req.method === 'GET' && req.url === '/key.html'){ | ||
@@ -633,39 +621,42 @@ res.setHeader('Content-Type', 'text/html') | ||
| // else handle websockets as usual | ||
| if(req.url.startsWith('/announce/')){ | ||
| const useTest = req.url.replace('/announce/', '') | ||
| // const useTest = crypto.createHash('sha1').update(req.url.slice(req.url.lastIndexOf('/')).slice(1)).digest('hex') | ||
| if(!this.hashes.has(useTest)){ | ||
| socket.send(JSON.stringify({action: 'failure reason', error: 'infohash is not supported'})) | ||
| if(req.url === '/announce'){ | ||
| socket.upgradeReq = req | ||
| self.onWebSocketConnection(socket) | ||
| } else if(req.url.startsWith('/relay?')){ | ||
| const params = new URLSearchParams(req.url.slice(req.url.indexOf('?'))) | ||
| const getRelayHash = params.has('relay_hash') ? params.get('relay_hash') : null | ||
| const checkRelayHash = getRelayHash && typeof(getRelayHash) === 'string' || getRelayHash.length === 20 | ||
| const getId = params.has('id') ? params.get('id') : null | ||
| const checkId = getId && typeof(getId) === 'string' && getId.length === 20 | ||
| const checkHasSocket = getId ? this.sockets.has(getId) : null | ||
| const checkHasRelay = getRelayHash ? this.relays.has(getRelayHash) : null | ||
| if(!checkRelayHash || !checkId || checkHasSocket || !checkHasRelay){ | ||
| socket.send(JSON.stringify({action: 'failure reason', error: 'there was a error, check the other properties of this object', relay_hash: checkRelayHash, id: checkId, socket: checkHasSocket, relay: checkHasRelay})) | ||
| socket.close() | ||
| return | ||
| } | ||
| socket.upgradeReq = req | ||
| self.onWebSocketConnection(socket) | ||
| } else if(req.url.startsWith('/relay/')){ | ||
| // have id and relay in the url routes | ||
| // have different functions to handle connections and extras | ||
| const useTest = req.url.replace('/relay/', '') | ||
| // const useTest = crypto.createHash('sha1').update(req.url.slice(req.url.lastIndexOf('/')).slice(1)).digest('hex') | ||
| if(!this.relays.has(useTest)){ | ||
| socket.send(JSON.stringify({action: 'failure reason', error: 'relay is not supported'})) | ||
| socket.close() | ||
| return | ||
| } | ||
| if(this.limit.serverConnections){ | ||
| if(this.relays.get(useTest).length < this.limit.serverConnections){ | ||
| socket.hash = null | ||
| if(this.relays.get(getRelayHash).length < this.limit.serverConnections){ | ||
| socket.id = getId | ||
| socket.server = true | ||
| socket.active = true | ||
| socket.relay = getRelayHash | ||
| socket.relays = [] | ||
| socket.proc = false | ||
| // socket.relay = useTest | ||
| this.sockets.set(socket.id, socket) | ||
| this.onRelaySocketConnection(socket) | ||
| } | ||
| } else { | ||
| socket.hash = null | ||
| socket.id = getId | ||
| socket.server = true | ||
| socket.relay = getRelayHash | ||
| socket.relays = [] | ||
| socket.active = true | ||
| socket.proc = false | ||
| // socket.relay = useTest | ||
| this.sockets.set(socket.id, socket) | ||
| this.onRelaySocketConnection(socket) | ||
@@ -710,9 +701,9 @@ } | ||
| const hash = crypto.createHash('sha1').update(peer.host + ':' + peer.port).digest('hex') | ||
| if(self.hash === hash){ | ||
| const id = crypto.createHash('sha1').update(peer.host + ':' + peer.port).digest('hex') | ||
| if(self.id === id){ | ||
| return | ||
| } | ||
| if(this.triedAlready.has(hash)){ | ||
| const check = this.triedAlready.get(hash) | ||
| if(this.triedAlready.has(id)){ | ||
| const check = this.triedAlready.get(id) | ||
| const checkStamp = (Date.now() - check.stamp) / 1000 | ||
@@ -724,6 +715,6 @@ if(check.wait >= checkStamp){ | ||
| // if(this.sockets.has(hash)){ | ||
| // const checkTracker = this.sockets.get(hash) | ||
| // if(this.sockets.has(id)){ | ||
| // const checkTracker = this.sockets.get(id) | ||
| // const checkRelay = this.relays.get(ih) | ||
| // if(checkRelay.every((data) => {return checkTracker.hash !== data.hash})){ | ||
| // if(checkRelay.every((data) => {return checkTracker.id !== data.id})){ | ||
| // // checkRelay.push(checkTracker) | ||
@@ -738,6 +729,6 @@ // // if(!checkTracker.relays.includes(ih)){ | ||
| if(this.sockets.has(hash)){ | ||
| const checkTracker = this.sockets.get(hash) | ||
| if(this.sockets.has(id)){ | ||
| const checkTracker = this.sockets.get(id) | ||
| const checkRelay = this.relays.get(ih) | ||
| if(checkRelay.every((data) => {return checkTracker.hash !== data.hash})){ | ||
| if(checkRelay.every((data) => {return checkTracker.id !== data.id})){ | ||
| // checkRelay.push(checkTracker) | ||
@@ -754,12 +745,11 @@ // if(!checkTracker.relays.includes(ih)){ | ||
| if(this.relays.get(ih).length < this.limit.serverConnections){ | ||
| const relay = `ws://${peer.host}:${peer.port}/relay/${ih}` | ||
| const relay = `ws://${peer.host}:${peer.port}/relay?relay_hash=${ih}&id=${this.id}` | ||
| const con = new WebSocket(relay) | ||
| con.server = false | ||
| con.active = true | ||
| con.relay = ih | ||
| con.relays = [] | ||
| con.relay = ih | ||
| con.proc = false | ||
| con.hash = ih | ||
| this.sockets.set(con.hash, con) | ||
| // con.hash = hash | ||
| con.id = id | ||
| this.sockets.set(con.id, con) | ||
| self.onRelaySocketConnection(con) | ||
@@ -769,12 +759,11 @@ return | ||
| } else { | ||
| const relay = `ws://${peer.host}:${peer.port}/relay/${ih}` | ||
| const relay = `ws://${peer.host}:${peer.port}/relay?relay_hash=${ih}&id=${this.id}` | ||
| const con = new WebSocket(relay) | ||
| con.server = false | ||
| con.active = true | ||
| con.relay = ih | ||
| con.relays = [] | ||
| con.relay = ih | ||
| con.proc = false | ||
| con.hash = ih | ||
| this.sockets.set(con.hash, con) | ||
| // con.hash = hash | ||
| con.id = id | ||
| this.sockets.set(con.id, con) | ||
| self.onRelaySocketConnection(con) | ||
@@ -792,7 +781,7 @@ return | ||
| turnOnHTTP(){ | ||
| this.http.listen(this.port, this.host) | ||
| this.http.listen(this.port, this.server) | ||
| } | ||
| turnOnDHT(){ | ||
| this.relay.listen(this.port, this.host) | ||
| this.relay.listen(this.port, this.server) | ||
| } | ||
@@ -835,11 +824,6 @@ | ||
| _filter(infoHash, params, cb){ | ||
| // const hashes = (() => {if(!opts.hashes){throw new Error('must have hashes')}return Array.isArray(opts.hashes) ? opts.hashes : opts.hashes.split(',')})() | ||
| if(this.status){ | ||
| if(this.hashes.has(infoHash)){ | ||
| cb(null) | ||
| } else { | ||
| cb(new Error('disallowed torrent')) | ||
| } | ||
| if(this.hashes.has(infoHash)){ | ||
| cb(null) | ||
| } else { | ||
| cb(null) | ||
| cb(new Error('disallowed torrent')) | ||
| } | ||
@@ -932,2 +916,3 @@ } | ||
| this.hashes.clear() | ||
| this.triedAlready.clear() | ||
| if(cb){ | ||
@@ -962,10 +947,10 @@ cb() | ||
| // send the right messages | ||
| // self.sockets[socket.hash] = socket | ||
| if(socket.hash){ | ||
| if(self.triedAlready.has(socket.hash)){ | ||
| self.triedAlready.delete(socket.hash) | ||
| // self.sockets[socket.id] = socket | ||
| if(socket.id){ | ||
| if(self.triedAlready.has(socket.id)){ | ||
| self.triedAlready.delete(socket.id) | ||
| } | ||
| } | ||
| if(!socket.server){ | ||
| socket.send(JSON.stringify({hash: self.hash, key: self.key, address: self.address, web: self.web, host: self.host, port: self.port, domain: self.domain, relay: socket.relay, status: self.status, sig: self.sig, action: 'session', reply: true})) | ||
| socket.send(JSON.stringify({id: self.id, key: self.key, address: self.address, web: self.web, host: self.host, port: self.port, domain: self.domain, relay: socket.relay, status: self.status, sig: self.sig, action: 'session', reply: true})) | ||
| delete socket.relay | ||
@@ -976,10 +961,10 @@ } | ||
| let useSocket | ||
| if(socket.hash){ | ||
| useSocket = socket.hash | ||
| if(self.triedAlready.has(socket.hash)){ | ||
| const check = self.triedAlready.get(socket.hash) | ||
| if(socket.id){ | ||
| useSocket = socket.id | ||
| if(self.triedAlready.has(socket.id)){ | ||
| const check = self.triedAlready.get(socket.id) | ||
| check.stamp = Date.now() | ||
| check.wait = check.wait * 2 | ||
| } else { | ||
| self.triedAlready.set(socket.hash, {stamp: Date.now(), wait: 1}) | ||
| self.triedAlready.set(socket.id, {stamp: Date.now(), wait: 1}) | ||
| } | ||
@@ -999,3 +984,3 @@ } else { | ||
| if(message.action === 'session'){ | ||
| if(!message.sig || !ed.verify(message.sig, self.test, message.key) || self.sockets.has(message.hash)){ | ||
| if(socket.relay !== message.relay || message.id !== crypto.createHash('sha1').update(message.address).digest('hex') || !ed.verify(message.sig, self.test, message.key) || self.sockets.has(message.id)){ | ||
| socket.close() | ||
@@ -1008,7 +993,6 @@ return | ||
| // message.relays = [useRelay] | ||
| const useRelay = message.relay | ||
| delete message.relay | ||
| for(const m in message){ | ||
| if(!socket[m]){ | ||
| socket[m] = message[m] | ||
| } | ||
| socket[m] = message[m] | ||
| } | ||
@@ -1021,7 +1005,6 @@ for(const r of socket.relays){ | ||
| socket.session = true | ||
| // self.sockets.set(socket.hash, socket) | ||
| if(socket.server){ | ||
| self.sockets.set(socket.hash, socket) | ||
| socket.send(JSON.stringify({hash: self.hash, key: self.key, address: self.address, web: self.web, host: self.host, port: self.port, domain: self.domain, relay: useRelay, status: self.status, sig: self.sig, action: 'session', reply: false})) | ||
| socket.send(JSON.stringify({id: self.id, key: self.key, address: self.address, web: self.web, host: self.host, port: self.port, domain: self.domain, relay: useRelay, status: self.status, sig: self.sig, action: 'session', reply: false})) | ||
| } | ||
| delete socket.relay | ||
| } | ||
@@ -1034,3 +1017,3 @@ if(message.action === 'add'){ | ||
| const checkRelay = self.relays.get(message.relay) | ||
| const i = checkRelay.findIndex((data) => {return socket.hash === data.hash}) | ||
| const i = checkRelay.findIndex((data) => {return socket.id === data.id}) | ||
| if(i === -1){ | ||
@@ -1054,3 +1037,3 @@ checkRelay.push(socket) | ||
| const checkRelay = self.relays.get(message.relay) | ||
| const i = checkRelay.findIndex((data) => {return socket.hash === data.hash}) | ||
| const i = checkRelay.findIndex((data) => {return socket.id === data.id}) | ||
| if(i !== -1){ | ||
@@ -1074,3 +1057,3 @@ checkRelay.splice(i, 1) | ||
| const checkRelay = self.relays.get(r) | ||
| const i = checkRelay.find((soc) => {return socket.hash === soc.hash}) | ||
| const i = checkRelay.find((soc) => {return socket.id === soc.id}) | ||
| if(i){ | ||
@@ -1086,3 +1069,3 @@ i.session = true | ||
| const checkRelay = self.relays.get(r) | ||
| const i = checkRelay.find((soc) => {return socket.hash === soc.hash}) | ||
| const i = checkRelay.find((soc) => {return socket.id === soc.id}) | ||
| if(i){ | ||
@@ -1095,3 +1078,3 @@ i.session = false | ||
| } catch (error) { | ||
| self.emit('ev', socket.hash || 'socket' + ' had an error, will wait and try to connect later, ' + error.message) | ||
| self.emit('ev', socket.id || 'socket' + ' had an error, will wait and try to connect later, ' + error.message) | ||
| socket.close() | ||
@@ -1108,3 +1091,3 @@ } | ||
| const checkRelay = self.relays.get(soc) | ||
| const i = checkRelay.findIndex((data) => {return socket.hash === data.hash}) | ||
| const i = checkRelay.findIndex((data) => {return socket.id === data.id}) | ||
| if(i !== -1){ | ||
@@ -1117,5 +1100,5 @@ checkRelay.splice(i, 1) | ||
| if(socket.hash){ | ||
| if(self.sockets.has(socket.hash)){ | ||
| self.sockets.delete(socket.hash) | ||
| if(socket.id){ | ||
| if(self.sockets.has(socket.id)){ | ||
| self.sockets.delete(socket.id) | ||
| } | ||
@@ -1175,2 +1158,21 @@ } | ||
| onHttpRelay(req, res, par){ | ||
| // const useRelayHash = req.url.slice(0, req.url.indexOf('?')).replace('/relay/', '') | ||
| const getRelayHash = par.has('relay_hash') ? par.get('relay_hash') : null | ||
| const checkRelayHash = getRelayHash && typeof(getRelayHash) === 'string' && getRelayHash.length === 20 | ||
| const getId = par.has('id') ? par.get('id') : null | ||
| const checkId = getId && typeof(getId) === 'string' && getId.length === 20 | ||
| if(!checkRelayHash && !checkId){ | ||
| res.end(bencode.encode({'failure reason': 'must have relay-hash or id'})) | ||
| } | ||
| const check = {} | ||
| if(checkRelayHash){ | ||
| check.relay_hash = this.relays.has(par.relay_hash) | ||
| } | ||
| if(checkId){ | ||
| check.id = this.sockets.has(par.id) | ||
| } | ||
| res.end(bencode.encode(check)) | ||
| } | ||
| onWebSocketConnection (socket, opts = {}) { | ||
@@ -1370,3 +1372,3 @@ this.clientCount = this.clientCount + 1 | ||
| const checkGet = this.relays.get(relay).filter((data) => {return data.session}) | ||
| params.relay = checkGet.length ? `${params.type}://${checkGet[Math.floor(Math.random() * checkGet.length)].web}/announce/${params.info_hash}` : '' | ||
| params.relay = checkGet.length ? `${params.type}://${checkGet[Math.floor(Math.random() * checkGet.length)].web}/announce` : '' | ||
| } else { | ||
@@ -1378,7 +1380,7 @@ params.relay = '' | ||
| } else if(this.limit.clientConnections && this.clientCount >= this.limit.clientConnections){ | ||
| const relay = crypto.createHash('sha1').update(hash).digest('hex') | ||
| const relay = crypto.createHash('sha1').update(params.info_hash).digest('hex') | ||
| const checkHas = this.relays.has(relay) | ||
| if(checkHas){ | ||
| const checkGet = this.relays.get(relay).filter((data) => {return data.session}) | ||
| params.relay = checkGet.length ? `${params.type}://${checkGet[Math.floor(Math.random() * checkGet.length)].web}/announce/${params.info_hash}` : '' | ||
| params.relay = checkGet.length ? `${params.type}://${checkGet[Math.floor(Math.random() * checkGet.length)].web}/announce` : '' | ||
| } else { | ||
@@ -1402,7 +1404,19 @@ params.relay = '' | ||
| getOrCreateSwarm((err, swarm) => { | ||
| if (err) return cb(err) | ||
| announce(swarm) | ||
| }) | ||
| if (this.status) { | ||
| this._filter(params.info_hash, params, err => { | ||
| // Presence of `err` means that this announce request is disallowed | ||
| if (err) return cb(err) | ||
| getOrCreateSwarm((err, swarm) => { | ||
| if (err) return cb(err) | ||
| announce(swarm) | ||
| }) | ||
| }) | ||
| } else { | ||
| getOrCreateSwarm((err, swarm) => { | ||
| if (err) return cb(err) | ||
| announce(swarm) | ||
| }) | ||
| } | ||
| // Get existing swarm, or create one if one does not exist | ||
@@ -1409,0 +1423,0 @@ function getOrCreateSwarm (cb) { |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
102639
0.19%2708
0.48%