Block infohash spammers from tracker
authorChocobozzz <me@florianbigard.com>
Thu, 25 Jun 2020 14:27:35 +0000 (16:27 +0200)
committerChocobozzz <me@florianbigard.com>
Thu, 25 Jun 2020 14:28:07 +0000 (16:28 +0200)
server/controllers/tracker.ts
server/initializers/constants.ts
server/lib/redis.ts
server/tests/api/server/tracker.ts

index cacff36ecb82a6e7e870f9705642f6b0293212bc..c962fada5250564cb45d31d2d2585f9886d6ab8f 100644 (file)
@@ -1,13 +1,14 @@
-import { logger } from '../helpers/logger'
+import * as bitTorrentTracker from 'bittorrent-tracker'
 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 { Redis } from '@server/lib/redis'
+import { logger } from '../helpers/logger'
+import { CONFIG } from '../initializers/config'
 import { TRACKER_RATE_LIMITS } from '../initializers/constants'
 import { VideoFileModel } from '../models/video/video-file'
 import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
-import { CONFIG } from '../initializers/config'
 
 const TrackerServer = bitTorrentTracker.Server
 
@@ -53,7 +54,16 @@ const trackerServer = new TrackerServer({
       const playlistExists = await VideoStreamingPlaylistModel.doesInfohashExist(infoHash)
       if (playlistExists === true) return cb()
 
-      return cb(new Error(`Unknown infoHash ${infoHash} requested by ip ${ip}`))
+      cb(new Error(`Unknown infoHash ${infoHash} requested by ip ${ip}`))
+
+      // Close socket connection and block IP for a few time
+      if (params.type === 'ws') {
+        Redis.Instance.setTrackerBlockIP(ip)
+          .catch(err => logger.error('Cannot set tracker block ip.', { err }))
+
+        // setTimeout to wait filter response
+        setTimeout(() => params.socket.close(), 0)
+      }
     } catch (err) {
       logger.error('Error in tracker filter.', { err })
       return cb(err)
@@ -88,7 +98,21 @@ function createWebsocketTrackerServer (app: express.Application) {
 
   server.on('upgrade', (request: express.Request, socket, head) => {
     if (request.url === '/tracker/socket') {
-      wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request))
+      const ip = proxyAddr(request, CONFIG.TRUST_PROXY)
+
+      Redis.Instance.doesTrackerBlockIPExist(ip)
+        .then(result => {
+          if (result === true) {
+            logger.debug('Blocking IP %s from tracker.', ip)
+
+            socket.write('HTTP/1.1 403 Forbidden\r\n\r\n')
+            socket.destroy()
+            return
+          }
+
+          return wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request))
+        })
+        .catch(err => logger.error('Cannot check if tracker block ip exists.', { err }))
     }
 
     // Don't destroy socket, we have Socket.IO too
index dd79c0e168a46066e3ed63346a2e70210de6e95a..9a262fd4b5989aba058d42242fafd4b2f118815d 100644 (file)
@@ -633,7 +633,8 @@ const AUDIT_LOG_FILENAME = 'peertube-audit.log'
 const TRACKER_RATE_LIMITS = {
   INTERVAL: 60000 * 5, // 5 minutes
   ANNOUNCES_PER_IP_PER_INFOHASH: 15, // maximum announces per torrent in the interval
-  ANNOUNCES_PER_IP: 30 // maximum announces for all our torrents in the interval
+  ANNOUNCES_PER_IP: 30, // maximum announces for all our torrents in the interval
+  BLOCK_IP_LIFETIME: 60000 * 10 // 10 minutes
 }
 
 const P2P_MEDIA_LOADER_PEER_VERSION = 2
index b4cd6f8e7e0afe75189f775d7433519ee931a242..5313c46857a95254df77cc2d4717e763a803db3d 100644 (file)
@@ -8,7 +8,8 @@ import {
   USER_PASSWORD_RESET_LIFETIME,
   USER_PASSWORD_CREATE_LIFETIME,
   VIDEO_VIEW_LIFETIME,
-  WEBSERVER
+  WEBSERVER,
+  TRACKER_RATE_LIMITS
 } from '../initializers/constants'
 import { CONFIG } from '../initializers/config'
 
@@ -121,6 +122,16 @@ class Redis {
     return this.exists(this.generateViewKey(ip, videoUUID))
   }
 
+  /* ************ Tracker IP block ************ */
+
+  setTrackerBlockIP (ip: string) {
+    return this.setValue(this.generateTrackerBlockIPKey(ip), '1', TRACKER_RATE_LIMITS.BLOCK_IP_LIFETIME)
+  }
+
+  async doesTrackerBlockIPExist (ip: string) {
+    return this.exists(this.generateTrackerBlockIPKey(ip))
+  }
+
   /* ************ API cache ************ */
 
   async getCachedRoute (req: express.Request) {
@@ -213,6 +224,10 @@ class Redis {
     return `views-${videoUUID}-${ip}`
   }
 
+  private generateTrackerBlockIPKey (ip: string) {
+    return `tracker-block-ip-${ip}`
+  }
+
   private generateContactFormKey (ip: string) {
     return 'contact-form-' + ip
   }
index 5b56a83bb73ee4eb46c45cc86184e3b0f5b63073..4b86e0b904f3f888421d04ddd5d2c66ee445a562 100644 (file)
@@ -40,21 +40,6 @@ describe('Test tracker', function () {
     }
   })
 
-  it('Should return an error when adding an incorrect infohash', function (done) {
-    this.timeout(10000)
-    const webtorrent = new WebTorrent()
-
-    const torrent = webtorrent.add(badMagnet)
-
-    torrent.on('error', done)
-    torrent.on('warning', warn => {
-      const message = typeof warn === 'string' ? warn : warn.message
-      if (message.includes('Unknown infoHash ')) return done()
-    })
-
-    torrent.on('done', () => done(new Error('No error on infohash')))
-  })
-
   it('Should succeed with the correct infohash', function (done) {
     this.timeout(10000)
     const webtorrent = new WebTorrent()
@@ -76,6 +61,7 @@ describe('Test tracker', function () {
     const errCb = () => done(new Error('Tracker is enabled'))
 
     killallServers([ server ])
+
     reRunServer(server, { tracker: { enabled: false } })
       .then(() => {
         const webtorrent = new WebTorrent()
@@ -96,6 +82,39 @@ describe('Test tracker', function () {
       })
   })
 
+  it('Should return an error when adding an incorrect infohash', function (done) {
+    this.timeout(20000)
+
+    killallServers([ server ])
+
+    reRunServer(server)
+      .then(() => {
+        const webtorrent = new WebTorrent()
+
+        const torrent = webtorrent.add(badMagnet)
+
+        torrent.on('error', done)
+        torrent.on('warning', warn => {
+          const message = typeof warn === 'string' ? warn : warn.message
+          if (message.includes('Unknown infoHash ')) return done()
+        })
+
+        torrent.on('done', () => done(new Error('No error on infohash')))
+      })
+  })
+
+  it('Should block the IP after the failed infohash', function (done) {
+    const webtorrent = new WebTorrent()
+
+    const torrent = webtorrent.add(goodMagnet)
+
+    torrent.on('error', done)
+    torrent.on('warning', warn => {
+      const message = typeof warn === 'string' ? warn : warn.message
+      if (message.includes('Unsupported tracker protocol')) return done()
+    })
+  })
+
   after(async function () {
     await cleanupTests([ server ])
   })