Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use asyncio to avoid blocking tube.remote when DNS resolution fail #2541

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
31 changes: 30 additions & 1 deletion pwnlib/tubes/remote.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import absolute_import
from __future__ import division

import asyncio
import concurrent
import socket
import socks

Expand Down Expand Up @@ -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]:
Expand Down
Loading