Skip to content

Commit

Permalink
feat: address manager
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos committed Apr 23, 2020
1 parent 1a344be commit 50e0b11
Show file tree
Hide file tree
Showing 24 changed files with 491 additions and 60 deletions.
41 changes: 41 additions & 0 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
* [`handle`](#handle)
* [`unhandle`](#unhandle)
* [`ping`](#ping)
* [`addressManager.listen`](#addressManagerlisten)
* [`addressManager.announce`](#addressManagerannounce)
* [`addressManager.noAnnounce`](#addressManagernoannounce)
* [`peerRouting.findPeer`](#peerroutingfindpeer)
* [`contentRouting.findProviders`](#contentroutingfindproviders)
* [`contentRouting.provide`](#contentroutingprovide)
Expand Down Expand Up @@ -357,6 +360,44 @@ Pings a given peer and get the operation's latency.
const latency = await libp2p.ping(otherPeerId)
```

### addressManager.listen

Getter for getting the addresses that the peer is using for listening on libp2p transports.

`libp2p.addressManager.listen`

#### Example

```js
// ...
const listenAddresses = libp2p.addressManager.listen
// [ <Multiaddr 047f00000106f9ba - /ip4/127.0.0.1/tcp/63930> ]
```

### addressManager.announce

Getter for getting the addresses that the peer is announcing to other peers in the network.

`libp2p.addressManager.announce`

```js
// ...
const announceAddresses = libp2p.addressManager.announce
// [ <Multiaddr 047f00000106f9ba - /dns4/peer.io/...> ]
```

### addressManager.noAnnounce

Getter for getting the addresses that the peer is not announcing in the network.

`libp2p.addressManager.noAnnounce`

```js
// ...
const noAnnounceAddresses = libp2p.addressManager.noAnnounce
// [ <Multiaddr 047f00000106f9ba - /ip4/127.0.0.1/tcp/63930> ]
```

### peerRouting.findPeer

Iterates over all peer routers in series to find the given peer. If the DHT is enabled, it will be tried first.
Expand Down
2 changes: 2 additions & 0 deletions doc/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ Besides the `modules` and `config`, libp2p allows other internal options and con
- `peerInfo`: a previously created instance of [libp2p/js-peer-info](https://github.com/libp2p/js-peer-info).
- This is particularly useful if you want to reuse the same `peer-id`, as well as for modules like `libp2p-delegated-content-routing`, which need a `peer-id` in their instantiation.

TODO: Add listen/announce addresses and remove peerInfo!!

### Examples

#### Basic setup
Expand Down
49 changes: 49 additions & 0 deletions src/address-manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Address Manager

The Address manager is responsible for keeping an updated register of the peer's addresses. It includes 3 different types of Addresses: `Listen Addresses`, `Announce Addresses` and `No Announce Addresses`.

These Addresses should be specified in your libp2p [configuration](../../doc/CONFIGURATION.md) when you create your node.

## Listen Addresses

A libp2p node should have a set of listen addresses, which will be used by libp2p underlying transports to listen for dials from other nodes in the network.

Before a libp2p node starts, a set of listen addresses should be provided to the AddressManager, so that when the node is started, the libp2p transports can use them to listen for connections. Accordingly, listen addresses should be specified through the libp2p configuration, in order to have the `AddressManager` created with them.

It is important pointing out that libp2p accepts to listen on addresses that intend to rely on any available local port. In this context, once a transport selects a port to listen, the listen addresses will need to be updated. For example tcp may replace `/ip4/0.0.0.0/tcp/0` with something like `/ip4/0.0.0.0/tcp/8989`.

## Announce Addresses

In some scenarios, a libp2p node will need to announce addresses that it is not listening on. In other words, Announce Addresses are an amendment to the Listen Addresses that aim to enable other nodes to achieve connectivity to this node.

Scenarios for Announce Addresses include:
- when you setup a libp2p node in your private network at home, but you need to announce your public IP Address to the outside world;
- when you want to announce a DNS address, which maps to your public IP Address.

## No Announce Addresses

While we need to add Announce Addresses to enable peers' connectivity, we can also not announce addresses that will not be reachable. This way, No Announce Addresses should be specified so that they are not announced by the peer as addresses that other peers can use to dial it.

As stated into the Listen Addresses section, Listen Addresses might get modified after libp2p transports get in action and use them to listen for new connections. This way, No Announce Addresses should also take into account these changes so that they can be matched when No Announce Addresses are being filtered out.

## Implementation

Once a libp2p node is started, the Transport Manager component will gather the addresses that the configured libp2p transports should use to listen on. As soon as this node has its transports listening, the Transport Manager should replace the listen addresses with the updated list.

After the transports being started, other libp2p components/subsystems will kickoff, namely the Identify Service and the DHT. Both of them will announce the node addresses to the other peers in the network and should gather these addresses from the Address Manager.

## Future Considerations

### Dynamic address modifications

In a future iteration, we can enable these addresses to be modified in runtime. For this, the Address Manager should be responsible for notifying interested subsystems of these changes, through an Event Emitter.

#### Modify Listen Addresses

While adding new addresses to listen on runtime is a feasible operation, removing one listen address might have bad implications for the node, since all the connections using that listen address will be closed. With this in mind and taking also into consideration the lack of good use cases for removing listen addresses, the Address Manager API only allows libp2p users to add new Listen Addresses on runtime.

Every time a new listen address is added, the Address Manager should emit an event with the new multiaddrs to listen. The Transport Manager should listen to this events and act accordingly.

#### Modify Announce Addresses

When the announce addresses are modified, the Address Manager should emit an event so that other subsystems can act accordingly. For example, libp2p identify service should use the libp2p push protocol to inform other peers about these changes.
73 changes: 73 additions & 0 deletions src/address-manager/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict'

const debug = require('debug')
const log = debug('libp2p:addresses')
log.error = debug('libp2p:addresses:error')

const MultiaddrSet = require('./multiaddr-set')

/**
* Responsible for managing known peers, as well as their addresses, protocols and metadata.
*/
class AddressManager {
/**
* @constructor
* @param {object} [options]
* @param {Array<string>} [options.listen = []] list of multiaddrs string representation to listen.
* @param {Array<string>} [options.announce = []] list of multiaddrs string representation to announce.
* @param {Array<string>} [options.noAnnounce = []] list of multiaddrs string representation to not announce.
*/
constructor ({ listen = [], announce = [], noAnnounce = [] } = {}) {
this._listen = new MultiaddrSet(listen)
this._announce = new MultiaddrSet(announce)
this._noAnnounce = new MultiaddrSet(noAnnounce)
}

/**
* Get peer addresses to listen.
* @return {Array<Multiaddr>}
*/
get listen () {
return this._listen.toArray()
}

/**
* Get peer addresses to announce.
* @return {Array<Multiaddr>}
*/
get announce () {
return this._listen.toArray()
.concat(this._announce.toArray()) // Concat announce amendment
.filter((ma) => !this._noAnnounce.has(ma)) // Remove no announce addresses
}

/**
* Get peer addresses to not announce.
* @return {Array<Multiaddr>}
*/
get noAnnounce () {
return this._noAnnounce.toArray()
}

/**
* Replace listen multiaddrs after trying to listen the original ones by the transports.
* This should also forward the replace side effects to the NoAnnounce sets.
* @param {Array<Multiaddr>} newMultiaddrs
* @return {Array<Multiaddr>}
*/
_replaceListen (newMultiaddrs) {
const currentListen = this._listen.toArray()

this._listen.replace(currentListen, newMultiaddrs)
const newListen = this._listen.toArray()

// TODO: Update noAnnounce on replace (new Port?) or improve the filter operation in announce getter?
// Note: noAnnounce should take into account encapsulated addresses.
// - For example, if I add /ip4/127.0.0.1 to noAnnounce
// - I expect addresses like /ip4/127.0.0.1/tcp/8080/ipfs/Qm to be filtered out.

return newListen
}
}

module.exports = AddressManager
88 changes: 88 additions & 0 deletions src/address-manager/multiaddr-set.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use strict'

const errCode = require('err-code')

const multiaddr = require('multiaddr')

const { codes } = require('../errors')

/**
* Multiaddr Set.
* Since JavaScript doesn't let you overload the compare in Set()..
*/
class MultiaddrSet {
/**
* @constructor
* @param {Array<string>} addresses addresses to store.
*/
constructor (addresses = []) {
this._multiaddrs = addresses.map(a => multiaddr(a))
}

get size () {
return this._multiaddrs.length
}

/**
* Add multiaddr to the set.
* @param {Multiaddr} ma
*/
add (ma) {
if (!multiaddr.isMultiaddr(ma)) {
throw errCode(new Error('Invalid multiaddr provided'), codes.ERR_INVALID_MULTIADDR)
}

if (!this.has(ma)) {
this._multiaddrs.push(ma)
}
}

/**
* Get array of multiaddrs.
* @return {Array<Multiaddr>}
*/
toArray () {
return this._multiaddrs.slice()
}

forEach (fn) {
return this._multiaddrs.forEach(fn)
}

/**
* Has provided multiaddr.
* @param {Multiaddr} ma
* @return {boolean}
*/
has (ma) {
return this._multiaddrs.some((m) => m.equals(ma))
}

delete (ma) {
this._multiaddrs.some((m, i) => {
if (m.equals(ma)) {
this._multiaddrs.splice(i, 1)
return true
}
})
}

/**
* Replace given multiaddrs (if existing) by fresh ones
* @param {Array<Multiaddr>} existing
* @param {Array<Multiaddr>} fresh
*/
replace (existing, fresh) {
existing.forEach((m) => this.delete(m))
fresh.forEach((m) => this.add(m))
}

/**
* Clear set,
*/
clear () {
this._multiaddrs = []
}
}

module.exports = MultiaddrSet
4 changes: 2 additions & 2 deletions src/circuit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Circuit {
this._registrar = libp2p.registrar
this._upgrader = upgrader
this._options = libp2p._config.relay
this.addresses = libp2p.addresses
this.addressManager = libp2p.addressManager
this.peerId = libp2p.peerId
this._registrar.handle(multicodec, this._onProtocol.bind(this))
}
Expand Down Expand Up @@ -121,7 +121,7 @@ class Circuit {
type: CircuitPB.Type.HOP,
srcPeer: {
id: this.peerId.toBytes(),
addrs: this.addresses.listen.map(addr => addr.buffer)
addrs: this.addressManager.listen.map(addr => addr.buffer)
},
dstPeer: {
id: destinationPeer.toBytes(),
Expand Down
4 changes: 3 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ const Constants = require('./constants')

const DefaultConfig = {
addresses: {
listen: []
listen: [],
announce: [],
noAnnounce: []
},
connectionManager: {
minPeers: 25
Expand Down
12 changes: 7 additions & 5 deletions src/identify/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class IdentifyService {
* @param {Registrar} options.registrar
* @param {Map<string, handler>} options.protocols A reference to the protocols we support
* @param {PeerId} options.peerId The peer running the identify service
* @param {{ listen: Array<Multiaddr>}} options.addresses The peer aaddresses
* @param {AddressManager} options.addressManager The peer address Manager
*/
constructor (options) {
/**
Expand All @@ -59,8 +59,10 @@ class IdentifyService {
* @property {PeerId}
*/
this.peerId = options.peerId

this.addresses = options.addresses || {}
/**
* @property {AddressManager}
*/
this.addressManager = options.addressManager

this._protocols = options.protocols

Expand All @@ -79,7 +81,7 @@ class IdentifyService {

await pipe(
[{
listenAddrs: this.addresses.listen.map((ma) => ma.buffer),
listenAddrs: this.addressManager.announce.map((ma) => ma.buffer),
protocols: Array.from(this._protocols.keys())
}],
pb.encode(Message),
Expand Down Expand Up @@ -204,7 +206,7 @@ class IdentifyService {
protocolVersion: PROTOCOL_VERSION,
agentVersion: AGENT_VERSION,
publicKey,
listenAddrs: this.addresses.listen.map((ma) => ma.buffer),
listenAddrs: this.addressManager.announce.map((ma) => ma.buffer),
observedAddr: connection.remoteAddr.buffer,
protocols: Array.from(this._protocols.keys())
})
Expand Down
Loading

0 comments on commit 50e0b11

Please sign in to comment.