diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f63c1ddc..43e93f86a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ The table below shows which release corresponds to each branch, and what date th ## 5.0.0 (`dev`) +- [#2541][2541] Fix blocking `tube.remote()` when DNS resolution fail. - [#2519][2519] Drop Python 2.7 support / Require Python 3.10 - [#2507][2507] Add `+LINUX` and `+WINDOWS` doctest options and start proper testing on Windows - [#2522][2522] Support starting a kitty debugging window with the 'kitten' command @@ -84,6 +85,7 @@ The table below shows which release corresponds to each branch, and what date th - [#2530][2530] Do NOT error when passing directory arguments in `checksec` commandline tool. - [#2529][2529] Add LoongArch64 support +[2541]: https://github.com/Gallopsled/pwntools/pull/2541 [2519]: https://github.com/Gallopsled/pwntools/pull/2519 [2507]: https://github.com/Gallopsled/pwntools/pull/2507 [2522]: https://github.com/Gallopsled/pwntools/pull/2522 diff --git a/pwnlib/tubes/remote.py b/pwnlib/tubes/remote.py index 9f69c0322..787bd868a 100644 --- a/pwnlib/tubes/remote.py +++ b/pwnlib/tubes/remote.py @@ -1,6 +1,8 @@ from __future__ import absolute_import from __future__ import division +import asyncio +import concurrent import socket import socks @@ -109,8 +111,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]: