Uh oh!
There was an error while loading. Please reload this page.
Add support for synchronous TLS-in-TLS connections. - #732
Conversation
We will most likely need to write these tests for each backend. without tls
with tls
with tls in tls
|
This tests are simply smoke tests that do not require any assertion; we simply want to know if it runs or not. (maybe excluding read tests) |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
karpetrosyan
commented
Jul 3, 2023
Now we have three failed tests, all of which are synchornous |
karpetrosyan
commented
Aug 8, 2023
Could you please review this? @tomchristie It's a complicated PR that may be difficult to review, but I believe we need it in 0.18.0. |
| def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: | ||
| raise NotImplementedError() # pragma: nocover | ||
| def __enter__(self) -> "Self": |
There was a problem hiding this comment.
I don't think we need the typing_extensions.Self here. (?)
Can we just have this return NetworkStream.
The override point is close(), not the __enter__/__exit__ which will stay the same even for subclasses.
There was a problem hiding this comment.
If we can inherit, I believe we should always use Self to avoid strange type issues, such as when the instance of SlowNetworkStream is NetworkStream.
There was a problem hiding this comment.
Example:
importtypingfromhttpcore._backends.baseimportNetworkStreamfromtimeimportsleepclassSlowNetworkStream(NetworkStream):
defread(self, max_bytes: int, timeout: float|None=None) ->bytes:
sleep(100)
withSlowNetworkStream() asstream:
reveal_type(stream)OUTPUT test.py:11: note: Revealed type is "httpcore._backends.base.NetworkStream"
There was a problem hiding this comment.
Ah okay right.
Could we use the TypeVar style, then?
Eg... in httpx
https://github.com/encode/httpx/blob/76c9cb65f2a159adb764c2236d139f85b46e1506/httpx/_client.py#L60
https://github.com/encode/httpx/blob/76c9cb65f2a159adb764c2236d139f85b46e1506/httpx/_client.py#L1263
Really prefer us avoiding introducing new third party packages wherever possible.
There was a problem hiding this comment.
Initially, we used TypeVar, but it was too complicated.
Okay, so...
Do those two changes seem acceptable to you? Otherwise looking great. Thanks! 😊 |
karpetrosyan
commented
Aug 8, 2023
If we do that, we will be able to hang out indefinitely even though a timeout has been set; do we want that? |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
lovelydinosaur
commented
Aug 8, 2023
I'm not suggesting that we remove the timeout, I'm suggesting that we keep it nice and simple and just call |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
karpetrosyan
commented
Aug 8, 2023
I'm not sure how we can do that 😢 ; could you please provide an example? |
T-256
commented
Aug 8, 2023
IMO the current behavior is correct. |
lovelydinosaur
commented
Aug 8, 2023
This... def_perform_io(
self,
func: typing.Callable[..., typing.Any],
timeout: typing.Optional[float] =None,
) ->typing.Any:
ret=Noneself._sock.settimeout(timeout)
whileTrue:
errno=Nonetry:
ret=func()
except (ssl.SSLWantReadError, ssl.SSLWantWriteError) ase:
errno=e.errnoself._sock.sendall(self._outgoing.read())
iferrno==ssl.SSL_ERROR_WANT_READ:
buf=self._sock.recv(self.TLS_RECORD_SIZE)
ifbuf:
self._incoming.write(buf)
else:
self._incoming.write_eof() # pragma: no coveriferrnoisNone:
returnretThe timeouts aren't perfect if we're in TLS-in-TLS, but we probably don't care. (vs the cost of this method being complex to understand.) |
karpetrosyan
commented
Aug 8, 2023
Time problem of that solution is: |
T-256
commented
Aug 8, 2023
So, according to current timeout calculation, what kind of timeout it would be? |
T-256
commented
Aug 8, 2023
And other question here is if |
karpetrosyan
commented
Aug 8, 2023
It has already been implemented, and it functions exactly like a standard TLS connection, with write timeouts for write operations, read timeouts for read operations, and so on. @tomchristie suggests setting the timeout only once, but I'm afraid we won't be able to do so. One TLS operation, such as If the socket timeout was only set once, it would be the same for each socket operation, so we need to decrease the timeout based on how long our socket operation takes. So, if we do this, we may have to wait 50 seconds when the timeout is set to 5 or even hang indefinitely. |
As a result, the problem I described is easily reproducible. change the defhandle_tunnel_connection(client_sock: socket.socket) ->None:
fromtimeimportsleepwithclient_sock, socket.create_connection(TLS_ADDRESS) asremote_socket: # type: ignorewhileTrue:
try:
try:
client_sock.settimeout(TUNNEL_READ_WRITE_TIMEOUT)
buffer=client_sock.recv(1024)
remote_socket.sendall(buffer)
exceptsocket.timeout: # pragma: no coverpasstry:
remote_socket.settimeout(TUNNEL_READ_WRITE_TIMEOUT)
buffer=remote_socket.recv(1024)
whilebuffer:
client_sock.sendall(buffer[:1])
buffer=buffer[1:]
sleep(1)
exceptsocket.timeout: # pragma: no coverpassexceptOSError:
breakAnd run The But, with @tomchristie's implementation, the test will hang indefinitely.. |
T-256
commented
Aug 8, 2023
@karosis88 |
karpetrosyan
commented
Aug 25, 2023
Sorry for the delayed response. I don't think we'll have any problems with this because we're updating the timeout before each network operation to ensure that our network operations use the correct timeout. Please correct me if I am wrong. |
T-256
commented
Aug 25, 2023
IMO we should follow implementation in other libraries, we should not apply any changes on sockets as it could have errors in other instances using the same socket. |
karpetrosyan
commented
Aug 28, 2023
I'm not sure what you mean, but if there is an issue, you can describe it and write some simple reproducible code so we can work through it. |
T-256
commented
Aug 28, 2023
I think in this PR the socket's timeout changes over time can cause problems in outer scopes where we set constant value for socket timeout. |
karpetrosyan
commented
Aug 30, 2023
@encode/maintainers Anyone? :( |
lovelydinosaur
commented
Aug 30, 2023
I've been quietly reviewing this today, and I'm still of the "let's have However... I'm okay with shelving my judgement on that. I'm up for merging this and #722 once I'm able to test and verify. If anyone's able to provide a nice example of how to set up a (ideally python based, just because that'd work for me) proxy setup that we can verify TLS-in-TLS support against then that'd be helpful. Otherwise I'll dig into it myself and update the ticket when sorted. |
lovelydinosaur
commented
Sep 1, 2023
I've issued #786 which is a minimal-viable-product alternative to this pull request. It's a bit atypical in that I'm advocating for a bit of a relaxed approach to testing there, see the description for my motivation there. |
lovelydinosaur
commented
Sep 1, 2023
Resolved in #786. (Credit to @karosis88) |
AsyncioandTriosupport TLS-in-TLS by default, but the standardsslmodule does not, so this PR adds TLS-in-TLS implementation on top of the standardsslmodule to fully support TLS-in-TLS backends.TLS-in-TLS implementations are required for all backends in order to support
HTTPSproxies.Closes#721
Refs: #722#714#722
Todo