diff --git a/CHANGELOG.md b/CHANGELOG.md index f6f6c8a7b..a6c6637c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -122,6 +122,7 @@ The table below shows which release corresponds to each branch, and what date th - [#2671][2671] ssh: support raw string input for 'key' argument as documented - [#2688][2688] Close SSH client connection when authentication failed - [#2686][2686] Add glibc safe-linking `glibc.reveal_ptr_same_page` +- [#2541][2541] Fix blocking `tube.remote()` when DNS resolution fail. [2677]: https://github.com/Gallopsled/pwntools/pull/2677 [2675]: https://github.com/Gallopsled/pwntools/pull/2675 @@ -170,6 +171,7 @@ The table below shows which release corresponds to each branch, and what date th [2671]: https://github.com/Gallopsled/pwntools/pull/2671 [2688]: https://github.com/Gallopsled/pwntools/pull/2688 [2686]: https://github.com/Gallopsled/pwntools/pull/2686 +[2541]: https://github.com/Gallopsled/pwntools/pull/2541 ## 4.15.0 (`stable`) diff --git a/pwnlib/tubes/remote.py b/pwnlib/tubes/remote.py index 1d28f6845..34d9e06c0 100644 --- a/pwnlib/tubes/remote.py +++ b/pwnlib/tubes/remote.py @@ -1,3 +1,5 @@ +import asyncio +import concurrent import socket import socks @@ -106,8 +108,35 @@ def _connect(self, fam, typ): sock = None timeout = self.timeout + async def async_getaddrinfo(host, port, fam=0, typ=0, proto=0, flags=0): + loop = asyncio.get_running_loop() + result = await loop.getaddrinfo(host, port, family=fam, type=typ, proto=proto, flags=flags) + return result + + def run_async_in_thread(coro): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + future = loop.run_until_complete(coro) + loop.close() + return future + + # Using asyncio to avoid process blocking when DNS resolution fail. It's probably better + # to use async all the ways to `sock.connect`. However, let's keep the changes small + # until we have the needs. + def sync_getaddrinfo(*args): + # Run in a separate thread to avoid deadlocks when users nest eventloops. + with concurrent.futures.ThreadPoolExecutor() as executor: + try: + future = executor.submit(run_async_in_thread, async_getaddrinfo(*args)) + return future.result() + except asyncio.exceptions.CancelledError: + return [] + except socket.gaierror: + return [] + with self.waitfor('Opening connection to %s on port %s' % (self.rhost, self.rport)) as h: - for res in socket.getaddrinfo(self.rhost, self.rport, fam, typ, 0, socket.AI_PASSIVE): + hostnames = sync_getaddrinfo(self.rhost, self.rport, fam, typ, 0, socket.AI_PASSIVE) + for res in hostnames: self.family, self.type, self.proto, _canonname, sockaddr = res if self.type not in [socket.SOCK_STREAM, socket.SOCK_DGRAM]: