fgimian / paramiko-expect

A Python expect-like extension for the Paramiko SSH library which also supports tailing logs.
MIT License
204 stars 78 forks source link

take_control always times out #84

Closed karypid closed 2 years ago

karypid commented 2 years ago

So, I want after some initial automation to hand over control to the user "forever" so that they may interact with the SSH session. To that end I invoke take_control() as the last step in my program. At this stage I can interact with the shell of the machine I've SSHed into, but if no data is read for the duration of the timeout paramiko throws an exception:

So I can run date once per second for as long as I want like this:

...(suppose I've been typing "date" once per second for the last 30 seconds, its fine)
Wed 22 Jun 04:26:44 BST 2022
[user@host~]$ date
date
Wed 22 Jun 04:26:45 BST 2022
[user@host~]$ date
date
Wed 22 Jun 04:26:46 BST 2022
[user@host~]$

...but if I stop at the last prompt and wait for 10 seconds (the timeout duration) then suddenly I get (at time 04:26:56):

Wed 22 Jun 04:26:46 BST 2022
[user@host~]$ Exception in thread Thread-3 (writeall):
Traceback (most recent call last):
  File "C:\Users\user\python-playground\paramiko\.venv-py3.10-win32\lib\site-packages\paramiko\channel.py", line 699, in recv
    out = self.in_buffer.read(nbytes, self.timeout)
  File "C:\Users\user\python-playground\paramiko\.venv-py3.10-win32\lib\site-packages\paramiko\buffered_pipe.py", line 164, in read
    raise PipeTimeout()
paramiko.buffered_pipe.PipeTimeout

During handling of the above exception, another exception occurred:
...

The current code sets the timeout to zero stating:

                # We must set the timeout to 0 so that we can bypass times when
                # there is no available text to receive
                self.channel.settimeout(0)

I am not familiar with paramiko itself but is this sufficient? Is there a bug in paramiko_expect?

karypid commented 2 years ago

Some more information:

Looking at the code, the timeout is set to zero only if has_termios is true. Since I am on Windows this is False for me and therefore the timeout is not updated and remains 10 seconds (the default).

I tried setting the timeout to zero myself:

            interact.channel.settimeout(0)
            interact.take_control()

However this caused the disconnection to become immediate. Therefore on Windows I set:

            interact.channel.settimeout(365*24*3600) # 1 year
            interact.take_control()

This fixed the issue and the exception is no longer occuring.

It may be a good idea to put this in take_control() for the Windows case, maybe setting the timeout to the max integer (practically forever) so that you get the same behaviour as with non-Windows systems?

fruch commented 2 years ago

@karypid seems legit, care to open a PR handling it ?

CI doesn't really cover windows (that something also can be addressed, a little harder, but doable)

fruch commented 2 years ago

@karypid seems legit, care to open a PR handling it ?

CI doesn't really cover windows (that something also can be addressed, a little harder, but doable)