Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for NAT traversal techniques #1419

Closed
wants to merge 18 commits into from
Closed
Changes from 8 commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -110,6 +110,8 @@ function WebTorrent (opts) {
}
}

self._natTraversal = require('./lib/nat-traversal') // browser exclude

if (typeof TCPPool === 'function') {
self._tcpPool = new TCPPool(self)
} else {
@@ -132,7 +134,12 @@ function WebTorrent (opts) {

self.dht.once('listening', function () {
var address = self.dht.address()
if (address) self.dhtPort = address.port
if (address) {
self.dhtPort = address.port
if (self._natTraversal.portMapping) {
self._natTraversal.portMapping(self.dhtPort)
}
}
})

// Ignore warning when there are > 10 torrents in the client
@@ -431,6 +438,12 @@ WebTorrent.prototype._destroy = function (err, cb) {
})
}

if (self._natTraversal.destroy) {
tasks.push(function (cb) {
self._natTraversal.destroy(cb)
})
}

parallel(tasks, cb)

if (err) self.emit('error', err)
@@ -448,7 +461,12 @@ WebTorrent.prototype._onListening = function () {
// Sometimes server.address() returns `null` in Docker.
// WebTorrent issue: https://github.com/feross/bittorrent-swarm/pull/18
var address = this._tcpPool.server.address()
if (address) this.torrentPort = address.port
if (address) {
this.torrentPort = address.port
if (this._natTraversal.portMapping) {
this._natTraversal.portMapping(this.torrentPort)
}
}
}

this.emit('listening')
@@ -0,0 +1,156 @@
var arrayRemove = require('unordered-array-remove')
var debug = require('debug')('webtorrent:nat-traversal')
var natUpnp = require('nat-upnp') // browser exclude
var natpmp = require('nat-pmp') // browser exclude
var network = require('network') // browser exclude

// Use single instance
module.exports = new NatTraversal()

function NatTraversal () {
var self = this
self._destroyed = false

// The RECOMMENDED Port Mapping Lifetime is 7200 seconds (two hours).
self.ttl = 7200
// Refresh the mapping 10 minutes before the end of its lifetime
self.timeout = (self.ttl - 600) * 1000
self._openedPorts = []
self._intervalsUpnp = {}
self._intervalsPmp = {}

self._upnpPortMapping = function (port, cb) {
var self = this

debug('Mapping port %d on router using UPnP', port)
self._upnpClient.portMapping({
public: port,
private: port,
description: 'WebTorrent',
ttl: self.ttl
}, function (err) {
if (self._destroyed) return typeof cb === 'function' && cb()
if (err) {
return typeof cb === 'function' && cb(err)
}
self._intervalsUpnp[port] = setInterval(self._pmpPortMapping.bind(self, port), self.timeout)
debug('Port %d mapped on router using UPnP', port)
if (typeof cb === 'function') cb()
})
}

self._pmpPortMapping = function (port, cb) {
var self = this

debug('Mapping port %d on router using NAT-PMP', port)
self._pmpClient.portMapping({
private: port,
public: port,
ttl: self.ttl,
type: 'tcp'
}, function (err/* , info */) {
if (self._destroyed) return typeof cb === 'function' && cb()
if (err) {
debug('Error mapping port %d using NAT-PMP', port, err)
return typeof cb === 'function' && cb(err)
}
self._intervalsPmp[port] = setInterval(self._pmpPortMapping.bind(self, port), self.timeout)
debug('Port %d mapped on router using NAT-PMP', port)
if (typeof cb === 'function') cb()
})
}

debug('UPnP client creation')
self._upnpClient = natUpnp.createClient()

// Lookup gateway IP
debug('Lookup gateway IP')
network.get_gateway_ip(function (err, ip) {
if (self._destroyed) return
if (err) {
return debug('Could not find gateway IP for NAT-PMP', err)
}
debug('NAT-PMP client creation', ip)
self._pmpClient = natpmp.connect(ip)
self._openedPorts.forEach(function (port) {
self._pmpPortMapping(port)
})
})
}

NatTraversal.prototype.portMapping = function (port, cb) {
var self = this
if (self._destroyed) return typeof cd === 'function' && cb()

self._openedPorts.push(port)

// Try UPnP first
self._upnpPortMapping(port, function (err) {
if (self._destroyed) return typeof cb === 'function' && cb()
if (err) {
debug('UPnP port mapping failed on %d', port, err.message)
}

// Then NAT-PMP
if (self._pmpClient) {
self._pmpPortMapping(port, cb)
} else if (typeof cb === 'function') {
cb()
}
})
}

NatTraversal.prototype.portUnMapping = function (port, cb) {
var self = this
if (self._destroyed) return typeof cd === 'function' && cb()
arrayRemove(self._openedPorts, self._openedPorts.indexOf(port))

// Clear intervals
if (self._intervalsUpnp[port]) {
clearInterval(self._intervalsUpnp[port])
delete self._intervalsUpnp[port]
}
if (self._intervalsPmp[port]) {
clearInterval(self._intervalsPmp[port])
delete self._intervalsPmp[port]
}

debug('Unmapping port %d on router using UPnP', port)
self._upnpClient.portUnmapping({
public: port
}, function (err) {
if (!err) debug('Port %d unmapped on router using UPnP', port)
if (self._pmpClient) {
debug('Unmapping port %d on router using NAT-PMP', port)
self._pmpClient.portUnmapping({
private: port,
public: port
}, cb)
} else {
if (typeof cb === 'function') cb()
}
})
}

NatTraversal.prototype.destroy = function (cb) {
var self = this
if (self._destroyed) return cb()

// Unmap all ports
self._openedPorts.forEach(function (port) {
self.portUnMapping(port)
})
self._destroyed = true

if (self._pmpClient) {
debug('Close pmp client')
self._pmpClient.close()
}

// Waiting next tick to prevent breaking some sockets
process.nextTick(function () {
debug('Close UPnP client')
self._upnpClient.close()
cb()
})
}
@@ -9,13 +9,15 @@ var url = require('url')

function Server (torrent, opts) {
var server = http.createServer(opts)
var natTraversal = require('./nat-traversal')

var sockets = []
var pendingReady = []
var closed = false

server.on('connection', onConnection)
server.on('request', onRequest)
server.on('listening', onListening)

var _close = server.close
server.close = function (cb) {
@@ -35,6 +37,10 @@ function Server (torrent, opts) {
socket.destroy()
})

if (server.address()) {
natTraversal.portUnMapping(server.address().port)
}

// Only call `server.close` if user has not called it already
if (!cb) cb = function () {}
if (closed) process.nextTick(cb)
@@ -195,6 +201,10 @@ function Server (torrent, opts) {
}
}

function onListening () {
natTraversal.portMapping(server.address().port)
}

return server
}

@@ -8,12 +8,16 @@
"url": "https://webtorrent.io"
},
"browser": {
"./lib/nat-traversal.js": false,
"./lib/server.js": false,
"./lib/tcp-pool.js": false,
"bittorrent-dht/client": false,
"fs-chunk-store": "memory-chunk-store",
"load-ip-set": false,
"nat-pmp": false,
"nat-upnp": false,
"net": false,
"network": false,
"os": false,
"ut_pex": false
},
@@ -41,6 +45,9 @@
"memory-chunk-store": "^1.2.0",
"mime": "^1.3.4",
"multistream": "^2.0.5",
"nat-pmp": "github:yciabaud/node-nat-pmp#patch-1",
"nat-upnp": "^1.0.4",
"network": "^0.3.2",
"package-json-versionify": "^1.0.2",
"parse-torrent": "^5.8.0",
"pump": "^1.0.1",
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.