From 9e86764ce30641c2b5a417281dfd3e57a60d31d8 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 16 Feb 2018 14:39:54 +0100 Subject: [PATCH] linux: support IPv6 destinations in transparent mode This fixes #2869 with the help of @aniketpanjwani. --- mitmproxy/platform/linux.py | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/mitmproxy/platform/linux.py b/mitmproxy/platform/linux.py index 4fa3191ad..f446bb729 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