├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── IMPLEMENTATION_NOTES.md
├── LICENSE
├── README.md
├── package.json
├── src
├── circuit.js
├── circuit
│ ├── dialer.js
│ ├── hop.js
│ ├── stop.js
│ ├── stream-handler.js
│ └── utils.js
├── index.js
├── listener.js
├── multicodec.js
└── protocol
│ └── index.js
└── test
├── dialer.spec.js
├── fixtures
└── nodes.js
├── helpers
├── test-node.js
└── utils.js
├── hop.spec.js
├── listener.spec.js
├── proto.spec.js
└── stop.spec.js
/.gitignore:
--------------------------------------------------------------------------------
1 | docs
2 | package-lock.json
3 | yarn.lock
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22 | .grunt
23 |
24 | # node-waf configuration
25 | .lock-wscript
26 |
27 | # Compiled binary addons (http://nodejs.org/api/addons.html)
28 | build/Release
29 |
30 | # Dependency directory
31 | node_modules
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | # Vim editor swap files
40 | *.swp
41 |
42 | dist
43 |
44 | .history
45 | .vscode
46 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
20 | .grunt
21 |
22 | # node-waf configuration
23 | .lock-wscript
24 |
25 | # Compiled binary addons (http://nodejs.org/api/addons.html)
26 | build/Release
27 |
28 | # Dependency directory
29 | node_modules
30 |
31 | # Optional npm cache directory
32 | .npm
33 |
34 | # Optional REPL history
35 | .node_repl_history
36 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | cache: npm
3 | stages:
4 | - check
5 | - test
6 | - cov
7 |
8 | node_js:
9 | - '10'
10 |
11 | os:
12 | - linux
13 | - osx
14 | - windows
15 |
16 | script: npx nyc -s npm run test:node -- --bail
17 | after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
18 |
19 | jobs:
20 | include:
21 | - stage: check
22 | script:
23 | - npx aegir commitlint --travis
24 | - npx aegir dep-check
25 | - npm run lint
26 |
27 | - stage: test
28 | name: chrome
29 | addons:
30 | chrome: stable
31 | script: npx aegir test -t browser
32 |
33 | - stage: test
34 | name: firefox
35 | addons:
36 | firefox: latest
37 | script: npx aegir test -t browser -- --browsers FirefoxHeadless
38 |
39 | notifications:
40 | email: false
41 |
42 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [0.3.7](https://github.com/libp2p/js-libp2p-circuit/compare/v0.3.6...v0.3.7) (2019-05-29)
3 |
4 |
5 | ### Bug Fixes
6 |
7 | * shim setImmediate in browser ([#51](https://github.com/libp2p/js-libp2p-circuit/issues/51)) ([9ea72f3](https://github.com/libp2p/js-libp2p-circuit/commit/9ea72f3))
8 |
9 |
10 |
11 |
12 | ## [0.3.6](https://github.com/libp2p/js-libp2p-circuit/compare/v0.3.5...v0.3.6) (2019-03-12)
13 |
14 |
15 |
16 |
17 | ## [0.3.5](https://github.com/libp2p/js-libp2p-circuit/compare/v0.3.4...v0.3.5) (2019-02-15)
18 |
19 |
20 | ### Bug Fixes
21 |
22 | * src/dst addrs validation ([68cccbe](https://github.com/libp2p/js-libp2p-circuit/commit/68cccbe))
23 |
24 |
25 |
26 |
27 | ## [0.3.4](https://github.com/libp2p/js-libp2p-circuit/compare/v0.3.3...v0.3.4) (2019-01-10)
28 |
29 |
30 | ### Bug Fixes
31 |
32 | * reduce bundle size ([#40](https://github.com/libp2p/js-libp2p-circuit/issues/40)) ([2c8ef1d](https://github.com/libp2p/js-libp2p-circuit/commit/2c8ef1d))
33 |
34 |
35 |
36 |
37 | ## [0.3.3](https://github.com/libp2p/js-libp2p-circuit/compare/v0.3.2...v0.3.3) (2019-01-04)
38 |
39 |
40 |
41 |
42 | ## [0.3.2](https://github.com/libp2p/js-libp2p-circuit/compare/v0.3.1...v0.3.2) (2019-01-02)
43 |
44 |
45 | ### Bug Fixes
46 |
47 | * connection establishment event handling ([#41](https://github.com/libp2p/js-libp2p-circuit/issues/41)) ([c27c344](https://github.com/libp2p/js-libp2p-circuit/commit/c27c344))
48 |
49 |
50 |
51 |
52 | ## [0.3.1](https://github.com/libp2p/js-libp2p-circuit/compare/v0.3.0...v0.3.1) (2018-11-15)
53 |
54 |
55 | ### Bug Fixes
56 |
57 | * catch bad peerid and update deps ([#39](https://github.com/libp2p/js-libp2p-circuit/issues/39)) ([f17539a](https://github.com/libp2p/js-libp2p-circuit/commit/f17539a))
58 |
59 |
60 |
61 |
62 | # [0.3.0](https://github.com/libp2p/js-libp2p-circuit/compare/v0.2.1...v0.3.0) (2018-10-01)
63 |
64 |
65 | ### Bug Fixes
66 |
67 | * listener should emit connection ([#30](https://github.com/libp2p/js-libp2p-circuit/issues/30)) ([1ac430d](https://github.com/libp2p/js-libp2p-circuit/commit/1ac430d))
68 |
69 |
70 |
71 |
72 | ## [0.2.1](https://github.com/libp2p/js-libp2p-circuit/compare/v0.2.0...v0.2.1) (2018-08-13)
73 |
74 |
75 | ### Bug Fixes
76 |
77 | * close streams ([546af97](https://github.com/libp2p/js-libp2p-circuit/commit/546af97))
78 | * options ([22e8518](https://github.com/libp2p/js-libp2p-circuit/commit/22e8518))
79 |
80 |
81 |
82 |
83 | # [0.2.0](https://github.com/libp2p/js-libp2p-circuit/compare/v0.1.5...v0.2.0) (2018-04-05)
84 |
85 |
86 |
87 |
88 | ## [0.1.5](https://github.com/libp2p/js-libp2p-circuit/compare/v0.1.4...v0.1.5) (2018-03-14)
89 |
90 |
91 | ### Bug Fixes
92 |
93 | * creating a proper peerId instead of using a simple buffer or string ([d061cda](https://github.com/libp2p/js-libp2p-circuit/commit/d061cda))
94 |
95 |
96 |
97 |
98 | ## [0.1.4](https://github.com/libp2p/js-libp2p-circuit/compare/v0.1.3...v0.1.4) (2017-11-03)
99 |
100 |
101 | ### Bug Fixes
102 |
103 | * switch protobufs to protons ([#19](https://github.com/libp2p/js-libp2p-circuit/issues/19)) ([56b37da](https://github.com/libp2p/js-libp2p-circuit/commit/56b37da))
104 |
105 |
106 |
107 |
108 | ## [0.1.3](https://github.com/libp2p/js-libp2p-circuit/compare/v0.1.0...v0.1.3) (2017-10-26)
109 |
110 |
111 | ### Bug Fixes
112 |
113 | * listenner callback expects a connection from transport ([#17](https://github.com/libp2p/js-libp2p-circuit/issues/17)) ([13179cb](https://github.com/libp2p/js-libp2p-circuit/commit/13179cb))
114 |
115 |
116 |
117 |
118 | # 0.1.0 (2017-10-21)
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/IMPLEMENTATION_NOTES.md:
--------------------------------------------------------------------------------
1 | EDIT: This document is outdated and here only for historical purposes
2 |
3 | NOTE: This document is structured in an `if-then/else[if]-then` manner, each line is a precondition for following lines with a higher number of indentation
4 |
5 | Example:
6 |
7 | - if there are apples
8 | - eat them
9 | - if not, check for pears
10 | - then eat them
11 | - if not, check for cherries
12 | - then eat them
13 |
14 | Or,
15 |
16 | - if there are apples
17 | - eat them
18 | - if not
19 | - check for pears
20 | - then eat them
21 | - if not
22 | - check for cherries
23 | - then eat them
24 |
25 | In order to minimize nesting, the first example is preferred
26 |
27 | # Relay flow
28 |
29 | ## Relay transport (dialer/listener)
30 |
31 | - ### Dial over a relay
32 | - See if there is a relay that's already connected to the destination peer, if not
33 | - Ask all the peer's known relays to dial the destination peer until an active relay (one that can dial on behalf of other peers), or a relay that may have recently acquired a connection to the destination peer is successful.
34 | - If successful
35 | - Write the `/ipfs/relay/circuit/1.0.0` header to the relay, followed by the destination address
36 | - e.g. `/ipfs/relay/circuit/1.0.0\n/p2p-circuit/ipfs/QmDest`.
37 | - If no relays could connect, fail the same way a regular transport would
38 | - Once the connection has been established, the swarm should treat it as a regular connection,
39 | - i.e. muxing, encrypt, etc should all be performed on the relayed connection
40 |
41 | - ### Listen for relayed connections
42 | - Peer mounts the `/ipfs/relay/circuit/1.0.0` proto and listens for relayed connections
43 | - A connection arrives
44 | - read the address of the source peer from the incoming connection stream
45 | - if valid, create a PeerInfo object for that peer and add the incoming address to its multiaddresses list
46 | - pass the connection to `protocolMuxer(swarm.protocols, conn)` to have it go through the regular muxing/encryption flow
47 |
48 | - ### Relay discovery and static relay addresses in swarm config
49 |
50 | - #### Relay address in swarm config
51 | - A peer has relay addresses in its swarm config section
52 | - On node startup, connect to the relays in swarm config
53 | - if successful add address to swarms PeerInfo's multiaddresses
54 | - `identify` should take care of announcing that the peer is reachable over the listed relays
55 |
56 | - #### Passive relay discovery
57 | - A peer that can dial over `/ipfs/relay/circuit/1.0.0` listens for the `peer-mux-established` swarm event, every time a new muxed connection arrives, it checks if the incoming peer is a relay. (How would this work? Some way of discovering if its a relay is required.)
58 | - *Useful in cases when the peer/node doesn't know of any relays on startup and also, to learn of as many additional relays in the network as possible*
59 | - *Useful during startup, when connecting to bootstrap nodes. It allows us to implicitly learn if its a relay without having to explicitly add `/p2p-circuit` addresses to the bootstrap list*
60 | - *Also useful if the relay communicates its capabilities upon connecting to it, as to avoid additional unnecessary requests/queries. I.e. if it supports weather its able to forward connections and weather it supports the `ls` or other commands.*
61 | - *Should it be possible to disable passive relay discovery?*
62 | - This could be useful when the peer wants to be reachable **only** over the listed relays
63 | - If the incoming peer is a relay, send an `ls` and record its peers
64 |
65 | ## Relay Nodes
66 |
67 | - ### Passive relay node
68 | - *A passive relay does not explicitly dial into any requested peer, only those that it's swarm already has connections to.*
69 | - When the relay gets a request, read the the destination peer's multiaddr from the connection stream and if its a valid address and peer id
70 | - check its swarm's peerbook(?) see if its a known peer, if it is
71 | - use the swarms existing connection and
72 | - send the multistream header and the source peer address to the dest peer
73 | - e.g. `/ipfs/relay/circuit/1.0.0\n/p2p-circuit/ipfs/QmSource`
74 | - circuit the source and dest connections
75 | - if couldn't dial, or the connection/stream to the dest peer closed prematurelly
76 | - close the src stream
77 |
78 |
79 | - ### Active relay node
80 | - *An active relay node can dial other peers even if its swarm doesnt know about those peers*
81 | - When the relay gets a request, read the the destination peer's multiaddr from the connection stream and if its a valid address and peer id
82 | - use the swarm to dial to the dest node
83 | - send the multistream header and the source peer address to the dest peer
84 | - e.g. `/ipfs/relay/circuit/1.0.0\n/p2p-circuit/ipfs/QmSource`
85 | - circuit the source and dest connections
86 | - if couldn't dial, or the connection/stream to the dest peer closed prematurely
87 | - close the src stream
88 |
89 | - ### `ls` command
90 | - *A relay node can allow the peers known to it's swarm to be listed*
91 | - *this should be possible to enable/disable from the config*
92 | - when a relay gets the `ls` request
93 | - if enabled, get its swarm's peerbook's known peers and return their ids and multiaddrs
94 | - e.g `[{id: /ipfs/QmPeerId, addrs: ['ma1', 'ma2', 'ma3']}, ...]`
95 | - if disabled, respond with `na`
96 |
97 |
98 | ## Relay Implementation notes
99 |
100 | - ### Relay transport
101 | - Currently I've implemented the dialer and listener parts of the relay as a transport, meaning that it *tries* to implement the `interface-transport` interface as closely as possible. This seems to work pretty well and it's makes the dialer/listener parts really easy to plug in into the swarm. I think this is the cleanest solution.
102 |
103 | - ### `circuit-relay`
104 | - This is implemented as a separate piece (not a transport), and it can be enabled/disabled with a config. The transport listener however, will do the initial parsing of the incoming header and figure out weather it's a connection that's needs to be handled by the circuit-relay, or its a connection that is being relayed from a circuit-relay.
105 |
106 | ## Relay swarm integration
107 |
108 | - The relay transport is mounted explicitly by calling the `swarm.connection.relay(config.relay)` from libp2p
109 | - Swarm will register the dialer and listener using the swarm `transport.add` and `transport.listen` methods
110 |
111 | - ### Listener
112 | - the listener registers itself as a multistream handler on the `/ipfs/relay/circuit/1.0.0` proto
113 | - if `circuit-relay` is enabled, the listener will delegate connections to it if appropriate
114 | - when the listener receives a connection, it will read the multiaddr and determine if its a connection that needs to be relayed, or its a connection that is being relayed
115 |
116 | - ### Dialer
117 | - When the swarm attempts to dial to a peer, it will filter the protocols that the peer can be reached on
118 | - *The relay will be used in two cases*
119 | - If the peer has an explicit relay address that it can be reached on
120 | - no other transport is available
121 | - The relay will attempt to dial the peer over that relay
122 | - If no explicit relay address is provided
123 | - no other transport is available
124 | - A generic circuit address will be added to the peers multiaddr list
125 | - i.e. `/p2p-circuit/ipfs/QmDest`
126 | - If another transport is available, then use that instead of the relay
127 |
128 |
129 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Protocol Labs Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ⛔️ DEPRECATED: libp2p-circuit is now included in [js-libp2p](https://github.com/libp2p/js-libp2p)
2 | ======
3 |
4 | # js-libp2p-circuit
5 |
6 |
7 | [](http://protocol.ai)
8 | [](http://libp2p.io/)
9 | [](http://webchat.freenode.net/?channels=%23libp2p)
10 | [](https://discuss.libp2p.io)
11 | [](https://travis-ci.com/libp2p/js-libp2p-circuit)
12 | [](https://codecov.io/gh/libp2p/js-libp2p-circuit)
13 | [](https://david-dm.org/libp2p/js-libp2p-circuit)
14 | [](https://github.com/feross/standard)
15 |
16 | 
17 | 
18 |
19 | > Node.js implementation of the Circuit module that libp2p uses, which implements the [interface-connection](https://github.com/libp2p/interface-connection) interface for dial/listen.
20 |
21 | `libp2p-circuit` implements the circuit-relay mechanism that allows nodes that don't speak the same protocol to communicate using a third _relay_ node.
22 |
23 | **Note:** This module uses [pull-streams](https://pull-stream.github.io) for all stream based interfaces.
24 |
25 | ### Why?
26 |
27 | `circuit-relaying` uses additional nodes in order to transfer traffic between two otherwise unreachable nodes. This allows nodes that don't speak the same protocols or are running in limited environments, e.g. browsers and IoT devices, to communicate, which would otherwise be impossible given the fact that for example browsers don't have any socket support and as such cannot be directly dialed.
28 |
29 | The use of circuit-relaying is not limited to routing traffic between browser nodes, other uses include:
30 | - routing traffic between private nets and circumventing NAT layers
31 | - route mangling for better privacy (matreshka/shallot dialing).
32 |
33 | It's also possible to use it for clients that implement exotic transports such as devices that only have bluetooth radios to be reachable over bluetooth enabled relays and become full p2p nodes.
34 |
35 | ### libp2p-circuit and IPFS
36 |
37 | Prior to `libp2p-circuit` there was a rift in the IPFS network, were IPFS nodes could only access content from nodes that speak the same protocol, for example TCP only nodes could only dial to other TCP only nodes, same for any other protocol combination. In practice, this limitation was most visible in JS-IPFS browser nodes, since they can only dial out but not be dialed in over WebRTC or WebSockets, hence any content that the browser node held was not reachable by the rest of the network even through it was announced on the DHT. Non browser IPFS nodes would usually speak more than one protocol such as TCP, WebSockets and/or WebRTC, this made the problem less severe outside of the browser. `libp2p-circuit` solves this problem completely, as long as there are `relay nodes` capable of routing traffic between those nodes their content should be available to the rest of the IPFS network.
38 |
39 | ## Lead Maintainer
40 |
41 | [Jacob Heun](https://github.com/jacobheun)
42 |
43 | ## Table of Contents
44 |
45 | - [Install](#install)
46 | - [npm](#npm)
47 | - [Usage](#usage)
48 | - [Example](#example)
49 | - [This module uses `pull-streams`](#this-module-uses-pull-streams)
50 | - [Converting `pull-streams` to Node.js Streams](#converting-pull-streams-to-nodejs-streams)
51 | - [API](#api)
52 | - [Contribute](#contribute)
53 | - [License](#license)
54 |
55 | ## Install
56 |
57 | ### npm
58 |
59 | ```sh
60 | > npm i libp2p-circuit
61 | ```
62 |
63 | ## Usage
64 |
65 | ### Example
66 |
67 | #### Create dialer/listener
68 |
69 | ```js
70 | const Circuit = require('libp2p-circuit')
71 | const multiaddr = require('multiaddr')
72 | const pull = require('pull-stream')
73 |
74 | const mh1 = multiaddr('/p2p-circuit/ipfs/QmHash') // dial /ipfs/QmHash over any circuit
75 |
76 | const circuit = new Circuit(swarmInstance, options) // pass swarm instance and options
77 |
78 | const listener = circuit.createListener(mh1, (connection) => {
79 | console.log('new connection opened')
80 | pull(
81 | pull.values(['hello']),
82 | socket
83 | )
84 | })
85 |
86 | listener.listen(() => {
87 | console.log('listening')
88 |
89 | pull(
90 | circuit.dial(mh1),
91 | pull.log,
92 | pull.onEnd(() => {
93 | circuit.close()
94 | })
95 | )
96 | })
97 | ```
98 |
99 | Outputs:
100 |
101 | ```sh
102 | listening
103 | new connection opened
104 | hello
105 | ```
106 |
107 | #### Create `relay`
108 |
109 | ```js
110 | const Relay = require('libp2p-circuit').Relay
111 |
112 | const relay = new Relay(options)
113 |
114 | relay.mount(swarmInstance) // start relaying traffic
115 | ```
116 |
117 | ### This module uses `pull-streams`
118 |
119 | We expose a streaming interface based on `pull-streams`, rather then on the Node.js core streams implementation (aka Node.js streams). `pull-streams` offers us a better mechanism for error handling and flow control guarantees. If you would like to know more about why we did this, see the discussion at this [issue](https://github.com/ipfs/js-ipfs/issues/362).
120 |
121 | You can learn more about pull-streams at:
122 |
123 | - [The history of Node.js streams, nodebp April 2014](https://www.youtube.com/watch?v=g5ewQEuXjsQ)
124 | - [The history of streams, 2016](http://dominictarr.com/post/145135293917/history-of-streams)
125 | - [pull-streams, the simple streaming primitive](http://dominictarr.com/post/149248845122/pull-streams-pull-streams-are-a-very-simple)
126 | - [pull-streams documentation](https://pull-stream.github.io/)
127 |
128 | #### Converting `pull-streams` to Node.js Streams
129 |
130 | If you are a Node.js streams user, you can convert a pull-stream to a Node.js stream using the module [`pull-stream-to-stream`](https://github.com/dominictarr/pull-stream-to-stream), giving you an instance of a Node.js stream that is linked to the pull-stream. For example:
131 |
132 | ```js
133 | const pullToStream = require('pull-stream-to-stream')
134 |
135 | const nodeStreamInstance = pullToStream(pullStreamInstance)
136 | // nodeStreamInstance is an instance of a Node.js Stream
137 | ```
138 |
139 | To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.
140 |
141 | ## API
142 |
143 | [](https://github.com/libp2p/interface-transport)
144 |
145 | `libp2p-circuit` accepts Circuit addresses for both IPFS and non IPFS encapsulated addresses, i.e:
146 |
147 | `/p2p-circuit/ip4/127.0.0.1/tcp/4001/ipfs/QmHash`
148 |
149 | Both for dialing and listening.
150 |
151 | ### Implementation rational
152 |
153 | This module is not a transport, however it implements `interface-transport` interface in order to allow circuit to be plugged with `libp2p-swarm`. The rational behind it is that, `libp2p-circuit` has a dial and listen flow, which fits nicely with other transports, moreover, it requires the _raw_ connection to be encrypted and muxed just as a regular transport's connection does. All in all, `interface-transport` ended up being the correct level of abstraction for circuit, as well as allowed us to reuse existing integration points in `libp2p-swarm` and `libp2p` without adding any ad-hoc logic. All parts of `interface-transport` are used, including `.getAddr` which returns a list of `/p2p-circuit` addresses that circuit is currently listening.
154 |
155 | ```
156 | libp2p libp2p-circuit (transport)
157 | +-------------------------------------------------+ +--------------------------+
158 | | +---------------------------------+ | | |
159 | | | | | | +------------------+ |
160 | | | | | circuit-relay listens for the HOP | | | |
161 | | | libp2p-swarm <------------------------------------------------| circuit-relay | |
162 | | | | | message to handle incomming relay | | | |
163 | | | | | requests from other nodes | +------------------+ |
164 | | +---------------------------------+ | | |
165 | | ^ ^ ^ ^ ^ ^ | | +------------------+ |
166 | | | | | | | | | | | +-------------+ | |
167 | | | | | | | | | dialer uses libp2p-swarm to dial | | | | | |
168 | | | | | +----------------------------------------------------------------------> dialer | | |
169 | | | | transports | | to a circuit-relay node using the | | | | | |
170 | | | | | | | | HOP message | | +-------------+ | |
171 | | | | | | | | | | | |
172 | | v v | v v | | | | |
173 | |+------------------|----------------------------+| | | +-------------+ | |
174 | || | | | | || | | | | | |
175 | ||libp2p-tcp |libp2p-ws | .... |libp2p-circuit || listener handles STOP messages from| | | listener | | |
176 | || | +--------------------------------------------------------------------------> | | |
177 | || | | |plugs in just || circuit-relay nodes | | +-------------+ | |
178 | || | | |as any other || | | | |
179 | || | | |transport || | +------------------+ |
180 | |+-----------------------------------------------+| | |
181 | +-------------------------------------------------+ +--------------------------+
182 | ```
183 |
184 | ## Contribute
185 |
186 | Contributions are welcome! The libp2p implementation in JavaScript is a work in progress. As such, there's a few things you can do right now to help out:
187 |
188 | - [Check out the existing issues](//github.com/libp2p/js-libp2p-circuit/issues).
189 | - **Perform code reviews**.
190 | - **Add tests**. There can never be enough tests.
191 |
192 | Please be aware that all interactions related to libp2p are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
193 |
194 | Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
195 |
196 | ## License
197 |
198 | [MIT](LICENSE) © 2017 Protocol Labs
199 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "libp2p-circuit",
3 | "version": "0.3.7",
4 | "description": "JavaScript implementation of circuit/switch relaying",
5 | "leadMaintainer": "Jacob Heun ",
6 | "main": "src/index.js",
7 | "files": [
8 | "src",
9 | "dist"
10 | ],
11 | "scripts": {
12 | "lint": "aegir lint",
13 | "build": "aegir build",
14 | "test": "aegir test --target node --target browser --no-parallel",
15 | "test:node": "aegir test --target node",
16 | "test:browser": "aegir test --target browser",
17 | "release": "aegir release --target node --target browser",
18 | "release-minor": "aegir release --type minor --target node --target browser",
19 | "release-major": "aegir release --type major --target node --target browser",
20 | "coverage": "aegir coverage",
21 | "coverage-publish": "aegir coverage --provider coveralls"
22 | },
23 | "pre-push": [
24 | "lint"
25 | ],
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/libp2p/js-libp2p-circuit.git"
29 | },
30 | "keywords": [
31 | "IPFS"
32 | ],
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/libp2p/js-libp2p-circuit/issues"
36 | },
37 | "homepage": "https://github.com/libp2p/js-libp2p-circuit#readme",
38 | "contributors": [
39 | "David Dias ",
40 | "Dmitriy Ryajov ",
41 | "Friedel Ziegelmayer ",
42 | "Hugo Dias ",
43 | "Jacob Heun ",
44 | "Jacob Heun ",
45 | "Maciej Krüger ",
46 | "Oli Evans ",
47 | "Pedro Teixeira ",
48 | "Vasco Santos ",
49 | "Victor Bjelkholm ",
50 | "Yusef Napora ",
51 | "dirkmc "
52 | ],
53 | "devDependencies": {
54 | "aegir": "^18.2.2",
55 | "chai": "^4.2.0",
56 | "dirty-chai": "^2.0.1",
57 | "libp2p": "~0.25.0",
58 | "libp2p-secio": "~0.11.1",
59 | "pull-protocol-buffers": "~0.1.2",
60 | "sinon": "^7.3.1"
61 | },
62 | "dependencies": {
63 | "async": "^2.6.2",
64 | "debug": "^4.1.1",
65 | "interface-connection": "~0.3.3",
66 | "mafmt": "^6.0.7",
67 | "multiaddr": "^6.0.6",
68 | "once": "^1.4.0",
69 | "peer-id": "~0.12.2",
70 | "peer-info": "~0.15.1",
71 | "protons": "^1.0.1",
72 | "pull-handshake": "^1.1.4",
73 | "pull-length-prefixed": "^1.3.2",
74 | "pull-pair": "^1.1.0",
75 | "pull-stream": "^3.6.9"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/circuit.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const mafmt = require('mafmt')
4 | const multiaddr = require('multiaddr')
5 |
6 | const CircuitDialer = require('./circuit/dialer')
7 | const utilsFactory = require('./circuit/utils')
8 |
9 | const debug = require('debug')
10 | const log = debug('libp2p:circuit:transportdialer')
11 | log.err = debug('libp2p:circuit:error:transportdialer')
12 |
13 | const createListener = require('./listener')
14 |
15 | class Circuit {
16 | static get tag () {
17 | return 'Circuit'
18 | }
19 |
20 | /**
21 | * Creates an instance of Dialer.
22 | *
23 | * @param {Swarm} swarm - the swarm
24 | * @param {any} options - config options
25 | *
26 | * @memberOf Dialer
27 | */
28 | constructor (swarm, options) {
29 | this.options = options || {}
30 |
31 | this.swarm = swarm
32 | this.dialer = null
33 | this.utils = utilsFactory(swarm)
34 | this.peerInfo = this.swarm._peerInfo
35 | this.relays = this.filter(this.peerInfo.multiaddrs.toArray())
36 |
37 | // if no explicit relays, add a default relay addr
38 | if (this.relays.length === 0) {
39 | this.peerInfo
40 | .multiaddrs
41 | .add(`/p2p-circuit/ipfs/${this.peerInfo.id.toB58String()}`)
42 | }
43 |
44 | this.dialer = new CircuitDialer(swarm, options)
45 |
46 | this.swarm.on('peer-mux-established', (peerInfo) => {
47 | this.dialer.canHop(peerInfo)
48 | })
49 | this.swarm.on('peer-mux-closed', (peerInfo) => {
50 | this.dialer.relayPeers.delete(peerInfo.id.toB58String())
51 | })
52 | }
53 |
54 | /**
55 | * Dial the relays in the Addresses.Swarm config
56 | *
57 | * @param {Array} relays
58 | * @return {void}
59 | */
60 | _dialSwarmRelays () {
61 | // if we have relay addresses in swarm config, then dial those relays
62 | this.relays.forEach((relay) => {
63 | let relaySegments = relay
64 | .toString()
65 | .split('/p2p-circuit')
66 | .filter(segment => segment.length)
67 |
68 | relaySegments.forEach((relaySegment) => {
69 | const ma = this.utils.peerInfoFromMa(multiaddr(relaySegment))
70 | this.dialer._dialRelay(ma)
71 | })
72 | })
73 | }
74 |
75 | /**
76 | * Dial a peer over a relay
77 | *
78 | * @param {multiaddr} ma - the multiaddr of the peer to dial
79 | * @param {Object} options - dial options
80 | * @param {Function} cb - a callback called once dialed
81 | * @returns {Connection} - the connection
82 | *
83 | * @memberOf Dialer
84 | */
85 | dial (ma, options, cb) {
86 | return this.dialer.dial(ma, options, cb)
87 | }
88 |
89 | /**
90 | * Create a listener
91 | *
92 | * @param {any} options
93 | * @param {Function} handler
94 | * @return {listener}
95 | */
96 | createListener (options, handler) {
97 | if (typeof options === 'function') {
98 | handler = options
99 | options = this.options || {}
100 | }
101 |
102 | const listener = createListener(this.swarm, options, handler)
103 | listener.on('listen', this._dialSwarmRelays.bind(this))
104 | return listener
105 | }
106 |
107 | /**
108 | * Filter check for all multiaddresses
109 | * that this transport can dial on
110 | *
111 | * @param {any} multiaddrs
112 | * @returns {Array}
113 | *
114 | * @memberOf Dialer
115 | */
116 | filter (multiaddrs) {
117 | if (!Array.isArray(multiaddrs)) {
118 | multiaddrs = [multiaddrs]
119 | }
120 | return multiaddrs.filter((ma) => {
121 | return mafmt.Circuit.matches(ma)
122 | })
123 | }
124 | }
125 |
126 | module.exports = Circuit
127 |
--------------------------------------------------------------------------------
/src/circuit/dialer.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const once = require('once')
4 | const PeerId = require('peer-id')
5 | const waterfall = require('async/waterfall')
6 | const setImmediate = require('async/setImmediate')
7 | const multiaddr = require('multiaddr')
8 |
9 | const Connection = require('interface-connection').Connection
10 |
11 | const utilsFactory = require('./utils')
12 | const StreamHandler = require('./stream-handler')
13 |
14 | const debug = require('debug')
15 | const log = debug('libp2p:circuit:dialer')
16 | log.err = debug('libp2p:circuit:error:dialer')
17 |
18 | const multicodec = require('../multicodec')
19 | const proto = require('../protocol')
20 |
21 | class Dialer {
22 | /**
23 | * Creates an instance of Dialer.
24 | * @param {Swarm} swarm - the swarm
25 | * @param {any} options - config options
26 | *
27 | * @memberOf Dialer
28 | */
29 | constructor (swarm, options) {
30 | this.swarm = swarm
31 | this.relayPeers = new Map()
32 | this.relayConns = new Map()
33 | this.options = options
34 | this.utils = utilsFactory(swarm)
35 | }
36 |
37 | /**
38 | * Helper that returns a relay connection
39 | *
40 | * @param {*} relay
41 | * @param {*} callback
42 | * @returns {Function} - callback
43 | */
44 | _dialRelayHelper (relay, callback) {
45 | if (this.relayConns.has(relay.id.toB58String())) {
46 | return callback(null, this.relayConns.get(relay.id.toB58String()))
47 | }
48 |
49 | return this._dialRelay(relay, callback)
50 | }
51 |
52 | /**
53 | * Dial a peer over a relay
54 | *
55 | * @param {multiaddr} ma - the multiaddr of the peer to dial
56 | * @param {Function} cb - a callback called once dialed
57 | * @returns {Connection} - the connection
58 | *
59 | */
60 | dial (ma, cb) {
61 | cb = cb || (() => { })
62 | const strMa = ma.toString()
63 | if (!strMa.includes('/p2p-circuit')) {
64 | log.err('invalid circuit address')
65 | return cb(new Error('invalid circuit address'))
66 | }
67 |
68 | const addr = strMa.split('p2p-circuit') // extract relay address if any
69 | const relay = addr[0] === '/' ? null : multiaddr(addr[0])
70 | const peer = multiaddr(addr[1] || addr[0])
71 |
72 | const dstConn = new Connection()
73 | setImmediate(
74 | this._dialPeer.bind(this),
75 | peer,
76 | relay,
77 | (err, conn) => {
78 | if (err) {
79 | log.err(err)
80 | return cb(err)
81 | }
82 |
83 | dstConn.setInnerConn(conn)
84 | cb(null, dstConn)
85 | })
86 |
87 | return dstConn
88 | }
89 |
90 | /**
91 | * Does the peer support the HOP protocol
92 | *
93 | * @param {PeerInfo} peer
94 | * @param {Function} callback
95 | * @returns {void}
96 | */
97 | canHop (peer, callback) {
98 | callback = once(callback || (() => { }))
99 |
100 | this._dialRelayHelper(peer, (err, conn) => {
101 | if (err) {
102 | return callback(err)
103 | }
104 |
105 | const sh = new StreamHandler(conn)
106 | waterfall([
107 | (cb) => sh.write(proto.CircuitRelay.encode({
108 | type: proto.CircuitRelay.Type.CAN_HOP
109 | }), cb),
110 | (cb) => sh.read(cb)
111 | ], (err, msg) => {
112 | if (err) {
113 | return callback(err)
114 | }
115 | const response = proto.CircuitRelay.decode(msg)
116 |
117 | if (response.code !== proto.CircuitRelay.Status.SUCCESS) {
118 | const err = new Error(`HOP not supported, skipping - ${this.utils.getB58String(peer)}`)
119 | log(err)
120 | return callback(err)
121 | }
122 |
123 | log('HOP supported adding as relay - %s', this.utils.getB58String(peer))
124 | this.relayPeers.set(this.utils.getB58String(peer), peer)
125 | sh.close()
126 | callback()
127 | })
128 | })
129 | }
130 |
131 | /**
132 | * Dial the destination peer over a relay
133 | *
134 | * @param {multiaddr} dstMa
135 | * @param {Connection|PeerInfo} relay
136 | * @param {Function} cb
137 | * @return {Function|void}
138 | * @private
139 | */
140 | _dialPeer (dstMa, relay, cb) {
141 | if (typeof relay === 'function') {
142 | cb = relay
143 | relay = null
144 | }
145 |
146 | if (!cb) {
147 | cb = () => {}
148 | }
149 |
150 | dstMa = multiaddr(dstMa)
151 | // if no relay provided, dial on all available relays until one succeeds
152 | if (!relay) {
153 | const relays = Array.from(this.relayPeers.values())
154 | let next = (nextRelay) => {
155 | if (!nextRelay) {
156 | let err = `no relay peers were found or all relays failed to dial`
157 | log.err(err)
158 | return cb(err)
159 | }
160 |
161 | return this._negotiateRelay(
162 | nextRelay,
163 | dstMa,
164 | (err, conn) => {
165 | if (err) {
166 | log.err(err)
167 | return next(relays.shift())
168 | }
169 | cb(null, conn)
170 | })
171 | }
172 | next(relays.shift())
173 | } else {
174 | return this._negotiateRelay(
175 | relay,
176 | dstMa,
177 | (err, conn) => {
178 | if (err) {
179 | log.err('An error has occurred negotiating the relay connection', err)
180 | return cb(err)
181 | }
182 |
183 | return cb(null, conn)
184 | })
185 | }
186 | }
187 |
188 | /**
189 | * Negotiate the relay connection
190 | *
191 | * @param {Multiaddr|PeerInfo|Connection} relay - the Connection or PeerInfo of the relay
192 | * @param {multiaddr} dstMa - the multiaddr of the peer to relay the connection for
193 | * @param {Function} callback - a callback which gets the negotiated relay connection
194 | * @returns {void}
195 | * @private
196 | *
197 | * @memberOf Dialer
198 | */
199 | _negotiateRelay (relay, dstMa, callback) {
200 | dstMa = multiaddr(dstMa)
201 | relay = this.utils.peerInfoFromMa(relay)
202 | const srcMas = this.swarm._peerInfo.multiaddrs.toArray()
203 | this._dialRelayHelper(relay, (err, conn) => {
204 | if (err) {
205 | log.err(err)
206 | return callback(err)
207 | }
208 | let sh = new StreamHandler(conn)
209 | waterfall([
210 | (cb) => {
211 | log('negotiating relay for peer %s', dstMa.getPeerId())
212 | let dstPeerId
213 | try {
214 | dstPeerId = PeerId.createFromB58String(dstMa.getPeerId()).id
215 | } catch (err) {
216 | return cb(err)
217 | }
218 | sh.write(
219 | proto.CircuitRelay.encode({
220 | type: proto.CircuitRelay.Type.HOP,
221 | srcPeer: {
222 | id: this.swarm._peerInfo.id.id,
223 | addrs: srcMas.map((addr) => addr.buffer)
224 | },
225 | dstPeer: {
226 | id: dstPeerId,
227 | addrs: [dstMa.buffer]
228 | }
229 | }), cb)
230 | },
231 | (cb) => sh.read(cb)
232 | ], (err, msg) => {
233 | if (err) {
234 | return callback(err)
235 | }
236 | const message = proto.CircuitRelay.decode(msg)
237 | if (message.type !== proto.CircuitRelay.Type.STATUS) {
238 | return callback(new Error(`Got invalid message type - ` +
239 | `expected ${proto.CircuitRelay.Type.STATUS} got ${message.type}`))
240 | }
241 |
242 | if (message.code !== proto.CircuitRelay.Status.SUCCESS) {
243 | return callback(new Error(`Got ${message.code} error code trying to dial over relay`))
244 | }
245 |
246 | callback(null, new Connection(sh.rest()))
247 | })
248 | })
249 | }
250 |
251 | /**
252 | * Dial a relay peer by its PeerInfo
253 | *
254 | * @param {PeerInfo} peer - the PeerInfo of the relay peer
255 | * @param {Function} cb - a callback with the connection to the relay peer
256 | * @returns {void}
257 | * @private
258 | */
259 | _dialRelay (peer, cb) {
260 | cb = once(cb || (() => { }))
261 |
262 | this.swarm.dial(
263 | peer,
264 | multicodec.relay,
265 | once((err, conn) => {
266 | if (err) {
267 | log.err(err)
268 | return cb(err)
269 | }
270 | cb(null, conn)
271 | }))
272 | }
273 | }
274 |
275 | module.exports = Dialer
276 |
--------------------------------------------------------------------------------
/src/circuit/hop.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const pull = require('pull-stream/pull')
4 | const debug = require('debug')
5 | const PeerInfo = require('peer-info')
6 | const PeerId = require('peer-id')
7 | const EE = require('events').EventEmitter
8 | const once = require('once')
9 | const utilsFactory = require('./utils')
10 | const StreamHandler = require('./stream-handler')
11 | const proto = require('../protocol').CircuitRelay
12 | const multiaddr = require('multiaddr')
13 | const series = require('async/series')
14 | const waterfall = require('async/waterfall')
15 | const setImmediate = require('async/setImmediate')
16 |
17 | const multicodec = require('./../multicodec')
18 |
19 | const log = debug('libp2p:circuit:relay')
20 | log.err = debug('libp2p:circuit:error:relay')
21 |
22 | class Hop extends EE {
23 | /**
24 | * Construct a Circuit object
25 | *
26 | * This class will handle incoming circuit connections and
27 | * either start a relay or hand the relayed connection to
28 | * the swarm
29 | *
30 | * @param {Swarm} swarm
31 | * @param {Object} options
32 | */
33 | constructor (swarm, options) {
34 | super()
35 | this.swarm = swarm
36 | this.peerInfo = this.swarm._peerInfo
37 | this.utils = utilsFactory(swarm)
38 | this.config = options || { active: false, enabled: false }
39 | this.active = this.config.active
40 | }
41 |
42 | /**
43 | * Handle the relay message
44 | *
45 | * @param {CircuitRelay} message
46 | * @param {StreamHandler} sh
47 | * @returns {*}
48 | */
49 | handle (message, sh) {
50 | if (!this.config.enabled) {
51 | this.utils.writeResponse(
52 | sh,
53 | proto.Status.HOP_CANT_SPEAK_RELAY)
54 | return sh.close()
55 | }
56 |
57 | // check if message is `CAN_HOP`
58 | if (message.type === proto.Type.CAN_HOP) {
59 | this.utils.writeResponse(
60 | sh,
61 | proto.Status.SUCCESS)
62 | return sh.close()
63 | }
64 |
65 | // This is a relay request - validate and create a circuit
66 | let srcPeerId = null
67 | let dstPeerId = null
68 | try {
69 | srcPeerId = PeerId.createFromBytes(message.srcPeer.id).toB58String()
70 | dstPeerId = PeerId.createFromBytes(message.dstPeer.id).toB58String()
71 | } catch (err) {
72 | log.err(err)
73 |
74 | if (!srcPeerId) {
75 | this.utils.writeResponse(
76 | sh,
77 | proto.Status.HOP_SRC_MULTIADDR_INVALID)
78 | return sh.close()
79 | }
80 |
81 | if (!dstPeerId) {
82 | this.utils.writeResponse(
83 | sh,
84 | proto.Status.HOP_DST_MULTIADDR_INVALID)
85 | return sh.close()
86 | }
87 | }
88 |
89 | if (srcPeerId === dstPeerId) {
90 | this.utils.writeResponse(
91 | sh,
92 | proto.Status.HOP_CANT_RELAY_TO_SELF)
93 | return sh.close()
94 | }
95 |
96 | if (!message.dstPeer.addrs.length) {
97 | // TODO: use encapsulate here
98 | const addr = multiaddr(`/p2p-circuit/ipfs/${dstPeerId}`).buffer
99 | message.dstPeer.addrs.push(addr)
100 | }
101 |
102 | log('trying to establish a circuit: %s <-> %s', srcPeerId, dstPeerId)
103 | const noPeer = () => {
104 | // log.err(err)
105 | this.utils.writeResponse(
106 | sh,
107 | proto.Status.HOP_NO_CONN_TO_DST)
108 | return sh.close()
109 | }
110 |
111 | const isConnected = (cb) => {
112 | let dstPeer
113 | try {
114 | dstPeer = this.swarm._peerBook.get(dstPeerId)
115 | if (!dstPeer.isConnected() && !this.active) {
116 | const err = new Error(`No Connection to peer ${dstPeerId}`)
117 | noPeer(err)
118 | return cb(err)
119 | }
120 | } catch (err) {
121 | if (!this.active) {
122 | noPeer(err)
123 | return cb(err)
124 | }
125 | }
126 | cb()
127 | }
128 |
129 | series([
130 | (cb) => this.utils.validateAddrs(message, sh, proto.Type.HOP, cb),
131 | (cb) => isConnected(cb),
132 | (cb) => this._circuit(sh, message, cb)
133 | ], (err) => {
134 | if (err) {
135 | log.err(err)
136 | sh.close()
137 | return setImmediate(() => this.emit('circuit:error', err))
138 | }
139 | setImmediate(() => this.emit('circuit:success'))
140 | })
141 | }
142 |
143 | /**
144 | * Connect to STOP
145 | *
146 | * @param {PeerInfo} peer
147 | * @param {StreamHandler} srcSh
148 | * @param {function} callback
149 | * @returns {void}
150 | */
151 | _connectToStop (peer, srcSh, callback) {
152 | this._dialPeer(peer, (err, dstConn) => {
153 | if (err) {
154 | this.utils.writeResponse(
155 | srcSh,
156 | proto.Status.HOP_CANT_DIAL_DST)
157 | log.err(err)
158 | return callback(err)
159 | }
160 |
161 | return this.utils.writeResponse(
162 | srcSh,
163 | proto.Status.SUCCESS,
164 | (err) => {
165 | if (err) {
166 | log.err(err)
167 | return callback(err)
168 | }
169 | return callback(null, dstConn)
170 | })
171 | })
172 | }
173 |
174 | /**
175 | * Negotiate STOP
176 | *
177 | * @param {StreamHandler} dstSh
178 | * @param {StreamHandler} srcSh
179 | * @param {CircuitRelay} message
180 | * @param {function} callback
181 | * @returns {void}
182 | */
183 | _negotiateStop (dstSh, srcSh, message, callback) {
184 | const stopMsg = Object.assign({}, message, {
185 | type: proto.Type.STOP // change the message type
186 | })
187 | dstSh.write(proto.encode(stopMsg),
188 | (err) => {
189 | if (err) {
190 | this.utils.writeResponse(
191 | srcSh,
192 | proto.Status.HOP_CANT_OPEN_DST_STREAM)
193 | log.err(err)
194 | return callback(err)
195 | }
196 |
197 | // read response from STOP
198 | dstSh.read((err, msg) => {
199 | if (err) {
200 | log.err(err)
201 | return callback(err)
202 | }
203 |
204 | const message = proto.decode(msg)
205 | if (message.code !== proto.Status.SUCCESS) {
206 | return callback(new Error(`Unable to create circuit!`))
207 | }
208 |
209 | return callback(null, msg)
210 | })
211 | })
212 | }
213 |
214 | /**
215 | * Attempt to make a circuit from A <-> R <-> B where R is this relay
216 | *
217 | * @param {StreamHandler} srcSh - the source stream handler
218 | * @param {CircuitRelay} message - the message with the src and dst entries
219 | * @param {Function} callback - callback to signal success or failure
220 | * @returns {void}
221 | * @private
222 | */
223 | _circuit (srcSh, message, callback) {
224 | let dstSh = null
225 | waterfall([
226 | (cb) => this._connectToStop(message.dstPeer, srcSh, cb),
227 | (_dstConn, cb) => {
228 | dstSh = new StreamHandler(_dstConn)
229 | this._negotiateStop(dstSh, srcSh, message, cb)
230 | }
231 | ], (err) => {
232 | if (err) {
233 | // close/end the source stream if there was an error
234 | if (srcSh) {
235 | srcSh.close()
236 | }
237 |
238 | if (dstSh) {
239 | dstSh.close()
240 | }
241 | return callback(err)
242 | }
243 |
244 | const src = srcSh.rest()
245 | const dst = dstSh.rest()
246 |
247 | const srcIdStr = PeerId.createFromBytes(message.srcPeer.id).toB58String()
248 | const dstIdStr = PeerId.createFromBytes(message.dstPeer.id).toB58String()
249 |
250 | // circuit the src and dst streams
251 | pull(
252 | src,
253 | dst,
254 | src
255 | )
256 | log('circuit %s <-> %s established', srcIdStr, dstIdStr)
257 | callback()
258 | })
259 | }
260 |
261 | /**
262 | * Dial the dest peer and create a circuit
263 | *
264 | * @param {Multiaddr} dstPeer
265 | * @param {Function} callback
266 | * @returns {void}
267 | * @private
268 | */
269 | _dialPeer (dstPeer, callback) {
270 | const peerInfo = new PeerInfo(PeerId.createFromBytes(dstPeer.id))
271 | dstPeer.addrs.forEach((a) => peerInfo.multiaddrs.add(a))
272 | this.swarm.dial(peerInfo, multicodec.relay, once((err, conn) => {
273 | if (err) {
274 | log.err(err)
275 | return callback(err)
276 | }
277 |
278 | callback(null, conn)
279 | }))
280 | }
281 | }
282 |
283 | module.exports = Hop
284 |
--------------------------------------------------------------------------------
/src/circuit/stop.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const setImmediate = require('async/setImmediate')
4 |
5 | const EE = require('events').EventEmitter
6 | const Connection = require('interface-connection').Connection
7 | const utilsFactory = require('./utils')
8 | const PeerInfo = require('peer-info')
9 | const proto = require('../protocol').CircuitRelay
10 | const series = require('async/series')
11 |
12 | const debug = require('debug')
13 |
14 | const log = debug('libp2p:circuit:stop')
15 | log.err = debug('libp2p:circuit:error:stop')
16 |
17 | class Stop extends EE {
18 | constructor (swarm) {
19 | super()
20 | this.swarm = swarm
21 | this.utils = utilsFactory(swarm)
22 | }
23 |
24 | /**
25 | * Handle the incoming STOP message
26 | *
27 | * @param {{}} msg - the parsed protobuf message
28 | * @param {StreamHandler} sh - the stream handler wrapped connection
29 | * @param {Function} callback - callback
30 | * @returns {undefined}
31 | */
32 | handle (msg, sh, callback) {
33 | callback = callback || (() => {})
34 |
35 | series([
36 | (cb) => this.utils.validateAddrs(msg, sh, proto.Type.STOP, cb),
37 | (cb) => this.utils.writeResponse(sh, proto.Status.Success, cb)
38 | ], (err) => {
39 | if (err) {
40 | // we don't return the error here,
41 | // since multistream select don't expect one
42 | callback()
43 | return log(err)
44 | }
45 |
46 | const peerInfo = new PeerInfo(this.utils.peerIdFromId(msg.srcPeer.id))
47 | msg.srcPeer.addrs.forEach((addr) => peerInfo.multiaddrs.add(addr))
48 | const newConn = new Connection(sh.rest())
49 | newConn.setPeerInfo(peerInfo)
50 | setImmediate(() => this.emit('connection', newConn))
51 | callback(newConn)
52 | })
53 | }
54 | }
55 |
56 | module.exports = Stop
57 |
--------------------------------------------------------------------------------
/src/circuit/stream-handler.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const values = require('pull-stream/sources/values')
4 | const collect = require('pull-stream/sinks/collect')
5 | const empty = require('pull-stream/sources/empty')
6 | const pull = require('pull-stream/pull')
7 | const lp = require('pull-length-prefixed')
8 | const handshake = require('pull-handshake')
9 |
10 | const debug = require('debug')
11 | const log = debug('libp2p:circuit:stream-handler')
12 | log.err = debug('libp2p:circuit:error:stream-handler')
13 |
14 | class StreamHandler {
15 | /**
16 | * Create a stream handler for connection
17 | *
18 | * @param {Connection} conn - connection to read/write
19 | * @param {Function|undefined} cb - handshake callback called on error
20 | * @param {Number} timeout - handshake timeout
21 | * @param {Number} maxLength - max bytes length of message
22 | */
23 | constructor (conn, cb, timeout, maxLength) {
24 | this.conn = conn
25 | this.stream = null
26 | this.shake = null
27 | this.timeout = cb || 1000 * 60
28 | this.maxLength = maxLength || 4096
29 |
30 | if (typeof cb === 'function') {
31 | this.timeout = timeout || 1000 * 60
32 | }
33 |
34 | this.stream = handshake({ timeout: this.timeout }, cb)
35 | this.shake = this.stream.handshake
36 |
37 | pull(this.stream, conn, this.stream)
38 | }
39 |
40 | isValid () {
41 | return this.conn && this.shake && this.stream
42 | }
43 |
44 | /**
45 | * Read and decode message
46 | *
47 | * @param {Function} cb
48 | * @returns {void|Function}
49 | */
50 | read (cb) {
51 | if (!this.isValid()) {
52 | return cb(new Error(`handler is not in a valid state`))
53 | }
54 |
55 | lp.decodeFromReader(
56 | this.shake,
57 | { maxLength: this.maxLength },
58 | (err, msg) => {
59 | if (err) {
60 | log.err(err)
61 | // this.shake.abort(err)
62 | return cb(err)
63 | }
64 |
65 | return cb(null, msg)
66 | })
67 | }
68 |
69 | /**
70 | * Encode and write array of buffers
71 | *
72 | * @param {Buffer[]} msg
73 | * @param {Function} [cb]
74 | * @returns {Function}
75 | */
76 | write (msg, cb) {
77 | cb = cb || (() => {})
78 |
79 | if (!this.isValid()) {
80 | return cb(new Error(`handler is not in a valid state`))
81 | }
82 |
83 | pull(
84 | values([msg]),
85 | lp.encode(),
86 | collect((err, encoded) => {
87 | if (err) {
88 | log.err(err)
89 | this.shake.abort(err)
90 | return cb(err)
91 | }
92 |
93 | encoded.forEach((e) => this.shake.write(e))
94 | cb()
95 | })
96 | )
97 | }
98 |
99 | /**
100 | * Get the raw Connection
101 | *
102 | * @returns {null|Connection|*}
103 | */
104 | getRawConn () {
105 | return this.conn
106 | }
107 |
108 | /**
109 | * Return the handshake rest stream and invalidate handler
110 | *
111 | * @return {*|{source, sink}}
112 | */
113 | rest () {
114 | const rest = this.shake.rest()
115 |
116 | this.conn = null
117 | this.stream = null
118 | this.shake = null
119 | return rest
120 | }
121 |
122 | /**
123 | * Close the stream
124 | *
125 | * @returns {undefined}
126 | */
127 | close () {
128 | if (!this.isValid()) {
129 | return
130 | }
131 |
132 | // close stream
133 | pull(
134 | empty(),
135 | this.rest()
136 | )
137 | }
138 | }
139 |
140 | module.exports = StreamHandler
141 |
--------------------------------------------------------------------------------
/src/circuit/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const multiaddr = require('multiaddr')
4 | const PeerInfo = require('peer-info')
5 | const PeerId = require('peer-id')
6 | const proto = require('../protocol')
7 |
8 | module.exports = function (swarm) {
9 | /**
10 | * Get b58 string from multiaddr or peerinfo
11 | *
12 | * @param {Multiaddr|PeerInfo} peer
13 | * @return {*}
14 | */
15 | function getB58String (peer) {
16 | let b58Id = null
17 | if (multiaddr.isMultiaddr(peer)) {
18 | const relayMa = multiaddr(peer)
19 | b58Id = relayMa.getPeerId()
20 | } else if (PeerInfo.isPeerInfo(peer)) {
21 | b58Id = peer.id.toB58String()
22 | }
23 |
24 | return b58Id
25 | }
26 |
27 | /**
28 | * Helper to make a peer info from a multiaddrs
29 | *
30 | * @param {Multiaddr|PeerInfo|PeerId} ma
31 | * @param {Swarm} swarm
32 | * @return {PeerInfo}
33 | * @private
34 | */
35 | // TODO: this is ripped off of libp2p, should probably be a generally available util function
36 | function peerInfoFromMa (peer) {
37 | let p
38 | // PeerInfo
39 | if (PeerInfo.isPeerInfo(peer)) {
40 | p = peer
41 | // Multiaddr instance (not string)
42 | } else if (multiaddr.isMultiaddr(peer)) {
43 | const peerIdB58Str = peer.getPeerId()
44 | try {
45 | p = swarm._peerBook.get(peerIdB58Str)
46 | } catch (err) {
47 | p = new PeerInfo(PeerId.createFromB58String(peerIdB58Str))
48 | }
49 | p.multiaddrs.add(peer)
50 | // PeerId
51 | } else if (PeerId.isPeerId(peer)) {
52 | const peerIdB58Str = peer.toB58String()
53 | p = swarm._peerBook.has(peerIdB58Str) ? swarm._peerBook.get(peerIdB58Str) : peer
54 | }
55 |
56 | return p
57 | }
58 |
59 | /**
60 | * Checks if peer has an existing connection
61 | *
62 | * @param {String} peerId
63 | * @param {Swarm} swarm
64 | * @return {Boolean}
65 | */
66 | function isPeerConnected (peerId) {
67 | return swarm.muxedConns[peerId] || swarm.conns[peerId]
68 | }
69 |
70 | /**
71 | * Write a response
72 | *
73 | * @param {StreamHandler} streamHandler
74 | * @param {CircuitRelay.Status} status
75 | * @param {Function} cb
76 | * @returns {*}
77 | */
78 | function writeResponse (streamHandler, status, cb) {
79 | cb = cb || (() => {})
80 | streamHandler.write(proto.CircuitRelay.encode({
81 | type: proto.CircuitRelay.Type.STATUS,
82 | code: status
83 | }))
84 | return cb()
85 | }
86 |
87 | /**
88 | * Validate incomming HOP/STOP message
89 | *
90 | * @param {CircuitRelay} msg
91 | * @param {StreamHandler} streamHandler
92 | * @param {CircuitRelay.Type} type
93 | * @returns {*}
94 | * @param {Function} cb
95 | */
96 | function validateAddrs (msg, streamHandler, type, cb) {
97 | try {
98 | msg.dstPeer.addrs.forEach((addr) => {
99 | return multiaddr(addr)
100 | })
101 | } catch (err) {
102 | writeResponse(streamHandler, type === proto.CircuitRelay.Type.HOP
103 | ? proto.CircuitRelay.Status.HOP_DST_MULTIADDR_INVALID
104 | : proto.CircuitRelay.Status.STOP_DST_MULTIADDR_INVALID)
105 | return cb(err)
106 | }
107 |
108 | try {
109 | msg.srcPeer.addrs.forEach((addr) => {
110 | return multiaddr(addr)
111 | })
112 | } catch (err) {
113 | writeResponse(streamHandler, type === proto.CircuitRelay.Type.HOP
114 | ? proto.CircuitRelay.Status.HOP_SRC_MULTIADDR_INVALID
115 | : proto.CircuitRelay.Status.STOP_SRC_MULTIADDR_INVALID)
116 | return cb(err)
117 | }
118 |
119 | return cb(null)
120 | }
121 |
122 | function peerIdFromId (id) {
123 | if (typeof id === 'string') {
124 | return PeerId.createFromB58String(id)
125 | }
126 |
127 | return PeerId.createFromBytes(id)
128 | }
129 |
130 | return {
131 | getB58String,
132 | peerInfoFromMa,
133 | isPeerConnected,
134 | validateAddrs,
135 | writeResponse,
136 | peerIdFromId
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = require('./circuit')
4 |
--------------------------------------------------------------------------------
/src/listener.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const setImmediate = require('async/setImmediate')
4 |
5 | const multicodec = require('./multicodec')
6 | const EE = require('events').EventEmitter
7 | const multiaddr = require('multiaddr')
8 | const mafmt = require('mafmt')
9 | const Stop = require('./circuit/stop')
10 | const Hop = require('./circuit/hop')
11 | const proto = require('./protocol')
12 | const utilsFactory = require('./circuit/utils')
13 |
14 | const StreamHandler = require('./circuit/stream-handler')
15 |
16 | const debug = require('debug')
17 |
18 | const log = debug('libp2p:circuit:listener')
19 | log.err = debug('libp2p:circuit:error:listener')
20 |
21 | module.exports = (swarm, options, connHandler) => {
22 | const listener = new EE()
23 | const utils = utilsFactory(swarm)
24 |
25 | listener.stopHandler = new Stop(swarm)
26 | listener.stopHandler.on('connection', (conn) => listener.emit('connection', conn))
27 | listener.hopHandler = new Hop(swarm, options.hop)
28 |
29 | /**
30 | * Add swarm handler and listen for incoming connections
31 | *
32 | * @param {Multiaddr} ma
33 | * @param {Function} callback
34 | * @return {void}
35 | */
36 | listener.listen = (ma, callback) => {
37 | callback = callback || (() => {})
38 |
39 | swarm.handle(multicodec.relay, (_, conn) => {
40 | const sh = new StreamHandler(conn)
41 |
42 | sh.read((err, msg) => {
43 | if (err) {
44 | log.err(err)
45 | return
46 | }
47 |
48 | let request = null
49 | try {
50 | request = proto.CircuitRelay.decode(msg)
51 | } catch (err) {
52 | return utils.writeResponse(
53 | sh,
54 | proto.CircuitRelay.Status.MALFORMED_MESSAGE)
55 | }
56 |
57 | switch (request.type) {
58 | case proto.CircuitRelay.Type.CAN_HOP:
59 | case proto.CircuitRelay.Type.HOP: {
60 | return listener.hopHandler.handle(request, sh)
61 | }
62 |
63 | case proto.CircuitRelay.Type.STOP: {
64 | return listener.stopHandler.handle(request, sh, connHandler)
65 | }
66 |
67 | default: {
68 | utils.writeResponse(
69 | sh,
70 | proto.CircuitRelay.Status.INVALID_MSG_TYPE)
71 | return sh.close()
72 | }
73 | }
74 | })
75 | })
76 |
77 | setImmediate(() => listener.emit('listen'))
78 | callback()
79 | }
80 |
81 | /**
82 | * Remove swarm listener
83 | *
84 | * @param {Function} cb
85 | * @return {void}
86 | */
87 | listener.close = (cb) => {
88 | swarm.unhandle(multicodec.relay)
89 | setImmediate(() => listener.emit('close'))
90 | cb()
91 | }
92 |
93 | /**
94 | * Get fixed up multiaddrs
95 | *
96 | * NOTE: This method will grab the peers multiaddrs and expand them such that:
97 | *
98 | * a) If it's an existing /p2p-circuit address for a specific relay i.e.
99 | * `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit` this method will expand the
100 | * address to `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit/ipfs/QmPeer` where
101 | * `QmPeer` is this peers id
102 | * b) If it's not a /p2p-circuit address, it will encapsulate the address as a /p2p-circuit
103 | * addr, such when dialing over a relay with this address, it will create the circuit using
104 | * the encapsulated transport address. This is useful when for example, a peer should only
105 | * be dialed over TCP rather than any other transport
106 | *
107 | * @param {Function} callback
108 | * @return {void}
109 | */
110 | listener.getAddrs = (callback) => {
111 | let addrs = swarm._peerInfo.multiaddrs.toArray()
112 |
113 | // get all the explicit relay addrs excluding self
114 | let p2pAddrs = addrs.filter((addr) => {
115 | return mafmt.Circuit.matches(addr) &&
116 | !addr.toString().includes(swarm._peerInfo.id.toB58String())
117 | })
118 |
119 | // use the explicit relays instead of any relay
120 | if (p2pAddrs.length) {
121 | addrs = p2pAddrs
122 | }
123 |
124 | let listenAddrs = []
125 | addrs.forEach((addr) => {
126 | const peerMa = `/p2p-circuit/ipfs/${swarm._peerInfo.id.toB58String()}`
127 | if (addr.toString() === peerMa) {
128 | listenAddrs.push(multiaddr(peerMa))
129 | return
130 | }
131 |
132 | if (!mafmt.Circuit.matches(addr)) {
133 | if (addr.getPeerId()) {
134 | // by default we're reachable over any relay
135 | listenAddrs.push(multiaddr(`/p2p-circuit`).encapsulate(addr))
136 | } else {
137 | const ma = `${addr}/ipfs/${swarm._peerInfo.id.toB58String()}`
138 | listenAddrs.push(multiaddr(`/p2p-circuit`).encapsulate(ma))
139 | }
140 | } else {
141 | listenAddrs.push(addr.encapsulate(`/ipfs/${swarm._peerInfo.id.toB58String()}`))
142 | }
143 | })
144 |
145 | callback(null, listenAddrs)
146 | }
147 |
148 | return listener
149 | }
150 |
--------------------------------------------------------------------------------
/src/multicodec.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | relay: '/libp2p/circuit/relay/0.1.0'
5 | }
6 |
--------------------------------------------------------------------------------
/src/protocol/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const protobuf = require('protons')
3 | module.exports = protobuf(`
4 | message CircuitRelay {
5 |
6 | enum Status {
7 | SUCCESS = 100;
8 | HOP_SRC_ADDR_TOO_LONG = 220;
9 | HOP_DST_ADDR_TOO_LONG = 221;
10 | HOP_SRC_MULTIADDR_INVALID = 250;
11 | HOP_DST_MULTIADDR_INVALID = 251;
12 | HOP_NO_CONN_TO_DST = 260;
13 | HOP_CANT_DIAL_DST = 261;
14 | HOP_CANT_OPEN_DST_STREAM = 262;
15 | HOP_CANT_SPEAK_RELAY = 270;
16 | HOP_CANT_RELAY_TO_SELF = 280;
17 | STOP_SRC_ADDR_TOO_LONG = 320;
18 | STOP_DST_ADDR_TOO_LONG = 321;
19 | STOP_SRC_MULTIADDR_INVALID = 350;
20 | STOP_DST_MULTIADDR_INVALID = 351;
21 | STOP_RELAY_REFUSED = 390;
22 | MALFORMED_MESSAGE = 400;
23 | }
24 |
25 | enum Type { // RPC identifier, either HOP, STOP or STATUS
26 | HOP = 1;
27 | STOP = 2;
28 | STATUS = 3;
29 | CAN_HOP = 4;
30 | }
31 |
32 | message Peer {
33 | required bytes id = 1; // peer id
34 | repeated bytes addrs = 2; // peer's known addresses
35 | }
36 |
37 | optional Type type = 1; // Type of the message
38 |
39 | optional Peer srcPeer = 2; // srcPeer and dstPeer are used when Type is HOP or STATUS
40 | optional Peer dstPeer = 3;
41 |
42 | optional Status code = 4; // Status code, used when Type is STATUS
43 | }
44 | `)
45 |
--------------------------------------------------------------------------------
/test/dialer.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | /* eslint max-nested-callbacks: ["error", 5] */
3 |
4 | 'use strict'
5 |
6 | const Dialer = require('../src/circuit/dialer')
7 | const nodes = require('./fixtures/nodes')
8 | const Connection = require('interface-connection').Connection
9 | const multiaddr = require('multiaddr')
10 | const PeerInfo = require('peer-info')
11 | const PeerId = require('peer-id')
12 | const waterfall = require('async/waterfall')
13 | const pull = require('pull-stream/pull')
14 | const values = require('pull-stream/sources/values')
15 | const asyncMap = require('pull-stream/throughs/async-map')
16 | const pair = require('pull-pair/duplex')
17 | const pb = require('pull-protocol-buffers')
18 |
19 | const proto = require('../src/protocol')
20 | const utilsFactory = require('../src/circuit/utils')
21 |
22 | const sinon = require('sinon')
23 | const chai = require('chai')
24 | const dirtyChai = require('dirty-chai')
25 | const expect = chai.expect
26 | chai.use(dirtyChai)
27 |
28 | describe(`dialer tests`, function () {
29 | describe(`.dial`, function () {
30 | const dialer = sinon.createStubInstance(Dialer)
31 |
32 | beforeEach(function () {
33 | dialer.relayPeers = new Map()
34 | dialer.relayPeers.set(nodes.node2.id, new Connection())
35 | dialer.relayPeers.set(nodes.node3.id, new Connection())
36 | dialer.dial.callThrough()
37 | })
38 |
39 | afterEach(function () {
40 | dialer._dialPeer.reset()
41 | })
42 |
43 | it(`fail on non circuit addr`, function () {
44 | const dstMa = multiaddr(`/ipfs/${nodes.node4.id}`)
45 | expect(() => dialer.dial(dstMa, (err) => {
46 | err.to.match(/invalid circuit address/)
47 | }))
48 | })
49 |
50 | it(`dial a peer`, function (done) {
51 | const dstMa = multiaddr(`/p2p-circuit/ipfs/${nodes.node3.id}`)
52 | dialer._dialPeer.callsFake(function (dstMa, relay, callback) {
53 | return callback(null, dialer.relayPeers.get(nodes.node3.id))
54 | })
55 |
56 | dialer.dial(dstMa, (err, conn) => {
57 | expect(err).to.not.exist()
58 | expect(conn).to.be.an.instanceOf(Connection)
59 | done()
60 | })
61 | })
62 |
63 | it(`dial a peer over the specified relay`, function (done) {
64 | const dstMa = multiaddr(`/ipfs/${nodes.node3.id}/p2p-circuit/ipfs/${nodes.node4.id}`)
65 | dialer._dialPeer.callsFake(function (dstMa, relay, callback) {
66 | expect(relay.toString()).to.equal(`/ipfs/${nodes.node3.id}`)
67 | return callback(null, new Connection())
68 | })
69 |
70 | dialer.dial(dstMa, (err, conn) => {
71 | expect(err).to.not.exist()
72 | expect(conn).to.be.an.instanceOf(Connection)
73 | done()
74 | })
75 | })
76 | })
77 |
78 | describe(`.canHop`, function () {
79 | const dialer = sinon.createStubInstance(Dialer)
80 |
81 | let fromConn = null
82 | let peer = new PeerInfo(PeerId.createFromB58String('QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA'))
83 |
84 | let p = null
85 | beforeEach(function () {
86 | p = pair()
87 | fromConn = new Connection(p[0])
88 |
89 | dialer.relayPeers = new Map()
90 | dialer.relayConns = new Map()
91 | dialer.utils = utilsFactory({})
92 | dialer.canHop.callThrough()
93 | dialer._dialRelayHelper.callThrough()
94 | })
95 |
96 | afterEach(function () {
97 | dialer._dialRelay.reset()
98 | })
99 |
100 | it(`should handle successful CAN_HOP`, (done) => {
101 | dialer._dialRelay.callsFake((_, cb) => {
102 | pull(
103 | values([{
104 | type: proto.CircuitRelay.type.HOP,
105 | code: proto.CircuitRelay.Status.SUCCESS
106 | }]),
107 | pb.encode(proto.CircuitRelay),
108 | p[1]
109 | )
110 | cb(null, fromConn)
111 | })
112 |
113 | dialer.canHop(peer, (err) => {
114 | expect(err).to.not.exist()
115 | expect(dialer.relayPeers.has(peer.id.toB58String())).to.be.ok()
116 | done()
117 | })
118 | })
119 |
120 | it(`should handle failed CAN_HOP`, function (done) {
121 | dialer._dialRelay.callsFake((_, cb) => {
122 | pull(
123 | values([{
124 | type: proto.CircuitRelay.type.HOP,
125 | code: proto.CircuitRelay.Status.HOP_CANT_SPEAK_RELAY
126 | }]),
127 | pb.encode(proto.CircuitRelay),
128 | p[1]
129 | )
130 | cb(null, fromConn)
131 | })
132 |
133 | dialer.canHop(peer, (err) => {
134 | expect(err).to.exist()
135 | expect(dialer.relayPeers.has(peer.id.toB58String())).not.to.be.ok()
136 | done()
137 | })
138 | })
139 | })
140 |
141 | describe(`._dialPeer`, function () {
142 | const dialer = sinon.createStubInstance(Dialer)
143 |
144 | beforeEach(function () {
145 | dialer.relayPeers = new Map()
146 | dialer.relayPeers.set(nodes.node1.id, new Connection())
147 | dialer.relayPeers.set(nodes.node2.id, new Connection())
148 | dialer.relayPeers.set(nodes.node3.id, new Connection())
149 | dialer._dialPeer.callThrough()
150 | })
151 |
152 | afterEach(function () {
153 | dialer._negotiateRelay.reset()
154 | })
155 |
156 | it(`should dial a peer over any relay`, function (done) {
157 | const dstMa = multiaddr(`/ipfs/${nodes.node4.id}`)
158 | dialer._negotiateRelay.callsFake(function (conn, dstMa, callback) {
159 | if (conn === dialer.relayPeers.get(nodes.node3.id)) {
160 | return callback(null, dialer.relayPeers.get(nodes.node3.id))
161 | }
162 |
163 | callback(new Error(`error`))
164 | })
165 |
166 | dialer._dialPeer(dstMa, (err, conn) => {
167 | expect(err).to.not.exist()
168 | expect(conn).to.be.an.instanceOf(Connection)
169 | expect(conn).to.deep.equal(dialer.relayPeers.get(nodes.node3.id))
170 | done()
171 | })
172 | })
173 |
174 | it(`should fail dialing a peer over any relay`, function (done) {
175 | const dstMa = multiaddr(`/ipfs/${nodes.node4.id}`)
176 | dialer._negotiateRelay.callsFake(function (conn, dstMa, callback) {
177 | callback(new Error(`error`))
178 | })
179 |
180 | dialer._dialPeer(dstMa, (err, conn) => {
181 | expect(conn).to.be.undefined()
182 | expect(err).to.not.be.null()
183 | expect(err).to.equal(`no relay peers were found or all relays failed to dial`)
184 | done()
185 | })
186 | })
187 | })
188 |
189 | describe(`._negotiateRelay`, function () {
190 | const dialer = sinon.createStubInstance(Dialer)
191 | const dstMa = multiaddr(`/ipfs/${nodes.node4.id}`)
192 |
193 | let conn = null
194 | let peer = null
195 | let p = null
196 | let callback = sinon.stub()
197 |
198 | beforeEach(function (done) {
199 | waterfall([
200 | (cb) => PeerId.createFromJSON(nodes.node4, cb),
201 | (peerId, cb) => PeerInfo.create(peerId, cb),
202 | (peer, cb) => {
203 | peer.multiaddrs.add(`/p2p-circuit/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`)
204 | dialer.swarm = {
205 | _peerInfo: peer
206 | }
207 | cb()
208 | },
209 | (cb) => {
210 | dialer.utils = utilsFactory({})
211 | dialer.relayConns = new Map()
212 | dialer._negotiateRelay.callThrough()
213 | dialer._dialRelayHelper.callThrough()
214 | peer = new PeerInfo(PeerId.createFromB58String(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`))
215 | p = pair()
216 | conn = new Connection(p[1])
217 | cb()
218 | }
219 | ], done)
220 | })
221 |
222 | afterEach(() => {
223 | callback.reset()
224 | })
225 |
226 | it(`should write the correct dst addr`, function (done) {
227 | dialer._dialRelay.callsFake((_, cb) => {
228 | pull(
229 | p[0],
230 | pb.decode(proto.CircuitRelay),
231 | asyncMap((msg, cb) => {
232 | expect(msg.dstPeer.addrs[0]).to.deep.equal(dstMa.buffer)
233 | cb(null, {
234 | type: proto.CircuitRelay.Type.STATUS,
235 | code: proto.CircuitRelay.Status.SUCCESS
236 | })
237 | }),
238 | pb.encode(proto.CircuitRelay),
239 | p[0]
240 | )
241 | cb(null, conn)
242 | })
243 |
244 | dialer._negotiateRelay(peer, dstMa, done)
245 | })
246 |
247 | it(`should negotiate relay`, function (done) {
248 | dialer._dialRelay.callsFake((_, cb) => {
249 | pull(
250 | p[0],
251 | pb.decode(proto.CircuitRelay),
252 | asyncMap((msg, cb) => {
253 | expect(msg.dstPeer.addrs[0]).to.deep.equal(dstMa.buffer)
254 | cb(null, {
255 | type: proto.CircuitRelay.Type.STATUS,
256 | code: proto.CircuitRelay.Status.SUCCESS
257 | })
258 | }),
259 | pb.encode(proto.CircuitRelay),
260 | p[0]
261 | )
262 | cb(null, conn)
263 | })
264 |
265 | dialer._negotiateRelay(peer, dstMa, (err, conn) => {
266 | expect(err).to.not.exist()
267 | expect(conn).to.be.instanceOf(Connection)
268 | done()
269 | })
270 | })
271 |
272 | it(`should fail with an invalid peer id`, function (done) {
273 | const dstMa = multiaddr('/ip4/127.0.0.1/tcp/4001')
274 | dialer._dialRelay.callsFake((_, cb) => {
275 | pull(
276 | p[0],
277 | pb.decode(proto.CircuitRelay),
278 | asyncMap((msg, cb) => {
279 | expect(msg.dstPeer.addrs[0]).to.deep.equal(dstMa.buffer)
280 | cb(null, {
281 | type: proto.CircuitRelay.Type.STATUS,
282 | code: proto.CircuitRelay.Status.SUCCESS
283 | })
284 | }),
285 | pb.encode(proto.CircuitRelay),
286 | p[0]
287 | )
288 | cb(null, conn)
289 | })
290 |
291 | dialer._negotiateRelay(peer, dstMa, (err, conn) => {
292 | expect(err).to.exist()
293 | expect(conn).to.not.exist()
294 | done()
295 | })
296 | })
297 |
298 | it(`should handle failed relay negotiation`, function (done) {
299 | dialer._dialRelay.callsFake((_, cb) => {
300 | cb(null, conn)
301 | pull(
302 | values([{
303 | type: proto.CircuitRelay.Type.STATUS,
304 | code: proto.CircuitRelay.Status.MALFORMED_MESSAGE
305 | }]),
306 | pb.encode(proto.CircuitRelay),
307 | p[0]
308 | )
309 | })
310 |
311 | dialer._negotiateRelay(peer, dstMa, (err, conn) => {
312 | expect(err).to.not.be.null()
313 | expect(err).to.be.an.instanceOf(Error)
314 | expect(err.message).to.be.equal(`Got 400 error code trying to dial over relay`)
315 | done()
316 | })
317 | })
318 | })
319 | })
320 |
--------------------------------------------------------------------------------
/test/fixtures/nodes.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | exports.node1 = {
4 | id: 'QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE',
5 | privKey: 'CAASpwkwggSjAgEAAoIBAQDJwzJPar4nylKY71Mm5q2BOED8uPf1ILvIi15VwVZWqter6flnlii/RKEcBypPbFqJHHa56MvybgQgrFmHKwDjnJvq4jyOZfR+o/D/99Ft1p2FAEBjImSXAgNpK4YsbyV5r0Q1+Avcj++aWWlLu6enUrL9WGzeUkf0U5L6XwXEPRUQdEojAIQi241P1hyqXX5gKAZVGqcPtKb6p1db3fcXodkS1G6JR90TopJHCqTCECp3SB9c6LlG7KXU92sIHJBlhOEEzGkEI1pM1SWnNnW5VLEypU7P56ifzzp4QxPNiJeC+cmE5SrgR3cXP44iKOuNVRJwBpCh5oNYqECzgqJ9AgMBAAECggEBAJpCdqXHrAmKJCqv2HiGqCODGhTfax1s4IYNIJwaTOPIjUrwgfKUGSVb2H4wcEX3RyVLsO6lMcFyIg/FFlJFK9HavE8SmFAbXZqxx6I9HE+JZjf5IEFrW1Mlg+wWDejNNe7adSF6O79wATaWo+32VNGWZilTQTGd4UvJ1jc9DZCh8zZeNhm4C6exXD45gMB0HI1t2ZNl47scsBEE4rV+s7F7y8Yk/tIsf0wSI/H8KSXS5I9aFxr3Z9c3HOfbVwhnIfNUDqcFTeU5BnhByYNLJ4v9xGj7puidcabVXkt2zLmm/LHbKVeGzec9LW5D+KkuB/pKaslsCXN6bVlu+SbVr9UCgYEA7MXfzZw36vDyfn4LPCN0wgzz11uh3cm31QzOPlWpA7hIsL/eInpvc8wa9yBRC1sRk41CedPHn913MR6EJi0Ne6/B1QOmRYBUjr60VPRNdTXCAiLykjXg6+TZ+AKnxlUGK1hjTo8krhpWq7iD/JchVlLoqDAXGFHvSxN0H3WEUm8CgYEA2iWC9w1v+YHfT2PXcLxYde9EuLVkIS4TM7Kb0N3wr/4+K4xWjVXuaJJLJoAbihNAZw0Y+2s1PswDUEpSG0jXeNXLs6XcQxYSEAu/pFdvHFeg2BfwVQoeEFlWyTJR29uti9/APaXMo8FSVAPPR5lKZLStJDM9hEfAPfUaHyic39MCgYAKQbwjNQw7Ejr+/cjQzxxkt5jskFyftfhPs2FP0/ghYB9OANHHnpQraQEWCYFZQ5WsVac2jdUM+NQL/a1t1e/Klt+HscPHKPsAwAQh1f9w/2YrH4ZwjQL0VRKYKs1HyzEcOZT7tzm4jQ2KHNEi5Q0dpzPK7WJivFHoZ6xVHIsh4wKBgAQq20mk9BKsLHvzyFXbA0WdgI6WyIbpvmwqaVegJcz26nEiiTTCA3/z64OcxunoXD6bvXJwJeBBPX73LIJg7dzdGLsh3AdcEJRF5S9ajEDaW7RFIM4/FzvwuPu2/mFY3QPjDmUfGb23H7+DIx6XCxjJatVaNT6lsEJ+wDUALZ8JAoGAO0YJSEziA7y0dXPK5azkJUMJ5yaN+zRDmoBnEggza34rQW0s16NnIR0EBzKGwbpNyePlProv4dQEaLF1kboKsSYvV2rW2ftLVdNqBHEUYFRC9ofPctCxwM1YU21TI2/k1squ+swApg2EHMev2+WKd+jpVPIbCIvJ3AjiAKZtiGQ=',
6 | pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJwzJPar4nylKY71Mm5q2BOED8uPf1ILvIi15VwVZWqter6flnlii/RKEcBypPbFqJHHa56MvybgQgrFmHKwDjnJvq4jyOZfR+o/D/99Ft1p2FAEBjImSXAgNpK4YsbyV5r0Q1+Avcj++aWWlLu6enUrL9WGzeUkf0U5L6XwXEPRUQdEojAIQi241P1hyqXX5gKAZVGqcPtKb6p1db3fcXodkS1G6JR90TopJHCqTCECp3SB9c6LlG7KXU92sIHJBlhOEEzGkEI1pM1SWnNnW5VLEypU7P56ifzzp4QxPNiJeC+cmE5SrgR3cXP44iKOuNVRJwBpCh5oNYqECzgqJ9AgMBAAE='
7 | }
8 |
9 | exports.node2 = {
10 | id: 'QmYJjAri5soV8RbeQcHaYYcTAYTET17QTvcoFMyKvRDTXe',
11 | privKey: 'CAASpgkwggSiAgEAAoIBAQDt7YgUeBQsoN/lrgo690mB7yEh8G9iXhZiDecgZCLRRSl3v2cH9w4WjhoW9erfnVbdoTqkCK+se8uK01ySi/ubQQDPcrjacXTa6wAuRTbCG/0bUR9RxKtxZZBS1HaY7L923ulgGDTiVaRQ3JQqhzmQkaU0ikNcluSGaw0kmhXP6JmcL+wndKgW5VD9etcp2Qlk8uUFC/GAO90cOAuER3wnI3ocHGm9on9zyb97g4TDzIfjSaTW4Wanmx2yVbURQxmCba16X3LT9IMPqQaGOzq3+EewMLeCESbUm/uJaJLdqWrWRK4oNzxcMgmUkzav+s476HdA9CRo72am+g3Vdq+lAgMBAAECggEAcByKD6MZVoIjnlVo6qoVUA1+3kAuK/rLrz5/1wp4QYXGaW+eO+mVENm6v3D3UJESGnLbb+nL5Ymbunmn2EHvuBNkL1wOcJgfiPxM5ICmscaAeHu8N0plwpQp8m28yIheG8Qj0az2VmQmfhfCFVwMquuGHgC8hwdu/Uu6MLIObx1xjtaGbY9kk7nzAeXHeJ4RDeuNN0QrYuQVKwrIz1NtPNDR/cli298ZXJcm+HEhBCIHVIYpAq6BHSuiXVqPGEOYWYXo+yVhEtDJ8BmNqlN1Y1s6bnfu/tFkKUN6iQQ46vYnQEGTGR9lg7J/c6tqfRs9FcywWb9J1SX6HxPO8184zQKBgQD6vDYl20UT4ZtrzhFfMyV/1QUqFM/TdwNuiOsIewHBol9o7aOjrxrrbYVa1HOxETyBjmFsW+iIfOVl61SG2HcU4CG+O2s9WBo4JdRlOm4YQ8/83xO3YfbXzuTx8BMCyP/i1uPIZTKQFFAN0HiL96r4L60xHoWB7tQsbZiEbIO/2wKBgQDy7HnkgVeTld6o0+sT84FYRUotjDB00oUWiSeGtj0pFC4yIxhMhD8QjKiWoJyJItcoCsQ/EncuuwwRtuXi83793lJQR1DBYd+TSPg0M8J1pw97fUIPi/FU+jHtrsx7Vn/7Bk9voictsYVLAfbi68tYdsZpAaYOWYMY9NUfVuAmfwKBgCYZDwk1hgt9TkZVK2KRvPLthTldrC5veQAEoeHJ/vxTFbg105V9d9Op8odYnLOc8NqmrbrvRCfpAlo4JcHPhliPrdDf6m2Jw4IgjWNMO4pIU4QSyUYmBoHIGBWC6wCTVf47tKSwa7xkub0/nfF2km3foKtD/fk+NtMBXBlS+7ndAoGAJo6GIlCtN82X07AfJcGGjB4jUetoXYJ0gUkvruAKARUk5+xOFQcAg33v3EiNz+5pu/9JesFRjWc+2Sjwf/8p7t10ry1Ckg8Yz2XLj22PteDYQj91VsZdfaFgf1s5NXJbSdqMjSltkoEUqP0c1JOcaOQhRdVvJ+PpPPLPSPQfC70CgYBvJE1I06s7BEM1DOli3VyfNaJDI4k9W2dCJOU6Bh2MNmbdRjM3xnpOKH5SqRlCz/oI9pn4dxgbX6WPg331MD9CNYy2tt5KBQRrSuDj8p4jlzMIpX36hsyTTrzYU6WWSIPz6jXW8IexXKvXEmr8TVb78ZPiQfbG012cdUhAJniNgg==',
12 | pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDt7YgUeBQsoN/lrgo690mB7yEh8G9iXhZiDecgZCLRRSl3v2cH9w4WjhoW9erfnVbdoTqkCK+se8uK01ySi/ubQQDPcrjacXTa6wAuRTbCG/0bUR9RxKtxZZBS1HaY7L923ulgGDTiVaRQ3JQqhzmQkaU0ikNcluSGaw0kmhXP6JmcL+wndKgW5VD9etcp2Qlk8uUFC/GAO90cOAuER3wnI3ocHGm9on9zyb97g4TDzIfjSaTW4Wanmx2yVbURQxmCba16X3LT9IMPqQaGOzq3+EewMLeCESbUm/uJaJLdqWrWRK4oNzxcMgmUkzav+s476HdA9CRo72am+g3Vdq+lAgMBAAE='
13 | }
14 |
15 | exports.node3 = {
16 | id: 'QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA',
17 | privKey: 'CAASpwkwggSjAgEAAoIBAQDdnGp0X7Pix5dIawfyuffVryRDRS5JXdyjayKUkgikJLYoiijB5TakrFKhx1SDKpmVLxxqAGz8m5iA2cHwetIQXTZvdYx7XXxv332En3ji8TiGRUiEFM8KQ5WCJ5G7yw8R2pv/pYdnMrPd04QbtSCn0cFVCiiA2Zkl5KnwBo/lf+sVI/TEeiwmVD9nxi13qWgBTmCysqH8Ppyu8fq+bQgqRZSlalVDswyIhgWlepPkD0uYakJJhhOxY+2RlbNhGY0qjRyMTYou2uR/hfd6j8uR++WdB0v3+DYWG2Kc3sWa4BLYb5r4trvQGO1Iagnwuk3AVoi7PldsaInekzWEVljDAgMBAAECggEAXx0jE49/xXWkmJBXePYYSL5C8hxfIV4HtJvm251R2CFpjTy/AXk/Wq4bSRQkUaeXA1CVAWntXP3rFmJfurb8McnP80agZNJa9ikV1jYbzEt71yUlWosT0XPwV0xkYBVnAmKxUafZ1ZENYcfGi53RxjVgpP8XIzZBZOIfjcVDPVw9NAOzQmq4i3DJEz5xZAkaeSM8mn5ZFl1JMBUOgyOHB7d4BWd3zuLyvnn0/08HlsaSUl0mZa3f2Lm2NlsjOiNfMCJTOIT+xDEP9THm5n2cqieSjvtpAZzV4kcoD0rB8OsyHQlFAEXzkgELDr5dVXji0rrIdVz8stYAKGfi996OAQKBgQDuviV1sc+ClJQA59vqbBiKxWqcuCKMzvmL4Yk1e/AkQeRt+JX9kALWzBx65fFmHTj4Lus8AIQoiruPxa0thtqh/m3SlucWnrdaW410xbz3KqQWS7bx+0sFWZIEi4N+PESrIYhtVbFuRiabYgliqdSU9shxtXXnvfhjl+9quZltiwKBgQDtoUCKqrZbm0bmzLvpnKdNodg1lUHaKGgEvWgza2N1t3b/GE07iha2KO3hBDta3bdfIEEOagY8o13217D0VIGsYNKpiEGLEeNIjfcXBEqAKiTfa/sXUfTprpWBZQ/7ZS+eZIYtQjq14EHa7ifAby1v3yDrMIuxphz5JfKdXFgYqQKBgHr47FikPwu2tkmFJCyqgzWvnEufOQSoc7eOc1tePIKggiX2/mM+M4gqWJ0hJeeAM+D6YeZlKa2sUBItMxeZN7JrWGw5mEx5cl4TfFhipgP2LdDiLRiVZL4bte+rYQ67wm8XdatDkYIIlkhBBi6Q5dPZDcQsQNAedPvvvb2OXi4jAoGBAKp06FpP+L2fle2LYSRDlhNvDCvrpDA8mdkEkRGJb/AKKdb09LnH5WDH3VNy+KzGrHoVJfWUAmNPAOFHeYzabaZcUeEAd5utui7afytIjbSABrEpwRTKWneiH2aROzSnMdBZ5ZHjlz/N3Q+RlHxKg/piwTdUPHCzasch/HX6vsr5AoGAGvhCNPKyCwpu8Gg5GQdx0yN6ZPar9wieD345cLanDZWKkLRQbo4SfkfoS+PDfOLzDbWFdPRnWQ0qhdPm3D/N1YD/nudHqbeDlx0dj/6lEHmmPKFFO2kiNFEhn8DycNGbvWyVBKksacuRXav21+LvW+TatUkRMhi8fgRoypnbJjg=',
18 | pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdnGp0X7Pix5dIawfyuffVryRDRS5JXdyjayKUkgikJLYoiijB5TakrFKhx1SDKpmVLxxqAGz8m5iA2cHwetIQXTZvdYx7XXxv332En3ji8TiGRUiEFM8KQ5WCJ5G7yw8R2pv/pYdnMrPd04QbtSCn0cFVCiiA2Zkl5KnwBo/lf+sVI/TEeiwmVD9nxi13qWgBTmCysqH8Ppyu8fq+bQgqRZSlalVDswyIhgWlepPkD0uYakJJhhOxY+2RlbNhGY0qjRyMTYou2uR/hfd6j8uR++WdB0v3+DYWG2Kc3sWa4BLYb5r4trvQGO1Iagnwuk3AVoi7PldsaInekzWEVljDAgMBAAE='
19 | }
20 |
21 | exports.node4 = {
22 | id: 'QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy',
23 | privKey: 'CAASqAkwggSkAgEAAoIBAQC6pg6LYWbY+49SOYdYap6RPqKqZxg80IXeo3hiTUbiGtTruxVYZpnz3UbernL9J9mwlXJGRUQJUKmXmi1yePTQiyclpH0KyPefaWLbpxJQdCBI1TPZpDWo2hutWSPqhKBU1QyH2FLKQPWLdxuIX1cNFPtIlSl5gCxN6oiDIwh7++kxNM1G+d5XgJX6iHLlLoNv3Wn6XYX+VtYdyZRFk8gYyT2BrISbxmsrSjSOodwUUzF8TYTjsqW6ksL2x0mrRm2cMM9evULktqwU+I8l9ulASDbFWBXUToXaZSL9M+Oq5JvZO0WIjPeYVpAgWladtayhdxg5dBv8aTbDaM5DZvyRAgMBAAECggEAR65YbZz1k6Vg0HI5kXI4/YzxicHYJBrtHqjnJdGJxHILjZCmzPFydJ5phkG29ZRlXRS381bMn0s0Jn3WsFzVoHWgjitSvl6aAsXFapgKR42hjHcc15vh47wH3xYZ3gobTRkZG96vRO+XnX0bvM7orqR9MM3gRMI9wZqt3LcKnhpiqSlyEZ3Zehu7ZZ8B+XcUw42H6ZTXgmg5mCFEjS/1rVt+EsdZl7Ll7jHigahPA6qMjyRiZB6T20qQ0FFYfmaNuRuuC6cWUXf8DOgnEjMB/Mi/Feoip9bTqNBrVYn2XeDxdMv5pDznNKXpalsMkZwx5FpNOMKnIMdQFyAGtkeQ9QKBgQD3rjTiulitpbbQBzF8VXeymtMJAbR1TAqNv2yXoowhL3JZaWICM7nXHjjsJa3UzJygbi8bO0KWrw7tY0nUbPy5SmHtNYhmUsEjiTjqEnNRrYN68tEKr0HlgX+9rArsjOcwucl2svFSfk+rTYDHU5neZkDDhu1QmnZm/pQI92Lo4wKBgQDA6wpMd53fmX9DhWegs3xelRStcqBTw1ucWVRyPgY1hO1cJ0oReYIXKEw9CHNLW0RHvnVM26kRnqCl+dTcg7dhLuqrckuyQyY1KcRYG1ryJnz3euucaSF2UCsZCHvFNV7Vz8dszUMUVCogWmroVP6HE/BoazUCNh25s/dNwE+i+wKBgEfa1WL1luaBzgCaJaQhk4FQY2sYgIcLEYDACTwQn0C9aBpCdXmYEhEzpmX0JHM5DTOJ48atsYrPrK/3/yJOoB8NUk2kGzc8SOYLWGSoB6aphRx1N2o3IBH6ONoJAH5R/nxnWehCz7oUBP74lCS/v0MDPUS8bzrUJQeKUd4sDxjrAoGBAIRO7rJA+1qF+J1DWi4ByxNHJXZLfh/UhPj23w628SU1dGDWZVsUvZ7KOXdGW2RcRLj7q5E5uXtnEoCillViVJtnRPSun7Gzkfm2Gn3ezQH0WZKVkA+mnpd5JgW2JsS69L6pEPnS0OWZT4b+3AFZgXL8vs2ucR2CJeLdxYdilHuPAoGBAPLCzBkAboXZZtvEWqzqtVNqdMrjLHihFrpg4TXSsk8+ZQZCVN+sRyTGTvBX8+Jvx4at6ClaSgT3eJ/412fEH6CHvrFXjUE9W9y6X0axxaT63y1OXmFiB/hU3vjLWZKZWSDGNS7St02fYri4tWmGtJDjYG1maLRhMSzcoj4fP1xz',
24 | pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6pg6LYWbY+49SOYdYap6RPqKqZxg80IXeo3hiTUbiGtTruxVYZpnz3UbernL9J9mwlXJGRUQJUKmXmi1yePTQiyclpH0KyPefaWLbpxJQdCBI1TPZpDWo2hutWSPqhKBU1QyH2FLKQPWLdxuIX1cNFPtIlSl5gCxN6oiDIwh7++kxNM1G+d5XgJX6iHLlLoNv3Wn6XYX+VtYdyZRFk8gYyT2BrISbxmsrSjSOodwUUzF8TYTjsqW6ksL2x0mrRm2cMM9evULktqwU+I8l9ulASDbFWBXUToXaZSL9M+Oq5JvZO0WIjPeYVpAgWladtayhdxg5dBv8aTbDaM5DZvyRAgMBAAE='
25 | }
26 |
--------------------------------------------------------------------------------
/test/helpers/test-node.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Libp2p = require('libp2p')
4 | const secio = require('libp2p-secio')
5 |
6 | class TestNode extends Libp2p {
7 | constructor (peerInfo, transports, muxer, options) {
8 | options = options || {}
9 |
10 | const modules = {
11 | transport: transports,
12 | connection: {
13 | muxer: [muxer],
14 | crypto: options.isCrypto ? [secio] : null
15 | },
16 | discovery: []
17 | }
18 | super(modules, peerInfo, null, options)
19 | }
20 | }
21 |
22 | module.exports = TestNode
23 |
--------------------------------------------------------------------------------
/test/helpers/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const TestNode = require('./test-node')
4 | const PeerInfo = require('peer-info')
5 | const PeerId = require('peer-id')
6 | const eachAsync = require('async/each')
7 |
8 | exports.createNodes = function createNodes (configNodes, callback) {
9 | const nodes = {}
10 | eachAsync(Object.keys(configNodes), (key, cb1) => {
11 | let config = configNodes[key]
12 |
13 | const setup = (err, peer) => {
14 | if (err) {
15 | callback(err)
16 | }
17 |
18 | eachAsync(config.addrs, (addr, cb2) => {
19 | peer.multiaddrs.add(addr)
20 | cb2()
21 | }, (err) => {
22 | if (err) {
23 | return callback(err)
24 | }
25 |
26 | nodes[key] = new TestNode(peer, config.transports, config.muxer, config.config)
27 | cb1()
28 | })
29 | }
30 |
31 | if (config.id) {
32 | PeerId.createFromJSON(config.id, (err, peerId) => {
33 | if (err) return callback(err)
34 | PeerInfo.create(peerId, setup)
35 | })
36 | } else {
37 | PeerInfo.create(setup)
38 | }
39 | }, (err) => {
40 | if (err) {
41 | return callback(err)
42 | }
43 |
44 | startNodes(nodes, (err) => {
45 | if (err) {
46 | callback(err)
47 | }
48 |
49 | callback(null, nodes)
50 | })
51 | })
52 | }
53 |
54 | function startNodes (nodes, callback) {
55 | eachAsync(Object.keys(nodes),
56 | (key, cb) => {
57 | nodes[key].start(cb)
58 | },
59 | (err) => {
60 | if (err) {
61 | return callback(err)
62 | }
63 | callback(null)
64 | })
65 | }
66 |
67 | exports.stopNodes = function stopNodes (nodes, callback) {
68 | eachAsync(Object.keys(nodes),
69 | (key, cb) => {
70 | nodes[key].stop(cb)
71 | },
72 | (err) => {
73 | if (err) {
74 | return callback(err)
75 | }
76 | callback()
77 | })
78 | }
79 |
--------------------------------------------------------------------------------
/test/hop.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | /* eslint max-nested-callbacks: ["error", 5] */
3 | 'use strict'
4 |
5 | const Hop = require('../src/circuit/hop')
6 | const nodes = require('./fixtures/nodes')
7 | const Connection = require('interface-connection').Connection
8 | const handshake = require('pull-handshake')
9 | const waterfall = require('async/waterfall')
10 | const PeerInfo = require('peer-info')
11 | const PeerId = require('peer-id')
12 | const multiaddr = require('multiaddr')
13 | const pull = require('pull-stream/pull')
14 | const values = require('pull-stream/sources/values')
15 | const collect = require('pull-stream/sinks/collect')
16 | const lp = require('pull-length-prefixed')
17 | const proto = require('../src/protocol')
18 | const StreamHandler = require('../src/circuit/stream-handler')
19 |
20 | const sinon = require('sinon')
21 | const chai = require('chai')
22 | const dirtyChai = require('dirty-chai')
23 | const expect = chai.expect
24 | chai.use(dirtyChai)
25 |
26 | describe('relay', () => {
27 | describe(`.handle`, () => {
28 | let relay
29 | let swarm
30 | let fromConn
31 | let stream
32 | let shake
33 |
34 | beforeEach((done) => {
35 | stream = handshake({ timeout: 1000 * 60 })
36 | shake = stream.handshake
37 | fromConn = new Connection(stream)
38 | const peerInfo = new PeerInfo(PeerId.createFromB58String('QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA'))
39 | fromConn.setPeerInfo(peerInfo)
40 |
41 | let peers = {
42 | QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE:
43 | new PeerInfo(PeerId.createFromB58String(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`)),
44 | QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA:
45 | new PeerInfo(PeerId.createFromB58String(`QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`)),
46 | QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy:
47 | new PeerInfo(PeerId.createFromB58String(`QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`))
48 | }
49 |
50 | Object.keys(peers).forEach((key) => { peers[key]._connectedMultiaddr = true }) // make it truthy
51 |
52 | waterfall([
53 | (cb) => PeerId.createFromJSON(nodes.node4, cb),
54 | (peerId, cb) => PeerInfo.create(peerId, cb),
55 | (peer, cb) => {
56 | peer.multiaddrs.add('/p2p-circuit/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE')
57 | swarm = {
58 | _peerInfo: peer,
59 | conns: {
60 | QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE: new Connection(),
61 | QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA: new Connection(),
62 | QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy: new Connection()
63 | },
64 | _peerBook: {
65 | get: (peer) => {
66 | if (!peers[peer]) {
67 | throw new Error()
68 | }
69 |
70 | return peers[peer]
71 | }
72 | }
73 | }
74 |
75 | cb()
76 | }
77 | ], () => {
78 | relay = new Hop(swarm, { enabled: true })
79 | relay._circuit = sinon.stub()
80 | relay._circuit.callsArgWith(2, null, new Connection())
81 | done()
82 | })
83 | })
84 |
85 | afterEach(() => {
86 | relay._circuit.reset()
87 | })
88 |
89 | it(`should handle a valid circuit request`, (done) => {
90 | let relayMsg = {
91 | type: proto.CircuitRelay.Type.HOP,
92 | srcPeer: {
93 | id: PeerId.createFromB58String(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`).id,
94 | addrs: [multiaddr(`/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`).buffer]
95 | },
96 | dstPeer: {
97 | id: PeerId.createFromB58String(`QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).id,
98 | addrs: [multiaddr(`/ipfs/QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).buffer]
99 | }
100 | }
101 |
102 | relay.on('circuit:success', () => {
103 | expect(relay._circuit.calledWith(sinon.match.any, relayMsg)).to.be.ok()
104 | done()
105 | })
106 |
107 | relay.handle(relayMsg, new StreamHandler(fromConn))
108 | })
109 |
110 | it(`should handle a request to passive circuit`, (done) => {
111 | let relayMsg = {
112 | type: proto.CircuitRelay.Type.HOP,
113 | srcPeer: {
114 | id: PeerId.createFromB58String(`QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).id,
115 | addrs: [multiaddr(`/ipfs/QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).buffer]
116 | },
117 | dstPeer: {
118 | id: PeerId.createFromB58String(`QmYJjAri5soV8RbeQcHaYYcTAYTET17QTvcoFMyKvRDTXe`).id,
119 | addrs: [multiaddr(`/ipfs/QmYJjAri5soV8RbeQcHaYYcTAYTET17QTvcoFMyKvRDTXe`).buffer]
120 | }
121 | }
122 |
123 | relay.active = false
124 | lp.decodeFromReader(
125 | shake,
126 | (err, msg) => {
127 | expect(err).to.not.exist()
128 |
129 | const response = proto.CircuitRelay.decode(msg)
130 | expect(response.code).to.equal(proto.CircuitRelay.Status.HOP_NO_CONN_TO_DST)
131 | expect(response.type).to.equal(proto.CircuitRelay.Type.STATUS)
132 | done()
133 | })
134 |
135 | relay.handle(relayMsg, new StreamHandler(fromConn))
136 | })
137 |
138 | it(`should handle a request to active circuit`, (done) => {
139 | let relayMsg = {
140 | type: proto.CircuitRelay.Type.HOP,
141 | srcPeer: {
142 | id: PeerId.createFromB58String(`QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).id,
143 | addrs: [multiaddr(`/ipfs/QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).buffer]
144 | },
145 | dstPeer: {
146 | id: PeerId.createFromB58String(`QmYJjAri5soV8RbeQcHaYYcTAYTET17QTvcoFMyKvRDTXe`).id,
147 | addrs: [multiaddr(`/ipfs/QmYJjAri5soV8RbeQcHaYYcTAYTET17QTvcoFMyKvRDTXe`).buffer]
148 | }
149 | }
150 |
151 | relay.active = true
152 | relay.on('circuit:success', () => {
153 | expect(relay._circuit.calledWith(sinon.match.any, relayMsg)).to.be.ok()
154 | done()
155 | })
156 |
157 | relay.on('circuit:error', (err) => {
158 | done(err)
159 | })
160 |
161 | relay.handle(relayMsg, new StreamHandler(fromConn))
162 | })
163 |
164 | it(`not dial to self`, (done) => {
165 | let relayMsg = {
166 | type: proto.CircuitRelay.Type.HOP,
167 | srcPeer: {
168 | id: PeerId.createFromB58String(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`).id,
169 | addrs: [multiaddr(`/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`).buffer]
170 | },
171 | dstPeer: {
172 | id: PeerId.createFromB58String(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`).id,
173 | addrs: [multiaddr(`/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`).buffer]
174 | }
175 | }
176 |
177 | lp.decodeFromReader(
178 | shake,
179 | (err, msg) => {
180 | expect(err).to.not.exist()
181 |
182 | const response = proto.CircuitRelay.decode(msg)
183 | expect(response.code).to.equal(proto.CircuitRelay.Status.HOP_CANT_RELAY_TO_SELF)
184 | expect(response.type).to.equal(proto.CircuitRelay.Type.STATUS)
185 | done()
186 | })
187 |
188 | relay.handle(relayMsg, new StreamHandler(fromConn))
189 | })
190 |
191 | it(`fail on invalid src address`, (done) => {
192 | let relayMsg = {
193 | type: proto.CircuitRelay.Type.HOP,
194 | srcPeer: {
195 | id: `sdfkjsdnfkjdsb`,
196 | addrs: [`sdfkjsdnfkjdsb`]
197 | },
198 | dstPeer: {
199 | id: PeerId.createFromB58String(`QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).id,
200 | addrs: [multiaddr(`/ipfs/QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).buffer]
201 | }
202 | }
203 |
204 | lp.decodeFromReader(
205 | shake,
206 | (err, msg) => {
207 | expect(err).to.not.exist()
208 |
209 | const response = proto.CircuitRelay.decode(msg)
210 | expect(response.code).to.equal(proto.CircuitRelay.Status.HOP_SRC_MULTIADDR_INVALID)
211 | expect(response.type).to.equal(proto.CircuitRelay.Type.STATUS)
212 | done()
213 | })
214 |
215 | relay.handle(relayMsg, new StreamHandler(fromConn))
216 | })
217 |
218 | it(`fail on invalid dst address`, (done) => {
219 | let relayMsg = {
220 | type: proto.CircuitRelay.Type.HOP,
221 | srcPeer: {
222 | id: PeerId.createFromB58String(`QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).id,
223 | addrs: [multiaddr(`/ipfs/QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`).buffer]
224 | },
225 | dstPeer: {
226 | id: PeerId.createFromB58String(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`).id,
227 | addrs: [`sdfkjsdnfkjdsb`]
228 | }
229 | }
230 |
231 | lp.decodeFromReader(
232 | shake,
233 | (err, msg) => {
234 | expect(err).to.not.exist()
235 |
236 | const response = proto.CircuitRelay.decode(msg)
237 | expect(response.code).to.equal(proto.CircuitRelay.Status.HOP_DST_MULTIADDR_INVALID)
238 | expect(response.type).to.equal(proto.CircuitRelay.Type.STATUS)
239 | done()
240 | })
241 |
242 | relay.handle(relayMsg, new StreamHandler(fromConn))
243 | })
244 | })
245 |
246 | describe(`._circuit`, () => {
247 | let relay
248 | let swarm
249 | let srcConn
250 | let dstConn
251 | let srcStream
252 | let dstStream
253 | let srcShake
254 | let dstShake
255 |
256 | before((done) => {
257 | srcStream = handshake({ timeout: 1000 * 60 })
258 | srcShake = srcStream.handshake
259 | srcConn = new Connection(srcStream)
260 | dstStream = handshake({ timeout: 1000 * 60 })
261 | dstShake = dstStream.handshake
262 | dstConn = new Connection(dstStream)
263 | const peerInfo = new PeerInfo(PeerId.createFromB58String('QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA'))
264 | srcConn.setPeerInfo(peerInfo)
265 |
266 | let peers = {
267 | QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE:
268 | new PeerInfo(PeerId.createFromB58String(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`)),
269 | QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA:
270 | new PeerInfo(PeerId.createFromB58String(`QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`)),
271 | QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy:
272 | new PeerInfo(PeerId.createFromB58String(`QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`))
273 | }
274 |
275 | Object.keys(peers).forEach((key) => { peers[key]._connectedMultiaddr = true }) // make it truthy
276 |
277 | waterfall([
278 | (cb) => PeerId.createFromJSON(nodes.node4, cb),
279 | (peerId, cb) => PeerInfo.create(peerId, cb),
280 | (peer, cb) => {
281 | peer.multiaddrs.add('/p2p-circuit/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE')
282 | swarm = {
283 | _peerInfo: peer,
284 | conns: {
285 | QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE: new Connection(),
286 | QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA: new Connection(),
287 | QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy: new Connection()
288 | },
289 | _peerBook: {
290 | get: (peer) => {
291 | if (!peers[peer]) {
292 | throw new Error()
293 | }
294 |
295 | return peers[peer]
296 | }
297 | }
298 | }
299 |
300 | cb()
301 | }
302 | ], () => {
303 | relay = new Hop(swarm, { enabled: true })
304 | relay._dialPeer = sinon.stub()
305 | relay._dialPeer.callsArgWith(1, null, dstConn)
306 |
307 | done()
308 | })
309 | })
310 |
311 | after(() => relay._dialPeer.reset())
312 |
313 | describe('should correctly dial destination node', () => {
314 | let msg = {
315 | type: proto.CircuitRelay.Type.STOP,
316 | srcPeer: {
317 | id: Buffer.from(`QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`),
318 | addrs: [Buffer.from(`dsfsdfsdf`)]
319 | },
320 | dstPeer: {
321 | id: Buffer.from(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`),
322 | addrs: [Buffer.from(`sdflksdfndsklfnlkdf`)]
323 | }
324 | }
325 |
326 | before(() => {
327 | relay._circuit(
328 | new StreamHandler(srcConn),
329 | msg,
330 | (err) => {
331 | expect(err).to.not.exist()
332 | })
333 | })
334 |
335 | it('should respond with SUCCESS to source node', (done) => {
336 | lp.decodeFromReader(
337 | srcShake,
338 | (err, msg) => {
339 | expect(err).to.not.exist()
340 |
341 | const response = proto.CircuitRelay.decode(msg)
342 | expect(response.type).to.equal(proto.CircuitRelay.Type.STATUS)
343 | expect(response.code).to.equal(proto.CircuitRelay.Status.SUCCESS)
344 | done()
345 | })
346 | })
347 |
348 | it('should send STOP message to destination node', (done) => {
349 | lp.decodeFromReader(
350 | dstShake,
351 | (err, _msg) => {
352 | expect(err).to.not.exist()
353 |
354 | const response = proto.CircuitRelay.decode(_msg)
355 | expect(response.type).to.deep.equal(msg.type)
356 | expect(response.srcPeer).to.deep.equal(msg.srcPeer)
357 | expect(response.dstPeer).to.deep.equal(msg.dstPeer)
358 | done()
359 | })
360 | })
361 |
362 | it('should create circuit', (done) => {
363 | pull(
364 | values([proto.CircuitRelay.encode({
365 | type: proto.CircuitRelay.Type.STATUS,
366 | code: proto.CircuitRelay.Status.SUCCESS
367 | })]),
368 | lp.encode(),
369 | collect((err, encoded) => {
370 | expect(err).to.not.exist()
371 |
372 | encoded.forEach((e) => dstShake.write(e))
373 | pull(
374 | values([Buffer.from('hello')]),
375 | lp.encode(),
376 | collect((err, encoded) => {
377 | expect(err).to.not.exist()
378 |
379 | encoded.forEach((e) => srcShake.write(e))
380 | lp.decodeFromReader(
381 | dstShake,
382 | (err, _msg) => {
383 | expect(err).to.not.exist()
384 | expect(_msg.toString()).to.equal('hello')
385 |
386 | done()
387 | })
388 | })
389 | )
390 | })
391 | )
392 | })
393 | })
394 |
395 | describe('should fail creating circuit', () => {
396 | let msg = {
397 | type: proto.CircuitRelay.Type.STOP,
398 | srcPeer: {
399 | id: Buffer.from(`QmQWqGdndSpAkxfk8iyiJyz3XXGkrDNujvc8vEst3baubA`),
400 | addrs: [Buffer.from(`dsfsdfsdf`)]
401 | },
402 | dstPeer: {
403 | id: Buffer.from(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`),
404 | addrs: [Buffer.from(`sdflksdfndsklfnlkdf`)]
405 | }
406 | }
407 |
408 | it('should not create circuit', (done) => {
409 | relay._circuit(
410 | new StreamHandler(srcConn),
411 | msg,
412 | (err) => {
413 | expect(err).to.exist()
414 | expect(err).to.match(/Unable to create circuit!/)
415 | done()
416 | })
417 |
418 | pull(
419 | values([proto.CircuitRelay.encode({
420 | type: proto.CircuitRelay.Type.STATUS,
421 | code: proto.CircuitRelay.Status.STOP_RELAY_REFUSED
422 | })]),
423 | lp.encode(),
424 | collect((err, encoded) => {
425 | expect(err).to.not.exist()
426 |
427 | encoded.forEach((e) => dstShake.write(e))
428 | })
429 | )
430 | })
431 | })
432 | })
433 | })
434 |
--------------------------------------------------------------------------------
/test/listener.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict'
3 |
4 | const Listener = require('../src/listener')
5 | const nodes = require('./fixtures/nodes')
6 | const waterfall = require('async/waterfall')
7 | const PeerInfo = require('peer-info')
8 | const PeerId = require('peer-id')
9 | const multiaddr = require('multiaddr')
10 | const handshake = require('pull-handshake')
11 | const Connection = require('interface-connection').Connection
12 | const proto = require('../src/protocol')
13 | const lp = require('pull-length-prefixed')
14 | const pull = require('pull-stream/pull')
15 | const values = require('pull-stream/sources/values')
16 | const collect = require('pull-stream/sinks/collect')
17 | const multicodec = require('../src/multicodec')
18 |
19 | const chai = require('chai')
20 | const dirtyChai = require('dirty-chai')
21 | const expect = chai.expect
22 | chai.use(dirtyChai)
23 | const sinon = require('sinon')
24 |
25 | describe('listener', function () {
26 | describe(`listen`, function () {
27 | let swarm = null
28 | let handlerSpy = null
29 | let listener = null
30 | let stream = null
31 | let shake = null
32 | let conn = null
33 |
34 | beforeEach(function (done) {
35 | stream = handshake({ timeout: 1000 * 60 })
36 | shake = stream.handshake
37 | conn = new Connection(stream)
38 | conn.setPeerInfo(new PeerInfo(PeerId
39 | .createFromB58String('QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE')))
40 |
41 | waterfall([
42 | (cb) => PeerId.createFromJSON(nodes.node4, cb),
43 | (peerId, cb) => PeerInfo.create(peerId, cb),
44 | (peer, cb) => {
45 | swarm = {
46 | _peerInfo: peer,
47 | handle: sinon.spy((proto, h) => {
48 | handlerSpy = sinon.spy(h)
49 | }),
50 | conns: {
51 | QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE: new Connection()
52 | }
53 | }
54 |
55 | listener = Listener(swarm, {}, () => {})
56 | listener.listen()
57 | cb()
58 | }
59 | ], done)
60 | })
61 |
62 | afterEach(() => {
63 | listener = null
64 | })
65 |
66 | it(`should handle HOP`, function (done) {
67 | handlerSpy(multicodec.relay, conn)
68 |
69 | let relayMsg = {
70 | type: proto.CircuitRelay.Type.HOP,
71 | srcPeer: {
72 | id: `QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`,
73 | addrs: [`/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`]
74 | },
75 | dstPeer: {
76 | id: `QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`,
77 | addrs: [`/ipfs/QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`]
78 | }
79 | }
80 |
81 | listener.hopHandler.handle = (message, conn) => {
82 | expect(message.type).to.equal(proto.CircuitRelay.Type.HOP)
83 |
84 | expect(message.srcPeer.id.toString()).to.equal(relayMsg.srcPeer.id)
85 | expect(message.srcPeer.addrs[0].toString()).to.equal(relayMsg.srcPeer.addrs[0])
86 |
87 | expect(message.dstPeer.id.toString()).to.equal(relayMsg.dstPeer.id)
88 | expect(message.dstPeer.addrs[0].toString()).to.equal(relayMsg.dstPeer.addrs[0])
89 |
90 | done()
91 | }
92 |
93 | pull(
94 | values([proto.CircuitRelay.encode(relayMsg)]),
95 | lp.encode(),
96 | collect((err, encoded) => {
97 | expect(err).to.not.exist()
98 | encoded.forEach((e) => shake.write(e))
99 | })
100 | )
101 | })
102 |
103 | it(`should handle STOP`, function (done) {
104 | handlerSpy(multicodec.relay, conn)
105 |
106 | let relayMsg = {
107 | type: proto.CircuitRelay.Type.STOP,
108 | srcPeer: {
109 | id: `QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`,
110 | addrs: [`/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`]
111 | },
112 | dstPeer: {
113 | id: `QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`,
114 | addrs: [`/ipfs/QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`]
115 | }
116 | }
117 |
118 | listener.stopHandler.handle = (message, conn) => {
119 | expect(message.type).to.equal(proto.CircuitRelay.Type.STOP)
120 |
121 | expect(message.srcPeer.id.toString()).to.equal(relayMsg.srcPeer.id)
122 | expect(message.srcPeer.addrs[0].toString()).to.equal(relayMsg.srcPeer.addrs[0])
123 |
124 | expect(message.dstPeer.id.toString()).to.equal(relayMsg.dstPeer.id)
125 | expect(message.dstPeer.addrs[0].toString()).to.equal(relayMsg.dstPeer.addrs[0])
126 |
127 | done()
128 | }
129 |
130 | pull(
131 | values([proto.CircuitRelay.encode(relayMsg)]),
132 | lp.encode(),
133 | collect((err, encoded) => {
134 | expect(err).to.not.exist()
135 | encoded.forEach((e) => shake.write(e))
136 | })
137 | )
138 | })
139 |
140 | it(`should emit 'connection'`, function (done) {
141 | handlerSpy(multicodec.relay, conn)
142 |
143 | let relayMsg = {
144 | type: proto.CircuitRelay.Type.STOP,
145 | srcPeer: {
146 | id: `QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`,
147 | addrs: [`/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`]
148 | },
149 | dstPeer: {
150 | id: `QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`,
151 | addrs: [`/ipfs/QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`]
152 | }
153 | }
154 |
155 | listener.stopHandler.handle = (message, sh) => {
156 | const newConn = new Connection(sh.rest())
157 | listener.stopHandler.emit('connection', newConn)
158 | }
159 |
160 | listener.on('connection', (conn) => {
161 | expect(conn).to.be.instanceof(Connection)
162 | done()
163 | })
164 |
165 | pull(
166 | values([proto.CircuitRelay.encode(relayMsg)]),
167 | lp.encode(),
168 | collect((err, encoded) => {
169 | expect(err).to.not.exist()
170 | encoded.forEach((e) => shake.write(e))
171 | })
172 | )
173 | })
174 |
175 | it(`should handle CAN_HOP`, function (done) {
176 | handlerSpy(multicodec.relay, conn)
177 |
178 | let relayMsg = {
179 | type: proto.CircuitRelay.Type.CAN_HOP,
180 | srcPeer: {
181 | id: `QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`,
182 | addrs: [`/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`]
183 | },
184 | dstPeer: {
185 | id: `QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`,
186 | addrs: [`/ipfs/QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`]
187 | }
188 | }
189 |
190 | listener.hopHandler.handle = (message, conn) => {
191 | expect(message.type).to.equal(proto.CircuitRelay.Type.CAN_HOP)
192 |
193 | expect(message.srcPeer.id.toString()).to.equal(relayMsg.srcPeer.id)
194 | expect(message.srcPeer.addrs[0].toString()).to.equal(relayMsg.srcPeer.addrs[0])
195 |
196 | expect(message.dstPeer.id.toString()).to.equal(relayMsg.dstPeer.id)
197 | expect(message.dstPeer.addrs[0].toString()).to.equal(relayMsg.dstPeer.addrs[0])
198 |
199 | done()
200 | }
201 |
202 | pull(
203 | values([proto.CircuitRelay.encode(relayMsg)]),
204 | lp.encode(),
205 | collect((err, encoded) => {
206 | expect(err).to.not.exist()
207 | encoded.forEach((e) => shake.write(e))
208 | })
209 | )
210 | })
211 |
212 | it(`should handle invalid message correctly`, function (done) {
213 | handlerSpy(multicodec.relay, conn)
214 |
215 | let relayMsg = {
216 | type: 100000,
217 | srcPeer: {
218 | id: Buffer.from(`QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`),
219 | addrs: [multiaddr(`/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`).buffer]
220 | },
221 | dstPeer: {
222 | id: Buffer.from(`QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`),
223 | addrs: [multiaddr(`/ipfs/QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`).buffer]
224 | }
225 | }
226 |
227 | pull(
228 | values([Buffer.from([relayMsg])]),
229 | lp.encode(),
230 | collect((err, encoded) => {
231 | expect(err).to.not.exist()
232 | encoded.forEach((e) => shake.write(e))
233 | }),
234 | lp.decodeFromReader(shake, { maxLength: this.maxLength }, (err, msg) => {
235 | expect(err).to.not.exist()
236 | expect(proto.CircuitRelay.decode(msg).type).to.equal(proto.CircuitRelay.Type.STATUS)
237 | expect(proto.CircuitRelay.decode(msg).code).to.equal(proto.CircuitRelay.Status.MALFORMED_MESSAGE)
238 | done()
239 | })
240 | )
241 | })
242 | })
243 |
244 | describe(`getAddrs`, function () {
245 | let swarm = null
246 | let listener = null
247 | let peerInfo = null
248 |
249 | beforeEach(function (done) {
250 | waterfall([
251 | (cb) => PeerId.createFromJSON(nodes.node4, cb),
252 | (peerId, cb) => PeerInfo.create(peerId, cb),
253 | (peer, cb) => {
254 | swarm = {
255 | _peerInfo: peer
256 | }
257 |
258 | peerInfo = peer
259 | listener = Listener(swarm, {}, () => {})
260 | cb()
261 | }
262 | ], done)
263 | })
264 |
265 | afterEach(() => {
266 | peerInfo = null
267 | })
268 |
269 | it(`should return correct addrs`, function () {
270 | peerInfo.multiaddrs.add(`/ip4/0.0.0.0/tcp/4002`)
271 | peerInfo.multiaddrs.add(`/ip4/127.0.0.1/tcp/4003/ws`)
272 |
273 | listener.getAddrs((err, addrs) => {
274 | expect(err).to.not.exist()
275 | expect(addrs).to.deep.equal([
276 | multiaddr(`/p2p-circuit/ip4/0.0.0.0/tcp/4002/ipfs/QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`),
277 | multiaddr(`/p2p-circuit/ip4/127.0.0.1/tcp/4003/ws/ipfs/QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`)])
278 | })
279 | })
280 |
281 | it(`don't return default addrs in an explicit p2p-circuit addres`, function () {
282 | peerInfo.multiaddrs.add(`/ip4/127.0.0.1/tcp/4003/ws`)
283 | peerInfo.multiaddrs.add(`/p2p-circuit/ip4/0.0.0.0/tcp/4002`)
284 | listener.getAddrs((err, addrs) => {
285 | expect(err).to.not.exist()
286 | expect(addrs[0]
287 | .toString())
288 | .to.equal(`/p2p-circuit/ip4/0.0.0.0/tcp/4002/ipfs/QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`)
289 | })
290 | })
291 | })
292 | })
293 |
--------------------------------------------------------------------------------
/test/proto.spec.js:
--------------------------------------------------------------------------------
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 |
9 | const multiaddr = require('multiaddr')
10 |
11 | const proto = require('../src/protocol')
12 |
13 | describe('protocol', function () {
14 | let msgObject = null
15 | let message = null
16 |
17 | before(() => {
18 | msgObject = {
19 | type: proto.CircuitRelay.Type.HOP,
20 | srcPeer: {
21 | id: Buffer.from('QmSource'),
22 | addrs: [
23 | multiaddr('/p2p-circuit/ipfs/QmSource').buffer,
24 | multiaddr('/p2p-circuit/ip4/0.0.0.0/tcp/9000/ipfs/QmSource').buffer,
25 | multiaddr('/ip4/0.0.0.0/tcp/9000/ipfs/QmSource').buffer
26 | ]
27 | },
28 | dstPeer: {
29 | id: Buffer.from('QmDest'),
30 | addrs: [
31 | multiaddr('/p2p-circuit/ipfs/QmDest').buffer,
32 | multiaddr('/p2p-circuit/ip4/1.1.1.1/tcp/9000/ipfs/QmDest').buffer,
33 | multiaddr('/ip4/1.1.1.1/tcp/9000/ipfs/QmDest').buffer
34 | ]
35 | }
36 | }
37 |
38 | let buff = proto.CircuitRelay.encode(msgObject)
39 | message = proto.CircuitRelay.decode(buff)
40 | })
41 |
42 | it(`should source and dest`, () => {
43 | expect(message.srcPeer).to.deep.equal(msgObject.srcPeer)
44 | expect(message.dstPeer).to.deep.equal(msgObject.dstPeer)
45 | })
46 |
47 | it(`should encode message`, () => {
48 | expect(message.message).to.deep.equal(msgObject.message)
49 | })
50 | })
51 |
--------------------------------------------------------------------------------
/test/stop.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict'
3 |
4 | const Stop = require('../src/circuit/stop')
5 | const nodes = require('./fixtures/nodes')
6 | const Connection = require('interface-connection').Connection
7 | const handshake = require('pull-handshake')
8 | const waterfall = require('async/waterfall')
9 | const PeerInfo = require('peer-info')
10 | const PeerId = require('peer-id')
11 | const StreamHandler = require('../src/circuit/stream-handler')
12 | const proto = require('../src/protocol')
13 |
14 | const chai = require('chai')
15 | const dirtyChai = require('dirty-chai')
16 | const expect = chai.expect
17 | chai.use(dirtyChai)
18 |
19 | describe('stop', function () {
20 | describe(`handle relayed connections`, function () {
21 | let stopHandler
22 |
23 | let swarm
24 | let conn
25 | let stream
26 |
27 | beforeEach(function (done) {
28 | stream = handshake({ timeout: 1000 * 60 })
29 | conn = new Connection(stream)
30 | const peerId = PeerId.createFromB58String('QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE')
31 | conn.setPeerInfo(new PeerInfo(peerId))
32 |
33 | waterfall([
34 | (cb) => PeerId.createFromJSON(nodes.node4, cb),
35 | (peerId, cb) => PeerInfo.create(peerId, cb),
36 | (peer, cb) => {
37 | peer.multiaddrs.add('/p2p-circuit/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE')
38 | swarm = {
39 | _peerInfo: peer,
40 | conns: {
41 | QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE: new Connection()
42 | }
43 | }
44 |
45 | stopHandler = new Stop(swarm)
46 | cb()
47 | }
48 | ], done)
49 | })
50 |
51 | it(`handle request with a valid multiaddr`, function (done) {
52 | stopHandler.handle({
53 | type: proto.CircuitRelay.Type.STOP,
54 | srcPeer: {
55 | id: `QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`,
56 | addrs: [`/ipfs/QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`]
57 | },
58 | dstPeer: {
59 | id: `QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`,
60 | addrs: [`/ipfs/QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`]
61 | }
62 | }, new StreamHandler(conn), (conn) => { // multistream handler doesn't expect errors...
63 | expect(conn).to.be.instanceOf(Connection)
64 | done()
65 | })
66 | })
67 |
68 | it(`handle request with invalid multiaddr`, function (done) {
69 | stopHandler.handle({
70 | type: proto.CircuitRelay.Type.STOP,
71 | srcPeer: {
72 | id: `QmSswe1dCFRepmhjAMR5VfHeokGLcvVggkuDJm7RMfJSrE`,
73 | addrs: [`dsfsdfsdf`]
74 | },
75 | dstPeer: {
76 | id: `QmQvM2mpqkjyXWbTHSUidUAWN26GgdMphTh9iGDdjgVXCy`,
77 | addrs: [`sdflksdfndsklfnlkdf`]
78 | }
79 | }, new StreamHandler(conn), (conn) => {
80 | expect(conn).to.not.exist()
81 | done()
82 | })
83 | })
84 | })
85 | })
86 |
--------------------------------------------------------------------------------