ronf / asyncssh

AsyncSSH is a Python package which provides an asynchronous client and server implementation of the SSHv2 protocol on top of the Python asyncio framework.
Eclipse Public License 2.0
1.56k stars 156 forks source link

The handle is invalid #680

Open wh1te-moon opened 3 months ago

wh1te-moon commented 3 months ago

Python 3.11.9 or 3.9.18 asyncssh 2.14.1 windowns11 os build 26120.1350

when I run

import asyncio, asyncssh, sys

async def run_client():
    async with asyncssh.connect(ip,22,username="username",password="password") as conn:
        await conn.run(stdin=sys.stdin,stdout=sys.stdout, stderr=sys.stderr)

try:
    asyncio.get_event_loop().run_until_complete(run_client())
except (OSError, asyncssh.Error) as exc:
    sys.exit('SSH connection failed: ' + str(exc))

it will be

AttributeError: '_ProactorReadPipeTransport' object has no attribute '_empty_waiter'
SSH connection failed: [WinError 6] The handle is invalid

and when I run

async def run_client():
    async with asyncssh.connect(ip,22,username="username",password="password") as conn:
        SSHClientProcess=await conn.create_process()
        while True:
            print(await SSHClientProcess.stdout.readline(),end='')

try:
    asyncio.get_event_loop().run_until_complete(run_client())
except (OSError, asyncssh.Error) as exc:
    sys.exit('SSH connection failed: ' + str(exc))

It will get stuck at image

I really have no idea.

ronf commented 3 months ago

In the second block of code, it looks to me like it is doing the right thing, assuming that the next output after what is shown here is a prompt of some kind, without a newline at the end. You are calling readline() on stdout, so it will remain blocking waiting for a complete line or for end-of-file on stdout before it returns.

As for the first error you saw, that appears to be a bug in asyncio on Windows. There's a reference to _empty_waiter in _ProactorBasePipeTransport._force_close, but _empty_waiter is only set in _ProactorBaseWritePipeTransport and _ProactorDatagramTransport and not in _ProactorReadPipeTransport. The assignment of _empty_waiter to None should probably happen in the _ProactorBasePipeTransport constructor instead of _ProactorBaseWritePipeTransport and _ProactorDatagramTransport, so that it will always exist before being referenced in _force_close.

wh1te-moon commented 3 months ago

In the second block of code, it looks to me like it is doing the right thing, assuming that the next output after what is shown here is a prompt of some kind, without a newline at the end. You are calling readline() on stdout, so it will remain blocking waiting for a complete line or for end-of-file on stdout before it returns.

As for the first error you saw, that appears to be a bug in asyncio on Windows. There's a reference to _empty_waiter in _ProactorBasePipeTransport._force_close, but _empty_waiter is only set in _ProactorBaseWritePipeTransport and _ProactorDatagramTransport and not in _ProactorReadPipeTransport. The assignment of _empty_waiter to None should probably happen in the _ProactorBasePipeTransport constructor instead of _ProactorBaseWritePipeTransport and _ProactorDatagramTransport, so that it will always exist before being referenced in _force_close.

Thanks for your swift response. For the second code block,when it's

print(await SSHClientProcess.stdout.read(1),end='')

or

print(await SSHClientProcess.stdout.readexactly(1),end='')

it will get stuck too.

ronf commented 3 months ago

Yes - both read() and readexactly() will eventually block as well. The only difference is that you should see the prompt get output with those, where you wouldn't see the prompt get output in the readline() case. Since the remote system is waiting for input after that, you can't just keep reading. You need to provide some input or write EOF on stdin if you want to see any more output.

wh1te-moon commented 3 months ago

if it works well,It should output root@OpenWrt:~#.But when I use debug mode,it will get stuck at stdout.read. I have tried to use stdin.write to write both 'll\n' and like 'pwd\n', but there is no any response when I debug. Just using keyboard fails too.

ronf commented 3 months ago

On some remote systems, trying to write the input before the prompt comes up won't work. That input gets thrown away by the remote system, since it wasn't actively reading input at the time. I don't know if that's the case here or not, but if you write the input before seeing the prompt, that may be the source of the problem. If you know what the prompt looks like, you can use readuntil() to read data which matches the prompt, and it should be safe after that returns to send your input.

If you're not seeing the prompt even after switching to read() or readexactly(), that may be a stdio buffering issue. Try adding something like sys.stdout.flush() after your call to print(). I think stdout is normally line-buffered by default.

wh1te-moon commented 3 months ago

Sorry,I don't find a method like flush() in stdout. But I tried to use stdout.feed_data("something"),and the stdout.read()works well. So I guess it may be something wrong with the connection so there is no data in stdout.

ronf commented 3 months ago

The flush() I mentioned was on sys.stdout (which print is outputting to), not the process stdout.

Are you trying to mix process redirection (assigning the stdin, stdout, and stderr on a process or run() command) with doing your own I/O? that doesn't really work as you can't control the ordering of the data -- you can use automatic redirection or manual I/O, but not both at once. You can provide some initial input through the input argument, but doesn't work well for interactive sessions, as AsyncSSH sends EOF after sending the requested input.

If you are doing manual I/O, you should be doing stdin.write() on the process, but that will only work if you don't already have stdin redirected to something else (like sys.stdin).

As for why things might not be working interactively using the redirects, are you requesting a PTY on the SSH session? If not, trying to send data from a terminal might not work well. For instance, you may have issues like needing to type Ctrl-J instead of Enter to end a line. You can request a PTY by passing in the term_type argument. For instance, term_type='ansi'.