Skip to content
This repository has been archived by the owner on Mar 14, 2023. It is now read-only.

Commit

Permalink
feat: add implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos committed Nov 30, 2020
1 parent 5eed64e commit 7171f76
Show file tree
Hide file tree
Showing 5 changed files with 381 additions and 6 deletions.
117 changes: 115 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,115 @@
# js-libp2p-hop-relay-server
A out of the box libp2p relay server with HOP
# js-libp2p-hop-relay-server <!-- omit in toc -->

[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
[![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
[![](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/libp2p/js-libp2p-hop-relay-server/ci?label=ci&style=flat-square)](https://github.com/libp2p/js-libp2p-hop-relay-server/actions?query=branch%3Amaster+workflow%3Aci+)

> An out of the box libp2p relay server with HOP
## Lead Maintainer <!-- omit in toc -->

[Vasco Santos](https://github.com/vasco-santos)

## Table of Contents<!-- omit in toc -->

- [Background](#background)
- [Usage](#usage)
- [Install](#install)
- [CLI](#cli)
- [Docker](#docker)
- [Contribute](#contribute)
- [License](#license)

## Background

Libp2p nodes acting as circuit relay aim to establish connectivity between libp2p nodes (e.g. IPFS nodes) that wouldn't otherwise be able to establish a direct connection to each other.

A relay is needed in situations where nodes are behind NAT, reverse proxies, firewalls and/or simply don't support the same transports (e.g. go-libp2p vs. browser-libp2p). The circuit relay protocol exists to overcome those scenarios. Nodes with the `auto-relay` feature enabled can automatically bind themselves on a relay to listen for connections on their behalf.

You can read more in its [SPEC](https://github.com/libp2p/specs/tree/master/relay).

## Usage

### Install

```bash
> npm install --global libp2p-hop-relay-server
```

Now you can use the cli command `libp2p-hop-relay-server` to spawn an out of the box libp2p hop relay server.

### CLI

After installing the relay server, you can use its binary. It accepts several arguments: `--peerId`, `--listenMultiaddrs`, `--announceMultiaddrs`, `--metricsMultiaddr`, `--disableMetrics`, `--delegateMultiaddr` and `--disableAdvertise`.

```sh
libp2p-hop-relay-server [--peerId <jsonFilePath>] [--listenMultiaddrs <ma> ... <ma>] [--announceMultiaddrs <ma> ... <ma>] [--metricsMultiaddr <ma>] [--disableMetrics] [--delegateMultiaddr <ma>] [--disableAdvertise]
```

#### PeerId

You can create a [PeerId](https://github.com/libp2p/js-peer-id) via its [CLI](https://github.com/libp2p/js-peer-id#cli).

```sh
libp2p-hop-relay-server --peerId id.json
```

#### Multiaddrs

You can specify the libp2p rendezvous server listen and announce multiaddrs. This server is configured with [libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp) and [libp2p-websockets](https://github.com/libp2p/js-libp2p-websockets) and addresses with this transports should be used. It can always be modified via the API.

```sh
libp2p-hop-relay-server --peerId id.json --listenMultiaddrs '/ip4/127.0.0.1/tcp/15002/ws' '/ip4/127.0.0.1/tcp/8000' --announceMultiaddrs '/dns4/test.io/tcp/443/wss/p2p/12D3KooWAuEpJKhCAfNcHycKcZCv9Qy69utLAJ3MobjKpsoKbrGA' '/dns6/test.io/tcp/443/wss/p2p/12D3KooWAuEpJKhCAfNcHycKcZCv9Qy69utLAJ3MobjKpsoKbrGA'
```

By default it listens on `/ip4/127.0.0.1/tcp/15002/ws` and has no announce multiaddrs specified.

#### Metrics

Metrics are enabled by default on `/ip4/127.0.0.1/tcp/8003` via Prometheus. This address can also be modified with:

```sh
libp2p-hop-relay-server --metricsMultiaddr '/ip4/127.0.0.1/tcp/8000'
```

Moreover, metrics can also be disabled with:

```sh
libp2p-hop-relay-server --disableMetrics
```

#### Advertise

The relay server will advertise its HOP capability by default, using a delegate node on `/dns4/node0.delegate.ipfs.io/tcp/443/https`. This is important for peers that will try to find HOP relays on the network to bind themselves.

This advertise can be disabled with:

```sh
libp2p-hop-relay-server --disableAdvertise
```

You can also customize the delegate node to use with:

```sh
libp2p-hop-relay-server --delegateMultiaddr '/dns4/node1.delegate.ipfs.io/tcp/443/https'
```

Note: In the future this will leverage libp2p's [DHT](https://github.com/libp2p/js-libp2p-kad-dht).

### Docker

TODO

## Contribute

Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-hop-relay-server/issues)!

This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).

[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md)

## License

MIT - Protocol Labs 2020
45 changes: 41 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,58 @@
"name": "libp2p-hop-relay-server",
"version": "0.0.0",
"description": "A out of the box libp2p relay server with HOP",
"leadMaintainer": "Vasco Santos <santos.vasco10@gmail.com>",
"main": "src/index.js",
"bin": {
"libp2p-hop-relay-server": "src/bin.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"lint": "aegir lint",
"test": "aegir test",
"test:node": "aegir test -t node",
"test:browser": "aegir test -t browser",
"test:types": "aegir ts -p check",
"build": "aegir build",
"release": "aegir release",
"release-minor": "aegir release --type minor",
"release-major": "aegir release --type major",
"docs": "aegir docs",
"size": "aegir build -b"
},
"files": [
"src",
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/vasco-santos/js-libp2p-hop-relay-server.git"
},
"keywords": [
"libp2p"
"libp2p",
"relay",
"auto relay",
"hop"
],
"author": "",
"author": "Vasco Santos <santos.vasco10@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/vasco-santos/js-libp2p-hop-relay-server/issues"
},
"homepage": "https://github.com/vasco-santos/js-libp2p-hop-relay-server#readme"
"homepage": "https://github.com/vasco-santos/js-libp2p-hop-relay-server#readme",
"dependencies": {
"ipfs-http-client": "^48.1.2",
"libp2p": "libp2p/js-libp2p#0.30.x",
"libp2p-delegated-content-routing": "^0.8.0",
"libp2p-mplex": "^0.10.1",
"libp2p-noise": "^2.0.1",
"libp2p-tcp": "^0.15.1",
"libp2p-websockets": "^0.14.0",
"menoetius": "0.0.2",
"minimist": "^1.2.5",
"multiaddr": "^8.1.1",
"peer-id": "^0.14.2"
},
"devDependencies": {
"aegir": "^29.1.0"
}
}
105 changes: 105 additions & 0 deletions src/bin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env node

'use strict'

// Usage: $0 [--peerId <jsonFilePath>] [--listenMultiaddrs <ma> ... <ma>] [--announceMultiaddrs <ma> ... <ma>]
// [--metricsMultiaddr <ma>] [--disableMetrics] [--delegateMultiaddr <ma>] [--disableAdvertise]

/* eslint-disable no-console */

const debug = require('debug')
const log = debug('libp2p:hop-relay:bin')

const fs = require('fs')
const http = require('http')
const menoetius = require('menoetius')
const argv = require('minimist')(process.argv.slice(2))

const multiaddr = require('multiaddr')
const PeerId = require('peer-id')

const { getAnnounceAddresses, getListenAddresses } = require('./utils')
const createRelay = require('./index')

async function main () {
// Metrics
let metricsServer
const metrics = !(argv.disableMetrics)
const metricsMa = multiaddr(argv.metricsMultiaddr || argv.ma || '/ip4/127.0.0.1/tcp/8003')
const metricsAddr = metricsMa.nodeAddress()

// multiaddrs
const listenAddresses = getListenAddresses(argv)
const announceAddresses = getAnnounceAddresses(argv)

// Should advertise
const shouldAdvertise = !(argv.disableAdvertise)

// Delegate
let delegateOptions
if (argv.delegateMultiaddr || argv.dm) {
const delegateAddr = multiaddr(argv.delegateMultiaddr || argv.dm).toOptions()
delegateOptions = {
host: delegateAddr.host,
protocol: delegateAddr.port === '443' ? 'https' : 'http',
port: delegateAddr.port
}
}

// PeerId
let peerId
if (argv.peerId) {
const peerData = fs.readFileSync(argv.peerId)
peerId = await PeerId.createFromJSON(JSON.parse(peerData))
} else {
peerId = await PeerId.create()
log('You are using an automatically generated peer.')
log('If you want to keep the same address for the server you should provide a peerId with --peerId <jsonFilePath>')
}

// Create Relay
const relay = await createRelay({
peerId,
listenAddresses,
announceAddresses,
shouldAdvertise,
delegateOptions
})

relay.peerStore.on('change:multiaddrs', ({ peerId: changedPeerId, multiaddrs }) => {
if (peerId.equals(changedPeerId)) {
console.log('Relay server listening on:')
multiaddrs.forEach((m) => console.log(m))
}
})

await relay.start()

if (metrics) {
log('enabling metrics')
metricsServer = http.createServer((req, res) => {
if (req.url !== '/metrics') {
res.statusCode = 200
res.end()
}
})

menoetius.instrument(metricsServer)

metricsServer.listen(metricsAddr.port, metricsAddr.address, () => {
console.log(`metrics server listening on ${metricsAddr.port}`)
})
}

const stop = async () => {
console.log('Stopping...')
await relay.stop()
metricsServer && await metricsServer.close()
process.exit(0)
}

process.on('SIGTERM', stop)
process.on('SIGINT', stop)
}

main()
79 changes: 79 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict'

const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const Websockets = require('libp2p-websockets')
const Muxer = require('libp2p-mplex')
const { NOISE: Crypto } = require('libp2p-noise')
const DelegatedContentRouting = require('libp2p-delegated-content-routing')
const ipfsHttpClient = require('ipfs-http-client')

/**
* @typedef {import('peer-id')} PeerId
*/

/**
* @typedef {Object} DelegateOptions
* @property {string} host
* @property {string} protocol
* @property {number} port
*/

const defaulDelegateOptions = {
host: 'node0.delegate.ipfs.io',
protocol: 'https',
port: 443
}

/**
* @typedef {Object} HopRelayOptions
* @property {PeerId} peerId
* @property {DelegateOptions} [delegateOptions = defaulDelegateOptions]
* @property {string[]} [listenAddresses = []]
* @property {string[]} [announceAddresses = []]
* @property {boolean} [shouldAdvertise = true]
*/

/**
* Create a Libp2p Relay with HOP service
*
* @param {HopRelayOptions} options
* @returns {Promise<Libp2p>}
*/
function create ({ peerId, delegateOptions = defaulDelegateOptions, listenAddresses = [], announceAddresses = [], shouldAdvertise = true }) {
let contentRouting = []

if (shouldAdvertise) {
const httpClient = ipfsHttpClient(delegateOptions)
contentRouting.push(new DelegatedContentRouting(peerId, httpClient))
}

return Libp2p.create({
peerId,
modules: {
transport: [Websockets, TCP],
streamMuxer: [Muxer],
connEncryption: [Crypto],
contentRouting
},
peerId,
addresses: {
listen: listenAddresses,
announce: announceAddresses
},
config: {
relay: {
enabled: true, // Allows you to dial and accept relayed connections. Does not make you a relay.
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: {
enabled: shouldAdvertise // Allows you to advertise the Hop service
}
}
}
})
}

module.exports = create
Loading

0 comments on commit 7171f76

Please sign in to comment.