(csock: socket.socket)
| 7 | |
| 8 | |
| 9 | def original_addr(csock: socket.socket) -> tuple[str, int]: |
| 10 | # Get the original destination on Linux. |
| 11 | # In theory, this can be done using the following syscalls: |
| 12 | # sock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16) |
| 13 | # sock.getsockopt(SOL_IPV6, SO_ORIGINAL_DST, 28) |
| 14 | # |
| 15 | # In practice, it is a bit more complex: |
| 16 | # 1. We cannot rely on sock.family to decide which syscall to use because of IPv4-mapped |
| 17 | # IPv6 addresses. If sock.family is AF_INET6 while sock.getsockname() is ::ffff:127.0.0.1, |
| 18 | # we need to call the IPv4 version to get a result. |
| 19 | # 2. We can't just try the IPv4 syscall and then do IPv6 if that doesn't work, |
| 20 | # because doing the wrong syscall can apparently crash the whole Python runtime. |
| 21 | # As such, we use a heuristic to check which syscall to do. |
| 22 | is_ipv4 = "." in csock.getsockname()[0] # either 127.0.0.1 or ::ffff:127.0.0.1 |
| 23 | if is_ipv4: |
| 24 | # the struct returned here should only have 8 bytes, but invoking sock.getsockopt |
| 25 | # with buflen=8 doesn't work. |
| 26 | dst = csock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16) |
| 27 | port, raw_ip = struct.unpack_from("!2xH4s", dst) |
| 28 | ip = socket.inet_ntop(socket.AF_INET, raw_ip) |
| 29 | else: |
| 30 | dst = csock.getsockopt(SOL_IPV6, SO_ORIGINAL_DST, 28) |
| 31 | port, raw_ip = struct.unpack_from("!2xH4x16s", dst) |
| 32 | ip = socket.inet_ntop(socket.AF_INET6, raw_ip) |
| 33 | return ip, port |
nothing calls this directly
no test coverage detected
searching dependent graphs…