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
def get_sock_cred(conn: socket.socket) -> ta.Tuple[int, int, int]: if LINUX: creds = conn.getsockopt(socket.SOL_SOCKET, libc.SO_PEERCRED, struct.calcsize('3i')) elif DARWIN: creds = conn.getsockopt(libc.SOL_LOCAL, libc.LOCAL_PEERCRED, struct.calcsize('3i')) else: raise OSError pid, uid, gid = struct.unpack('3i', creds) return pid, uid, gid
def _save_ttl(sock: socket.socket): old_ttl = sock.getsockopt(socket.SOL_IP, socket.IP_TTL) try: yield finally: sock.setsockopt(socket.SOL_IP, socket.IP_TTL, old_ttl) return
def read_from_socket(s: socket) -> bytearray: data = bytearray() buff_size = s.getsockopt(SOL_SOCKET, SO_RCVBUF) while True: chunk = s.recv(buff_size) if chunk: data += chunk if len(chunk) == 0 or chunk[-1:] == b'/n': break return data
def get_original_destination(socket: Socket) -> Tuple[str, int]: """ Extracts the original destination from the socket. """ SO_ORIGINAL_DST = 80 # Not in socket module original_dst = socket.getsockopt(SOL_IP, SO_ORIGINAL_DST, 16) _, port, a1, a2, a3, a4 = unpack("!HHBBBBxxxxxxxx", original_dst) host = f"{a1}.{a2}.{a3}.{a4}" return (host, port)
def __init__(self): global server_socket, serverpid server_socket = Socket(AF_INET, SOCK_STREAM) server_socket.setblocking(0) try: server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, server_socket.getsockopt(SOL_SOCKET, SO_REUSEADDR) | 1) except error: pass serverpid = server_socket.fileno() pollster.register(serverpid, RE)
def bind_port(sock: socket.socket, host: str = HOST) -> int: """Bind the socket to a free port and return the port number. Relies on ephemeral ports in order to ensure we are using an unbound port. This is important as many tests may be running simultaneously, especially in a buildbot environment. This method raises an exception if the sock.family is AF_INET and sock.type is SOCK_STREAM, *and* the socket has SO_REUSEADDR or SO_REUSEPORT set on it. Tests should *never* set these socket options for TCP/IP sockets. The only case for setting these options is testing multicasting via multiple UDP sockets. Additionally, if the SO_EXCLUSIVEADDRUSE socket option is available (i.e. on Windows), it will be set on the socket. This will prevent anyone else from bind()'ing to our host/port for the duration of the test. """ if sock.family == socket.AF_INET and sock.type == socket.SOCK_STREAM: if hasattr(socket, "SO_REUSEADDR"): if sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) == 1: raise ValueError("tests should never set the SO_REUSEADDR " "socket option on TCP/IP sockets!") if hasattr(socket, "SO_REUSEPORT"): try: if sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT) == 1: raise ValueError("tests should never set the SO_REUSEPORT " "socket option on TCP/IP sockets!") except OSError: # Python's socket module was compiled using modern headers # thus defining SO_REUSEPORT but this process is running # under an older kernel that does not support SO_REUSEPORT. pass if hasattr(socket, "SO_EXCLUSIVEADDRUSE"): sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) sock.bind((host, 0)) port = sock.getsockname()[1] assert isinstance(port, int) return port
def get_tcp_info(connection: socket.socket): """Get tcp_info TCP_ESTABLISHED = 1, TCP_SYN_SENT, TCP_SYN_RECV, TCP_FIN_WAIT1, TCP_FIN_WAIT2, TCP_TIME_WAIT, TCP_CLOSE, TCP_CLOSE_WAIT, TCP_LAST_ACK, TCP_LISTEN, TCP_CLOSING ref: https://stackoverflow.com/a/18189190 """ fmt = "B" * 7 + "I" * 21 tcp_info = struct.unpack(fmt, connection.getsockopt(socket.IPPROTO_TCP, socket.TCP_INFO, 92)) return tcp_info
def _launch_repo_server( *, repo_server_bin: Path, sock: socket.socket, snapshot_dir: Path, debug: bool, ): ''' Invokes `repo-server` with the given snapshot; passes it ownership of the bound TCP socket -- it listens & accepts connections. ''' # This could be a thread, but it's probably not worth the risks # involved in mixing threads & subprocess (yes, lots of programs do, # but yes, far fewer do it safely). with sock, subprocess.Popen([ repo_server_bin, '--socket-fd', str(sock.fileno()), '--snapshot-dir', snapshot_dir, *(['--debug'] if debug else []), ], pass_fds=[sock.fileno()]) as server_proc: try: log.info('Waiting for repo server to listen') while server_proc.poll() is None: if sock.getsockopt(socket.SOL_SOCKET, socket.SO_ACCEPTCONN): break time.sleep(0.1) yield finally: # Although `repo-server` is a read-only proxy, give it the # chance to do graceful cleanup. log.info('Trying to gracefully terminate `repo-server`') # `atexit` (used in an FB-specific `repo-server` plugin) only # works with SIGINT. We signal once, and need to wait for it to # clean up the resources it must to free. Signaling twice would # interrupt cleanup (because this is Python, lol). server_proc.send_signal(signal.SIGINT) # `atexit` needs this try: server_proc.wait(60.0) except subprocess.TimeoutExpired: # pragma: no cover log.info('Killing unresponsive `repo-server`') server_proc.kill()
def supports_dualstack(self, sock: socket.socket = None) -> bool: ''' Checks if the system kernel supports dual stack sockets, listening to both: IPv4 + IPv6 If a socket is provided, the check is made against the provided socket ''' try: socket.AF_INET6 socket.IPPROTO_IPV6 socket.IPV6_V6ONLY except AttributeError: return False try: if sock is not None: return not sock.getsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY) else: sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) with contextlib.closing(sock): sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) return True except socket.error: return False
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