Skip to content

start_tls() does not call connection_lost() when the handshake is aborted #160513

Description

@gpshead

Bug report

Bug description:

When the connection is lost while a TLS handshake started by loop.start_tls() or StreamWriter.start_tls() is in progress, the protocol's connection_lost() is never called. start_tls() raises, but SSLProtocol.connection_lost() skips the application protocol whenever its own state is DO_HANDSHAKE, although with start_tls() that protocol has already had connection_made(). With streams, StreamWriter.wait_closed() then never returns.

import asyncio, socket, ssl, struct, threading

def serve(lsock):
    for _ in range(2):
        conn, _ = lsock.accept()
        conn.recv(65536)  # ClientHello
        # Reset the connection in the middle of the handshake.
        conn.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
        conn.close()

class Proto(asyncio.Protocol):
    lost = False
    def connection_lost(self, exc):
        self.lost = True

async def main():
    loop = asyncio.get_running_loop()
    ctx = ssl.create_default_context()
    lsock = socket.create_server(('127.0.0.1', 0))
    threading.Thread(target=serve, args=(lsock,), daemon=True).start()
    addr = lsock.getsockname()

    tr, proto = await loop.create_connection(Proto, *addr)
    try:
        await loop.start_tls(tr, proto, ctx, server_hostname='localhost')
    except OSError as exc:
        print('loop.start_tls():', repr(exc))
    await asyncio.sleep(1)
    print('connection_lost() called:', proto.lost)

    reader, writer = await asyncio.open_connection(*addr)
    try:
        await writer.start_tls(ctx, server_hostname='localhost')
    except OSError as exc:
        print('writer.start_tls():', repr(exc))
    writer.close()
    try:
        await asyncio.wait_for(writer.wait_closed(), 5)
    except TimeoutError:
        print('wait_closed() still pending after 5 seconds')
    except OSError as exc:
        print('wait_closed():', repr(exc))
    lsock.close()

asyncio.run(main())

Output on main:

loop.start_tls(): ConnectionResetError(104, 'Connection reset by peer')
connection_lost() called: False
writer.start_tls(): ConnectionResetError(104, 'Connection reset by peer')
wait_closed() still pending after 5 seconds

Expected: connection_lost() is called and wait_closed() completes, raising the same error. wait_closed() hangs in the same way when the handshake fails because ssl_handshake_timeout expired. The check dates back to the addition of loop.start_tls() in 3.7.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

  • Status
    Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions