Skip to content
Merged
Changes from 5 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
89 changes: 89 additions & 0 deletions connections/simopen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Simultaneous Open for bootstrapping connections in multistream-select
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# Simultaneous Open for bootstrapping connections in multistream-select
# Connection bootstrapping: Handling simultaneous open in multistream-select.

As per @raulk's comment in #196 (comment).


| Lifecycle Stage | Maturity | Status | Latest Revision |
|-----------------|---------------|--------|-----------------|
| 1A | Working Draft | Active | DRAFT |
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
| 1A | Working Draft | Active | DRAFT |
| 1A | Working Draft | Active | r0, 2021-05-06 |


Authors: [@vyzo]

Interest Group: [@raulk], [@stebalien]

[@vyzo]: https://github.com/vyzo
[@raulk]: https://github.com/raulk
[@stebalien]: https://github.com/stebalien

See the [lifecycle document][lifecycle-spec] for context about maturity level
and spec status.

[lifecycle-spec]: https://github.com/libp2p/specs/blob/master/00-framework-01-spec-lifecycle.md


## Introduction

In order to support direct connections through NATs with hole
punching, we need to account for simultaneous open. In such cases,
there is no single initiator and responder, but instead both peers act
as initiators. This breaks protocol negotiation in
multistream-select, which assumes a single initator.

This draft proposes a simple extension to the multistream protocol
negotiation in order to select a single initator when both peers are
acting as such.

## The Protocol

When a peer acting as the initiator enters protocol negotiation, it
sends the string `iamclient` as first protocol selector. If the other
peers is a responder or doesn't support the extension, then it
responds with `na` and protocol negotiation continues as normal.

If both peers believe they are the initiator, then they both send
`iamclient`. If this is the case, they enter an initiator selection
phase, where one of the peers is selected to act as the initiator. In
order to do so, they both generate a random 256-bit integer and send
it as response to the `iamclient` directive, prefixed with the
`select:` string. The integer is in big-endian format, encoded in base64.
Copy link
Contributor

@aarshkshah1992 aarshkshah1992 Jan 7, 2021

Choose a reason for hiding this comment

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

@vyzo Why do we need to encode to base64 here ? Why not simply append the 256 bits ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd assume it's because multistream 1 is a text-based, human-readable protocol (with all the pros and cons that come with it).

That said, base64 seems like a suboptimal choice here. I propose switching to hexadecimal encoding. Encoding math.MaxUint64 (18446744073709551615) is 0xFFFFFFFFFFFFFFFF in hex and MTg0NDY3NDQwNzM3MDk1NTE2MTU= in base64.
Also, 256 bits seem excessive here. I think we can live with a collision probability of 2^-64, which would allow implementations to use a regular uint64 instead of math/big.Int. We might even want to limit this to 52 (?) bits to make things easier in JavaScript (but I'm no expert on that).

Copy link
Member

Choose a reason for hiding this comment

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

Encoding math.MaxUint64 (18446744073709551615) is 0xFFFFFFFFFFFFFFFF in hex and MTg0NDY3NDQwNzM3MDk1NTE2MTU= in base64.

I think it would be //////////8=

Copy link
Contributor

@marten-seemann marten-seemann Jan 13, 2021

Choose a reason for hiding this comment

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

Would it? I used https://www.base64encode.net/, and that gives the same result (except for the last character?) as echo 18446744073709551615 | base64. But maybe that's decoding the string "18446744073709551615" and not the number?

As / is a separator we use in multistream, so base64 encoding might get us into trouble, if we don't escape it, right?

Copy link
Member

Choose a reason for hiding this comment

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

Yes I guess you're encoding the string.
It doesn't make sense that encoding something in base64 would give something longer than encoding that same thing in base16

I think that / isn't a problem because of the select: prefix. Otherwise I'd suggest base58.

Copy link
Contributor

@marten-seemann marten-seemann Jan 13, 2021

Choose a reason for hiding this comment

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

Not opposed to base58, but also not sure if it's easily accessible in all the languages we care about. In Go, we'd have to import a third-party library (it's not in the standard library).

Any thoughts on the length of this value?

The peer with the highest integer is selected to act
as the initator and sends an `initiator` message. The peer with the
lowest integer responds with `responder` message and both peers
transition to protocol negotiation with a distinct initiator.

Note the importance of the prefix in the random integer, as it allows
peers to match the selection token and ignore potentially pipelined
security protocol negotiation messages.

The following schematic illustrates, for the case where A's integer is
higher than B's integer:

```
A ---> B: iamclient
B ---> A: iamclient
A: generate random integer IA
B: generate random integer IB
A ---> B: select:{IA}
B ---> A: select:{IB}
A ---> B: initiator
B ---> A: responder
```

In the unlikely case where both peers selected the same integer, they
generate a fresh one and enter another round of the protocol. If
multiple rounds of the protocol result in the same integers, this is
indicative of a bug and both peers should abort the connection.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
In the unlikely case where both peers selected the same integer, they
generate a fresh one and enter another round of the protocol. If
multiple rounds of the protocol result in the same integers, this is
indicative of a bug and both peers should abort the connection.
In the unlikely case where both peers selected the same integer, connection establishment fails.

The Golang implementation does not retry. See multiformats/go-multistream#42 (comment).


## Implementation Considerations

The protocol is simple to implement and is backwards compatible with
vanilla multistream-select. An important consideration is avoiding RTT
overhead in the common case of a single initiator. In this case, the
initiator pipelines the security protocol negotiation together with the
selection, sending `multistream,iamclient,secproto`. If the receiving
peer is a responder, then it replies with `multistream,na,secproto`,
negotiating the security protocol without any overhead.

If the peer is also a client, then it also sends
`multistream,iamclient,secproto`. On seeing the `iamclient` message,
both peers enter the initiator selection protocol and ignore the
`secproto` in the original packet. They can do so because the random
integer is prefixed with the `select:` string, allowing peers to match
the selection and ignore pipelined protocols.