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

Add support for DTLS timeouts #1180

Merged
merged 2 commits into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
-------------------

Expand Down
31 changes: 31 additions & 0 deletions src/OpenSSL/SSL.py
Original file line number Diff line number Diff line change
Expand Up @@ -2159,6 +2159,37 @@ def DTLSv1_listen(self):
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
Expand Down
46 changes: 42 additions & 4 deletions tests/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import gc
import select
import sys
import time
import uuid
from errno import (
EAFNOSUPPORT,
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -4403,7 +4405,7 @@ def verify_cookie(ssl, cookie):

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*
Expand Down Expand Up @@ -4483,3 +4485,39 @@ def pump():
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)
Copy link
Member

Choose a reason for hiding this comment

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

How long are we sleeping here roughly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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()