aboutsummaryrefslogtreecommitdiffstats
path: root/mitmproxy/platform
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2018-02-16 14:39:54 +0100
committerMaximilian Hils <git@maximilianhils.com>2018-02-16 15:36:21 +0100
commit9e86764ce30641c2b5a417281dfd3e57a60d31d8 (patch)
tree8ab1b6ace9f022d9616069c5c5d08d6a104107f9 /mitmproxy/platform
parentbaf4b5dc03c3b87fbef6e5df422eb7654594978f (diff)
downloadmitmproxy-9e86764ce30641c2b5a417281dfd3e57a60d31d8.tar.gz
mitmproxy-9e86764ce30641c2b5a417281dfd3e57a60d31d8.tar.bz2
mitmproxy-9e86764ce30641c2b5a417281dfd3e57a60d31d8.zip
linux: support IPv6 destinations in transparent mode
This fixes #2869 with the help of @aniketpanjwani.
Diffstat (limited to 'mitmproxy/platform')
-rw-r--r--mitmproxy/platform/linux.py34
1 files changed, 28 insertions, 6 deletions
diff --git a/mitmproxy/platform/linux.py b/mitmproxy/platform/linux.py
index 4fa3191a..f446bb72 100644
--- a/mitmproxy/platform/linux.py
+++ b/mitmproxy/platform/linux.py
@@ -1,12 +1,34 @@
import socket
import struct
+import typing
-# Python socket module does not have this constant
+# Python's socket module does not have these constants
SO_ORIGINAL_DST = 80
+SOL_IPV6 = 41
-def original_addr(csock: socket.socket):
- odestdata = csock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16)
- _, port, a1, a2, a3, a4 = struct.unpack("!HHBBBBxxxxxxxx", odestdata)
- address = "%d.%d.%d.%d" % (a1, a2, a3, a4)
- return address, port
+def original_addr(csock: socket.socket) -> typing.Tuple[str, int]:
+ # Get the original destination on Linux.
+ # In theory, this can be done using the following syscalls:
+ # sock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16)
+ # sock.getsockopt(SOL_IPV6, SO_ORIGINAL_DST, 28)
+ #
+ # In practice, it is a bit more complex:
+ # 1. We cannot rely on sock.family to decide which syscall to use because of IPv4-mapped
+ # IPv6 addresses. If sock.family is AF_INET6 while sock.getsockname() is ::ffff:127.0.0.1,
+ # we need to call the IPv4 version to get a result.
+ # 2. We can't just try the IPv4 syscall and then do IPv6 if that doesn't work,
+ # because doing the wrong syscall can apparently crash the whole Python runtime.
+ # As such, we use a heuristic to check which syscall to do.
+ is_ipv4 = "." in csock.getsockname()[0] # either 127.0.0.1 or ::ffff:127.0.0.1
+ if is_ipv4:
+ # the struct returned here should only have 8 bytes, but invoking sock.getsockopt
+ # with buflen=8 doesn't work.
+ dst = csock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16)
+ port, raw_ip = struct.unpack_from("!2xH4s", dst)
+ ip = socket.inet_ntop(socket.AF_INET, raw_ip)
+ else:
+ dst = csock.getsockopt(SOL_IPV6, SO_ORIGINAL_DST, 28)
+ port, raw_ip = struct.unpack_from("!2xH4x16s", dst)
+ ip = socket.inet_ntop(socket.AF_INET6, raw_ip)
+ return ip, port