js-libp2p/test/nat-manager/nat-manager.node.js
Alex Potsides 0a6bc0d101
feat: add UPnP NAT manager (#810)
* feat: add uPnP nat manager

Adds a really basic nat manager that attempts to use UPnP to punch
a hole through your router for any IPV4 tcp addresses you have
configured.

Adds any configured addresses to the node's observed addresses list
and adds observed addresses to `libp2p.multiaddrs` so we exchange
them with peers when performing `identify` and people can dial you.

Adds configuration options under `config.nat`

Hole punching is async to not affect start up time.

Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
2021-01-27 14:55:26 +01:00

245 lines
6.6 KiB
JavaScript

'use strict'
/* eslint-env mocha */
const { expect } = require('aegir/utils/chai')
const sinon = require('sinon')
const AddressManager = require('../../src/address-manager')
const TransportManager = require('../../src/transport-manager')
const Transport = require('libp2p-tcp')
const mockUpgrader = require('../utils/mockUpgrader')
const NatManager = require('../../src/nat-manager')
const delay = require('delay')
const peers = require('../fixtures/peers')
const PeerId = require('peer-id')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../../src/errors')
const DEFAULT_ADDRESSES = [
'/ip4/127.0.0.1/tcp/0',
'/ip4/0.0.0.0/tcp/0'
]
describe('Nat Manager (TCP)', () => {
const teardown = []
async function createNatManager (addrs = DEFAULT_ADDRESSES, natManagerOptions = {}) {
const peerId = await PeerId.createFromJSON(peers[0])
const addressManager = new AddressManager(peerId, { listen: addrs })
const transportManager = new TransportManager({
libp2p: {
peerId,
addressManager,
peerStore: {
addressBook: {
consumePeerRecord: sinon.stub()
}
}
},
upgrader: mockUpgrader,
onConnection: () => {},
faultTolerance: TransportManager.FaultTolerance.NO_FATAL
})
const natManager = new NatManager({
peerId,
addressManager,
transportManager,
enabled: true,
...natManagerOptions
})
natManager._client = {
externalIp: sinon.stub().resolves('82.3.1.5'),
map: sinon.stub(),
destroy: sinon.stub()
}
transportManager.add(Transport.prototype[Symbol.toStringTag], Transport)
await transportManager.listen(addressManager.getListenAddrs())
teardown.push(async () => {
await natManager.stop()
await transportManager.removeAll()
expect(transportManager._transports.size).to.equal(0)
})
return {
natManager,
addressManager,
transportManager
}
}
afterEach(() => Promise.all(teardown))
it('should map TCP connections to external ports', async () => {
const {
natManager,
addressManager,
transportManager
} = await createNatManager()
let addressChangedEventFired = false
addressManager.on('change:addresses', () => {
addressChangedEventFired = true
})
natManager._client = {
externalIp: sinon.stub().resolves('82.3.1.5'),
map: sinon.stub(),
destroy: sinon.stub()
}
let observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
await natManager._start()
observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.not.be.empty()
const internalPorts = transportManager.getAddrs()
.filter(ma => ma.isThinWaistAddress())
.map(ma => ma.toOptions())
.filter(({ host, transport }) => host !== '127.0.0.1' && transport === 'tcp')
.map(({ port }) => port)
expect(natManager._client.map.called).to.be.true()
internalPorts.forEach(port => {
expect(natManager._client.map.getCall(0).args[0]).to.include({
privatePort: port,
protocol: 'TCP'
})
})
expect(addressChangedEventFired).to.be.true()
})
it('should not map TCP connections when double-natted', async () => {
const {
natManager,
addressManager
} = await createNatManager()
natManager._client.externalIp = sinon.stub().resolves('192.168.1.1')
let observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
await expect(natManager._start()).to.eventually.be.rejectedWith(/double NAT/)
observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
expect(natManager._client.map.called).to.be.false()
})
it('should do nothing when disabled', async () => {
const {
natManager
} = await createNatManager(DEFAULT_ADDRESSES, {
enabled: false
})
natManager.start()
await delay(100)
expect(natManager._client.externalIp.called).to.be.false()
expect(natManager._client.map.called).to.be.false()
})
it('should not map non-ipv4 connections to external ports', async () => {
const {
natManager,
addressManager
} = await createNatManager([
'/ip6/::/tcp/5001'
])
let observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
await natManager._start()
observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
})
it('should not map non-ipv6 loopback connections to external ports', async () => {
const {
natManager,
addressManager
} = await createNatManager([
'/ip6/::1/tcp/5001'
])
let observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
await natManager._start()
observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
})
it('should not map non-TCP connections to external ports', async () => {
const {
natManager,
addressManager
} = await createNatManager([
'/ip4/0.0.0.0/utp'
])
let observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
await natManager._start()
observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
})
it('should not map loopback connections to external ports', async () => {
const {
natManager,
addressManager
} = await createNatManager([
'/ip4/127.0.0.1/tcp/5900'
])
let observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
await natManager._start()
observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
})
it('should not map non-thin-waist connections to external ports', async () => {
const {
natManager,
addressManager
} = await createNatManager([
'/ip4/0.0.0.0/tcp/5900/sctp/49832'
])
let observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
await natManager._start()
observed = addressManager.getObservedAddrs().map(ma => ma.toString())
expect(observed).to.be.empty()
})
it('should specify large enough TTL', () => {
expect(() => {
new NatManager({ ttl: 5 }) // eslint-disable-line no-new
}).to.throw().with.property('code', ERR_INVALID_PARAMETERS)
})
})