]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/controllers/tracker.ts
Implement signup approval in server
[github/Chocobozzz/PeerTube.git] / server / controllers / tracker.ts
index 1deb8c40292f6ae997eb77004b858c1165481e56..c4f3a8889f75586049a2868298ac5c02f2396802 100644 (file)
@@ -1,17 +1,22 @@
+import { Server as TrackerServer } from 'bittorrent-tracker'
+import express from 'express'
+import { createServer } from 'http'
+import LRUCache from 'lru-cache'
+import proxyAddr from 'proxy-addr'
+import { WebSocketServer } from 'ws'
 import { logger } from '../helpers/logger'
-import * as express from 'express'
-import * as http from 'http'
-import * as bitTorrentTracker from 'bittorrent-tracker'
-import * as proxyAddr from 'proxy-addr'
-import { Server as WebSocketServer } from 'ws'
-import { CONFIG, TRACKER_RATE_LIMITS } from '../initializers/constants'
+import { CONFIG } from '../initializers/config'
+import { LRU_CACHE, TRACKER_RATE_LIMITS } from '../initializers/constants'
 import { VideoFileModel } from '../models/video/video-file'
-import { parse } from 'url'
-
-const TrackerServer = bitTorrentTracker.Server
+import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
 
 const trackerRouter = express.Router()
 
+const blockedIPs = new LRUCache<string, boolean>({
+  max: LRU_CACHE.TRACKER_IPS.MAX_SIZE,
+  ttl: TRACKER_RATE_LIMITS.BLOCK_IP_LIFETIME
+})
+
 let peersIps = {}
 let peersIpInfoHash = {}
 runPeersChecker()
@@ -20,12 +25,15 @@ const trackerServer = new TrackerServer({
   http: false,
   udp: false,
   ws: false,
-  dht: false,
-  filter: function (infoHash, params, cb) {
+  filter: async function (infoHash, params, cb) {
+    if (CONFIG.TRACKER.ENABLED === false) {
+      return cb(new Error('Tracker is disabled on this instance.'))
+    }
+
     let ip: string
 
     if (params.type === 'ws') {
-      ip = params.socket.ip
+      ip = params.ip
     } else {
       ip = params.httpReq.ip
     }
@@ -35,33 +43,57 @@ const trackerServer = new TrackerServer({
     peersIps[ip] = peersIps[ip] ? peersIps[ip] + 1 : 1
     peersIpInfoHash[key] = peersIpInfoHash[key] ? peersIpInfoHash[key] + 1 : 1
 
-    if (peersIpInfoHash[key] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) {
-      return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`))
+    if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[key] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) {
+      return cb(new Error(`Too many requests (${peersIpInfoHash[key]} of ip ${ip} for torrent ${infoHash}`))
     }
 
-    VideoFileModel.isInfohashExists(infoHash)
-      .then(exists => {
-        if (exists === false) return cb(new Error(`Unknown infoHash ${infoHash}`))
+    try {
+      if (CONFIG.TRACKER.PRIVATE === false) return cb()
+
+      const videoFileExists = await VideoFileModel.doesInfohashExistCached(infoHash)
+      if (videoFileExists === true) return cb()
 
-        return cb()
-      })
+      const playlistExists = await VideoStreamingPlaylistModel.doesInfohashExistCached(infoHash)
+      if (playlistExists === true) return cb()
+
+      cb(new Error(`Unknown infoHash ${infoHash} requested by ip ${ip}`))
+
+      // Close socket connection and block IP for a few time
+      if (params.type === 'ws') {
+        blockedIPs.set(ip, true)
+
+        // setTimeout to wait filter response
+        setTimeout(() => params.socket.close(), 0)
+      }
+    } catch (err) {
+      logger.error('Error in tracker filter.', { err })
+      return cb(err)
+    }
   }
 })
 
-trackerServer.on('error', function (err) {
-  logger.error('Error in tracker.', { err })
-})
+if (CONFIG.TRACKER.ENABLED !== false) {
+  trackerServer.on('error', function (err) {
+    logger.error('Error in tracker.', { err })
+  })
 
-trackerServer.on('warning', function (err) {
-  logger.warn('Warning in tracker.', { err })
-})
+  trackerServer.on('warning', function (err) {
+    const message = err.message || ''
+
+    if (CONFIG.LOG.LOG_TRACKER_UNKNOWN_INFOHASH === false && message.includes('Unknown infoHash')) {
+      return
+    }
+
+    logger.warn('Warning in tracker.', { err })
+  })
+}
 
 const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer)
 trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' }))
 trackerRouter.get('/tracker/scrape', (req, res) => onHttpRequest(req, res, { action: 'scrape' }))
 
 function createWebsocketTrackerServer (app: express.Application) {
-  const server = http.createServer(app)
+  const server = createServer(app)
   const wss = new WebSocketServer({ noServer: true })
 
   wss.on('connection', function (ws, req) {
@@ -70,17 +102,26 @@ function createWebsocketTrackerServer (app: express.Application) {
     trackerServer.onWebSocketConnection(ws)
   })
 
-  server.on('upgrade', (request, socket, head) => {
-    const pathname = parse(request.url).pathname
+  server.on('upgrade', (request: express.Request, socket, head) => {
+    if (request.url === '/tracker/socket') {
+      const ip = proxyAddr(request, CONFIG.TRUST_PROXY)
+
+      if (blockedIPs.has(ip)) {
+        logger.debug('Blocking IP %s from tracker.', ip)
+
+        socket.write('HTTP/1.1 403 Forbidden\r\n\r\n')
+        socket.destroy()
+        return
+      }
 
-    if (pathname === '/tracker/socket') {
-      wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request))
+      // FIXME: typings
+      return wss.handleUpgrade(request, socket as any, head, ws => wss.emit('connection', ws, request))
     }
 
     // Don't destroy socket, we have Socket.IO too
   })
 
-  return server
+  return { server, trackerServer }
 }
 
 // ---------------------------------------------------------------------------