Skip to content
This repository was archived by the owner on Aug 23, 2019. It is now read-only.

Commit b6600da

Browse files
author
Jacob Heun
authored
Add private network support (#266)
* feat: add support for private networks fix: update protector.protect usage chore: fix linting and update deps test: add secio to pnet tests docs: add private network info the readme chore: update pnet package version test: add skipped test back in and update it
1 parent 5ee5ac2 commit b6600da

File tree

9 files changed

+224
-3
lines changed

9 files changed

+224
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ logs
99
*.log
1010

1111
coverage
12+
.nyc_output/
1213

1314
# Runtime data
1415
pids

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ If defined, `options` should be an object with the following keys and respective
7575
]
7676
```
7777

78+
### Private Networks
79+
80+
libp2p-switch supports private networking. In order to enabled private networks, the `switch.protector` must be
81+
set and must contain a `protect` method. You can see an example of this in the [private network
82+
tests]([./test/pnet.node.js]).
7883

7984
## API
8085

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"dirty-chai": "^2.0.1",
4444
"gulp": "^3.9.1",
4545
"libp2p-mplex": "~0.7.0",
46+
"libp2p-pnet": "~0.1.0",
4647
"libp2p-secio": "~0.10.0",
4748
"libp2p-spdy": "~0.12.1",
4849
"libp2p-tcp": "~0.12.0",

src/dial.js

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,18 @@ class Dialer {
143143
if (!this.protocol) {
144144
return cb()
145145
}
146+
146147
// If we have a muxer, create a new stream, otherwise it's a standard connection
147-
const connection = muxer.newStream ? muxer.newStream() : muxer
148-
this._performProtocolHandshake(connection, cb)
148+
if (muxer.newStream) {
149+
muxer.newStream((err, conn) => {
150+
if (err) return cb(err)
151+
152+
this._performProtocolHandshake(conn, cb)
153+
})
154+
return
155+
}
156+
157+
this._performProtocolHandshake(muxer, cb)
149158
}
150159
], (err, connection) => {
151160
callback(err, connection)
@@ -181,8 +190,12 @@ class Dialer {
181190
this._attemptDial(cb)
182191
},
183192
(baseConnection, cb) => {
193+
// Create a private connection if it's needed
194+
this._createPrivateConnection(baseConnection, cb)
195+
},
196+
(connection, cb) => {
184197
// Add the Switch's crypt encryption to the connection
185-
this._encryptConnection(baseConnection, cb)
198+
this._encryptConnection(connection, cb)
186199
}
187200
], (err, encryptedConnection) => {
188201
if (err) {
@@ -193,6 +206,31 @@ class Dialer {
193206
})
194207
}
195208

209+
/**
210+
* If the switch has a private network protector, `switch.protector`, its `protect`
211+
* method will be called with the given connection. The resulting, wrapped connection
212+
* will be returned via the callback.
213+
*
214+
* @param {Connection} connection The connection to protect
215+
* @param {function(Error, Connection)} callback
216+
* @returns {void}
217+
*/
218+
_createPrivateConnection (connection, callback) {
219+
if (this.switch.protector === null) {
220+
return callback(null, connection)
221+
}
222+
223+
// If the switch has a protector, be private
224+
const protectedConnection = this.switch.protector.protect(connection, (err) => {
225+
if (err) {
226+
return callback(err)
227+
}
228+
229+
protectedConnection.setPeerInfo(this.peerInfo)
230+
callback(null, protectedConnection)
231+
})
232+
}
233+
196234
/**
197235
* If the given PeerId key, `b58Id`, has an existing muxed connection
198236
* it will be returned via the callback, otherwise the connection

src/errors.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict'
2+
3+
module.exports.PROTECTOR_REQUIRED = 'No protector provided with private network enforced'

src/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const plaintext = require('./plaintext')
1212
const Observer = require('./observer')
1313
const Stats = require('./stats')
1414
const assert = require('assert')
15+
const Errors = require('./errors')
1516

1617
class Switch extends EE {
1718
constructor (peerInfo, peerBook, options) {
@@ -52,6 +53,8 @@ class Switch extends EE {
5253
// Crypto details
5354
this.crypto = plaintext
5455

56+
this.protector = this._options.protector || null
57+
5558
this.transport = new TransportManager(this)
5659
this.connection = new ConnectionManager(this)
5760

@@ -197,3 +200,4 @@ class Switch extends EE {
197200
}
198201

199202
module.exports = Switch
203+
module.exports.errors = Errors

src/transport.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,24 @@ class TransportManager {
8686
* @returns {void}
8787
*/
8888
listen (key, options, handler, callback) {
89+
let muxerHandler
90+
8991
// if no handler is passed, we pass conns to protocolMuxer
9092
if (!handler) {
9193
handler = this.switch.protocolMuxer(key)
9294
}
9395

96+
// If we have a protector make the connection private
97+
if (this.switch.protector) {
98+
muxerHandler = handler
99+
handler = (parentConnection) => {
100+
const connection = this.switch.protector.protect(parentConnection, () => {
101+
// If we get an error here, we should stop talking to this peer
102+
muxerHandler(connection)
103+
})
104+
}
105+
}
106+
94107
const transport = this.switch.transports[key]
95108
const multiaddrs = TransportManager.dialables(
96109
transport,

test/node.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict'
22

3+
require('./pnet.node')
34
require('./transports.node')
45
require('./stream-muxers.node')
56
require('./secio.node')

test/pnet.node.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const chai = require('chai')
5+
const dirtyChai = require('dirty-chai')
6+
const expect = chai.expect
7+
chai.use(dirtyChai)
8+
const parallel = require('async/parallel')
9+
const TCP = require('libp2p-tcp')
10+
const multiplex = require('libp2p-mplex')
11+
const pull = require('pull-stream')
12+
const PeerBook = require('peer-book')
13+
const secio = require('libp2p-secio')
14+
const Protector = require('libp2p-pnet')
15+
16+
const utils = require('./utils')
17+
const createInfos = utils.createInfos
18+
const tryEcho = utils.tryEcho
19+
const Switch = require('../src')
20+
21+
const generatePSK = Protector.generate
22+
23+
const psk = Buffer.alloc(95)
24+
const psk2 = Buffer.alloc(95)
25+
generatePSK(psk)
26+
generatePSK(psk2)
27+
28+
describe('Private Network', function () {
29+
this.timeout(20 * 1000)
30+
31+
let switchA
32+
let switchB
33+
let switchC
34+
let switchD
35+
36+
before((done) => createInfos(4, (err, infos) => {
37+
expect(err).to.not.exist()
38+
39+
const peerA = infos[0]
40+
const peerB = infos[1]
41+
const peerC = infos[2]
42+
const peerD = infos[3]
43+
44+
peerA.multiaddrs.add('/ip4/127.0.0.1/tcp/9001')
45+
peerB.multiaddrs.add('/ip4/127.0.0.1/tcp/9002')
46+
peerC.multiaddrs.add('/ip4/127.0.0.1/tcp/9003')
47+
peerD.multiaddrs.add('/ip4/127.0.0.1/tcp/9004')
48+
49+
switchA = new Switch(peerA, new PeerBook(), {
50+
protector: new Protector(psk)
51+
})
52+
switchB = new Switch(peerB, new PeerBook(), {
53+
protector: new Protector(psk)
54+
})
55+
// alternative way to add the protector
56+
switchC = new Switch(peerC, new PeerBook())
57+
switchC.protector = new Protector(psk)
58+
// Create a switch on a different private network
59+
switchD = new Switch(peerD, new PeerBook(), {
60+
protector: new Protector(psk2)
61+
})
62+
63+
switchA.transport.add('tcp', new TCP())
64+
switchB.transport.add('tcp', new TCP())
65+
switchC.transport.add('tcp', new TCP())
66+
switchD.transport.add('tcp', new TCP())
67+
68+
switchA.connection.crypto(secio.tag, secio.encrypt)
69+
switchB.connection.crypto(secio.tag, secio.encrypt)
70+
switchC.connection.crypto(secio.tag, secio.encrypt)
71+
switchD.connection.crypto(secio.tag, secio.encrypt)
72+
73+
switchA.connection.addStreamMuxer(multiplex)
74+
switchB.connection.addStreamMuxer(multiplex)
75+
switchC.connection.addStreamMuxer(multiplex)
76+
switchD.connection.addStreamMuxer(multiplex)
77+
78+
parallel([
79+
(cb) => switchA.transport.listen('tcp', {}, null, cb),
80+
(cb) => switchB.transport.listen('tcp', {}, null, cb),
81+
(cb) => switchC.transport.listen('tcp', {}, null, cb),
82+
(cb) => switchD.transport.listen('tcp', {}, null, cb)
83+
], done)
84+
}))
85+
86+
after(function (done) {
87+
this.timeout(3 * 1000)
88+
parallel([
89+
(cb) => switchA.stop(cb),
90+
(cb) => switchB.stop(cb),
91+
(cb) => switchC.stop(cb),
92+
(cb) => switchD.stop(cb)
93+
], done)
94+
})
95+
96+
it('should handle + dial on protocol', (done) => {
97+
switchB.handle('/abacaxi/1.0.0', (protocol, conn) => pull(conn, conn))
98+
99+
switchA.dial(switchB._peerInfo, '/abacaxi/1.0.0', (err, conn) => {
100+
expect(err).to.not.exist()
101+
expect(Object.keys(switchA.muxedConns).length).to.equal(1)
102+
tryEcho(conn, done)
103+
})
104+
})
105+
106+
it('should dial to warm conn', (done) => {
107+
switchB.dial(switchA._peerInfo, (err) => {
108+
expect(err).to.not.exist()
109+
expect(Object.keys(switchB.conns).length).to.equal(0)
110+
expect(Object.keys(switchB.muxedConns).length).to.equal(1)
111+
done()
112+
})
113+
})
114+
115+
it('should dial on protocol, reuseing warmed conn', (done) => {
116+
switchA.handle('/papaia/1.0.0', (protocol, conn) => pull(conn, conn))
117+
118+
switchB.dial(switchA._peerInfo, '/papaia/1.0.0', (err, conn) => {
119+
expect(err).to.not.exist()
120+
expect(Object.keys(switchB.conns).length).to.equal(0)
121+
expect(Object.keys(switchB.muxedConns).length).to.equal(1)
122+
tryEcho(conn, done)
123+
})
124+
})
125+
126+
it('should enable identify to reuse incomming muxed conn', (done) => {
127+
switchA.connection.reuse()
128+
switchC.connection.reuse()
129+
130+
switchC.dial(switchA._peerInfo, (err) => {
131+
expect(err).to.not.exist()
132+
setTimeout(() => {
133+
expect(Object.keys(switchC.muxedConns).length).to.equal(1)
134+
expect(Object.keys(switchA.muxedConns).length).to.equal(2)
135+
done()
136+
}, 500)
137+
})
138+
})
139+
140+
/**
141+
* This test is being skipped until a related issue with pull-reader overreading can be resolved
142+
* Currently this test will time out instead of returning an error properly. This is the same issue
143+
* in ipfs/interop, https://github.com/ipfs/interop/pull/24/commits/179978996ecaef39e78384091aa9669dcdb94cc0
144+
*/
145+
it('should fail to talk to a switch on a different private network', function (done) {
146+
switchD.dial(switchA._peerInfo, (err) => {
147+
expect(err).to.exist()
148+
})
149+
150+
// A successful connection will return in well under 2 seconds
151+
setTimeout(() => {
152+
done()
153+
}, 2000)
154+
})
155+
})

0 commit comments

Comments
 (0)