Open Stebalien opened 3 years ago
(cc @marten-seemann who helped design this)
Note: This protocol will not allow recovering a session if "lost" (i.e., the connection was cut). Doing so would require keeping large write buffers and tracking acknowledgement states in userspace. This protocol will primarily aid the connection manager combine duplicate connections into a single connection, or migrate streams from a worse connection (e.g., TCP) to a better connection (e.g., QUIC).
Will this be used for upgrade from relayed connections to direct connection ?
- When the receiver receives the EOF on stream A, it will send an EOF on stream B, switching over to stream A.
Is this reversed? It seems like the receiver should send EOF on stream A and switch to stream B.
Looks like a great proposal to me 👍
Will this be used for upgrade from relayed connections to direct connection ?
Yes.
Is this reversed? It seems like the receiver should send EOF on stream A and switch to stream B.
Yes...
Overhead should be at most a small per-stream cost (no additional framing, etc.)
I'm not sure if this is accurate. Based on step 1, "open a new stream on a new connection" there is a new connection made.
Maybe "at most, a small per-stream cost + new connection overhead," but I may lack understanding here.
The initiator will open a new stream (stream B), on a new connection, to the receiver. This is the target stream for the migration.
This and other lines are quite confusing. By "target" stream for the migration, do we mean the resulting stream? or target stream to be migrated? Technically, there are two streams targeted by a stream migration.
It would be nice to clarify the terminology for the two streams in the migration. It seems like Stream B is the "final" stream, and Stream A is the to-be-migrated stream. It would be nice to clarify and make language consistent in the spec.
Potential legend:
Term | Definition |
---|---|
Leader | The Peer who begins/initiates the connection with the Participant peer |
Participant | The Peer who receives/acknowledges the connection and streams with the Leader peer |
Negotiation-stream | An initial stream created in an existing connection between Leader and Participant peers. |
Goal-stream | A new stream, using an "upgraded" transport when compared to the Negotiation stream, created on a new connection between leader and participant peers. |
The receiver tries to stream A for writing.
"The receiver tries to [use?] stream A for writing.
I'm not sure if this is accurate. Based on step 1, "open a new stream on a new connection" there is a new connection made.
Well, this is a stream migration protocol. The goal is to migrate a stream from connection A to connection B. In this case, that "new connection" is connection B and the "new stream" is the the stream we're migrating from connection A.
I think the confusion is "new connection". I'll rename them to "target" and "source".
This and other lines are quite confusing. By "target" stream for the migration, do we mean the resulting stream? or target stream to be migrated? Technically, there are two streams targeted by a stream migration.
It's the stream to which we're migrating. I'll try to clarify it a bit.
I've tried to make it a bit more explicit.
fyi, we have this as a spec proposal: https://github.com/libp2p/specs/pull/406
We haven't merged because there hasn't been a real implementation nor the demand for it.
Longer term, I'd prefer more effort focused on connection migration in QUIC rather than this effort because:
In this migration protocol, I'm primarily targeting migrating streams off a relay and/or "combining" connections when we happen to establish multiple.
I think focusing on the "migrating off relay" use case is good. However I'm not sure in practice what you would do that starts on a public relay and continues on a direct connection. Because public relays are so limited (128KB/2min on Kubo) they aren't useful for much besides trying to get a direct connection. You wouldn't start fetching a file on a relayed connection and then continue on a direct one. Maybe there's a use case I'm missing?
"combining" connections when we happen to establish multiple.
Hopefully this is less prevalent now with the smart dialing work: https://github.com/libp2p/go-libp2p/releases/tag/v0.29.0
You wouldn't start fetching a file on a relayed connection and then continue on a direct one.
I could see sending a wantlist (bitswap) over a relay. Technically we could just kill the stream and re-create it.
But yeah, QUIC stream migration is higher priority and likely better in most cases.
If / when https://datatracker.ietf.org/doc/draft-seemann-quic-nat-traversal/ ever becomes a reality, you'll be able to migrate your relayed QUIC connection to a hole-punched connection. Just to set expectations, this is very likely not going to happen within the next 12 months.
I think focusing on the "migrating off relay" use case is good. However I'm not sure in practice what you would do that starts on a public relay and continues on a direct connection.
One example is a browser js-libp2p node who ends up having only p2p-circuit dialable multiaddrs.
Couldn't any node who has limited transport capabilities, and relies on relays to talk to the network, benefit from this? or is DCUtR supposed to handle most of those use-cases?
DCUtR attempted to solve this for us in js-libp2p and Helia land. To my untrained eyes, it seems very similar, but instead of an up-front connection migration (transient -> direct), it would be a mid-flight migration.
If we did implement a stream-migration protocol, would that allow us to stop limiting relay throughput, and instead depend upon DCUtR + stream-migration(SM) in order to transition the relay-started-transfer to a stream on the direct connection? If the DCUtR+SM process failed, we could drop the connection.. but in that case, isn't it better to just attempt DCUtR and never start the transfer if it doesn't succeed?
(apologies for the dumb questions, just trying to get on all of your libp2p-experts'-brainwaves)
Libp2p should support protocol agnostic stream migration. This will make upgrading to better transports "seamless".
Requirement
Protocol
Here we describe a protocol for migrating a "source" stream (stream A) to a "target" stream (stream B).
When opening a stream, if the remote peer supports the stream migration protocol (discovered through identify) and the stream is "long lived" (opt in or opt out?):
Because we "know" that the peer supports this stream migration protocol, we can pipeline these steps so as not to take any additional round-trips.
To migrate a stream:
At this point, the stream is fully migrated.
Resets
If either stream is "reset" before both ends are closed, both streams must be reset and the stream as a whole should be considered "aborted" (reset).
Half-Closed
If stream A was half-closed (either for reading or writing), that state must be replicated on the new stream after the initial handshake. Importantly, there's an edge-case:
This is fine. The stream will be migrated and the EOF will be re-played on stream B, leaving stream B in the intended state.
Analysis