Add ability to disable tracker
authorChocobozzz <me@florianbigard.com>
Wed, 10 Apr 2019 07:23:18 +0000 (09:23 +0200)
committerChocobozzz <me@florianbigard.com>
Wed, 10 Apr 2019 07:23:18 +0000 (09:23 +0200)
16 files changed:
client/src/app/core/server/server.service.ts
client/src/app/shared/instance/instance-features-table.component.ts
client/src/app/videos/+video-watch/video-watch.component.scss
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/assets/player/utils.ts
client/src/sass/player/peertube-skin.scss
client/src/standalone/videos/embed.ts
config/default.yaml
config/production.yaml.example
server/controllers/api/config.ts
server/controllers/tracker.ts
server/initializers/checker-before-init.ts
server/initializers/constants.ts
server/tests/api/server/tracker.ts
shared/models/server/server-config.model.ts
support/docker/production/config/production.yaml

index b0c5d11303008aab7ffa2bbcf7f67e279f30574d..3a8a535fd4ae4e6ba47d5fd5f9752e6468cb006e 100644 (file)
@@ -105,6 +105,9 @@ export class ServerService {
           enabled: false
         }
       }
+    },
+    tracker: {
+      enabled: true
     }
   }
   private videoCategories: Array<VideoConstant<number>> = []
index c0257fd59512ec0cca0bc6b1be3a29454649fd5c..72e7c2730f3c93cd469c4a018e9d8cd263954696 100644 (file)
@@ -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
       }
     ]
   }
index d61a0bc3ee180949a01b36e6145b257cb43d312d..84b9aed397a363cbaf0e3e191b7ec9bcd46f1619 100644 (file)
@@ -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) {
index edc546b283f4e7a9856076549ea0e1b1115b5490..bce65221085f2908ee1f2032aa4d235fdd936abf 100644 (file)
@@ -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' ]
index 0966027ace04e171d1832f9c39a93596dff821bf..3666899625720302c0d14dac1f12ff0040c17d6c 100644 (file)
@@ -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,
index a6942001a26eb0c9cef8cdd00d5a9c11c68e5099..e63a2875cd80a65723632a3b820ef85ac3da1afe 100644 (file)
   .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);
     }
index 626d55a7c2fcfb93ee7364ed0235e0856ee97ba1..707f04253c3ad741fbbec2dd071a5e5420906e93 100644 (file)
@@ -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<Response> {
+    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
+        ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>'
+        : undefined
+
       this.player.dock({
         title: videoInfo.name,
-        description: this.player.localize('Uses P2P, others may know your IP is downloading this video.')
+        description
       })
     }
 
index 0d6e34d86b7d98d966a9f98a822dfe65670eecee..617159c2ccf22cf7fad11d8a5eb5e231000ab9be 100644 (file)
@@ -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
index 5029cc25b744e47af97dcaf9c17327e3a7e65695..dd5c9769bdb4fc2a0893053e9521a7c85853ec7e 100644 (file)
@@ -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
+
 
 ###############################################################################
 #
index 5c4f455ee5160f9cfa210e8d8d9a34865c31a68b..0d7fc862564157ec0f0d0ea6ffd599915a9095c4 100644 (file)
@@ -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
     }
   }
 
index 8b77d9de77945f2f0582e8eabcf468aa5b49cb41..56a3424a37096b8d44248aa21890dc179de3500d 100644 (file)
@@ -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' }))
index 3095913a3209f79b49a9b4a85a48753cf3bee50e..6b43debfb61db295ee8e88ee2a72d617b9ad8228 100644 (file)
@@ -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
index 097199f8405a82ca736cdb9c79308dcd0dabea4b..3f02572dbd505aeb07ab02ff9442b806e7763bd7 100644 (file)
@@ -243,6 +243,11 @@ const CONFIG = {
     REPORT_ONLY: config.get<boolean>('csp.report_only'),
     REPORT_URI: config.get<boolean>('csp.report_uri')
   },
+  TRACKER: {
+    ENABLED: config.get<boolean>('tracker.enabled'),
+    PRIVATE: config.get<boolean>('tracker.private'),
+    REJECT_TOO_MANY_ANNOUNCES: config.get<boolean>('tracker.reject_too_many_announces')
+  },
   ADMIN: {
     get EMAIL () { return config.get<string>('admin.email') }
   },
index 25ca00029bc9fc8d94731b27be1b9daf257f2257..41803aef133d61893458bdbcbdd6823ad80aa685 100644 (file)
@@ -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 ])
   })
index dcc45be8ae1e089cfa10c15b5cc5ca317670b971..d937e9c05a2d39b1c8039697cde01c022bcfc350 100644 (file)
@@ -97,4 +97,8 @@ export interface ServerConfig {
       intervalDays: number
     }
   }
+
+  tracker: {
+    enabled: boolean
+  }
 }
index 846c838e85c6d964147e45065cfaf6f52d036aba..d585cd73eade2cd8206f53260a5035c8a9df3d34 100644 (file)
@@ -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