Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions doc/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [Customizing DHT](#customizing-dht)
- [Setup with Content and Peer Routing](#setup-with-content-and-peer-routing)
- [Setup with Relay](#setup-with-relay)
- [Setup with Auto Relay](#setup-with-auto-relay)
- [Setup with Keychain](#setup-with-keychain)
- [Configuring Dialing](#configuring-dialing)
- [Configuring Connection Manager](#configuring-connection-manager)
Expand Down Expand Up @@ -419,6 +420,37 @@ const node = await Libp2p.create({
hop: {
enabled: true, // Allows you to be a relay for other peers
active: true // You will attempt to dial destination peers if you are not connected to them
},
advertise: {
bootDelay: 15 * 60 * 1000, // Delay before HOP relay service is advertised on the network
enabled: true, // Allows you to disable the advertise of the Hop service
ttl: 30 * 60 * 1000 // Delay Between HOP relay service advertisements on the network
}
}
}
})
```

#### Setup with Auto Relay

```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')

const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
},
config: {
relay: { // Circuit Relay options (this config is part of libp2p core configurations)
enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay.
autoRelay: {
enabled: true, // Allows you to bind to relays with HOP enabled for improving node dialability
maxListeners: 2 // Configure maximum number of HOP relays to use
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"aggregate-error": "^3.0.1",
"any-signal": "^1.1.0",
"bignumber.js": "^9.0.0",
"cids": "^1.0.0",
"class-is": "^1.1.0",
"debug": "^4.1.1",
"err-code": "^2.0.0",
Expand All @@ -60,12 +61,13 @@
"it-protocol-buffers": "^0.2.0",
"libp2p-crypto": "^0.18.0",
"libp2p-interfaces": "^0.5.1",
"libp2p-utils": "^0.2.0",
"libp2p-utils": "^0.2.1",
"mafmt": "^8.0.0",
"merge-options": "^2.0.0",
"moving-average": "^1.0.0",
"multiaddr": "^8.0.0",
"multicodec": "^2.0.0",
"multihashing-async": "^2.0.1",
"multistream-select": "^1.0.0",
"mutable-proxy": "^1.0.0",
"node-forge": "^0.9.1",
Expand All @@ -89,7 +91,6 @@
"chai-as-promised": "^7.1.1",
"chai-bytes": "^0.1.2",
"chai-string": "^1.5.0",
"cids": "^1.0.0",
"delay": "^4.3.0",
"dirty-chai": "^2.0.1",
"interop-libp2p": "^0.3.0",
Expand Down
82 changes: 69 additions & 13 deletions src/circuit/auto-relay.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@ const debug = require('debug')
const log = debug('libp2p:auto-relay')
log.error = debug('libp2p:auto-relay:error')

const isPrivate = require('libp2p-utils/src/multiaddr/is-private')

const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')

const { relay: multicodec } = require('./multicodec')
const { canHop } = require('./circuit/hop')

const circuitProtoCode = 290
const hopMetadataKey = 'hop_relay'
const hopMetadataValue = 'true'
const { namespaceToCid } = require('./utils')
const {
CIRCUIT_PROTO_CODE,
HOP_METADATA_KEY,
HOP_METADATA_VALUE,
RELAY_RENDEZVOUS_NS
} = require('./constants')

class AutoRelay {
/**
Expand Down Expand Up @@ -76,15 +81,15 @@ class AutoRelay {
const connection = this._connectionManager.get(peerId)

// Do not hop on a relayed connection
if (connection.remoteAddr.protoCodes().includes(circuitProtoCode)) {
if (connection.remoteAddr.protoCodes().includes(CIRCUIT_PROTO_CODE)) {
log(`relayed connection to ${id} will not be used to hop on`)
return
}

const supportsHop = await canHop({ connection })

if (supportsHop) {
this._peerStore.metadataBook.set(peerId, hopMetadataKey, uint8ArrayFromString(hopMetadataValue))
this._peerStore.metadataBook.set(peerId, HOP_METADATA_KEY, uint8ArrayFromString(HOP_METADATA_VALUE))
await this._addListenRelay(connection, id)
}
} catch (err) {
Expand Down Expand Up @@ -125,15 +130,25 @@ class AutoRelay {
}

// Create relay listen addr
let listenAddr, remoteMultiaddr
let listenAddr, remoteMultiaddr, remoteAddrs

try {
const remoteAddrs = this._peerStore.addressBook.get(connection.remotePeer)
// TODO: HOP Relays should avoid advertising private addresses!
// Get peer known addresses and sort them per public addresses first
remoteAddrs = this._peerStore.addressBook.get(connection.remotePeer)
// TODO: This sort should be customizable in the config (dialer addr sort)
remoteAddrs.sort(multiaddrsCompareFunction)

remoteMultiaddr = remoteAddrs.find(a => a.isCertified).multiaddr // Get first announced address certified
// TODO: HOP Relays should avoid advertising private addresses!
} catch (_) {
log.error(`${id} does not have announced certified multiaddrs`)
return

// Attempt first if existing
if (!remoteAddrs || !remoteAddrs.length) {
return
}

remoteMultiaddr = remoteAddrs[0].multiaddr
Copy link
Member Author

@vasco-santos vasco-santos Sep 17, 2020

Choose a reason for hiding this comment

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

While there is no support for signed peer records via delegate nodes (HTTP API), we need to fallback here

Copy link
Contributor

Choose a reason for hiding this comment

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

This address selection logic isn't great. If we don't have a certified record we might just be taking their private address if it's first (which I am concerned could be pretty common). The known public relays won't have this problem because they don't advertise private addresses, but we should be doing some level of sorting here to get the "best" address

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a sorter. In follow up PRs, I will add the support for custom sort function for dial and custom filter for announce. Then, it will be bubbled up to here. But I wanted to keep things simple in this PR

}

if (!remoteMultiaddr.protoNames().includes('p2p')) {
Expand Down Expand Up @@ -194,10 +209,10 @@ class AutoRelay {
continue
}

const supportsHop = metadataMap.get(hopMetadataKey)
const supportsHop = metadataMap.get(HOP_METADATA_KEY)

// Continue to next if it does not support Hop
if (!supportsHop || uint8ArrayToString(supportsHop) !== hopMetadataValue) {
if (!supportsHop || uint8ArrayToString(supportsHop) !== HOP_METADATA_VALUE) {
continue
}

Expand Down Expand Up @@ -229,8 +244,49 @@ class AutoRelay {
}
}

// TODO: Try to find relays to hop on the network
// Try to find relays to hop on the network
try {
const cid = await namespaceToCid(RELAY_RENDEZVOUS_NS)
for await (const provider of this._libp2p.contentRouting.findProviders(cid)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't have support to do this right now, but it would be great to actually prioritize the results of this based on latency of the connection. Worth adding an issue for a future enhancement here I think

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that would be good! I can create an issue for that. We should get that after the libp2p.discovery API, also with the rendezvous

Copy link
Member Author

Choose a reason for hiding this comment

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

if (!provider.multiaddrs.length) {
continue
}
const peerId = provider.id

this._peerStore.addressBook.add(peerId, provider.multiaddrs)
const connection = await this._libp2p.dial(peerId)

await this._addListenRelay(connection, peerId.toB58String())

// Check if already listening on enough relays
if (this._listenRelays.size >= this.maxListeners) {
return
}
}
} catch (err) {
log.error(err)
}
}
}

/**
* Compare function for array.sort().
* This sort aims to move the private adresses to the end of the array.
*
* @param {Address} a
* @param {Address} b
* @returns {number}
*/
function multiaddrsCompareFunction (a, b) {
const isAPrivate = isPrivate(a.multiaddr)
const isBPrivate = isPrivate(b.multiaddr)

if (isAPrivate && !isBPrivate) {
return 1
} else if (!isAPrivate && isBPrivate) {
return -1
}
return 0
}

module.exports = AutoRelay
12 changes: 12 additions & 0 deletions src/circuit/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict'

const minute = 60 * 1000

module.exports = {
ADVERTISE_BOOT_DELAY: 15 * minute, // Delay before HOP relay service is advertised on the network
ADVERTISE_TTL: 30 * minute, // Delay Between HOP relay service advertisements on the network
CIRCUIT_PROTO_CODE: 290, // Multicodec code
HOP_METADATA_KEY: 'hop_relay', // PeerStore metadaBook key for HOP relay service
HOP_METADATA_VALUE: 'true', // PeerStore metadaBook value for HOP relay service
RELAY_RENDEZVOUS_NS: '/libp2p/relay' // Relay HOP relay service namespace for discovery
}
Loading