Repository: mitmproxy/mitmproxy

Issue:
AssertionError: assert not self.tls
The repro website is NSFW / porn (see also https://hackerone.com/chaturbate)

#### Problem Description

```
[::1]:60890: mitmproxy has crashed!
Traceback (most recent call last):
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/server.py", line 276, in server_event
    for command in layer_commands:
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 144, in handle_event
    command = command_generator.send(send)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 255, in handle_event
    yield from self._handle(event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 144, in handle_event
    command = command_generator.send(send)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/http/__init__.py", line 613, in _handle_event
    yield from self.event_to_child(stream, event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/http/__init__.py", line 649, in event_to_child
    for command in child.handle_event(event):
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 144, in handle_event
    command = command_generator.send(send)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/http/__init__.py", line 531, in passthrough
    for command in self.child_layer.handle_event(event):
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 129, in handle_event
    yield from self.__continue(event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 220, in __continue
    yield from self.__process(command_generator, event.reply)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 183, in __process
    command = command_generator.send(send)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/tunnel.py", line 85, in _handle_event
    yield from self.event_to_child(event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/tls.py", line 306, in event_to_child
    yield from super().event_to_child(event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/tunnel.py", line 124, in event_to_child
    yield from self.start_handshake()
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/tls.py", line 294, in start_handshake
    yield from self.start_tls()
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/tls.py", line 152, in start_tls
    assert not self.tls
AssertionError
```

#### Steps to reproduce the behavior:
1. `mitmdump --quiet`
2. Visit `https://chaturbate.com` in Chromium

```
chromium --user-data-dir=`mktemp --directory` --start-maximized --no-default-browser-check --no-first-run --disable-translate --disable-extensions --incognito --proxy-server="localhost:8080" https://chaturbate.com
```
(don't mind all the arguments, I use that all the time aliased as `clean-chrome-instance`)

#### System Information

```
Mitmproxy: 7.0.0.dev (+417, commit 1b0fce6)
Python:    3.8.5
OpenSSL:   OpenSSL 1.1.1i  8 Dec 2020
Platform:  Linux-5.8.0-48-generic-x86_64-with-glibc2.29
```



Discussion:
Happens on https://imgur.com/ as well

I originally thought this was unrelated, but I'm seeing duplicate connection IDs with the crash (thank my UNIQUE contraints)

```py
_serverConnectionIds = {}

def server_connected(data):
  global _serverConnectionIds

  server = data.server

  if server.id in _serverConnectionIds:
    print('duplicate')
    print(server.id)
  else:
    _serverConnectionIds[server.id] = True
```

```
duplicate
08c89ca2-3f4d-45ae-ab1b-4ca71be87ed8
[::1]:33644: mitmproxy has crashed!
Traceback (most recent call last):
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/server.py", line 276, in server_event
    for command in layer_commands:
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 144, in handle_event
    command = command_generator.send(send)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 255, in handle_event
    yield from self._handle(event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 144, in handle_event
    command = command_generator.send(send)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/http/__init__.py", line 613, in _handle_event
    yield from self.event_to_child(stream, event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/http/__init__.py", line 649, in event_to_child
    for command in child.handle_event(event):
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 144, in handle_event
    command = command_generator.send(send)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/http/__init__.py", line 531, in passthrough
    for command in self.child_layer.handle_event(event):
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 129, in handle_event
    yield from self.__continue(event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 220, in __continue
    yield from self.__process(command_generator, event.reply)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layer.py", line 183, in __process
    command = command_generator.send(send)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/tunnel.py", line 85, in _handle_event
    yield from self.event_to_child(event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/tls.py", line 306, in event_to_child
    yield from super().event_to_child(event)
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/tunnel.py", line 124, in event_to_child
    yield from self.start_handshake()
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/tls.py", line 294, in start_handshake
    yield from self.start_tls()
  File "/home/alex/src/forks/mitmproxy/mitmproxy/proxy/layers/tls.py", line 152, in start_tls
    assert not self.tls
AssertionError

```
My guess (as always) are race conditions. That's why I use these websites, they produce a lot of requests (tons of images at once). There is definitely no other reason to use them.
Thanks! That's very likely caused by aa815a52469ce8e09f404c6f5e66af0f47c78829. We need a log with `--set proxy_debug -vvv`. If you have one great, otherwise I'll see if I can reproduce this in ~2 weeks.
There are some AssertionError in there 

[vvv.log](https://github.com/mitmproxy/mitmproxy/files/6273115/vvv.log)


Edit: it does not happen with connection_strategy=lazy

Resolving patch:
diff --git a/mitmproxy/proxy/layers/tls.py b/mitmproxy/proxy/layers/tls.py
--- a/mitmproxy/proxy/layers/tls.py
+++ b/mitmproxy/proxy/layers/tls.py
@@ -393,6 +393,11 @@ def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]:
             err = f"The client may not trust the proxy's certificate for {dest} ({err})"
         yield commands.Log(f"Client TLS handshake failed. {err}", level="warn")
         yield from super().on_handshake_error(err)
+        self.event_to_child = self.errored  # type: ignore
+
+    def errored(self, event: events.Event) -> layer.CommandGenerator[None]:
+        if self.debug is not None:
+            yield commands.Log(f"Swallowing {event} as handshake failed.", "debug")
 
 
 class MockTLSLayer(_TLSLayer):


Test patch:
diff --git a/test/helper_tools/loggrep.py b/test/helper_tools/loggrep.py
new file mode 100755
--- /dev/null
+++ b/test/helper_tools/loggrep.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python3
+import fileinput
+import sys
+
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        print(f"Usage: {sys.argv[0]} port filenames")
+        sys.exit()
+
+    port = sys.argv[1]
+    matches = False
+    for line in fileinput.input(sys.argv[2:]):
+        if line.startswith("["):
+            matches = port in line
+        if matches:
+            print(line, end="")
diff --git a/test/mitmproxy/proxy/layers/test_tls.py b/test/mitmproxy/proxy/layers/test_tls.py
--- a/test/mitmproxy/proxy/layers/test_tls.py
+++ b/test/mitmproxy/proxy/layers/test_tls.py
@@ -476,6 +476,15 @@ def test_cannot_parse_clienthello(self, tctx: context.Context):
         )
         assert not tctx.client.tls_established
 
+        # Make sure that an active server connection does not cause child layers to spawn.
+        client_layer.debug = ""
+        assert (
+            playbook
+            >> events.DataReceived(Server(None), b"data on other stream")
+            << commands.Log(">> DataReceived(server, b'data on other stream')", 'debug')
+            << commands.Log("Swallowing DataReceived(server, b'data on other stream') as handshake failed.", "debug")
+        )
+
     def test_mitmproxy_ca_is_untrusted(self, tctx: context.Context):
         """Test the scenario where the client doesn't trust the mitmproxy CA."""
         playbook, client_layer, tssl_client = make_client_tls_layer(tctx, sni=b"wrong.host.mitmproxy.org")