Set SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER in calling OpenSSL#1287
Conversation
|
See #1242 for more context. |
webknjaz
left a comment
There was a problem hiding this comment.
Add some empty line separators under title marks.
c2cae69 to
2e788b7
Compare
6c1d483 to
23e9e11
Compare
|
@mhils @alex it appears that https://app.codecov.io/gh/pyca/pyopenssl treats |
|
I've changed the default branch to be main |
|
Is there a smoke test we could include in this PR? |
|
@webknjaz The only test for setting the mode currently is: This checks that setting the mode for MODE_RELEASE_BUFFERS returns the same bit. I guess we could add another check to make sure passing SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER also returns the appropriate value? Not sure whether that counts as a smoke test but it's something perhaps? |
|
Maybe, find some existing test where a write buffer is passed, copy it and pass a moving buffer there? @mhils ideas? |
|
We could possibly adapt something like this: https://github.com/pyca/pyopenssl/blob/main/tests/test_ssl.py#L2837 If that's easy to add I'm all for it, but I also feel that not having an elaborate test here is not the end of the world. There's precedent (SSL_MODE_ENABLE_PARTIAL_WRITE has no test either) and, more importantly, if this fails in the future we should get a very explicit 'bad write retry' error. Does CPython have a dedicated test for this? This looks good to merge otherwise. @alex @reaperhulk, if you want to make a judgement call here please just merge. :) |
|
@mhils Strangely, when I try running pytest locally on my branch I am getting an exception on that test that makes it fail the test: That's just a fragment of the output so maybe not very meaningful but as I understand it, the test is meant to throw WantWriteError but for some reason although it's expected the exception is not being considered a success? How is this supposed to work? |
In your log, a more generic exception happens ( |
Ok to make any progress on this, I'm trying to understand the first test that is failing when I run pytest locally - test_set_default_verify_paths() in test_ssl.py. It's basically saying "certificate verify failed". I tried debugging this using the following in the command line:
and get:
I assume this generates an error because the return code is not 0. But how is this supposed to work? The test relies on Google's certificate but there seems to be problem with the cert? |
alex
left a comment
There was a problem hiding this comment.
Very close! Thanks for your persistence on this.
I'm a bit ambivalent on the logs, it's not really our house style and IME for things like this, while the logs are useful in the initial development, they're unlikely to be useful down the line. (In contrast to logs in production software, where you have a more continious validation process.)
| try: | ||
| if server: | ||
| server.shutdown() | ||
| except Exception: # pragma: no cover |
There was a problem hiding this comment.
Same comment as above
| def _badwriteretry( | ||
| self, | ||
| want_bad_retry: bool, | ||
| modeflag: int | None, | ||
| ) -> bool: | ||
| """ | ||
| Tests for a "bad write retry" error over an SSL connection | ||
| by using a moving buffer which is allowed by default with | ||
| SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER. If this mode is unset | ||
| we expect a bad write retry error when using a moving buffer. | ||
| want_bad_retry: If True, the caller expects a bad write retry error. | ||
| mode: If not None, unsets the defaults that include | ||
| SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER and replaces with the given mode. | ||
| Returns True if a bad write retry error occurs. | ||
| """ | ||
| request_buffer_size = 65536 # Size of the send buffer we'll request | ||
| client_socket, server_socket, client, server, sndbuf, rcvbuf = ( | ||
| create_ssl_nonblocking_connection(modeflag, request_buffer_size) | ||
| ) | ||
| result = False # Default return value | ||
| # set buffer size to half the minimum of send and receive buffers | ||
| buffer_size = min(sndbuf, rcvbuf) // 2 | ||
|
|
||
| # --- Main Test Flow --- | ||
| # _attempt_want_write_error() terminates the test | ||
| # if WantWriteError is not triggered | ||
| # The function also returns the message that triggered | ||
| # the WantWriteError so that when we attempt a retry | ||
| # we can ensure a different buffer location is allocated | ||
| # to a the new message we will send for the retry. | ||
| _ = self._attempt_want_write_error(client, buffer_size) | ||
|
|
||
| # proceed with draining so that a retry has a chance to succeed | ||
| self._drain_server_buffers(server, server_socket) | ||
|
|
||
| # now attempt the moving buffer retry | ||
| result = self._perform_moving_buffer_test( | ||
| client, buffer_size, want_bad_retry | ||
| ) | ||
| self._shutdown_connections( | ||
| client, server, client_socket, server_socket | ||
| ) | ||
| return result |
There was a problem hiding this comment.
This looks like a sort of thing that seems natural to wrap with @contextlib.contextmanager or (even better @pytest.fixture with a yield for cleanup and indirect params).
There was a problem hiding this comment.
Thanks for the suggestion will do.
…WRITE errors. When SSL_WANT_READ or SSL_WANT_WRITE are encountered, it's typical to retry the call but this must be repeated with the exact same arguments. Without this change, openSSL requires that the address of the buffer passed is the same. However, buffers in python can change location in some circumstances which cause the retry to fail. By add the setting SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER, the requirement for the same buffer address is forgiven and the retry has a better chance of success. See cherrypy/cheroot#245 for discussion.
|
There's some lint errors, and you'll need to update the minimum version in noxfile.py, but the code itself looks good. Thanks for your persistence herer! |
This reverts commit bb1d143.
|
Thanks! Hopefully, this will unblock Cheroot once released.. |
Agreed! I wanted to create a PR for Cheroot once we had this. |
It's been released a week ago. So that's finally unblocked! |
|
Great thanks for the heads up! |
See cherrypy/cheroot#245 for discussion.