Repository: mitmproxy/mitmproxy

Issue:
Add IPv6-only mode
**Is your feature request related to a problem? Please describe.**
When starting the proxy on a machine that has IPv4 disabled, the proxy fails to start. Taking a look at the code, it seems that issue occurs when the socket option IPV6_V6ONLY is set to 0 and the system does not allow IPv4-enabled sockets. 

#### Describe the solution you'd like
Adding an ipv6_only option was pretty quick & easy, and I have a potential patch, but I wanted to see if this is a reasonable thing to do?

#### Describe alternatives you've considered
It could also make sense to--instead of adding an ipv6_only option--add an option that gives the user full control to select ipv6_only, ipv4_only, or both ipv4 + ipv6, where the proxy does not attempt to fall back on another protocol.



Discussion:
You were probably looking at https://github.com/mitmproxy/mitmproxy/blob/dac0bfe786a8d1dfdd97f29d6bb262ed258153fa/mitmproxy/net/tcp.py#L560-L579?

Can we make it work out of the box without an extra option? For example, we could try 6+4, first, then 4, then 6?
Yup, that's exactly what I was looking at. I think the no-option route works. Do you have a preference toward trying 6+4 -> 4 -> 6? If line 567 is updated to be set conditionally, then we would end up trying 6+4 -> 6 -> 4. And that seems to line up with the comment on line 561: "possible IPv4". Thoughts?
What do you have in mind when you say "updated to be set conditionally"? Only if `address[0] == ""`? That would be sad, because (for example) `address == ("localhost",8000)` works with IPV6_V6ONLY=0 on Windows. I think we should always try to disable IPV6_V6ONLY, even if that comes at the cost of having to try again. If we need to try again anways, I would have a slight preference for 6+4 -> 4 -> 6 as that may spare us a few support requests. It ends up with v6 anyways in your case. :)
Sorry, didn't exactly phrase that one well. What I was thinking was: always **try** to disable IPV6_V6ONLY, but continue binding to the IPv6-only socket if disabling IPV6_V6ONLY fails. That said, since IPv6-only will be possible in either case, I'm not too picky here. Whatever makes life easier.

Resolving patch:
diff --git a/mitmproxy/net/tcp.py b/mitmproxy/net/tcp.py
--- a/mitmproxy/net/tcp.py
+++ b/mitmproxy/net/tcp.py
@@ -558,7 +558,7 @@ def __init__(self, address):
         self.socket = None
 
         try:
-            # First try to bind an IPv6 socket, with possible IPv4 if the OS supports it.
+            # First try to bind an IPv6 socket, attempting to enable IPv4 support if the OS supports it.
             # This allows us to accept connections for ::1 and 127.0.0.1 on the same socket.
             # Only works if self.address == ""
             self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
@@ -572,8 +572,20 @@ def __init__(self, address):
             self.socket = None
 
         if not self.socket:
-            # Binding to an IPv6 socket failed, lets fall back to IPv4.
-            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            try:
+                # Binding to an IPv6 + IPv4 socket failed, lets fall back to IPv4 only.
+                self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+                self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+                self.socket.bind(self.address)
+            except socket.error:
+                if self.socket:
+                    self.socket.close()
+                self.socket = None
+
+        if not self.socket:
+            # Binding to an IPv4 only socket failed, lets fall back to IPv6 only.
+            self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
             self.socket.bind(self.address)


Test patch: