qwj / python-proxy

HTTP/HTTP2/HTTP3/Socks4/Socks5/Shadowsocks/ShadowsocksR/SSH/Redirect/Pf TCP/UDP asynchronous tunnel proxy implemented in Python 3 asyncio.
MIT License
1.99k stars 333 forks source link

reconnect ssh proxy session #126

Closed keenser closed 3 years ago

keenser commented 3 years ago

Hi,

Please add ability to reconnect ssh session after disconnect. Current behavior after a failed or disconnected ssh session is just a session state notification in the log:

May 03 22:21:48 debian jumphost.py[1520]: ERROR:asyncio: Task exception was never retrieved
May 03 22:21:48 debian jumphost.py[1520]: future: <Task finished coro=<ProxySSH.patch_stream.<locals>.channel() done, defined at /usr/local/lib/python3.7/dist-packages/pproxy/server.py:610> exception=ConnectionLost('Connection lost')>
May 03 22:21:48 debian jumphost.py[1520]: Traceback (most recent call last):
May 03 22:21:48 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/pproxy/server.py", line 612, in channel
May 03 22:21:48 debian jumphost.py[1520]:     buf = await ssh_reader.read(65536)
May 03 22:21:48 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/asyncssh/stream.py", line 131, in read
May 03 22:21:48 debian jumphost.py[1520]:     return await self._session.read(n, self._datatype, exact=False)
May 03 22:21:48 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/asyncssh/stream.py", line 495, in read
May 03 22:21:48 debian jumphost.py[1520]:     raise exc
May 03 22:21:48 debian jumphost.py[1520]: asyncssh.misc.ConnectionLost: Connection lost
May 03 22:23:34 debian jumphost.py[1520]: DEBUG:jumphost: socks5 z.z.z.z:65418 -> sshtunnel x.x.x.x:22 -> y.y.y.y:22
May 03 22:23:34 debian jumphost.py[1520]: Traceback (most recent call last):
May 03 22:23:34 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/pproxy/server.py", line 87, in stream_handler
May 03 22:23:34 debian jumphost.py[1520]:     reader_remote, writer_remote = await roption.open_connection(host_name, port, local_addr, lbind)
May 03 22:23:34 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/pproxy/server.py", line 227, in open_connection
May 03 22:23:34 debian jumphost.py[1520]:     reader, writer = await asyncio.wait_for(wait, timeout=timeout)
May 03 22:23:34 debian jumphost.py[1520]:   File "/usr/lib/python3.7/asyncio/tasks.py", line 416, in wait_for
May 03 22:23:34 debian jumphost.py[1520]:     return fut.result()
May 03 22:23:34 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/pproxy/server.py", line 649, in wait_open_connection
May 03 22:23:34 debian jumphost.py[1520]:     reader, writer = await conn.open_connection(host, port)
May 03 22:23:34 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/asyncssh/connection.py", line 3537, in open_connection
May 03 22:23:34 debian jumphost.py[1520]:     *args, **kwargs)
May 03 22:23:34 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/asyncssh/connection.py", line 3508, in create_connection
May 03 22:23:34 debian jumphost.py[1520]:     chan = self.create_tcp_channel(encoding, errors, window, max_pktsize)
May 03 22:23:34 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/asyncssh/connection.py", line 2277, in create_tcp_channel
May 03 22:23:34 debian jumphost.py[1520]:     errors, window, max_pktsize)
May 03 22:23:34 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/asyncssh/channel.py", line 110, in __init__
May 03 22:23:34 debian jumphost.py[1520]:     self._recv_chan = conn.add_channel(self)
May 03 22:23:34 debian jumphost.py[1520]:   File "/usr/local/lib/python3.7/dist-packages/asyncssh/connection.py", line 939, in add_channel
May 03 22:23:34 debian jumphost.py[1520]:     'SSH connection closed')
May 03 22:23:34 debian jumphost.py[1520]: asyncssh.misc.ChannelOpenError: SSH connection closed
May 03 22:23:34 debian jumphost.py[1520]: DEBUG:jumphost: SSH connection closed from z.z.z.z

My monkeypatch as WA:

class ProxySSH(pproxy.server.ProxySSH):
    async def wait_open_connection(self, host, port, local_addr, family, tunnel=None):
        if self.sshconn is not None and self.sshconn.cancelled():
            self.sshconn = None
        try:
            await self.wait_ssh_connection(local_addr, family, tunnel)
            conn = self.sshconn.result()
            if isinstance(self.jump, pproxy.server.ProxySSH):
                reader, writer = await self.jump.wait_open_connection(host, port, None, None, conn)
            else:
                host, port = self.jump.destination(host, port)
                if self.jump.unix:
                    reader, writer = await conn.open_unix_connection(self.jump.bind)
                else:
                    reader, writer = await conn.open_connection(host, port)
                reader, writer = self.patch_stream(reader, writer, host, port)
            return reader, writer
        except Exception as ex:
            if not self.sshconn.done():
                self.sshconn.set_exception(ex)
            self.sshconn = None
            raise

pproxy.server.ProxySSH = ProxySSH
qwj commented 3 years ago

Hi,

Thanks for the patch. Code is upgraded the version to v2.7.8 with the ssh reconnect ability.

keenser commented 3 years ago

thanks, v2.7.8 works great