SSHClientChannel - how do send_signal / terminate / kill work

420 views
Skip to first unread message

todd freed

unread,
Oct 17, 2017, 3:50:54 PM10/17/17
to asyncssh-users
Hi,

I'm new to asyncssh, and I can't seem to get the send_signal / terminate / kill methods of SSHClientChannel to work. My code looks something like this

tasks = []
tasks.append(loop.create_task(foo))
tasks.append(loop.create_task(bar))
loop.run_until_complete(asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED))

# foo and bar each create an asyncssh session with create_session
# foo terminates after some time
# bar is still running

bar_chan = get_barchan() # SSHClientChannel instance returned from create_session for bar
bar_chan.terminate()  # or send_signal, or kill

# bar should terminate gracefully
loop.run_until_complete(asyncio.wait(tasks))

However bar never terminates and I see no evidence that the signal was actually received on the remote end.

I suspect I'm doing something wrong with asyncio - can I call terminate() on the channel while there is a task still executing that is blocked on "yield from channel.wait_closed()"?

Thanks

-Todd

Ron Frederick

unread,
Oct 17, 2017, 9:10:44 PM10/17/17
to todd freed, asyncssh-users
Hi Todd,
What are you using as your SSH server here? If you are using OpenSSH’s “sshd", I think the problem may be that OpenSSH doesn’t implement the SSH ‘signal’ channel request. So, even though AsyncSSH is sending the request properly, it is getting ignored by OpenSSH. You can find more info at https://bugzilla.mindrot.org/show_bug.cgi?id=1424. Unfortunately, this is a very old bug and as far as I know the patch listed there has never made it into a standard release.

It’s a bit of a hack, but depending on the signal you want to send it might be possible to do it by writing to stdin instead. If you set up the command to run in a PTY (by setting term_type), you can then write something like ‘\x03’ (Ctrl-C) to stdin to get the equivalent of a SIGINT, or ‘\x1c’ to get the equivalent of a SIGQUIT. I don’t know of anything you can write to get other signals like SIGTERM, SIGHUP, or SIGKILL, though.

Here’s an example of a program which uses the stdin trick I mentioned:

import asyncio, asyncssh, sys

async def run_client(loop):
    async with asyncssh.connect(‘localhost') as conn:
        proc = await conn.create_process('sleep 5', term_type='ansi')
        loop.call_later(1, proc.stdin.write, '\x03')
        await proc.wait_closed()
        print(proc.exit_signal)

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

This executes a remote command of ‘sleep 5’ but then arranges to abort after 1 second by sending a Ctrl-C. It prints the results of the signal which caused the process to exit. Try replacing ‘\x03’ with ‘\x1c’ to see the different between exiting with an INT signal vs. a QUIT signal.

Note the setting of term_type there to ‘ansi’ to make sure a PTY is requested on the remote system. Without that, I don’t believe these control characters will trigger a signal being sent to the process being executed.

As you can see from this example, there’s no problem arranging to abort even when there’s already an active wait_closed() on the channel/process.
-- 
Ron Frederick
ro...@timeheart.net



todd freed

unread,
Oct 18, 2017, 12:30:05 PM10/18/17
to asyncssh-users
Yes, I am using sshd from OpenSSH. I suspected at one point it might be a problem on the server, and I looked for sshd options to enable signals, but no luck. Good to understand what's happening.

The control characters hack is interesting but I don't think remote PTY will work for me, since I'm streaming stdout/stderr to the local terminal.

I've settled on a less than ideal solution of starting up a separate channel to originate the kill signal from on the remote end.

-Todd

Ron Frederick

unread,
Oct 18, 2017, 9:03:02 PM10/18/17
to todd freed, asyncssh-users
Hi Todd,

On Oct 18, 2017, at 9:30 AM, todd freed <todd....@gmail.com> wrote:
Yes, I am using sshd from OpenSSH. I suspected at one point it might be a problem on the server, and I looked for sshd options to enable signals, but no luck. Good to understand what's happening.

The control characters hack is interesting but I don't think remote PTY will work for me, since I'm streaming stdout/stderr to the local terminal.

If you need to preserve the separation between stdout and stderr, you’re right that you can’t request a PTY. Once you do that, all of the remote process output on both stdout and stderr will be mixed together as a single stream which will show up as stdout on the SSH channel.

Unfortunately, without the PTY, there won’t be anything to convert things like Ctrl-C or Ctrl-\ into signals to the remote process.


I've settled on a less than ideal solution of starting up a separate channel to originate the kill signal from on the remote end.

That sounds like it could work, as long as you have a way for the remote process to return its PID to you when it starts up, so you can communicate that over to your helper process that sends the signal.

It should be straightforward to open both of those SSH channels over a single SSH connection with AsyncSSH, avoiding the need to open multiple connections or authenticate multiple times.
-- 
Ron Frederick
ro...@timeheart.net



todd freed

unread,
Oct 19, 2017, 4:07:39 PM10/19/17
to asyncssh-users


On Wednesday, October 18, 2017 at 6:03:02 PM UTC-7, Ron Frederick wrote:
Hi Todd,

On Oct 18, 2017, at 9:30 AM, todd freed <todd....@gmail.com> wrote:
Yes, I am using sshd from OpenSSH. I suspected at one point it might be a problem on the server, and I looked for sshd options to enable signals, but no luck. Good to understand what's happening.

The control characters hack is interesting but I don't think remote PTY will work for me, since I'm streaming stdout/stderr to the local terminal.

If you need to preserve the separation between stdout and stderr, you’re right that you can’t request a PTY. Once you do that, all of the remote process output on both stdout and stderr will be mixed together as a single stream which will show up as stdout on the SSH channel.

Unfortunately, without the PTY, there won’t be anything to convert things like Ctrl-C or Ctrl-\ into signals to the remote process.


I've settled on a less than ideal solution of starting up a separate channel to originate the kill signal from on the remote end.

That sounds like it could work, as long as you have a way for the remote process to return its PID to you when it starts up, so you can communicate that over to your helper process that sends the signal.

It should be straightforward to open both of those SSH channels over a single SSH connection with AsyncSSH, avoiding the need to open multiple connections or authenticate multiple times.

I'm getting the PID by prefixing the command with "echo $$ ; exec". I consider this a hack, but it works. I'm doing both channels over the same connection.
Reply all
Reply to author
Forward
0 new messages