Ramana Reddy
Ramana Reddy

Reputation: 399

How to send control signals using paramiko in python?

I have a code snippet something like this:

ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip,port=Port, username=usr,password=Psw)
stdin, stdout, stderr= ssh.exec_command("watch -n1 ps")
print stdout.read(),stderr.read()

The problem here is I have to run watch or any infinitely running command for 10 seconds and after that I should send SIGINT(Ctrl + c) and print the status.

How do I do that?

Upvotes: 2

Views: 7031

Answers (3)

topin89
topin89

Reputation: 401

Since OpenSSH 7.9, there's support for signal requests, so by using private API you can send custom-tailored signal message. Note: support was added in 2018-10-19, about three years after the question was asked.

import os
import sys
import time
from signal import Signals

import paramiko
from paramiko.common import cMSG_CHANNEL_REQUEST

ssh_client = paramiko.SSHClient()
ssh_client.load_system_host_keys()
ssh_client.connect(
    hostname=sys.argv[1],
    port=sys.argv[2],
    username=sys.argv[3],
    key_filename=str(sys.argv[4])
)

stdin, stdout, stderr = ssh_client.exec_command(
    command=f"tail -f {os.getenv('HOME')}/test.txt",
    bufsize=0,
    get_pty=False
)
channel = stdin.channel
time.sleep(0.5)

message = paramiko.Message()
message.add_byte(cMSG_CHANNEL_REQUEST)
message.add_int(stdin.channel.remote_chanid)
message.add_string("signal")
message.add_boolean(False)
message.add_string(Signals.SIGTERM.name[3:])
channel.transport._send_user_message(message)  # angry pylint noises

while not channel.exit_status_ready():
    time.sleep(0.1)

print(f"{channel.exit_status=}")
print("stdout:")
print(stdout.read())
print()
print("channel.stderr:")
print(stderr.read())

Output:

> python3 test_ssh_sigterm.py localhost 2222 $USER keyfile
channel.exit_status=-1
stdout:
b'some string\nsome other string\n'

channel.stderr:
b''

Compare with resize_pty() source

    def resize_pty(self, width=80, height=24, width_pixels=0, height_pixels=0):
        """
        Docstring was there.
        """
        m = Message()
        m.add_byte(cMSG_CHANNEL_REQUEST)
        m.add_int(self.remote_chanid)
        m.add_string("window-change")
        m.add_boolean(False)
        m.add_int(width)
        m.add_int(height)
        m.add_int(width_pixels)
        m.add_int(height_pixels)
        self.transport._send_user_message(m)

If you have a question why there is no send_singal in the code, well, here's PR from 2019 (not mine). It's on Next feature release milestone now, hopefully it will be there this year.

Upvotes: 1

Aldehir
Aldehir

Reputation: 2053

One way to get around this would be to open your own session, pseudo-terminal, and then read in a non-blocking fashion, using recv_ready() to know when to read. After 10 seconds, you send ^C (0x03) to terminate the running process and then close the session. Since you're closing the session anyway, sending ^C is optional, but it may be useful if you want to keep the session alive and run commands multiple times.

import paramiko
import time
import sys

ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip, port=Port, username=usr,password=Psw)

transport = ssh.get_transport()
session = transport.open_session()
session.setblocking(0) # Set to non-blocking mode
session.get_pty()
session.invoke_shell()

# Send command
session.send('watch -n1 ps\n')

# Loop for 10 seconds
start = time.time()    
while time.time() - start < 10:
  if session.recv_ready():
    data = session.recv(512)

    sys.stdout.write(data)
    sys.stdout.flush() # Flushing is important!

  time.sleep(0.001) # Yield CPU so we don't take up 100% usage...

# After 10 seconds, send ^C and then close
session.send('\x03')
session.close()
print

Upvotes: 5

Juan Diego Godoy Robles
Juan Diego Godoy Robles

Reputation: 14965

The only way to transport signals is via terminal, from ssh man:

  -t   Force pseudo-tty allocation.  This can be used to execute
       arbitrary screen-based programs on a remote machine, which can be
       very useful, e.g. when implementing menu services.  Multiple -t
       options force tty allocation, even if ssh has no local tty.

For paramiko check: http://docs.paramiko.org/en/1.16/api/channel.html

get_pty(*args, **kwds)

Request a pseudo-terminal from the server. This is usually used right after creating a client channel, to ask the server to provide

some basic terminal semantics for a shell invoked with invoke_shell. It isn’t necessary (or desirable) to call this method if you’re going to exectue a single command with exec_command.

Upvotes: -1

Related Questions