-
-
Notifications
You must be signed in to change notification settings - Fork 350
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 send_eof to SSLStream and get rid of HalfCloseableStream #823
Comments
While we're adding optional half-close methods to the |
Does openssh support it? TCP doesn't – if you use Also, how are you imagining the sending code would find out that backpressure was being propagated? |
OpenSSH does definitely support this - it's how
Other uses: the current draft of QUIC (which supposedly will be the basis for HTTP/3) has a
In terms of the interface: if the receiver shuts down their receive side, the sender should probably get BrokenResourceError when they try to send. In terms of the implementation, this requires some way for the receiver to tell the sender they don't want more data. It's definitely a bit of a niche feature, and we can document it as such. If nothing else, StapledStream can support it pretty trivially! |
Given that none of TCP, TLS, standard SSH, or HTTP/2 actually support |
Especially after reading your comments on #895, leaving out Back on the write-side close front, I don't know what semantics you had in mind for the generalization of Another option (somewhat more provocative) would be to state that if the underlying protocol doesn't support reliable half-close, i.e., if the sequence of operations described above could lose data from the remote peer in some cases, then it should be implemented so that receiving EOF after calling |
I was just imagining that we'd add it to
I'm more worried about the overlying protocol :-). The way I think about it, So for protocols that want to assign particular meaning to sending an EOF, then the And then there are protocols where And then there's a practical problem: A lot of experience socket programmers have no idea that |
Oh, yeah, I absolutely agree that Taking a step back, it's definitely true that the EOF is a message whose meaning is ultimately determined by the protocol, but I think there's also a broadly recognized strategy for tearing down a full-duplex connection in such a way that both peers agree on what data was sent, which shows up in a lot of protocols. Something like:
If run over a transport that fully supports half-close, this works great; both sides send and receive EOF, so both can agree on what data was sent: "everything before the EOF marker". What about transports that claim to not support half-close, but still have shutdown procedures that look sort of like the graceful close procedure I described above?
Both of these have some interesting properties:
If you try to do the graceful-close dance above, and Bonus question: what should we do for (Any protocol implemented on top of TCP can support this "sorta half-close", even if it doesn't exchange protocol-level close messages, because you can send a FIN and keep receiving until you get one in response. I'm tempted to say that any application network protocol at all kind of has to support it, because of the data that might be in the network at the time you close the connection, even if some libraries-that-implement-protocols might not expose the ability. But there might be a case I'm missing.) |
Another thought: If you have a protocol that does assign semantics to the EOF marker of the underlying transport, it's a type error to use it on a transport that doesn't support sending an EOF marker. It's better for this error to be obvious statically than for everything to seem to work fine until the protocol wants to send the EOF marker. To me this argues that either we should require all transports (Stream implementations) to support sending an EOF marker, even if the outcome might be imperfect somehow, or we should keep distinguishing between Stream and HalfCloseableStream. |
I agree that there's a common pattern for accomplishing this, but I would have said that the common pattern is to never send EOF, and instead do all the shutdown handling by sending bytes back and forth. E.g., websocket and HTTP/2 are both in this class. Arguably HTTP/1.1 as well. And of course any protocol that wants to work over TLS can't assume the existence of
That's not really a bonus question.
In a perfect world, yes, but we don't know until after the TLS handshake what version of TLS we're using, and whether it supports I guess a two important principles for me also are: (1) there's a general consensus around "how a stream works", that's shared across at least the designers of TCP, SSH2, HTTP/2, and modern TLS... it seems very natural to me to use this as a guide for trio's |
Touché. I suspect I'm guilty here of doing too much thinking about how I would design an ad-hoc protocol for a niche need, and too little research into what widely used protocols do. And half-close is so obviously the way a byte stream "should work" that I think I want it to work in more places than maybe it actually does. :-) I'm on board with having |
Thank you for needing so much back-and-forth, it forces us to figure out the details and generate a record of the rationale :-) |
Based on the issues in #1110, perhaps we should also consider renaming |
I'd use +1 on changing the semantics. |
It definitely offends everyone's sense of symmetry, but |
The only reason HalfCloseableStream existed as a separate interface was because SSLStream couldn't support send_eof. But TLS 1.3 made it possible to support send_eof! So now the static split doesn't make sense. Instead, we move send_eof into the Stream interface (though it can still fail at runtime with NotImplementedError), and get rid of HalfCloseableStream. (Fixes: python-triogh-823.) This also includes a fix for StapledStream.send_eof, that was revealed by the new enhanced check_two_way_stream. (Fixes: python-triogh-1180).
We have an awkward thing in our stream abstraction: there's the abstract
Stream
interface, and then there's the abstractHalfCloseableStream
interface, which is justStream
with an addedsend_eof
method.In practice, every single
Stream
you'd ever meet in real life – TCP connections,StapledStream
, SSH tunnels, HTTP/2 streams, etc. etc. etc. – is aHalfCloseableStream
. Except...SSLStream
, because for some reason TLS doesn't support half-close. And unfortunately TLS is super important in practice, so even though in some abstract sense it's TLS that's broken and fails to implement a proper byte stream, we instead broke ourStream
interface to accomodate TLS.BUT! It turns out that TLS 1.3 fixed this! Quoting RFC 8446 §6.1:
Also @kroeckx says:
So TLS 1.3 now has the same semantics as every other common byte-stream transport, and apparently in some cases we can get that with earlier versions too, if we cross our fingers and the moon is full.
I was already on the fence about whether we should have two separate types for
Stream
andHalfCloseableStream
, versus merging them into a single type and just saying thatsend_eof
might fail in some cases. This clearly tips things over, given that it meansSSLStream
is going to want to implementsend_eof
.We'll still have to document clearly that (a)
send_eof
may not work in all cases, and (b) even if it works, there's lots of software out there that doesn't really understand half-close. But at least we can dropHalfCloseableStream
. I never liked that thing.The text was updated successfully, but these errors were encountered: