diff options
-rw-r--r-- | CHANGELOG.rst | 3 | ||||
-rw-r--r-- | src/OpenSSL/SSL.py | 31 | ||||
-rw-r--r-- | tests/test_ssl.py | 46 |
3 files changed, 76 insertions, 4 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 67a6ae3..7842545 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,9 @@ Deprecations: Changes: ^^^^^^^^ +- Add ``OpenSSL.SSL.Connection.DTLSv1_get_timeout`` and ``OpenSSL.SSL.Connection.DTLSv1_handle_timeout`` + to support DTLS timeouts `#1180 <https://github.com/pyca/pyopenssl/pull/1180>`_. + 23.0.0 (2023-01-01) ------------------- diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py index c1fb0f5..efbf790 100644 --- a/src/OpenSSL/SSL.py +++ b/src/OpenSSL/SSL.py @@ -2159,6 +2159,37 @@ class Connection: if result < 0: self._raise_ssl_error(self._ssl, result) + def DTLSv1_get_timeout(self): + """ + Determine when the DTLS SSL object next needs to perform internal + processing due to the passage of time. + + When the returned number of seconds have passed, the + :meth:`DTLSv1_handle_timeout` method needs to be called. + + :return: The time left in seconds before the next timeout or `None` + if no timeout is currently active. + """ + ptv_sec = _ffi.new("time_t *") + ptv_usec = _ffi.new("long *") + if _lib.Cryptography_DTLSv1_get_timeout(self._ssl, ptv_sec, ptv_usec): + return ptv_sec[0] + (ptv_usec[0] / 1000000) + else: + return None + + def DTLSv1_handle_timeout(self): + """ + Handles any timeout events which have become pending on a DTLS SSL + object. + + :return: `True` if there was a pending timeout, `False` otherwise. + """ + result = _lib.DTLSv1_handle_timeout(self._ssl) + if result < 0: + self._raise_ssl_error(self._ssl, result) + else: + return bool(result) + def bio_shutdown(self): """ If the Connection was created with a memory BIO, this method can be diff --git a/tests/test_ssl.py b/tests/test_ssl.py index bfc5ca8..e6c0cdc 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -9,6 +9,7 @@ import datetime import gc import select import sys +import time import uuid from errno import ( EAFNOSUPPORT, @@ -4369,10 +4370,11 @@ class TestDTLS: # new versions of OpenSSL, this is unnecessary, but harmless, because the # DTLS state machine treats it like a network hiccup that duplicated a # packet, which DTLS is robust against. - def test_it_works_at_all(self): - # arbitrary number larger than any conceivable handshake volley - LARGE_BUFFER = 65536 + # Arbitrary number larger than any conceivable handshake volley. + LARGE_BUFFER = 65536 + + def test_it_works_at_all(self): s_ctx = Context(DTLS_METHOD) def generate_cookie(ssl): @@ -4403,7 +4405,7 @@ class TestDTLS: def pump_membio(label, source, sink): try: - chunk = source.bio_read(LARGE_BUFFER) + chunk = source.bio_read(self.LARGE_BUFFER) except WantReadError: return False # I'm not sure this check is needed, but I'm not sure it's *not* @@ -4483,3 +4485,39 @@ class TestDTLS: assert 0 < c.get_cleartext_mtu() < 500 except NotImplementedError: # OpenSSL 1.1.0 and earlier pass + + def test_timeout(self, monkeypatch): + c_ctx = Context(DTLS_METHOD) + c = Connection(c_ctx) + + # No timeout before the handshake starts. + assert c.DTLSv1_get_timeout() is None + assert c.DTLSv1_handle_timeout() is False + + # Start handshake and check there is data to send. + c.set_connect_state() + try: + c.do_handshake() + except SSL.WantReadError: + pass + assert c.bio_read(self.LARGE_BUFFER) + + # There should now be an active timeout. + seconds = c.DTLSv1_get_timeout() + assert seconds is not None + + # Handle the timeout and check there is data to send. + time.sleep(seconds) + assert c.DTLSv1_handle_timeout() is True + assert c.bio_read(self.LARGE_BUFFER) + + # After the maximum number of allowed timeouts is reached, + # DTLSv1_handle_timeout will return -1. + # + # Testing this directly is prohibitively time consuming as the timeout + # duration is doubled on each retry, so the best we can do is to mock + # this condition. + monkeypatch.setattr(_lib, "DTLSv1_handle_timeout", lambda x: -1) + + with pytest.raises(Error): + c.DTLSv1_handle_timeout() |