diff options
-rw-r--r-- | server/controllers/tracker.ts | 34 | ||||
-rw-r--r-- | server/initializers/constants.ts | 3 | ||||
-rw-r--r-- | server/lib/redis.ts | 17 | ||||
-rw-r--r-- | server/tests/api/server/tracker.ts | 49 |
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 @@ | |||
1 | import { logger } from '../helpers/logger' | 1 | import * as bitTorrentTracker from 'bittorrent-tracker' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import * as http from 'http' | 3 | import * as http from 'http' |
4 | import * as bitTorrentTracker from 'bittorrent-tracker' | ||
5 | import * as proxyAddr from 'proxy-addr' | 4 | import * as proxyAddr from 'proxy-addr' |
6 | import { Server as WebSocketServer } from 'ws' | 5 | import { Server as WebSocketServer } from 'ws' |
6 | import { Redis } from '@server/lib/redis' | ||
7 | import { logger } from '../helpers/logger' | ||
8 | import { CONFIG } from '../initializers/config' | ||
7 | import { TRACKER_RATE_LIMITS } from '../initializers/constants' | 9 | import { TRACKER_RATE_LIMITS } from '../initializers/constants' |
8 | import { VideoFileModel } from '../models/video/video-file' | 10 | import { VideoFileModel } from '../models/video/video-file' |
9 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 11 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
10 | import { CONFIG } from '../initializers/config' | ||
11 | 12 | ||
12 | const TrackerServer = bitTorrentTracker.Server | 13 | const 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' | |||
633 | const TRACKER_RATE_LIMITS = { | 633 | const 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 | ||
639 | const P2P_MEDIA_LOADER_PEER_VERSION = 2 | 640 | const 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' |
13 | import { CONFIG } from '../initializers/config' | 14 | import { 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 | }) |