From 9274d301bac1684008cf1b0a084b8f77e36b3695 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 11 May 2021 16:27:05 +0300 Subject: [PATCH] connections: multistream-select 1.0 simultaneous open protocol extension (#196) > 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. Co-authored-by: Jacob Heun Co-authored-by: bigs Co-authored-by: Max Inden Co-authored-by: Steven Allen --- connections/README.md | 5 +++ connections/simopen.md | 88 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 connections/simopen.md diff --git a/connections/README.md b/connections/README.md index 9f1d09c67..0ef413aa4 100644 --- a/connections/README.md +++ b/connections/README.md @@ -229,6 +229,10 @@ Once security and stream multiplexing are both established, the connection upgrade process is complete, and both peers are able to use the resulting libp2p connection to open new secure multiplexed streams. +Note: In the case where both peers initially act as initiators, e.g. during NAT +hole punching, tie-breaking is done via the [multistream-select simultaneous +open protocol extension][simopen]. + ## Opening New Streams Over a Connection @@ -397,4 +401,5 @@ updated to incorporate the changes. [go-eventbus]: https://github.com/libp2p/go-eventbus [go-net-notifee]: https://github.com/libp2p/go-libp2p-core/blob/master/network/notifee.go [identify/push]: ../identify/README.md#identify-push +[simopen]: ./simopen.md [resource-manager-issue]: https://github.com/libp2p/go-libp2p/issues/635 diff --git a/connections/simopen.md b/connections/simopen.md new file mode 100644 index 000000000..5979debab --- /dev/null +++ b/connections/simopen.md @@ -0,0 +1,88 @@ +# Connection bootstrapping: Handling simultaneous open in multistream-select + +| Lifecycle Stage | Maturity | Status | Latest Revision | +|-----------------|---------------|--------|-----------------| +| 1A | Working Draft | Active | r0, 2021-05-07 | + +Authors: [@vyzo] + +Interest Group: [@raulk], [@stebalien], [@mxinden] + +[@vyzo]: https://github.com/vyzo +[@raulk]: https://github.com/raulk +[@stebalien]: https://github.com/stebalien +[@mxinden]: https://github.com/mxinden + +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 `/libp2p/simultaneous-connect` 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 +`/libp2p/simultaneous-connect`. 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 64-bit integer and send it as +response to the `/libp2p/simultaneous-connect` directive, prefixed with the +`select:` string. The integer is sent in its base-10 string representation. 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: /libp2p/simultaneous-connect +B ---> A: /libp2p/simultaneous-connect +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, connection +establishment fails. + +## 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,/libp2p/simultaneous-connect,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,/libp2p/simultaneous-connect,secproto`. On seeing the +`/libp2p/simultaneous-connect` 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.