-
Notifications
You must be signed in to change notification settings - Fork 503
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
WIP: Change Transport to allow for implementing partially reliable transport like QUIC #95
base: main
Are you sure you want to change the base?
Conversation
change handshake and connect to allow implementer to opt into providing a second unreliable unordered stream to carry the forwarded packets. rathole commands will still be sent over reliable ordered streams.
41866de
to
8a4cf93
Compare
@rapiz1 I would need some feedback, in order to proceed. I am not yet sure whether the currently implemented approach is the best way to do it. |
@emillynge Oh. I thought we're focused on #98 first. I will take a look |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if I miss something, but this doesn't seem right 🤔
run_data_channel_for_tcp::<T, T::ReliableStream>(reliable, &args.local_addr).await?; | ||
} | ||
TransportStream::PartiallyReliable(_, unreliable) => { | ||
run_data_channel_for_tcp::<T, T::UnreliableStream>(unreliable, &args.local_addr).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem right. After reading quinn::Datagrams
, I see it's really just datagram
, just like what UDP provides. And your AsyncWrite
wrapper simply wrap poll_write
to send a datagram. But when you do the forwarding for a TCP service, copy_bidirectional
is called and byte stream is forwarded, in which every byte is from the application layer, not a TCP packet, and should be guaranteed to be sent to the destination, while with T::UnreliableStream
, it can be lost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I must have misunderstood what the rathole client<->server data channel was carrying.
If we are ACK'ing data from the end user, then we absolutely have to ensure that the packet does indeed arrive (reliability).
But what we can still try to take advantage of is the ability to send QUIC packet unordered.
run_data_channel_for_udp::<T, T::ReliableStream>(reliable, &args.local_addr).await?; | ||
} | ||
TransportStream::PartiallyReliable(_, unreliable) => { | ||
run_data_channel_for_udp::<T, T::UnreliableStream>(unreliable, &args.local_addr).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may also not work as expect. run_data_channel_for_udp
call write_all
to write packets. And write_all
can call poll_write
multiple times and there's no guarantee that a packet passed to write_all
will not be fragmented and passed to poll_write
at a whole, which means it's possible multiple quic datagrams are used to carry one UdpTraffic
, and some of them are lost, causing the deserialization of UdpTraffic
to fail.
To move this forward, I think
|
78f1157
to
97541af
Compare
This PR is meant as a way to discuss how the
Transport
trait could be changed such that protocols that are partially reliable can leverage their ability to send forwarded packets unordered and unreliably.I will repeat my thought from #64 (comment)
Benefits from forwarding packets unordered and unreliably
An ordered reliable transport, mans that:
Now, this is nice (almost necessary) for the control channel. But it is actually a problem for the data channel.
As an example, retransmission of TCP packets in our "tunnel" will make it seem as if there is 0 loss from point of view of the original sender, which will make it impossible for them to detect congestion. (see TCP-meltdown)
Also, having all packets be ordered will introduce head-of-line blocking in the transport itself. If we are carrying UDP, this unnecessarily chkoes bandwidth. If we are carrying TCP, then we now have 2 layers of HOL blocking, where just one should suffice.
Some protocols, such as QUIC allows us to choose where any packet should be sent reliably, or unreliably. We can use that to simplify control channel and command packet code (no need for manual retransmission etc), whilst having all the forwarded packets be carried by unreliable streams.
The main protocol I envision building atop this PR is QUIC. However, I would like the approch to be useful for a TCP/UDP combination, such as seen in FTP:
Approaches
The first approach in this PR is to introduce an
enum TransportStream
that may either contain 1 or 2 streams depending on whether the protocol wishes (is capable) of providing an unreliable stream in addition to the reliable one that is must provide.An alternative would be to define a new "downgrade" method in the
Transport
trait that consumes the reliable stream into an unreliable one. This would likely suffice given that, as soon as we start the actual forwarding, no more commands needs to be sent reliable on the data channel. The control channel will keep needing a reliable connection, but it will simply never downgrade.Either way, I don't see how to do this without requiring all
Transport
implementers define anUnreliableStream
type. Unless of course, we just have 1Transport::Stream
type can be put into either reliable or unreliable mode. Likely, that would entail some flag or state that must be checked every time a packet is received or sent which may incur a slight amount of unwanted overhead. Yet, it may well be the simplest solution.