10 seconds by default.
Creating connections
--------------------
-.. coroutinemethod:: AbstractEventLoop.create_connection(protocol_factory, host=None, port=None, \*, ssl=None, family=0, proto=0, flags=0, sock=None, local_addr=None, server_hostname=None)
+.. coroutinemethod:: AbstractEventLoop.create_connection(protocol_factory, host=None, port=None, \*, ssl=None, family=0, proto=0, flags=0, sock=None, local_addr=None, server_hostname=None, ssl_handshake_timeout=10.0)
Create a streaming transport connection to a given Internet *host* and
*port*: socket family :py:data:`~socket.AF_INET` or
to bind the socket to locally. The *local_host* and *local_port*
are looked up using getaddrinfo(), similarly to *host* and *port*.
+ * *ssl_handshake_timeout* is (for an SSL connection) the time in seconds
+ to wait for the SSL handshake to complete before aborting the connection.
+
+ .. versionadded:: 3.7
+
+ The *ssl_handshake_timeout* parameter.
+
.. versionchanged:: 3.5
On Windows with :class:`ProactorEventLoop`, SSL/TLS is now supported.
:ref:`UDP echo server protocol <asyncio-udp-echo-server-protocol>` examples.
-.. coroutinemethod:: AbstractEventLoop.create_unix_connection(protocol_factory, path=None, \*, ssl=None, sock=None, server_hostname=None)
+.. coroutinemethod:: AbstractEventLoop.create_unix_connection(protocol_factory, path=None, \*, ssl=None, sock=None, server_hostname=None, ssl_handshake_timeout=10.0)
Create UNIX connection: socket family :py:data:`~socket.AF_UNIX`, socket
type :py:data:`~socket.SOCK_STREAM`. The :py:data:`~socket.AF_UNIX` socket
Availability: UNIX.
+ .. versionadded:: 3.7
+
+ The *ssl_handshake_timeout* parameter.
+
.. versionchanged:: 3.7
The *path* parameter can now be a :class:`~pathlib.Path` object.
Creating listening connections
------------------------------
-.. coroutinemethod:: AbstractEventLoop.create_server(protocol_factory, host=None, port=None, \*, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None)
+.. coroutinemethod:: AbstractEventLoop.create_server(protocol_factory, host=None, port=None, \*, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, ssl_handshake_timeout=10.0)
Create a TCP server (socket type :data:`~socket.SOCK_STREAM`) bound to
*host* and *port*.
set this flag when being created. This option is not supported on
Windows.
+ * *ssl_handshake_timeout* is (for an SSL server) the time in seconds to wait
+ for the SSL handshake to complete before aborting the connection.
+
+ .. versionadded:: 3.7
+
+ The *ssl_handshake_timeout* parameter.
+
.. versionchanged:: 3.5
On Windows with :class:`ProactorEventLoop`, SSL/TLS is now supported.
The *host* parameter can now be a sequence of strings.
-.. coroutinemethod:: AbstractEventLoop.create_unix_server(protocol_factory, path=None, \*, sock=None, backlog=100, ssl=None)
+.. coroutinemethod:: AbstractEventLoop.create_unix_server(protocol_factory, path=None, \*, sock=None, backlog=100, ssl=None, ssl_handshake_timeout=10.0)
Similar to :meth:`AbstractEventLoop.create_server`, but specific to the
socket family :py:data:`~socket.AF_UNIX`.
Availability: UNIX.
+ .. versionadded:: 3.7
+
+ The *ssl_handshake_timeout* parameter.
+
.. versionchanged:: 3.7
The *path* parameter can now be a :class:`~pathlib.Path` object.
-.. coroutinemethod:: BaseEventLoop.connect_accepted_socket(protocol_factory, sock, \*, ssl=None)
+.. coroutinemethod:: BaseEventLoop.connect_accepted_socket(protocol_factory, sock, \*, ssl=None, ssl_handshake_timeout=10.0)
Handle an accepted connection.
* *ssl* can be set to an :class:`~ssl.SSLContext` to enable SSL over the
accepted connections.
+ * *ssl_handshake_timeout* is (for an SSL connection) the time in seconds to
+ wait for the SSL handshake to complete before aborting the connection.
+
When completed it returns a ``(transport, protocol)`` pair.
+ .. versionadded:: 3.7
+
+ The *ssl_handshake_timeout* parameter.
+
.. versionadded:: 3.5.3
import warnings
import weakref
+from . import constants
from . import coroutines
from . import events
from . import futures
"""Create socket transport."""
raise NotImplementedError
- def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter=None,
- *, server_side=False, server_hostname=None,
- extra=None, server=None):
+ def _make_ssl_transport(
+ self, rawsock, protocol, sslcontext, waiter=None,
+ *, server_side=False, server_hostname=None,
+ extra=None, server=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
"""Create SSL transport."""
raise NotImplementedError
return await self.run_in_executor(
None, socket.getnameinfo, sockaddr, flags)
- async def create_connection(self, protocol_factory, host=None, port=None,
- *, ssl=None, family=0,
- proto=0, flags=0, sock=None,
- local_addr=None, server_hostname=None):
+ async def create_connection(
+ self, protocol_factory, host=None, port=None,
+ *, ssl=None, family=0,
+ proto=0, flags=0, sock=None,
+ local_addr=None, server_hostname=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
"""Connect to a TCP server.
Create a streaming transport connection to a given Internet host and
f'A Stream Socket was expected, got {sock!r}')
transport, protocol = await self._create_connection_transport(
- sock, protocol_factory, ssl, server_hostname)
+ sock, protocol_factory, ssl, server_hostname,
+ ssl_handshake_timeout=ssl_handshake_timeout)
if self._debug:
# Get the socket from the transport because SSL transport closes
# the old socket and creates a new SSL socket
sock, host, port, transport, protocol)
return transport, protocol
- async def _create_connection_transport(self, sock, protocol_factory, ssl,
- server_hostname, server_side=False):
+ async def _create_connection_transport(
+ self, sock, protocol_factory, ssl,
+ server_hostname, server_side=False,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
sock.setblocking(False)
sslcontext = None if isinstance(ssl, bool) else ssl
transport = self._make_ssl_transport(
sock, protocol, sslcontext, waiter,
- server_side=server_side, server_hostname=server_hostname)
+ server_side=server_side, server_hostname=server_hostname,
+ ssl_handshake_timeout=ssl_handshake_timeout)
else:
transport = self._make_socket_transport(sock, protocol, waiter)
raise OSError(f'getaddrinfo({host!r}) returned empty list')
return infos
- async def create_server(self, protocol_factory, host=None, port=None,
- *,
- family=socket.AF_UNSPEC,
- flags=socket.AI_PASSIVE,
- sock=None,
- backlog=100,
- ssl=None,
- reuse_address=None,
- reuse_port=None):
+ async def create_server(
+ self, protocol_factory, host=None, port=None,
+ *,
+ family=socket.AF_UNSPEC,
+ flags=socket.AI_PASSIVE,
+ sock=None,
+ backlog=100,
+ ssl=None,
+ reuse_address=None,
+ reuse_port=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
"""Create a TCP server.
The host parameter can be a string, in that case the TCP server is
for sock in sockets:
sock.listen(backlog)
sock.setblocking(False)
- self._start_serving(protocol_factory, sock, ssl, server, backlog)
+ self._start_serving(protocol_factory, sock, ssl, server, backlog,
+ ssl_handshake_timeout)
if self._debug:
logger.info("%r is serving", server)
return server
- async def connect_accepted_socket(self, protocol_factory, sock,
- *, ssl=None):
+ async def connect_accepted_socket(
+ self, protocol_factory, sock,
+ *, ssl=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
"""Handle an accepted connection.
This is used by servers that accept connections outside of
raise ValueError(f'A Stream Socket was expected, got {sock!r}')
transport, protocol = await self._create_connection_transport(
- sock, protocol_factory, ssl, '', server_side=True)
+ sock, protocol_factory, ssl, '', server_side=True,
+ ssl_handshake_timeout=ssl_handshake_timeout)
if self._debug:
# Get the socket from the transport because SSL transport closes
# the old socket and creates a new SSL socket
# The larger the number, the slower the operation in debug mode
# (see extract_stack() in format_helpers.py).
DEBUG_STACK_DEPTH = 10
+
+# Number of seconds to wait for SSL handshake to complete
+SSL_HANDSHAKE_TIMEOUT = 10.0
async def getnameinfo(self, sockaddr, flags=0):
raise NotImplementedError
- async def create_connection(self, protocol_factory, host=None, port=None,
- *, ssl=None, family=0, proto=0,
- flags=0, sock=None, local_addr=None,
- server_hostname=None):
- raise NotImplementedError
-
- async def create_server(self, protocol_factory, host=None, port=None,
- *, family=socket.AF_UNSPEC,
- flags=socket.AI_PASSIVE, sock=None, backlog=100,
- ssl=None, reuse_address=None, reuse_port=None):
+ async def create_connection(
+ self, protocol_factory, host=None, port=None,
+ *, ssl=None, family=0, proto=0,
+ flags=0, sock=None, local_addr=None,
+ server_hostname=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
+ raise NotImplementedError
+
+ async def create_server(
+ self, protocol_factory, host=None, port=None,
+ *, family=socket.AF_UNSPEC,
+ flags=socket.AI_PASSIVE, sock=None, backlog=100,
+ ssl=None, reuse_address=None, reuse_port=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
"""A coroutine which creates a TCP server bound to host and port.
The return value is a Server object which can be used to stop
the same port as other existing endpoints are bound to, so long as
they all set this flag when being created. This option is not
supported on Windows.
+
+ ssl_handshake_timeout is the time in seconds that an SSL server
+ will wait for completion of the SSL handshake before aborting the
+ connection. Default is 10s, longer timeouts may increase vulnerability
+ to DoS attacks (see https://support.f5.com/csp/article/K13834)
"""
raise NotImplementedError
- async def create_unix_connection(self, protocol_factory, path=None, *,
- ssl=None, sock=None,
- server_hostname=None):
+ async def create_unix_connection(
+ self, protocol_factory, path=None, *,
+ ssl=None, sock=None,
+ server_hostname=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
raise NotImplementedError
- async def create_unix_server(self, protocol_factory, path=None, *,
- sock=None, backlog=100, ssl=None):
+ async def create_unix_server(
+ self, protocol_factory, path=None, *,
+ sock=None, backlog=100, ssl=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
"""A coroutine which creates a UNIX Domain Socket server.
The return value is a Server object, which can be used to stop
ssl can be set to an SSLContext to enable SSL over the
accepted connections.
+
+ ssl_handshake_timeout is the time in seconds that an SSL server
+ will wait for the SSL handshake to complete (defaults to 10s).
"""
raise NotImplementedError
return _ProactorSocketTransport(self, sock, protocol, waiter,
extra, server)
- def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter=None,
- *, server_side=False, server_hostname=None,
- extra=None, server=None):
- ssl_protocol = sslproto.SSLProtocol(self, protocol, sslcontext, waiter,
- server_side, server_hostname)
+ def _make_ssl_transport(
+ self, rawsock, protocol, sslcontext, waiter=None,
+ *, server_side=False, server_hostname=None,
+ extra=None, server=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
+ ssl_protocol = sslproto.SSLProtocol(
+ self, protocol, sslcontext, waiter,
+ server_side, server_hostname,
+ ssl_handshake_timeout=ssl_handshake_timeout)
_ProactorSocketTransport(self, rawsock, ssl_protocol,
extra=extra, server=server)
return ssl_protocol._app_transport
self._csock.send(b'\0')
def _start_serving(self, protocol_factory, sock,
- sslcontext=None, server=None, backlog=100):
+ sslcontext=None, server=None, backlog=100,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
def loop(f=None):
try:
if sslcontext is not None:
self._make_ssl_transport(
conn, protocol, sslcontext, server_side=True,
- extra={'peername': addr}, server=server)
+ extra={'peername': addr}, server=server,
+ ssl_handshake_timeout=ssl_handshake_timeout)
else:
self._make_socket_transport(
conn, protocol,
return _SelectorSocketTransport(self, sock, protocol, waiter,
extra, server)
- def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter=None,
- *, server_side=False, server_hostname=None,
- extra=None, server=None):
- ssl_protocol = sslproto.SSLProtocol(self, protocol, sslcontext, waiter,
- server_side, server_hostname)
+ def _make_ssl_transport(
+ self, rawsock, protocol, sslcontext, waiter=None,
+ *, server_side=False, server_hostname=None,
+ extra=None, server=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
+ ssl_protocol = sslproto.SSLProtocol(
+ self, protocol, sslcontext, waiter,
+ server_side, server_hostname,
+ ssl_handshake_timeout=ssl_handshake_timeout)
_SelectorSocketTransport(self, rawsock, ssl_protocol,
extra=extra, server=server)
return ssl_protocol._app_transport
exc_info=True)
def _start_serving(self, protocol_factory, sock,
- sslcontext=None, server=None, backlog=100):
+ sslcontext=None, server=None, backlog=100,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
self._add_reader(sock.fileno(), self._accept_connection,
- protocol_factory, sock, sslcontext, server, backlog)
+ protocol_factory, sock, sslcontext, server, backlog,
+ ssl_handshake_timeout)
- def _accept_connection(self, protocol_factory, sock,
- sslcontext=None, server=None, backlog=100):
+ def _accept_connection(
+ self, protocol_factory, sock,
+ sslcontext=None, server=None, backlog=100,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
# This method is only called once for each event loop tick where the
# listening socket has triggered an EVENT_READ. There may be multiple
# connections waiting for an .accept() so it is called in a loop.
self.call_later(constants.ACCEPT_RETRY_DELAY,
self._start_serving,
protocol_factory, sock, sslcontext, server,
- backlog)
+ backlog, ssl_handshake_timeout)
else:
raise # The event loop will catch, log and ignore it.
else:
extra = {'peername': addr}
accept = self._accept_connection2(
- protocol_factory, conn, extra, sslcontext, server)
+ protocol_factory, conn, extra, sslcontext, server,
+ ssl_handshake_timeout)
self.create_task(accept)
- async def _accept_connection2(self, protocol_factory, conn, extra,
- sslcontext=None, server=None):
+ async def _accept_connection2(
+ self, protocol_factory, conn, extra,
+ sslcontext=None, server=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
protocol = None
transport = None
try:
if sslcontext:
transport = self._make_ssl_transport(
conn, protocol, sslcontext, waiter=waiter,
- server_side=True, extra=extra, server=server)
+ server_side=True, extra=extra, server=server,
+ ssl_handshake_timeout=ssl_handshake_timeout)
else:
transport = self._make_socket_transport(
conn, protocol, waiter=waiter, extra=extra,
ssl = None
from . import base_events
+from . import constants
from . import protocols
from . import transports
from .log import logger
def __init__(self, loop, app_protocol, sslcontext, waiter,
server_side=False, server_hostname=None,
- call_connection_made=True):
+ call_connection_made=True,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
if ssl is None:
raise RuntimeError('stdlib ssl module not available')
# transport, ex: SelectorSocketTransport
self._transport = None
self._call_connection_made = call_connection_made
+ self._ssl_handshake_timeout = ssl_handshake_timeout
def _wakeup_waiter(self, exc=None):
if self._waiter is None:
# the SSL handshake
self._write_backlog.append((b'', 1))
self._loop.call_soon(self._process_write_backlog)
+ self._handshake_timeout_handle = \
+ self._loop.call_later(self._ssl_handshake_timeout,
+ self._check_handshake_timeout)
+
+ def _check_handshake_timeout(self):
+ if self._in_handshake is True:
+ logger.warning("%r stalled during handshake", self)
+ self._abort()
def _on_handshake_complete(self, handshake_exc):
self._in_handshake = False
+ self._handshake_timeout_handle.cancel()
sslobj = self._sslpipe.ssl_object
try:
def _child_watcher_callback(self, pid, returncode, transp):
self.call_soon_threadsafe(transp._process_exited, returncode)
- async def create_unix_connection(self, protocol_factory, path=None, *,
- ssl=None, sock=None,
- server_hostname=None):
+ async def create_unix_connection(
+ self, protocol_factory, path=None, *,
+ ssl=None, sock=None,
+ server_hostname=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
assert server_hostname is None or isinstance(server_hostname, str)
if ssl:
if server_hostname is None:
sock.setblocking(False)
transport, protocol = await self._create_connection_transport(
- sock, protocol_factory, ssl, server_hostname)
+ sock, protocol_factory, ssl, server_hostname,
+ ssl_handshake_timeout=ssl_handshake_timeout)
return transport, protocol
- async def create_unix_server(self, protocol_factory, path=None, *,
- sock=None, backlog=100, ssl=None):
+ async def create_unix_server(
+ self, protocol_factory, path=None, *,
+ sock=None, backlog=100, ssl=None,
+ ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
if isinstance(ssl, bool):
raise TypeError('ssl argument must be an SSLContext or None')
server = base_events.Server(self, [sock])
sock.listen(backlog)
sock.setblocking(False)
- self._start_serving(protocol_factory, sock, ssl, server)
+ self._start_serving(protocol_factory, sock, ssl, server,
+ ssl_handshake_timeout=ssl_handshake_timeout)
return server
self.loop._make_ssl_transport.side_effect = mock_make_ssl_transport
ANY = mock.ANY
+ handshake_timeout = object()
# First try the default server_hostname.
self.loop._make_ssl_transport.reset_mock()
- coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True)
+ coro = self.loop.create_connection(
+ MyProto, 'python.org', 80, ssl=True,
+ ssl_handshake_timeout=handshake_timeout)
transport, _ = self.loop.run_until_complete(coro)
transport.close()
self.loop._make_ssl_transport.assert_called_with(
ANY, ANY, ANY, ANY,
server_side=False,
- server_hostname='python.org')
+ server_hostname='python.org',
+ ssl_handshake_timeout=handshake_timeout)
# Next try an explicit server_hostname.
self.loop._make_ssl_transport.reset_mock()
- coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True,
- server_hostname='perl.com')
+ coro = self.loop.create_connection(
+ MyProto, 'python.org', 80, ssl=True,
+ server_hostname='perl.com',
+ ssl_handshake_timeout=handshake_timeout)
transport, _ = self.loop.run_until_complete(coro)
transport.close()
self.loop._make_ssl_transport.assert_called_with(
ANY, ANY, ANY, ANY,
server_side=False,
- server_hostname='perl.com')
+ server_hostname='perl.com',
+ ssl_handshake_timeout=handshake_timeout)
# Finally try an explicit empty server_hostname.
self.loop._make_ssl_transport.reset_mock()
- coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True,
- server_hostname='')
+ coro = self.loop.create_connection(
+ MyProto, 'python.org', 80, ssl=True,
+ server_hostname='',
+ ssl_handshake_timeout=handshake_timeout)
transport, _ = self.loop.run_until_complete(coro)
transport.close()
- self.loop._make_ssl_transport.assert_called_with(ANY, ANY, ANY, ANY,
- server_side=False,
- server_hostname='')
+ self.loop._make_ssl_transport.assert_called_with(
+ ANY, ANY, ANY, ANY,
+ server_side=False,
+ server_hostname='',
+ ssl_handshake_timeout=handshake_timeout)
def test_create_connection_no_ssl_server_hostname_errors(self):
# When not using ssl, server_hostname must be None.
constants.ACCEPT_RETRY_DELAY,
# self.loop._start_serving
mock.ANY,
- MyProto, sock, None, None, mock.ANY)
+ MyProto, sock, None, None, mock.ANY, mock.ANY)
def test_call_coroutine(self):
@asyncio.coroutine
import asyncio
from asyncio import log
from asyncio import sslproto
+from asyncio import tasks
from test.test_asyncio import utils as test_utils
def ssl_protocol(self, waiter=None):
sslcontext = test_utils.dummy_ssl_context()
app_proto = asyncio.Protocol()
- proto = sslproto.SSLProtocol(self.loop, app_proto, sslcontext, waiter)
+ proto = sslproto.SSLProtocol(self.loop, app_proto, sslcontext, waiter,
+ ssl_handshake_timeout=0.1)
self.assertIs(proto._app_transport.get_protocol(), app_proto)
self.addCleanup(proto._app_transport.close)
return proto
with test_utils.disable_logger():
self.loop.run_until_complete(handshake_fut)
+ def test_handshake_timeout(self):
+ # bpo-29970: Check that a connection is aborted if handshake is not
+ # completed in timeout period, instead of remaining open indefinitely
+ ssl_proto = self.ssl_protocol()
+ transport = self.connection_made(ssl_proto)
+
+ with test_utils.disable_logger():
+ self.loop.run_until_complete(tasks.sleep(0.2, loop=self.loop))
+ self.assertTrue(transport.abort.called)
+
def test_eof_received_waiter(self):
waiter = asyncio.Future(loop=self.loop)
ssl_proto = self.ssl_protocol(waiter)
Jason Asbahr
David Ascher
Ammar Askar
+Neil Aspinall
Chris AtLee
Aymeric Augustin
Cathy Avery
--- /dev/null
+Abort asyncio SSLProtocol connection if handshake not complete within 10s