From 31b6ddf86652502e0c96d77fa10861ce4af11aa4 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 10 Apr 2019 09:23:18 +0200 Subject: [PATCH] Add ability to disable tracker --- client/src/app/core/server/server.service.ts | 3 +++ .../instance-features-table.component.ts | 5 +++- .../+video-watch/video-watch.component.scss | 1 + .../+video-watch/video-watch.component.ts | 18 ++++++++----- client/src/assets/player/utils.ts | 5 ++++ client/src/sass/player/peertube-skin.scss | 6 ++--- client/src/standalone/videos/embed.ts | 26 +++++++++++++------ config/default.yaml | 10 +++++++ config/production.yaml.example | 10 +++++++ server/controllers/api/config.ts | 3 +++ server/controllers/tracker.ts | 23 +++++++++++----- server/initializers/checker-before-init.ts | 3 ++- server/initializers/constants.ts | 5 ++++ server/tests/api/server/tracker.ts | 26 ++++++++++++++++--- shared/models/server/server-config.model.ts | 4 +++ .../docker/production/config/production.yaml | 4 +++ 16 files changed, 123 insertions(+), 29 deletions(-) diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index b0c5d1130..3a8a535fd 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts @@ -105,6 +105,9 @@ export class ServerService { enabled: false } } + }, + tracker: { + enabled: true } } private videoCategories: Array> = [] diff --git a/client/src/app/shared/instance/instance-features-table.component.ts b/client/src/app/shared/instance/instance-features-table.component.ts index c0257fd59..72e7c2730 100644 --- a/client/src/app/shared/instance/instance-features-table.component.ts +++ b/client/src/app/shared/instance/instance-features-table.component.ts @@ -1,7 +1,6 @@ import { Component, OnInit } from '@angular/core' import { ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' -import { ServerConfig } from '../../../../../shared' @Component({ selector: 'my-instance-features-table', @@ -65,6 +64,10 @@ export class InstanceFeaturesTableComponent implements OnInit { { label: this.i18n('Torrent import'), value: config.import.videos.torrent.enabled + }, + { + label: this.i18n('P2P enabled'), + value: config.tracker.enabled } ] } diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index d61a0bc3e..84b9aed39 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss @@ -427,6 +427,7 @@ my-video-comments { // If the view is not expanded, take into account the menu .privacy-concerns { width: calc(100% - #{$menu-width}); + margin-left: -15px; } @media screen and (max-width: $small-view) { diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index edc546b28..bce652210 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts @@ -29,6 +29,7 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' import { ComponentPagination } from '@app/shared/rest/component-pagination.model' import { Video } from '@app/shared/video/video.model' +import { isWebRTCDisabled } from '../../../assets/player/utils' @Component({ selector: 'my-video-watch', @@ -71,6 +72,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { private currentTime: number private paramsSub: Subscription private queryParamsSub: Subscription + private configSub: Subscription constructor ( private elementRef: ElementRef, @@ -100,12 +102,16 @@ export class VideoWatchComponent implements OnInit, OnDestroy { } ngOnInit () { - if ( - !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false || - peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' - ) { - this.hasAlreadyAcceptedPrivacyConcern = true - } + this.configSub = this.serverService.configLoaded + .subscribe(() => { + if ( + isWebRTCDisabled() || + this.serverService.getConfig().tracker.enabled === false || + peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' + ) { + this.hasAlreadyAcceptedPrivacyConcern = true + } + }) this.paramsSub = this.route.params.subscribe(routeParams => { const videoId = routeParams[ 'videoId' ] diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts index 0966027ac..366689962 100644 --- a/client/src/assets/player/utils.ts +++ b/client/src/assets/player/utils.ts @@ -4,6 +4,10 @@ function toTitleCase (str: string) { return str.charAt(0).toUpperCase() + str.slice(1) } +function isWebRTCDisabled () { + return !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false +} + // https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts // Don't import all Angular stuff, just copy the code with shame const dictionaryBytes: Array<{max: number, type: string}> = [ @@ -141,6 +145,7 @@ export { toTitleCase, timeToInt, secondsToTime, + isWebRTCDisabled, buildVideoLink, buildVideoEmbed, videoFileMaxByResolution, diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index a6942001a..e63a2875c 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss @@ -21,16 +21,16 @@ .vjs-dock-description { font-size: 11px; - &::before, &::after { + .text::before, .text::after { display: inline-block; content: '\1F308'; } - &::before { + .text::before { margin-right: 4px; } - &::after { + .text::after { margin-left: 4px; transform: scale(-1, 1); } diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 626d55a7c..707f04253 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -2,7 +2,7 @@ import './embed.scss' import * as Channel from 'jschannel' -import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared' +import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared' import { PeerTubeResolution } from '../player/definitions' import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' @@ -177,6 +177,10 @@ class PeerTubeEmbed { return fetch(this.getVideoUrl(videoId) + '/captions') } + loadConfig (): Promise { + return fetch('/api/v1/config') + } + removeElement (element: HTMLElement) { element.parentElement.removeChild(element) } @@ -237,10 +241,10 @@ class PeerTubeEmbed { try { const params = new URL(window.location.toString()).searchParams - this.autoplay = this.getParamToggle(params, 'autoplay') - this.controls = this.getParamToggle(params, 'controls') - this.muted = this.getParamToggle(params, 'muted') - this.loop = this.getParamToggle(params, 'loop') + this.autoplay = this.getParamToggle(params, 'autoplay', false) + this.controls = this.getParamToggle(params, 'controls', true) + this.muted = this.getParamToggle(params, 'muted', false) + this.loop = this.getParamToggle(params, 'loop', false) this.enableApi = this.getParamToggle(params, 'api', this.enableApi) this.scope = this.getParamString(params, 'scope', this.scope) @@ -258,10 +262,11 @@ class PeerTubeEmbed { const urlParts = window.location.pathname.split('/') const videoId = urlParts[ urlParts.length - 1 ] - const [ serverTranslations, videoResponse, captionsResponse ] = await Promise.all([ + const [ serverTranslations, videoResponse, captionsResponse, configResponse ] = await Promise.all([ PeertubePlayerManager.getServerTranslations(window.location.origin, navigator.language), this.loadVideoInfo(videoId), - this.loadVideoCaptions(videoId) + this.loadVideoCaptions(videoId), + this.loadConfig() ]) if (!videoResponse.ok) { @@ -338,9 +343,14 @@ class PeerTubeEmbed { window[ 'videojsPlayer' ] = this.player if (this.controls) { + const config: ServerConfig = await configResponse.json() + const description = config.tracker.enabled + ? '' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '' + : undefined + this.player.dock({ title: videoInfo.name, - description: this.player.localize('Uses P2P, others may know your IP is downloading this video.') + description }) } diff --git a/config/default.yaml b/config/default.yaml index 0d6e34d86..617159c2c 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -101,6 +101,16 @@ csp: report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk! report_uri: +tracker: + # If you disable the tracker, you disable the P2P aspect of PeerTube + enabled: true + # Only handle requests on your videos. + # If you set this to false it means you have a public tracker. + # Then, it is possible that clients overload your instance with external torrents + private: true + # Reject peers that do a lot of announces (could improve privacy of TCP/UDP peers) + reject_too_many_announces: false + cache: previews: size: 500 # Max number of previews you want to cache diff --git a/config/production.yaml.example b/config/production.yaml.example index 5029cc25b..dd5c9769b 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example @@ -102,6 +102,16 @@ csp: report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk! report_uri: +tracker: + # If you disable the tracker, you disable the P2P aspect of PeerTube + enabled: true + # Only handle requests on your videos. + # If you set this to false it means you have a public tracker. + # Then, it is possible that clients overload your instance with external torrents + private: true + # Reject peers that do a lot of announces (could improve privacy of TCP/UDP peers) + reject_too_many_announces: false + ############################################################################### # diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 5c4f455ee..0d7fc8625 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts @@ -136,6 +136,9 @@ async function getConfig (req: express.Request, res: express.Response) { videos: { intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS } + }, + tracker: { + enabled: CONFIG.TRACKER.ENABLED } } diff --git a/server/controllers/tracker.ts b/server/controllers/tracker.ts index 8b77d9de7..56a3424a3 100644 --- a/server/controllers/tracker.ts +++ b/server/controllers/tracker.ts @@ -23,6 +23,10 @@ const trackerServer = new TrackerServer({ ws: false, dht: false, 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') { @@ -36,11 +40,13 @@ 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) { + 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}`)) } try { + if (CONFIG.TRACKER.PRIVATE === false) return cb() + const videoFileExists = await VideoFileModel.doesInfohashExist(infoHash) if (videoFileExists === true) return cb() @@ -55,13 +61,16 @@ const trackerServer = new TrackerServer({ } }) -trackerServer.on('error', function (err) { - logger.error('Error in tracker.', { err }) -}) +if (CONFIG.TRACKER.ENABLED !== false) { -trackerServer.on('warning', function (err) { - logger.warn('Warning in tracker.', { err }) -}) + trackerServer.on('error', function (err) { + logger.error('Error in tracker.', { err }) + }) + + trackerServer.on('warning', function (err) { + logger.warn('Warning in tracker.', { err }) + }) +} const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer) trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' })) diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 3095913a3..6b43debfb 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts @@ -25,7 +25,8 @@ function checkMissedConfig () { 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt', 'services.twitter.username', 'services.twitter.whitelisted', - 'followers.instance.enabled', 'followers.instance.manual_approval' + 'followers.instance.enabled', 'followers.instance.manual_approval', + 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces' ] const requiredAlternatives = [ [ // set diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 097199f84..3f02572db 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -243,6 +243,11 @@ const CONFIG = { REPORT_ONLY: config.get('csp.report_only'), REPORT_URI: config.get('csp.report_uri') }, + TRACKER: { + ENABLED: config.get('tracker.enabled'), + PRIVATE: config.get('tracker.private'), + REJECT_TOO_MANY_ANNOUNCES: config.get('tracker.reject_too_many_announces') + }, ADMIN: { get EMAIL () { return config.get('admin.email') } }, diff --git a/server/tests/api/server/tracker.ts b/server/tests/api/server/tracker.ts index 25ca00029..41803aef1 100644 --- a/server/tests/api/server/tracker.ts +++ b/server/tests/api/server/tracker.ts @@ -2,7 +2,7 @@ import * as magnetUtil from 'magnet-uri' import 'mocha' -import { getVideo, killallServers, runServer, ServerInfo, uploadVideo } from '../../../../shared/utils' +import { getVideo, killallServers, reRunServer, runServer, ServerInfo, uploadVideo } from '../../../../shared/utils' import { flushTests, setAccessTokensToServers } from '../../../../shared/utils/index' import { VideoDetails } from '../../../../shared/models/videos' import * as WebTorrent from 'webtorrent' @@ -34,7 +34,7 @@ describe('Test tracker', function () { } }) - it('Should return an error when adding an incorrect infohash', done => { + it('Should return an error when adding an incorrect infohash', function (done) { this.timeout(10000) const webtorrent = new WebTorrent() @@ -49,7 +49,7 @@ describe('Test tracker', function () { torrent.on('done', () => done(new Error('No error on infohash'))) }) - it('Should succeed with the correct infohash', done => { + it('Should succeed with the correct infohash', function (done) { this.timeout(10000) const webtorrent = new WebTorrent() @@ -64,6 +64,26 @@ describe('Test tracker', function () { torrent.on('done', done) }) + it('Should disable the tracker', function (done) { + this.timeout(20000) + + killallServers([ server ]) + reRunServer(server, { tracker: { enabled: false } }) + .then(() => { + 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.indexOf('disabled ') !== -1) return done() + }) + + torrent.on('done', () => done(new Error('Tracker is enabled'))) + }) + }) + after(async function () { killallServers([ server ]) }) diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts index dcc45be8a..d937e9c05 100644 --- a/shared/models/server/server-config.model.ts +++ b/shared/models/server/server-config.model.ts @@ -97,4 +97,8 @@ export interface ServerConfig { intervalDays: number } } + + tracker: { + enabled: boolean + } } diff --git a/support/docker/production/config/production.yaml b/support/docker/production/config/production.yaml index 846c838e8..d585cd73e 100644 --- a/support/docker/production/config/production.yaml +++ b/support/docker/production/config/production.yaml @@ -46,5 +46,9 @@ storage: log: level: 'info' # debug/info/warning/error +tracker: + enabled: true + reject_too_many_announces: false # false because we have issues with traefik and ws ip/port forwarding + admin: email: null -- 2.25.1