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 #1051

Open
wants to merge 7 commits into
base: master
from
Open
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

Adds NAT-PMP port mapping

  • Loading branch information
yciabaud committed Feb 18, 2017
commit a2bd6977677e94754f2aff4ae6c301bc65db43c5
@@ -19,7 +19,6 @@ var randombytes = require('randombytes')
var speedometer = require('speedometer')
var zeroFill = require('zero-fill')

var NatTraversal = require('./lib/nat-traversal') // browser exclude
var TCPPool = require('./lib/tcp-pool') // browser exclude
var Torrent = require('./lib/torrent')

@@ -107,9 +106,7 @@ function WebTorrent (opts) {
if (global.WRTC && !self.tracker.wrtc) self.tracker.wrtc = global.WRTC
}

if (typeof NatTraversal === 'function') {
self._natTraversal = new NatTraversal()
}
self._natTraversal = require('./lib/nat-traversal') // browser exclude

if (typeof TCPPool === 'function') {
self._tcpPool = new TCPPool(self)
@@ -437,12 +434,6 @@ WebTorrent.prototype._destroy = function (err, cb) {

if (self._natTraversal) {
tasks.push(function (cb) {
if (self.dhtPort) {
self._natTraversal.portUnMapping(self.dhtPort)
}
if (self.torrentPort) {
self._natTraversal.portUnMapping(self.torrentPort)
}
self._natTraversal.destroy(cb)
})
}
@@ -1,69 +1,156 @@

module.exports = NatTraversal

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('Create UPnP client')
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()
self.ttl = 60 * 30
self.timeout = self.ttl - 60
self.intervals = {}

// 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()

debug('Mapping port %d on router using UPnP', port)
self._upnpClient.portMapping({
public: port,
private: port,
description: 'WebTorrent',
ttl: self.ttl
}, function (err) {
self._openedPorts.push(port)

// Try UPnP first
self._upnpPortMapping(port, function (err) {
if (self._destroyed) return typeof cb === 'function' && cb()
if (err) {
return typeof cb === 'function' && cb(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()
}
self.intervals[port] = setInterval(NatTraversal.prototype.portMapping.bind(self, port), self.timeout)
debug('Port %d mapped on router using UPnP', port)
if (typeof cb === 'function') cb()
})
}

NatTraversal.prototype.portUnMapping = function (port, cb) {
var self = this
if (!self.intervals[port]) return typeof cb === 'function' && cb(new Error('Port not mapped'))
if (self._destroyed) return typeof cd === 'function' && cb()
arrayRemove(self._openedPorts, self._openedPorts.indexOf(port))

debug('Unmapping port %d on router using UPnP', port)
clearInterval(self.intervals[port])
delete self.intervals[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) {
return typeof cb === 'function' && cb(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()
}
debug('Port %d unmapped on router using UPnP', port)
if (typeof cb === 'function') cb()
})
}

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

// Clear all intervals
Object.keys(self.intervals).forEach(function (port) {
// 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()
debug('UPnP client closed')
cb()
})
}
@@ -14,8 +14,10 @@
"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
},
@@ -43,7 +45,9 @@
"memory-chunk-store": "^1.2.0",
"mime": "^1.3.4",
"multistream": "^2.0.5",
"nat-pmp": "^1.0.0",
"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.