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 ability to pass a connection protector for private networking #210

Merged
merged 1 commit into from
Jul 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ const SECIO = require('libp2p-secio')
const MulticastDNS = require('libp2p-mdns')
const DHT = require('libp2p-kad-dht')
const defaultsDeep = require('@nodeutils/defaults-deep')
const Protector = require('libp2p-pnet')

class Node extends libp2p {
constructor (_peerInfo, _peerBook, _options) {
Expand All @@ -135,6 +136,7 @@ class Node extends libp2p {
connEncryption: [
SECIO
],
connProtector: new Protector(/*protector specific opts*/),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to have multiple Protectors or just just have it inside and enable it as a config value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is based off of go which just supports a single, configurable protector. Users will be able to supply any protector they want, so it's extendable in the future as more protectors are created. In theory we could turn it into a list of protectors but I can't think of a use case where you'd want to do that as multiple levels of encryption on top of secio is really expensive.

If we produce more "intelligent" protectors in the future, those would be easy to swap out.

peerDiscovery: [
MulticastDNS
],
Expand Down Expand Up @@ -428,6 +430,16 @@ Each one of these values is [an exponential moving-average instance](https://git

Stats are not updated in real-time. Instead, measurements are buffered and stats are updated at an interval. The maximum interval can be defined through the `Switch` constructor option `stats.computeThrottleTimeout`, defined in miliseconds.

### Private Networks

#### Enforcement

Libp2p provides support for connection protection, such as for private networks. You can enforce network protection by setting the environment variable `LIBP2P_FORCE_PNET=1`. When this variable is on, if no protector is set via `options.connProtector`, Libp2p will throw an error upon creation.

#### Protectors

Some available network protectors:
* [libp2p-pnet](https://github.com/libp2p/js-libp2p-pnet)

## Development

Expand Down
3 changes: 3 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const OptionsSchema = Joi.object({
transport: Joi.array().items(ModuleSchema).min(1).required(),
streamMuxer: Joi.array().items(ModuleSchema).allow(null),
connEncryption: Joi.array().items(ModuleSchema).allow(null),
connProtector: Joi.object().keys({
protect: Joi.func().required()
}).unknown(),
peerDiscovery: Joi.array().items(ModuleSchema).allow(null),
dht: ModuleSchema.allow(null)
}).required(),
Expand Down
7 changes: 7 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ class Node extends EventEmitter {
})
}

// Attach private network protector
if (this._modules.connProtector) {
this._switch.protector = this._modules.connProtector
} else if (process.env.LIBP2P_FORCE_PNET) {
throw new Error('Private network is enforced, but no protector was provided')
}

// dht provided components (peerRouting, contentRouting, dht)
if (this._config.EXPERIMENTAL.dht) {
const DHT = this._modules.dht
Expand Down
1 change: 1 addition & 0 deletions test/node.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

require('./pnet.node')
require('./transports.node')
require('./stream-muxing.node')
require('./peer-discovery.node')
Expand Down
90 changes: 90 additions & 0 deletions test/pnet.node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* eslint-env mocha */
'use strict'

const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
const PeerInfo = require('peer-info')
const PeerId = require('peer-id')
const waterfall = require('async/waterfall')
const WS = require('libp2p-websockets')
const defaultsDeep = require('@nodeutils/defaults-deep')

const Libp2p = require('../src')

describe('private network', () => {
let config

before((done) => {
waterfall([
(cb) => PeerId.create({ bits: 512 }, cb),
(peerId, cb) => PeerInfo.create(peerId, cb),
(peerInfo, cb) => {
config = {
peerInfo,
modules: {
transport: [ WS ]
}
}
cb()
}
], () => done())
})

describe('enforced network protection', () => {
before(() => {
process.env.LIBP2P_FORCE_PNET = 1
})

after(() => {
delete process.env.LIBP2P_FORCE_PNET
})

it('should throw an error without a provided protector', () => {
expect(() => {
return new Libp2p(config)
}).to.throw('Private network is enforced, but no protector was provided')
})

it('should create a libp2p node with a provided protector', () => {
let node
let protector = {
psk: '123',
tag: '/psk/1.0.0',
protect: () => { }
}

expect(() => {
let options = defaultsDeep(config, {
modules: {
connProtector: protector
}
})

node = new Libp2p(options)
return node
}).to.not.throw()
expect(node._switch.protector).to.deep.equal(protector)
})

it('should throw an error if the protector does not have a protect method', () => {
expect(() => {
let options = defaultsDeep(config, {
modules: {
connProtector: { }
}
})

return new Libp2p(options)
}).to.throw()
})
})

describe('network protection not enforced', () => {
it('should not throw an error with no provided protector', () => {
expect(() => {
return new Libp2p(config)
}).to.not.throw()
})
})
})