Skip to content

Discussion: composable dialing #1

Description

@rphmeier

Dialing hasn't really been sorted out yet. It's easy to do for simple multiaddrs, but for complex ones, particularly those with multiple hops, things get much fuzzier.

Imagine we have registered two forms of transport: TCP and p2p-circuit, which is used to relay connections.

One example which is difficult to make work is something like
ip4/1.2.3.4/tcp/8888/p2p-circuit/p2p/DestPeer

(circuit relay multiaddr format is <relay-peer>/p2p-circuit/<remote-peer>)

This address, when used for dialing, says "Connect to the peer DestPeer on any available address, through a relay node we will connect to via tcp on port 8888 over the ipv4 address 1.2.3.4"

ip4/4.5.6.7/tcp/30042/p2p-circuit/ip4/1.2.3.4/tcp/8888/p2p-circuit/p2p/DestPeer

Here's a double-hop relay:
Connect to the peer DestPeer through the relay on tcp://1.2.3.4:8888, which we will connect to through the relay on tcp://4.5.6.7:30042.

We'll need to require dialers to handle the whole address, and give them a closure or similar required to instantiate connections to different encapsulated multi-addresses.

So the logic for a circuit dial would (I think?) be

  1. find the left-most mention of p2p-circuit.
  2. split this multiaddr along that mention into the relay and remote addresses.
  3. context.dial(relay).and_then(move |conn| RelayProtocol::dial_through(remote, conn))

This reads as: dial to the relay node, and then when we get a connection to it, attempt to dial the rest of the address through it.

One interesting thing to note is that our individual transport Conns to peers can actually be instances of a Socket being multiplexed over a different Conn to the same peer.

Even in the multi-hop case, we only open one logical connection, through TCP. this means that we're limited to basically one layer of virtual calls. I am not sure this holds true in general, but it depends how fine-grained we want to go.

As in, if we have the multi-addr ip4/1.1.1.1/tcp/20202/ws, we can either have the ws transport wrap a dialled tcp connection, or have the ws transport handle the tcp connection internally. The first option is ideal for "composability" because it means that the ws transport can handle any kind of wire beneath it, but then you can get into the situation of multiple levels of boxed trait objects, which are bad for performance in a multitude of ways.

It may not really be avoidable though, as long as we ensure the cost of connecting/communicating with a peer remains roughly proportional to how complicated the address for that peer is: simple peers will have fewer layers of indirection. More complex peer connections pay a cost for their complexity.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions