Skip to content

bpo-23749: Implement loop.start_tls()#5039

Merged
1st1 merged 1 commit intopython:masterfrom
1st1:start_tls
Dec 30, 2017
Merged

bpo-23749: Implement loop.start_tls()#5039
1st1 merged 1 commit intopython:masterfrom
1st1:start_tls

Conversation

@1st1
Copy link
Copy Markdown
Member

@1st1 1st1 commented Dec 29, 2017

@1st1 1st1 self-assigned this Dec 29, 2017
@1st1 1st1 requested a review from asvetlov December 29, 2017 05:45
@1st1 1st1 changed the title Start tls bpo-23749: Implement loop.start_tls() Dec 29, 2017
Comment thread Lib/asyncio/events.py
raise NotImplementedError

async def start_tls(self, transport, protocol, sslcontext, *,
server_side=False,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Add server_hostname parameter maybe?
At least create_connection has it, checking hostname match increases security.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, server_hostname should be added. Or perhaps a bunch of kwargs to allow passing arbitrary arguments to wrap_socket or wrap_bio.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure, will add it.

Or perhaps a bunch of kwargs to allow passing arbitrary arguments to wrap_socket or wrap_bio.

We don't do that in loop.create_connection or in loop.create_server, we explicitly pass params there. Most of the stuff can be configured through the SSLContext anyways.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah... I guess that could be deferred to another PR. However, the ssl module may add some other parameters along the way, and it would be nicer to inherit them automatically. For example, Python 3.6 got the session parameter: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.wrap_socket

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure. Can you make a PR after this one lands?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clarification: asyncio doesn't use ssl.wrap_socket since Python 3.5

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, but it uses ssl.wrap_bio which accepts similar arguments.

Copy link
Copy Markdown
Contributor

@asvetlov asvetlov left a comment

Choose a reason for hiding this comment

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

Looks good except minor note

Comment thread Lib/asyncio/base_events.py Outdated
f'transport {self!r} is not supported by start_tls()')

if transport.get_write_buffer_size():
if transport._buffer_drained_fut is not None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it necessary to rely on an implementation detail here?

Copy link
Copy Markdown
Member Author

@1st1 1st1 Dec 29, 2017

Choose a reason for hiding this comment

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

Yes. Transports are intimately tied to event loops and are aware of each other private details. Another event loop, say uvloop, can't support asyncio Transport classes, and vice versa.

Currently, transports don't have APIs to notify the loop when their write buffers are drained. We probably need to design such a public API, but that's out of the scope of this PR.

That said, I think I'll add a private base class to share some functionality between Proactor and Selector transport classes. That will also make the code a bit prettier.

Comment thread Lib/asyncio/base_events.py Outdated

waiter = self.create_future()
app_transport = self._make_ssl_transport(
transport._sock, protocol, sslcontext, waiter,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps use transport.get_extra_info('socket')?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since asyncio loop doesn't work uvloop transport (it's true for any loop implementation) using private attributes is perfectly fine from my perspective.

Comment thread Lib/asyncio/base_events.py Outdated
transport._sock, protocol, sslcontext, waiter,
server_side=server_side,
ssl_handshake_timeout=ssl_handshake_timeout,
server=transport._server,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same question.

import threading


class FunctionalTestCaseMixin:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Add docstrings in classes and important methods?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That's a private unittest helper class in Lib/test/test_asyncio/... I copied it from uvloop and I'm still in the process of refactoring it and adapting it to asyncio/Python needs. So I'll add docstrings later.

@1st1 1st1 force-pushed the start_tls branch 2 times, most recently from c495ece to 64a4469 Compare December 30, 2017 04:52
@1st1
Copy link
Copy Markdown
Member Author

1st1 commented Dec 30, 2017

So after some investigation why loop.start_tls() did not work with the Proactor event loop, it turned out that creating a new transport is not the best approach. The Selector event loop supports that, but Proactor requires the old transport to be carefully detached from its underlying object. Doing that correctly is quite an elaborate task. Otherwise there are multiple races which all lead to mixed-up read and write events.

What I decided to do instead is to simply instantiate SSLProtocol directly in loop.start_tls(). That allowed me to pass an existing transport instance to it, eliminating the need to drain the buffer or creating a new transport. Now tests pass for the Proactor event loop on Windows.

The new approach is way safer than the previous one:

  • No need to touch transports implementation.
  • No need to drain the transport's write buffer -- SSLProtocol will simply write to the same buffer.
  • Draining buffers is hard. The previous implementation did that incorrectly, and did not handle situations when a transport object is closed due to an error and write callback is never called.
  • We reuse essentially the same code path that create_connection and create_server follow.

@1st1 1st1 merged commit f111b3d into python:master Dec 30, 2017
@1st1 1st1 deleted the start_tls branch December 30, 2017 05:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants