Repository: mitmproxy/mitmproxy

Issue:
Simplify Server.sni
#### Problem Description

When a property has a mix of unrelated types it takes me more time than it should take to wrap my head around. To me this always smells like something could be simplified, but I guess that's something I have to get used to with Python? I'm talking about `Server.sni: Union[str, Literal[True], NoneType] = True` vs `Client.sni: Optional[str] = None`

> The Server Name Indication (SNI) sent in the ClientHello. For server connections, this value may also be set to True, which means "use Server.address".

Is this something that could be hidden under the hood? As a user I don't care. I just want the string that was used for sni. I don't want to replicate internal logic inside my addon.

#### Proposal

When SNI is used just duplicate `address[0]` into `sni`.


Discussion:
server.sni will be set to server.address during the handshake, it will only be set to True before that. Does that help?
> Does that help?

Somewhat I guess. So that means in later hooks (e.g. `server_disconnected`) it is guaranteed to be either a string or `None`? I've seen this pattern more than once in mitmproxy, where a property is used to either hold a value or a sort of flag that like has a side effect on the meaning later in some internal parts of the code. I find it confusing. Could there be a (private?) boolean flag that does that job and `sni` is kept clean?

Is it correct that both of the current `tls_` hooks are too early for that? So `sni` could still be `True`? Or does SNI happen between `tls_start` and `tls_clienthello`?

Wait a second, isn't `tls_start` ambiguous? Is it the TLS start between client<->mitmproxy or mitmproxy<->server? Or does it fire for both? Since `TlsStartData` holds a generic `connection.Connection` I assume both? Maybe there needs to be `tls_clientstart` and `tls_serverstart`?
> Somewhat I guess. So that means in later hooks (e.g. server_disconnected) it is guaranteed to be either a string or None?

Yes, as long as you don't intentionally disable the TlsConfig addon somehow.

> Could there be a (private?) boolean flag that does that job and sni is kept clean?

I think you somewhat convinced me here. Let's make `sni` an `Optional[str]`. In `tlsconfig.py`, we just change the `if` statement here to `if server.sni is None:`

https://github.com/mitmproxy/mitmproxy/blob/438567f58c86ab13d721bd34f039fbddea52906f/mitmproxy/addons/tlsconfig.py#L166-L167

That should still cover all relevant cases. PRs welcome. 😊😇 


> Is it correct that both of the current tls_ hooks are too early for that?

`tls_clienthello` is too early, SNI is then processed by the TlsConfig addon during `tls_start`. So it depends on the addon call order here.

> Wait a second, isn't tls_start ambiguous? Is it the TLS start between client<->mitmproxy or mitmproxy<->server? Or does it fire for both?

Both:

https://github.com/mitmproxy/mitmproxy/blob/438567f58c86ab13d721bd34f039fbddea52906f/mitmproxy/addons/tlsconfig.py#L114-L118

The fact that the only place where we are using this has a check _and_ you are requesting essentially the same, I'd be happy to change it and split it into two separate events. Again, PRs welcome. 😊😇

Overall this is excellent feedback on unpolished APIs, please keep it coming! 🍰 
Nice, thanks for the details. I'm interested in making the PRs, but not right now. I'll self-assign the issue once I do to avoid conflicts.

Resolving patch:
diff --git a/mitmproxy/addons/tlsconfig.py b/mitmproxy/addons/tlsconfig.py
--- a/mitmproxy/addons/tlsconfig.py
+++ b/mitmproxy/addons/tlsconfig.py
@@ -171,7 +171,7 @@ def create_proxy_server_ssl_conn(self, tls_start: tls.TlsStartData) -> None:
         else:
             verify = net_tls.Verify.VERIFY_PEER
 
-        if server.sni is True:
+        if server.sni is None:
             server.sni = client.sni or server.address[0]
 
         if not server.alpn_offers:
diff --git a/mitmproxy/connection.py b/mitmproxy/connection.py
--- a/mitmproxy/connection.py
+++ b/mitmproxy/connection.py
@@ -2,7 +2,7 @@
 import warnings
 from abc import ABCMeta
 from enum import Flag
-from typing import Literal, Optional, Sequence, Tuple, Union
+from typing import Optional, Sequence, Tuple
 
 from mitmproxy import certs
 from mitmproxy.coretypes import serializable
@@ -85,10 +85,9 @@ class Connection(serializable.Serializable, metaclass=ABCMeta):
     """Ciphers accepted by the proxy server on this connection."""
     tls_version: Optional[str] = None
     """The active TLS version."""
-    sni: Union[str, Literal[True], None]
+    sni: Optional[str] = None
     """
     The [Server Name Indication (SNI)](https://en.wikipedia.org/wiki/Server_Name_Indication) sent in the ClientHello.
-    For server connections, this value may also be set to `True`, which means "use `Server.address`".
     """
 
     timestamp_start: Optional[float]
@@ -144,8 +143,6 @@ class Client(Connection):
     """
     The certificate used by mitmproxy to establish TLS with the client.
     """
-    sni: Union[str, None] = None
-    """The Server Name Indication sent by the client."""
 
     timestamp_start: float
     """*Timestamp:* TCP SYN received"""
@@ -272,7 +269,6 @@ class Server(Connection):
     timestamp_tcp_setup: Optional[float] = None
     """*Timestamp:* TCP ACK received."""
 
-    sni: Union[str, Literal[True], None] = True
     via: Optional[server_spec.ServerSpec] = None
     """An optional proxy server specification via which the connection should be established."""
 


Test patch: