aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/controllers/tracker.ts34
-rw-r--r--server/initializers/constants.ts3
-rw-r--r--server/lib/redis.ts17
-rw-r--r--server/tests/api/server/tracker.ts49
4 files changed, 81 insertions, 22 deletions
diff --git a/server/controllers/tracker.ts b/server/controllers/tracker.ts
index cacff36ec..c962fada5 100644
--- a/server/controllers/tracker.ts
+++ b/server/controllers/tracker.ts
@@ -1,13 +1,14 @@
1import { logger } from '../helpers/logger' 1import * as bitTorrentTracker from 'bittorrent-tracker'
2import * as express from 'express' 2import * as express from 'express'
3import * as http from 'http' 3import * as http from 'http'
4import * as bitTorrentTracker from 'bittorrent-tracker'
5import * as proxyAddr from 'proxy-addr' 4import * as proxyAddr from 'proxy-addr'
6import { Server as WebSocketServer } from 'ws' 5import { Server as WebSocketServer } from 'ws'
6import { Redis } from '@server/lib/redis'
7import { logger } from '../helpers/logger'
8import { CONFIG } from '../initializers/config'
7import { TRACKER_RATE_LIMITS } from '../initializers/constants' 9import { TRACKER_RATE_LIMITS } from '../initializers/constants'
8import { VideoFileModel } from '../models/video/video-file' 10import { VideoFileModel } from '../models/video/video-file'
9import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' 11import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
10import { CONFIG } from '../initializers/config'
11 12
12const TrackerServer = bitTorrentTracker.Server 13const TrackerServer = bitTorrentTracker.Server
13 14
@@ -53,7 +54,16 @@ const trackerServer = new TrackerServer({
53 const playlistExists = await VideoStreamingPlaylistModel.doesInfohashExist(infoHash) 54 const playlistExists = await VideoStreamingPlaylistModel.doesInfohashExist(infoHash)
54 if (playlistExists === true) return cb() 55 if (playlistExists === true) return cb()
55 56
56 return cb(new Error(`Unknown infoHash ${infoHash} requested by ip ${ip}`)) 57 cb(new Error(`Unknown infoHash ${infoHash} requested by ip ${ip}`))
58
59 // Close socket connection and block IP for a few time
60 if (params.type === 'ws') {
61 Redis.Instance.setTrackerBlockIP(ip)
62 .catch(err => logger.error('Cannot set tracker block ip.', { err }))
63
64 // setTimeout to wait filter response
65 setTimeout(() => params.socket.close(), 0)
66 }
57 } catch (err) { 67 } catch (err) {
58 logger.error('Error in tracker filter.', { err }) 68 logger.error('Error in tracker filter.', { err })
59 return cb(err) 69 return cb(err)
@@ -88,7 +98,21 @@ function createWebsocketTrackerServer (app: express.Application) {
88 98
89 server.on('upgrade', (request: express.Request, socket, head) => { 99 server.on('upgrade', (request: express.Request, socket, head) => {
90 if (request.url === '/tracker/socket') { 100 if (request.url === '/tracker/socket') {
91 wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request)) 101 const ip = proxyAddr(request, CONFIG.TRUST_PROXY)
102
103 Redis.Instance.doesTrackerBlockIPExist(ip)
104 .then(result => {
105 if (result === true) {
106 logger.debug('Blocking IP %s from tracker.', ip)
107
108 socket.write('HTTP/1.1 403 Forbidden\r\n\r\n')
109 socket.destroy()
110 return
111 }
112
113 return wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request))
114 })
115 .catch(err => logger.error('Cannot check if tracker block ip exists.', { err }))
92 } 116 }
93 117
94 // Don't destroy socket, we have Socket.IO too 118 // Don't destroy socket, we have Socket.IO too
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index dd79c0e16..9a262fd4b 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -633,7 +633,8 @@ const AUDIT_LOG_FILENAME = 'peertube-audit.log'
633const TRACKER_RATE_LIMITS = { 633const TRACKER_RATE_LIMITS = {
634 INTERVAL: 60000 * 5, // 5 minutes 634 INTERVAL: 60000 * 5, // 5 minutes
635 ANNOUNCES_PER_IP_PER_INFOHASH: 15, // maximum announces per torrent in the interval 635 ANNOUNCES_PER_IP_PER_INFOHASH: 15, // maximum announces per torrent in the interval
636 ANNOUNCES_PER_IP: 30 // maximum announces for all our torrents in the interval 636 ANNOUNCES_PER_IP: 30, // maximum announces for all our torrents in the interval
637 BLOCK_IP_LIFETIME: 60000 * 10 // 10 minutes
637} 638}
638 639
639const P2P_MEDIA_LOADER_PEER_VERSION = 2 640const P2P_MEDIA_LOADER_PEER_VERSION = 2
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index b4cd6f8e7..5313c4685 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -8,7 +8,8 @@ import {
8 USER_PASSWORD_RESET_LIFETIME, 8 USER_PASSWORD_RESET_LIFETIME,
9 USER_PASSWORD_CREATE_LIFETIME, 9 USER_PASSWORD_CREATE_LIFETIME,
10 VIDEO_VIEW_LIFETIME, 10 VIDEO_VIEW_LIFETIME,
11 WEBSERVER 11 WEBSERVER,
12 TRACKER_RATE_LIMITS
12} from '../initializers/constants' 13} from '../initializers/constants'
13import { CONFIG } from '../initializers/config' 14import { CONFIG } from '../initializers/config'
14 15
@@ -121,6 +122,16 @@ class Redis {
121 return this.exists(this.generateViewKey(ip, videoUUID)) 122 return this.exists(this.generateViewKey(ip, videoUUID))
122 } 123 }
123 124
125 /* ************ Tracker IP block ************ */
126
127 setTrackerBlockIP (ip: string) {
128 return this.setValue(this.generateTrackerBlockIPKey(ip), '1', TRACKER_RATE_LIMITS.BLOCK_IP_LIFETIME)
129 }
130
131 async doesTrackerBlockIPExist (ip: string) {
132 return this.exists(this.generateTrackerBlockIPKey(ip))
133 }
134
124 /* ************ API cache ************ */ 135 /* ************ API cache ************ */
125 136
126 async getCachedRoute (req: express.Request) { 137 async getCachedRoute (req: express.Request) {
@@ -213,6 +224,10 @@ class Redis {
213 return `views-${videoUUID}-${ip}` 224 return `views-${videoUUID}-${ip}`
214 } 225 }
215 226
227 private generateTrackerBlockIPKey (ip: string) {
228 return `tracker-block-ip-${ip}`
229 }
230
216 private generateContactFormKey (ip: string) { 231 private generateContactFormKey (ip: string) {
217 return 'contact-form-' + ip 232 return 'contact-form-' + ip
218 } 233 }
diff --git a/server/tests/api/server/tracker.ts b/server/tests/api/server/tracker.ts
index 5b56a83bb..4b86e0b90 100644
--- a/server/tests/api/server/tracker.ts
+++ b/server/tests/api/server/tracker.ts
@@ -40,21 +40,6 @@ describe('Test tracker', function () {
40 } 40 }
41 }) 41 })
42 42
43 it('Should return an error when adding an incorrect infohash', function (done) {
44 this.timeout(10000)
45 const webtorrent = new WebTorrent()
46
47 const torrent = webtorrent.add(badMagnet)
48
49 torrent.on('error', done)
50 torrent.on('warning', warn => {
51 const message = typeof warn === 'string' ? warn : warn.message
52 if (message.includes('Unknown infoHash ')) return done()
53 })
54
55 torrent.on('done', () => done(new Error('No error on infohash')))
56 })
57
58 it('Should succeed with the correct infohash', function (done) { 43 it('Should succeed with the correct infohash', function (done) {
59 this.timeout(10000) 44 this.timeout(10000)
60 const webtorrent = new WebTorrent() 45 const webtorrent = new WebTorrent()
@@ -76,6 +61,7 @@ describe('Test tracker', function () {
76 const errCb = () => done(new Error('Tracker is enabled')) 61 const errCb = () => done(new Error('Tracker is enabled'))
77 62
78 killallServers([ server ]) 63 killallServers([ server ])
64
79 reRunServer(server, { tracker: { enabled: false } }) 65 reRunServer(server, { tracker: { enabled: false } })
80 .then(() => { 66 .then(() => {
81 const webtorrent = new WebTorrent() 67 const webtorrent = new WebTorrent()
@@ -96,6 +82,39 @@ describe('Test tracker', function () {
96 }) 82 })
97 }) 83 })
98 84
85 it('Should return an error when adding an incorrect infohash', function (done) {
86 this.timeout(20000)
87
88 killallServers([ server ])
89
90 reRunServer(server)
91 .then(() => {
92 const webtorrent = new WebTorrent()
93
94 const torrent = webtorrent.add(badMagnet)
95
96 torrent.on('error', done)
97 torrent.on('warning', warn => {
98 const message = typeof warn === 'string' ? warn : warn.message
99 if (message.includes('Unknown infoHash ')) return done()
100 })
101
102 torrent.on('done', () => done(new Error('No error on infohash')))
103 })
104 })
105
106 it('Should block the IP after the failed infohash', function (done) {
107 const webtorrent = new WebTorrent()
108
109 const torrent = webtorrent.add(goodMagnet)
110
111 torrent.on('error', done)
112 torrent.on('warning', warn => {
113 const message = typeof warn === 'string' ? warn : warn.message
114 if (message.includes('Unsupported tracker protocol')) return done()
115 })
116 })
117
99 after(async function () { 118 after(async function () {
100 await cleanupTests([ server ]) 119 await cleanupTests([ server ])
101 }) 120 })