Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add and link circuit relay spec #6

Merged
merged 5 commits into from
Feb 13, 2017
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
5 changes: 5 additions & 0 deletions 3-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ It is recommended that implementations use one of the many NAT traversal librari

Unfortunately, due to symmetric NATs, container and VM NATs, and other impossible-to-bypass NATs, `libp2p` MUST fallback to relaying communication to establish a full connectivity graph. To be complete, implementations MUST support relay, though it SHOULD be optional and able to be turned off by end users.

Connection relaying SHOULD be implemented as a transport, in order to be transparent to upper layers.

For an instantiation of relaying, see the [/ipfs/relay/circuit transport](transports/circuit-relay.md).


# 3.6 Enable several network topologies

Different systems have different requirements and with that comes different topologies. In the P2P literature we can find these topologies being enumerated as: unstructured, structured, hybrid and centralized.
Expand Down
4 changes: 3 additions & 1 deletion 4-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ Protocol multiplexing is done through [`multistream`](https://github.com/jbenet/

### 4.2.6 Relay

See [Circuit Relay](/relay).

## 4.3 Distributed Record Store

### 4.3.1 Record

Follows [IPRS spec](../iprs).
Follows [IPRS spec](/iprs).

### 4.3.2 abstract-record-store

Expand Down
28 changes: 14 additions & 14 deletions 7-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,17 @@ The wire protocol is -- of course -- wrapped with encryption. We use cyphersuite

Here, we present a table with the multicodecs defined for each IPFS protocol that has a wire componenent. This list may change over time and currently exists as a guide for implementation.

protocol | multicodec
:---- | :----
secio | /secio/1.0.0
TLS | /tls/1.3.0
plaintext | /plaintext/1.0.0
spdy | /spdy/3.1.0
yamux | /yamux/1.0.0
multiplex | /mplex/6.7.0
identify | /ipfs/id/1.0.0
ping | /ipfs/ping/1.0.0
relay | /ipfs/relay/line/1.0.0
diagnostics | /ipfs/diag/net/1.0.0
Kademlia DHT | /ipfs/kad/1.0.0
bitswap | /ipfs/bitswap/1.0.0
protocol | multicodec | comment
:---- | :---- | :----
secio | /secio/1.0.0 |
TLS | /tls/1.3.0 | not implemented
plaintext | /plaintext/1.0.0 |
spdy | /spdy/3.1.0 |
yamux | /yamux/1.0.0 |
multiplex | /mplex/6.7.0 |
identify | /ipfs/id/1.0.0 |
ping | /ipfs/ping/1.0.0 |
circuit-relay | /libp2p/relay/circuit/0.1.0 | [spec](/relay)
diagnostics | /ipfs/diag/net/1.0.0 |
Kademlia DHT | /ipfs/kad/1.0.0 |
bitswap | /ipfs/bitswap/1.0.0 |
207 changes: 207 additions & 0 deletions relay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Circuit Relay

> Circuit Switching in libp2p

Table of Contents
- [Overview](#overview)
- [Dramatization](#dramatization)
- [Addressing](#addressing)
- [Wire protocol](#wire-protocol)
- [Interfaces](#interfaces)
- [Removing existing relay protocol](#removing-existing-relay-protocol)

* Note: as of 8-Feb-2017, this protocol isn't implemented yet, neither in go-libp2p nor js-libp2p.

We aim to implement and ship it [during the "ipfs in web browsers" sprint](https://github.com/ipfs/pm/issues/351) starting 12-Feb-2017.

## Overview

The circuit relay is a means of establishing connectivity between
libp2p nodes (such as IPFS) that wouldn't otherwise be able to connect to each other.

This helps in situations where nodes are behind NAT or reverse proxies,
or simply don't support the same transports (e.g. go-ipfs vs. browser-ipfs).
libp2p already has modules for NAT ([go-libp2p-nat](https://github.com/libp2p/go-libp2p-nat)),
but these don't always do the job, just because NAT traversal is complicated.
That's why it's useful to have a simple relay protocol.

One node asks a relay node to connect to another node on its behalf.
The relay node shortcircuits its streams to the two nodes,
and they are then connected through the relay.
This relayed connection behaves just like a regular connection would,
because it is in fact just that,
but over an existing swarm stream with another peer, instead of e.g. TCP.

Relayed connections are end-to-end encrypted just like regular connections.

The circuit relay is both a tunneled transport and a mounted swarm protocol.
The transport is the means of *establishing* and *accepting* connections,
and the swarm protocol is the means to *relaying* connections.

```
+-------+ /ip4/.../tcp/.../ws/ipfs/QmRelay +---------+ /ip4/.../tcp/.../ipfs/QmTwo +-------+
| QmOne | <------------------------------------> | QmRelay | <-----------------------------------> | QmTwo |
+-------+ (/ipfs/relay/circuit multistream) +---------+ (/ipfs/relay/circuit multistream) +-------+
^ +-----+ ^
| | | |
| /p2p-circuit/QmTwo | | |
+--------------------------------------------+ +-------------------------------------------+
```

TODO: the stream codec should be called `/libp2p/relay/circuit` instead.

Note: we're using the `/p2p` multiaddr protocol instead of `/ipfs` in this document.
`/ipfs` is currently the canonical way of addressing a libp2p or IPFS node,
but given the growing non-IPFS usage of libp2p, we'll migrate to using `/p2p`.

Note: at the moment we're not including a mechanism for discovering relay nodes.
For the time being, they should be configured statically.


## Dramatization

Cast:
- QmOne, the dialing node (browser).
- QmTwo, the listening node (go-ipfs).
- QmRelay, a node which speaks the circuit relay protocol (go-ipfs or js-ipfs).

Scene 1:
- QmOne wants to connect to QmTwo,
and through peer routing has acquired a set of addresses of QmTwo.
- QmTwo doesn't support any of the transports used by QmOne.
- Awkward silence.

Scene 2:
- All three nodes have learned to speak the `/ipfs/relay/circuit` protocol.
- QmRelay is configured to allow relaying connections between other nodes.
- QmOne is configured to use QmRelay for relaying.
- QmOne automatically added `/p2p-circuit/ipfs/QmTwo` to its set of QmTwo addresses.
- QmOne tries to connect via relaying, because it shares this transport with QmTwo.
- A lively and prolonged dialogue ensues.


## Addressing

`/p2p-circuit` multiaddrs don't carry any meaning of their own.
They need to encapsulate a `/p2p` address, or
be encapsulated in a `/p2p` address, or both.

As with all other multiaddrs, encapsulation of different protocols
determines which metaphorical tubes to connect to each other.

A few examples:

- `/p2p-circuit/p2p/QmTwo`
- Dial QmTwo, through any available relay node.
- The relay node will use peer routing to find an address for QmTwo.
- `/p2p/QmRelay/p2p-circuit/p2p/QmTwo`
- Dial QmTwo, through QmRelay.
- Use peer routing to find an address for QmRelay.
- The relay node will also use peer routing, to find an address for QmTwo.
- `/p2p-circuit/ip4/../tcp/../p2p/QmTwo`
- Dial QmTwo, through any available relay node,
but force the relay node to use the encapsulated `/ip4` multiaddr for connecting to QmTwo.
- We'll probably not support forced addresses for now, just because it's complicated.
- `/ip4/../tcp/../p2p/QmRelay/p2p-circuit`
- Listen for connections relayed through QmRelay.
- Includes info for connecting to QmRelay.
- Also makes QmRelay available for relayed dialing, based on how listeners currently relate to dialers.
- `/p2p/QmRelay/p2p-circuit`
- Same as previous example, but use peer routing to find an address for QmRelay.
- `/p2p-circuit/p2p/QmTwo/p2p-circuit/p2p/QmThree`
- Dial QmThree, through a relayed connection to QmTwo.
- The relay nodes will use peer routing to find an address for QmTwo and QmThree.
- We'll probably not support nested relayed connections for now, there are edge cases to think of.
- `/ip4/../tcp/../p2p/QmRelay/p2p-circuit/p2p/QmTwo`
- Dial QmTwo, through QmRelay.
- Includes info for connecting to QmRelay.
- The relay node will use peer routing to find an address for QmTwo.
- `/p2p-circuit`
- Use relay discovery to find a suitable relay node. (Neither specified nor implemented.)
- Listen for relayed connections.
- Dial through the discovered relay node for any `/p2p-circuit` multiaddr.

TODO: figure out forced addresses.
TODO: figure out nested relayed connections.

## Wire format

The wire format (or codec) is named `/ipfs/relay/circuit` and is simple.
A variable-length header consisting of two length-prefixed multiaddrs
is followed by a bidirectional stream of arbitrary data,
and the eventual closing of the stream.

```
<src><dst><data>

^ ^ ^
| | |
| | +-- bidirectional data stream
| | (usually /multistream-select in the case of /p2p multiaddrs)
| |
| +------- multiaddr of the listening node
|
+------------ multiaddr of the dialing node
```

After getting a stream to the relay node from its libp2p swarm,
the dialing transport writes the header to the stream.
The relaying node reads the header, gets a stream to the destination node,
then writes the header to the destination stream and shortcircuits the two streams.

Each relayed connection corresponds to two multistreams,
one between QmOne and QmRelay, the other between QmRelay and QmTwo.

Implementation details:
- The relay node has the `Swarm.EnableRelaying` config option enabled
- The relay node allows only one relayed connection between any two nodes.
- The relay node validates the `src` header field.
- The listening node validates the `dst` header field.


## Interfaces

As explained above, the relay is both a transport (`tpt.Transport`)
and a mounted stream protocol (`p2pnet.StreamHandler`).
In addition it provides a means of specifying relay nodes to listen/dial through.

TODO: the usage of p2pnet.StreamHandler is a little bit off, but it gets the point across.

```go
import (
tpt "github.com/libp2p/go-libp2p-transport"
p2phost "github.com/libp2p/go-libp2p-host"
p2pnet "github.com/libp2p/go-libp2p-net"
p2proto "github.com/libp2p/go-libp2p-protocol"
)

const ID p2proto.ID = "/ipfs/relay/circuit/0.1.0"

type CircuitRelay interface {
tpt.Transport
p2pnet.StreamHandler

EnableRelaying(enabled bool)
}

fund NewCircuitRelay(h p2phost.Host)
```


### Removing existing relay protocol

Note that there is an existing swarm protocol colloqiually called relay.
It lives in the go-libp2p package and is named `/ipfs/relay/line/0.1.0`.

- Introduced in ipfs/go-ipfs#478 (28-Dec-2014).
- No changes except for ipfs/go-ipfs@de50b2156299829c000b8d2df493b4c46e3f24e9.
- Changed to use multistream muxer.
- Shortcomings
- No end-to-end encryption.
- No rate limiting (DoS by resource exhaustion).
- Doesn't verify src id in ReadHeader(), easy to fix.
- Capable of *accepting* connections, and *relaying* connections.
- Not capable of *connecting* via relaying.

Since the existing protocol is incomplete, insecure, and certainly not used,
we can safely remove it.