Skip to content

In-memory channel implementation from PEP 543 #35

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

Merged
merged 22 commits into from
May 2, 2024
Merged

In-memory channel implementation from PEP 543 #35

merged 22 commits into from
May 2, 2024

Conversation

jvdprng
Copy link
Member

@jvdprng jvdprng commented Apr 19, 2024

I lifted the wrapped buffers implementation from PEP 543, sorted out typing information and arranged for some test coverage. It's pretty straightforward since we can reuse all of our context configuration functions. There are some TODOs in the PEP 543 code that I'm planning to tackle, but I would first like to have a discussion about what API we want.

@jvdprng jvdprng requested review from woodruffw and facutuesca April 19, 2024 16:56
@jvdprng
Copy link
Member Author

jvdprng commented Apr 19, 2024

The code still suffers from #31, because I wasn't sure if SSLObject would use the same underlying implementation as SSLSocket (I hate reading this cpython code, to be honest). Initial tests show that it is indeed not possible to trigger this exception on read here either.

Solved in 78dea66

)

if tls is not None:
TLSAgainErrors = (tls.WantReadError,) # TODO: How to catch ssl.SSLSyscallError?
Copy link
Member Author

Choose a reason for hiding this comment

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

asyncio also wants to handle ssl.SSLSyscallError, which is not something we currently expose in the API. Should we go through all stdlib modules that use the ssl module and check which exceptions need to be exposed? How do we deal with choosing exceptions to specify in the API in general?

Copy link
Contributor

Choose a reason for hiding this comment

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

That is a good question. Looking at the docs, there are 7 unique exception classes in ssl:

  • ssl.SSLError
  • ssl.SSLZeroReturnError
  • ssl.SSLWantReadError
  • ssl.SSLWantWriteError
  • ssl.SSLSyscallError
  • ssl.SSLEOFError
  • ssl.SSLCertVerificationError

We currently have our own types for WantReadError and WantWriteError, and the rest get converted to a generic TLSError.

IMO, creating our own types for the other 5 should be fine. We could document each new exception type so that it has the same docstring as in the ssl docs, and then future implementations can adapt their own errors to these 7 classes.

@woodruffw WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm -- I worry about exposing too many distinct exception types here, in part because of impedance mismatch between other future implementations (e.g. not all implementations may choose to distinguish ZeroReturnError from a generic SSLError).

In practice I think the only way to hit SSLSyscallError is to have an underlying socket syscall get hit with a signal, causing EINTR. So I think this might be appropriate to wrap in an equivalent WantRead or WantWrite, indicating that the call should be re-tried? But I'm not 100% sure about that.

Copy link
Member Author

Choose a reason for hiding this comment

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

I actually already added a conversion for ssl.SSLEOFError into RaggedEOF in #42, which for some reason was missing before. ssl.SSLZeroReturnError seems like a strange error (and is also very hard to trigger), but ssl.SSLCertVerificationError may make sense for our API.

I'm not sure about ssl.SSLSyscallError, but I can look into whether there are any other open source libraries handling it.

Copy link
Member Author

@jvdprng jvdprng May 1, 2024

Choose a reason for hiding this comment

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

A couple of asyncio clones (uvloop, Winloop) use it in the same way as asyncio.

Some other libraries seem to treat it as a ssl.SSLEEOFError that gets raised in strange situations.
From trio, which converts ssl.SSLSyscallError to a BrokenResourceError:

# For some reason, EOF before handshake sometimes raises
# SSLSyscallError instead of SSLEOFError (e.g. on my linux
# laptop, but not on appveyor). Thanks openssl.

Similarly, anyio also converts it to a BrokenResourceError (despite being built on top of asyncio?)

However, these are two completely contradictory ways to interpret the error: the asyncio interpretation means you should retry, whereas the EOF interpretation means you should shutdown.

self._extra.update(
peercert=peercert,
cipher=tlsobj.cipher(),
# compression=tlsobj.compression(), #TODO: compression?
Copy link
Member Author

Choose a reason for hiding this comment

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

Our API forces no compression (because the original PEP 543 implementation did). Should we allow users to choose instead, and expose the choice in the API?

Copy link
Contributor

@facutuesca facutuesca Apr 30, 2024

Choose a reason for hiding this comment

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

from a random SO anwser:

TLS compression is removed in TLS 1.3. And even for lower TLS versions it is no longer available in the browsers and disabled by default in OpenSSL.

So maybe it makes sense to not allow users to set it.

Copy link
Member

Choose a reason for hiding this comment

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

+1 -- TLS compression is a mis-feature.

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright, I will document this somewhere so that it's clear why we made this choice.

Copy link
Member Author

Choose a reason for hiding this comment

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

See 44edc90


# Add extra info that becomes available after handshake.
self._extra.update(
peercert=peercert,
Copy link
Member Author

@jvdprng jvdprng Apr 29, 2024

Choose a reason for hiding this comment

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

The peercert stored here now becomes an OpenSSLCertificate, which is basically a path to a (temporary) binary file containing the cert. In the current asyncio implementation it will provide a dict instead (using ssl's getpeercert(False)). We could add a flag to handle this, but I don't think it's a good idea because the concept of Certificate is not well-specified in the API. Allowing access to whatever ssl returns would limit other backends.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree. The dict is not well specified, and the code that converts the certificate to the dict when passing binary_form=False is not straightforward . I also vote for not adding the flag

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright, I will document this.

Copy link
Member Author

Choose a reason for hiding this comment

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

See 0f0abd1

except Exception as exc:
handshake_exc = None
self._set_state(TLSProtocolState.UNWRAPPED)
# if isinstance(exc, ssl.CertificateError): #TODO: how to catch certificateerror?
Copy link
Member Author

Choose a reason for hiding this comment

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

Same question regarding catching of errors as above: should we promote this exception to be specified in the API?

Copy link
Member

@woodruffw woodruffw left a comment

Choose a reason for hiding this comment

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

Thanks @jvdprng!

@woodruffw woodruffw merged commit d06e538 into main May 2, 2024
2 checks passed
@woodruffw woodruffw deleted the jp/memchan branch May 2, 2024 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants