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

TLS checkpoint: POC #5

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

TLS checkpoint: POC #5

wants to merge 1 commit into from

Conversation

dormando
Copy link
Member

@dormando dormando commented May 15, 2024

This is now a proof of concept stage for io_uring + openssl.

There is a lot of work remaining to be done. The code as-is does not reflect where it will be.

This proof of concept gives us:

  • A custom OpenSSL BIO that uses temporary memory buffers and avoids double-memcpy'ing data coming in or out, vs using BIO_s_mem directly.
  • Working handshake
  • Working writes (in perfect conditions)
  • Working reads (in perfect conditions)

This is missing:

  • Any close/error handling
  • Lots of edge cases around resizing buffers
  • Buffer caching
  • Lots of unknown crap I'm figuring out with how OpenSSL BIO's work.
  • Proper code isolation.

Next steps:

  • Need to rework the io_uring loop to not need to constantly check and handle SQE allocation errors.
  • Rework the client read/write buffers into structs similar to mcs_tls_buf and move common structures to a new file.
  • Start the long slog of ironing out all of the edge cases and figure out buffer object caching for the TLS read/write handlers.

Timing:

Think I'm going to put this on a slower burner and stretch it out over at least another month or two. First I'll do some mcshredder core work and rebase the branch on top of them. Then attempt to fill out the BIO requirements in chunks since it is mentally exhausting work.

This is now a proof of concept stage for io_uring + openssl.

There is a _lot_ of work remaining to be done. The code as-is does not
reflect where it will be.

This proof of concept gives us:
- A custom OpenSSL BIO that uses temporary memory buffers and avoids
  double-memcpy'ing data coming in or out, vs using BIO_s_mem directly.
- Working handshake
- Working writes (in perfect conditions)
- Working reads (in perfect conditions)

This is missing:

- Any close/error handling
- Lots of edge cases around resizing buffers
- Buffer caching
- Lots of unknown crap I'm figuring out with how OpenSSL BIO's work.
- Proper code isolation.

Next steps:

- Need to rework the io_uring loop to not need to constantly check and
  handle SQE allocation errors.
- Rework the client read/write buffers into structs similar to
  mcs_tls_buf and move common structures to a new file.
- Start the long slog of ironing out all of the edge cases and figure
  out buffer object caching for the TLS read/write handlers.
@dormando
Copy link
Member Author

While I found a couple examples online of people using BIO_s_mem wrappers to use OpenSSL with io_uring or similar asynchronous systems: in these cases the BIO's have permanent memory buffers and require double-copying everything. Considering OpenSSL is actually triple copying things internally, this adds up.

To illustrate the problem, taken from https://github.com/darrenjs/openssl_examples/ ...

  +------+                                    +-----+
  |......|--> read(fd) --> BIO_write(rbio) -->|.....|--> SSL_read(ssl)  --> IN
  |......|                                    |.....|
  |.sock.|                                    |.SSL.|
  |......|                                    |.....|
  |......|<-- write(fd) <-- BIO_read(wbio) <--|.....|<-- SSL_write(ssl) <-- OUT
  +------+                                    +-----+

          |                                  |       |                     |
          |<-------------------------------->|       |<------------------->|
          |         encrypted bytes          |       |  unencrypted bytes  |

After reading from a socket into a buffer, we write into another buffer, then SSL_read reads from that buffer and so on. This is necessary because io_uring is handling the actual socket read/writes, which are normally wrapped by OpenSSL itself. So we're forced to use an awkward interface. What's worse; each of the rbio/wbio's appear to have permanent memory buffers. This could make idle connections take dozens or hundreds of kilobytes of memory.

With the custom BIO in this POC we skip the first BIO_write(rbio) and save one data copy. Also on the SSL_write() side we write directly into a socket buffer, which we then flush to io_uring. We release the read/write buffers when they are empty. We also only need one BIO object instead of two, which saves a little extra memory.

While this attempts to be optimal, we still have some extra copies:

  • Without TLS, mcshredder generates requests directly into the socket write buffer. We then flush that buffer to the socket.
  • With TLS, we write to a to-encrypt buffer, which then gets encrypted into another buffer. We then flush that buffer to the socket.

In theory it should be possible to write up to the TLS record limit into a buffer that then gets directly encrypted into the write buffer. It appears OpenSSL has no such interface for this. So we do the best we can.

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.

None yet

1 participant