0

Edit: Using the snippet below I come across a few issues

import paramiko, threading

ssh=paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

def connectAndCMD(command):
    ssh.connect('127.0.0.1',22,'MY_USER','MY_SSH_PASS')
    stdin, stdout, stderr = ssh.exec_command(command)
    print stdout.read()

commandList=[
    "pwd",
    "whoami",
    "ls",
    "echo hello",
    "ping google.com", 
    "ifconfig | grep Bcast | awk {'print $2'}"
]
for command in commandList:
    print '[',command, '] command thread started'
    t=threading.Thread(target=connectAndCMD,args=(command,))
    t.start()

In the console I see: SSHException: No existing session Oddly enough, when I reduce the size of the list to just a few commands, reducing thread count, I am able to see the output of one of the ssh commands of a random thread but then the script hangs indefinitely. What is the problem and how do I go about rectifying this?

This is a mockup but in my actual program I have threads being made to run the same command with just different parameters/options

zweed4u
  • 207
  • 5
  • 18
  • maybe unrelated: `'/BIN_FILE '+binFileParam` should throw invalid string + integer concatenation. should be `'/BIN_FILE {}'.format(binFileParam)` – Jean-François Fabre Jan 06 '17 at 14:36
  • Nice catch! Yeah, I just rewrote incorrectly. – zweed4u Jan 06 '17 at 14:47
  • You may be experiencing [this](http://stackoverflow.com/documentation/ssh/2926/debugging-ssh-problems/25955/ssh-exchange-identification-connection-closed-by-remote-host#t=201701061520463626447) or [this](http://stackoverflow.com/documentation/ssh/2926/debugging-ssh-problems/25954/ssh-exchange-identification-read-connection-reset-by-peer#t=201701061520463626447). – Kenster Jan 06 '17 at 15:21
  • Dupe of http://stackoverflow.com/q/9520609/13317? – Kenster Jan 06 '17 at 15:33
  • @Kenster I don't believe so. I've been playing with it a bit and have streamlined the question/code sample. – zweed4u Jan 06 '17 at 16:57
  • You call connect on a single session multiple times. Pull the connect out of the worker and it should work. – tdelaney Jan 06 '17 at 17:27
  • @tdelaney This solves the problem! Just so I understand conceptually, is this opening a connection and closing the connection for each command? I guess what I'm indirectly asking is is this efficient? Will the shortest session be worked on as it does with threading? Thanks! – zweed4u Jan 06 '17 at 18:03
  • `SSHClient()` creates 1 transport layer (TCP) connection and `connect` authenticates that connection. (ssh calls this a session). I'm not sure what happens if you try to authenticate multiple times on that one connection, but its likely a protocol error. It should only be done once. After `connect`, authentication is complete. Each `exec_command` creates an ssh-level channel on that single TCP connection and those can be done in parallel in threads. So, you have many ssh-level channels opened and closed on a single TCP-level connection. – tdelaney Jan 06 '17 at 18:28

1 Answers1

1

The problem is that you attempt to re-authenticate the ssh connection for each command. SSH uses a single transport connection (usually TCP but theoretically others) to multiplex multiple ssh-level channels which run things like commands, pipes and shells. SSHClient.connect will open a transport connection, authenticate the server (notice the missing host key policy - you used a rather insecure one) and authenticate the client (a password in your case).

After that, the client can open any number of ssh-level channels to do work. Each exec_command opens a channel, starts a shell on the server, runs a single command and closes the channel. But since these channels are multiplexed, they can run in parallel and are a good option for mulithreading.

import paramiko, threading

ssh=paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('127.0.0.1',22,'MY_USER','MY_SSH_PASS')

def connectAndCMD(command):
    stdin, stdout, stderr = ssh.exec_command(command)
    print stdout.read()

commandList=[
    "pwd",
    "whoami",
    "ls",
    "echo hello",
    "ping google.com", 
    "ifconfig | grep Bcast | awk {'print $2'}"
]
for command in commandList:
    print '[',command, '] command thread started'
    t=threading.Thread(target=connectAndCMD,args=(command,))
    t.start()

UPDATE

Since ssh servers limit the number of simultaneous commands you can use a thread pool to make sure you stay under the limit.

import paramiko
import multiprocessing.pool

ssh=paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('127.0.0.1',22,'MY_USER','MY_SSH_PASS')

def connectAndCMD(command):
    stdin, stdout, stderr = ssh.exec_command(command)
    return command, stdout.read(), stderr.read()

commandList=[
    "pwd",
    "whoami",
    "ls",
    "echo hello",
    "ping google.com", 
    "ifconfig | grep Bcast | awk {'print $2'}"
]

pool = multiprocessing.pool.ThreadPool(min(10, len(commandList))
for command, out, err in pool.map(connectAndCMD, commandList, chunksize=1):
    print((command, out, err))
pool.close()
pool.join()
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • Awesome! Thank you, a last question; is there some kind of limit with paramiko? When adding commands to the list It successfully completes some threads but in some of the threads: ChannelException: (1, 'Administratively prohibited') is thrown and as well as SSHException: Unable to open channel. It seems that 10 threads is the most that are successfully output. – zweed4u Jan 06 '17 at 20:18
  • That's a channel error code coming back from the server side. I guess there is a server side limit to the number but I haven't bumped into it before.... and I'm surprised the number is so low! – tdelaney Jan 06 '17 at 20:25
  • My linux ssh configuration has a MaxSessions config parameter: _MaxSessions - Specifies the maximum number of open sessions permitted per network connection. The default is 10_ SSH lingo is odd but I think that means channels per session. And since the default is 10, it seems to fit. – tdelaney Jan 06 '17 at 20:29
  • I did some research and was wondering if simply specifying a limit such as 15 for the MaxSessions parameter in /etc/ssh/sshd_config would also do the trick. I tried editing the file and I noticed that the parameter wasn't there so I inserted and restarted sshd and still errors in my script when exceeding 10. – zweed4u Jan 07 '17 at 04:25