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 a multiselect 2.0 spec #227

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Binary file added connections/handshake.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
103 changes: 103 additions & 0 deletions connections/multiselect2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Multiselect 2.0

## Introduction

Multiselect 2.0 replaces the Multistream protocol. Compared to its predecessor, it offers:

1. Downgrade protection for the security protocol negotiation.
2. Zero-rountrip stream multiplexer negotiation for handshake protocols that take advantage of early data mechanisms (one-roundtrip negotation for protocols / implementations that don't).
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved
3. Compression for the protocol identifiers of frequently used protocols.

By using protobufs for all control messages, Multiselect 2.0 provides an easy path for future protocol upgrades. The protobuf format guarantees that unknown field in a message will be skipped, thus future version of the protocol can add new fields that signal support for new protocol features.
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

## High-Level Overview

### Handshake Protocol Selection
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

Handshake protocols are not being negotiated, but are announced in the peers' multiaddrs.
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 probably mention that they could be negotiated in some corner case, but that the general case will be to assume a secure transport by the time multiselect is initiated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What is that corner case you're referring to? I'm not sure if we should even build that flexibility into our stack, even if we could come up with a situation where a negotiation wouldn't be susceptible to a MITM attack. If it's not there, it can't be exploited.

marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

**TODO**: Do we need to describe the format here? I guess we don't, but we will probably need another document for that change, and we can link to it from here.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a link to some documentation on this would be nice! I don't believe it exists at the moment, so may be worth keeping this here.

Copy link
Member

Choose a reason for hiding this comment

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

We need an extension to the multiaddr spec + multicodec table to add secure channels as an atom. Also, @yusefnapora was writing this document to specify the semantics of multiaddrs: #191.


Peers advertising a multiaddr that includes a handshake protocol MUST support Multiselect 2.0 as described in this document.
Copy link
Member

Choose a reason for hiding this comment

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

I know we had discussed this assumption as a way to simplify things, but I do think it'll end up being short-sighted, and in some ways, a regression compared to multistream-select 1.0, which does announce its version (although admittedly the implementations are not ready to support multiple versions, but the protocol is).

Something I've been thinking about is to create an extension to multistream-select 1.0 / upgraders that would allow us to go straight into a cryptographic handshake, as a way to deliver censorship resistance to downstream users that require it before we realistically ship ms2.0.

Copy link
Contributor Author

@marten-seemann marten-seemann Nov 27, 2019

Choose a reason for hiding this comment

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

I'm not sure how this would work without either defining another multicoded or requiring an additional round-trip to negotiate.


#### TCP Simultaneous Open
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

TCP allows the establishment of a connection if two endpoints start initiating a connection at the same time. This is called TCP Simultaneous Open. For libp2p, this is problematic, since most stream multiplexers assign stream IDs based on the role (client or server) of and endpoint.

It is therefore desirable to fail a connection attempt as early as possible, if a TCP Simultaneous Open occurs. TLS 1.3 and Noise provide this guarantee: For example, the TLS handshake will fail if an endpoint receives a ClientHello instead of a ServerHello as a response to its ClientHello.
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

Since secio doesn't provide this property, secio cannot be used with Multiselect 2.0.

### Stream Multiplexer Selection
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

This section only applies if Multiselect 2 is run over a transport that is not natively multipexed. Transports that provide stream multiplexing on the transport layer (e.g. QUIC) don't need to do anything described in this section.
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

Some handshake protocols (TLS 1.3, some variants of Noise (**TODO**: specify which)) support sending of *Early Data*. Early Data can be sent by the server after receiving the first handshake message from the client. It is encrypted, however, at that point of the handshake the client's identity is not yet verified.
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

In Multiselect 2 the server makes use of Early Data by sending a list of stream multiplexers. This ensures that the client can choose a stream multiplexer as soon as the handshake completes (or fail the connection if it doesn't support any stream multiplexer offered by the server).
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

Note that this negotiation scheme allows peers to negotiate a "monoplexed" connection, i.e. a connection that doesn't use any stream multiplexer. Endpoints can offer support for monoplexed connections by offering the `/monoplex` stream multiplexer.

**TODO**: Do we need to define a way to send an error code / error string? Or do we have something like that in libp2p already?
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to be able to send an error message if, e.g., the responder doesn't support any of the initiator's multiplexers. Perhaps that would be a potential use-case for a Reject message, even in the streaming case?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't believe we have any libp2p-wide error codes. I think they should be protocol specific.


![](handshake.png)

Handshake protocols (or implementations of handshake protocols) that don't support sending of Early Data will have to run the stream multiplexer selection after the handshake completes.
Copy link
Member

Choose a reason for hiding this comment

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

Oh ok, I see that you added the fallback. We need to define how that'll work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure if there's anything we need to define. "Early data" is not a separate byte stream, it's the same byte stream as the rest of the connection. The only difference is that the data is sent earlier (and, depending on the handshake protocol, might use a different set of keys).
To the application Early Data and Late (?) Data is not distinguishable at all.


#### 0-RTT

When using 0-RTT session resumption as offered by TLS 1.3 and some variants of Noise (**TODO**: specify which), the endpoints MUST remember the negotiated stream multiplexer used on the original connection. This ensures that the client can send application data in the first flight when resuming a connection.
Copy link
Contributor

Choose a reason for hiding this comment

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

The Noise IK handshake, which is part of the "Noise Pipes" pattern described in the spec, supports 0-RTT encryption.

Copy link
Member

Choose a reason for hiding this comment

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

Take into account that multiplexers can change across restarts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@raulk That's on us to define. We could say that if you resume a connection, you have to use the same multiplexer. This would save us a roundtrip for negotiation the stream multiplexer in the common case.
In the rare case when a peer dropped support for a muxer between two connections, you would just reject 0-RTT and fall back to the 1-RTT handshake.


## Protocol Speficiation
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

All messages are Protobuf messages using the `proto3` syntax. Every message is wrapped by the `Multiselect` message:

```protobuf
# Wraps every message
message Multiselect {
oneof message {
Offer offer = 1;
Use use = 2;
}
}
```
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

The `Offer` message is used to initiate a conversation on a new stream. It contains either a single or multiple protocols that the endpoint would like to use on the stream.
A `Protocol` is the application protocol spoken on top of an ordered byte stream. The `name` of a protocol is the protocol identifier, e.g. `/ipfs/ping/1.0.0`. The `id` is a numeric abbreviation for this protocol (see below for details how `id`s are assigned).
If the endpoint only selects a single protocol, it MAY start sending application data right after the protobuf message. Since it has not received confirmation if the peer actually supports the protocol, any such data might be lost in that case.
If the endpoint selects multiple protocols, it MUST wait for the peer's choice of the application protocol (see description of the `Use` message) before sending application.

```protobuf
# Select a list of protocols.
message Offer {
message Protocol {
oneof protocol {
string name = 1;
uint64 id = 2;
}
}
repeated Protocol protocols = 1;
}
```

The `Use` message is sent in response to the `Offer`. And endpoint MUST treat the receipt of a `Use` message before having sent a `Offer` message on the stream as a connection error.
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved
If none of the protocol(s) listed in the `Offer` message are acceptable, and endpoint resets both the send- and the receive-side of the stream.
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

If an endpoint receives an `Offer` message that only offers a single protocol, it accepts this protocol by sending an empty `Use` message (i.e. a message that doesn't list any `protocol`), or a `Use` message that assigns a protocol id (see below). Sending an empty `Use` message in response to a`Offer` message that offers multiple protocols is not permitted, and MUST be treated as a connection error by an endpoint.
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

If an endpoint receives a `Offer` message that offers multiple protocols, it chooses an application protocol that it would like to speak on this stream. It informs the peer about its choice by sending its selection in the `protocol` field of the `Use` message.
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

When choosing a protocol, an endpoint can allow its peer to save bytes on the wire for future use of the same protocol by assigning a numeric identifier for the protocol by sending an `id`. The identifier is valid for the lifetime of the connection. The identifier must be unique for the protocol, an endpoint MUST NOT use the same identifier for different protocols.

```protobuf
# Declare that a protocol is used on this stream.
# By using an id (instead of a name), an endpoint can provide the peer
# an abbreviation to use for future uses of the same protocol.
message Use {
message Protocol {
uint64 id = 1;
string name = 2;
}
Protocol protocol = 1;
}
```