Skip to content
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
18 changes: 18 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ Note that newer Ubuntu disabled readline support in socat, so if you get

rlwrap socat - tcp:127.0.0.1:4444

Reverse connection
==================

To use a reverse connection, install netcat on your system, determine your host IP (using ip addr or ifconfig),
and run a listener on your host, e.g., ``nc -lvp 8888``. To enable a reverse connection in your code, use
the ``reverse=True`` option as follows:

.. code:: python

import remote_pdb
rpdb = remote_pdb.RemotePdb(host='your_host_ip', port=8888, reverse=True)
rpdb.set_trace()


Using in containers
===================

Expand Down Expand Up @@ -141,6 +155,10 @@ To quiet the output, set ``REMOTE_PDB_QUIET=1``, this will prevent
``RemotePdb`` from producing any output -- you'll probably want to specify
``REMOTE_PDB_PORT`` as well since the randomized port won't be printed.

If you want to use a reverse connection, you can set ``REMOTE_PDB_REVERSE=1``.
In this case, the host specified by ``REMOTE_PDB_HOST`` and the port ``REMOTE_PDB_PORT`` will be used as the address
to connect to. Be careful: the reverse connection will not work if the host is not reachable from the container,
which may cause additional errors.

Note about OS X
===============
Expand Down
28 changes: 17 additions & 11 deletions src/remote_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,22 @@ class RemotePdb(Pdb):
"""
active_instance = None

def __init__(self, host, port, patch_stdstreams=False, quiet=False):
def __init__(self, host, port, patch_stdstreams=False, quiet=False, reverse=False):
self._quiet = quiet
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
listen_socket.bind((host, port))
if not self._quiet:
cry("RemotePdb session open at %s:%s, waiting for connection ..." % listen_socket.getsockname())
listen_socket.listen(1)
connection, address = listen_socket.accept()
if not self._quiet:
cry("RemotePdb accepted connection from %s." % repr(address))
self.handle = LF2CRLF_FileWrapper(connection)
if reverse:
listen_socket.connect((host, port))
self.handle = LF2CRLF_FileWrapper(listen_socket)
else:
listen_socket.bind((host, port))
if not self._quiet:
cry("RemotePdb session open at %s:%s, waiting for connection ..." % listen_socket.getsockname())
listen_socket.listen(1)
connection, address = listen_socket.accept()
if not self._quiet:
cry("RemotePdb accepted connection from %s." % repr(address))
self.handle = LF2CRLF_FileWrapper(connection)
Pdb.__init__(self, completekey='tab', stdin=self.handle, stdout=self.handle)
self.backup = []
if patch_stdstreams:
Expand Down Expand Up @@ -118,7 +122,7 @@ def set_trace(self, frame=None):
raise


def set_trace(host=None, port=None, patch_stdstreams=False, quiet=None):
def set_trace(host=None, port=None, patch_stdstreams=False, quiet=None, reverse=None):
"""
Opens a remote PDB on first available port.
"""
Expand All @@ -128,5 +132,7 @@ def set_trace(host=None, port=None, patch_stdstreams=False, quiet=None):
port = int(os.environ.get('REMOTE_PDB_PORT', '0'))
if quiet is None:
quiet = bool(os.environ.get('REMOTE_PDB_QUIET', ''))
rdb = RemotePdb(host=host, port=port, patch_stdstreams=patch_stdstreams, quiet=quiet)
if reverse is None:
reverse = bool(os.environ.get('REMOTE_PDB_REVERSE', ''))
rdb = RemotePdb(host=host, port=port, patch_stdstreams=patch_stdstreams, quiet=quiet, reverse=reverse)
rdb.set_trace(frame=sys._getframe().f_back)
25 changes: 25 additions & 0 deletions tests/test_remote_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,27 @@ def test_trash_input():
wait_for_strings(proc.read, TIMEOUT, 'DIED.')


def test_reverse_connection():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
with server_socket:
server_socket.bind(('127.0.0.1', 0))
server_socket.listen(1)
host, port = server_socket.getsockname()

with TestProcess(sys.executable, __file__, 'daemon', 'test_reverse_connection', host, str(port)) as proc:
with dump_on_error(proc.read):
wait_for_strings(proc.read, TIMEOUT, '{a1}', '{b1}')

client_socket, addr = server_socket.accept()
with client_socket:
with TestSocket(client_socket) as client:
with dump_on_error(client.read):
wait_for_strings(client.read, TIMEOUT, "-> print('{b2}')")
client.fh.write(b'continue\r\n')
client.fh.flush()

wait_for_strings(proc.read, TIMEOUT, 'DIED.')

def func_b(**kwargs):
print('{b1}')
set_trace(**kwargs)
Expand Down Expand Up @@ -173,6 +194,10 @@ def func_a(block=lambda _: None, **kwargs):
elif test_name == 'test_redirect':
func_a(patch_stdstreams=True)
time.sleep(TIMEOUT)
elif test_name == 'test_reverse_connection':
host = sys.argv[3]
port = int(sys.argv[4])
func_a(host=host, port=port, reverse=True)
else:
raise RuntimeError('Invalid test spec %r.' % test_name)
logging.info('DIED.')