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

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

Merged
merged 1 commit into from
Dec 30, 2017
Merged

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

merged 1 commit into from
Dec 30, 2017

Conversation

1st1
Copy link
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
@@ -305,6 +305,16 @@ def set_default_executor(self, executor):
"""
raise NotImplementedError

async def start_tls(self, transport, protocol, sslcontext, *,
server_side=False,
Copy link
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
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
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
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
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
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
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
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

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
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
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.


waiter = self.create_future()
app_transport = self._make_ssl_transport(
transport._sock, protocol, sslcontext, waiter,
Copy link
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
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.

transport._sock, protocol, sslcontext, waiter,
server_side=server_side,
ssl_handshake_timeout=ssl_handshake_timeout,
server=transport._server,
Copy link
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
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
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
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.

None yet

5 participants